1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.example.android.home;
18 
19 import android.app.Activity;
20 import android.app.ActivityManager;
21 import android.app.SearchManager;
22 import android.content.BroadcastReceiver;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.content.pm.ActivityInfo;
28 import android.content.pm.PackageManager;
29 import android.content.pm.ResolveInfo;
30 import android.graphics.Bitmap;
31 import android.graphics.Canvas;
32 import android.graphics.Paint;
33 import android.graphics.PaintFlagsDrawFilter;
34 import android.graphics.PixelFormat;
35 import android.graphics.Rect;
36 import android.graphics.ColorFilter;
37 import android.graphics.drawable.BitmapDrawable;
38 import android.graphics.drawable.Drawable;
39 import android.graphics.drawable.PaintDrawable;
40 import android.os.Bundle;
41 import android.os.Environment;
42 import android.util.Log;
43 import android.util.Xml;
44 import android.view.KeyEvent;
45 import android.view.LayoutInflater;
46 import android.view.Menu;
47 import android.view.MenuItem;
48 import android.view.View;
49 import android.view.ViewGroup;
50 import android.view.animation.Animation;
51 import android.view.animation.AnimationUtils;
52 import android.view.animation.LayoutAnimationController;
53 import android.widget.AdapterView;
54 import android.widget.ArrayAdapter;
55 import android.widget.CheckBox;
56 import android.widget.GridView;
57 import android.widget.TextView;
58 
59 import java.io.IOException;
60 import java.io.FileReader;
61 import java.io.File;
62 import java.io.FileNotFoundException;
63 import java.util.ArrayList;
64 import java.util.Collections;
65 import java.util.LinkedList;
66 import java.util.List;
67 
68 import org.xmlpull.v1.XmlPullParser;
69 import org.xmlpull.v1.XmlPullParserException;
70 
71 public class Home extends Activity {
72     /**
73      * Tag used for logging errors.
74      */
75     private static final String LOG_TAG = "Home";
76 
77     /**
78      * Keys during freeze/thaw.
79      */
80     private static final String KEY_SAVE_GRID_OPENED = "grid.opened";
81 
82     private static final String DEFAULT_FAVORITES_PATH = "etc/favorites.xml";
83 
84     private static final String TAG_FAVORITES = "favorites";
85     private static final String TAG_FAVORITE = "favorite";
86     private static final String TAG_PACKAGE = "package";
87     private static final String TAG_CLASS = "class";
88 
89     // Identifiers for option menu items
90     private static final int MENU_WALLPAPER_SETTINGS = Menu.FIRST + 1;
91     private static final int MENU_SEARCH = MENU_WALLPAPER_SETTINGS + 1;
92     private static final int MENU_SETTINGS = MENU_SEARCH + 1;
93 
94     /**
95      * Maximum number of recent tasks to query.
96      */
97     private static final int MAX_RECENT_TASKS = 20;
98 
99     private static boolean mWallpaperChecked;
100     private static ArrayList<ApplicationInfo> mApplications;
101     private static LinkedList<ApplicationInfo> mFavorites;
102 
103     private final BroadcastReceiver mWallpaperReceiver = new WallpaperIntentReceiver();
104     private final BroadcastReceiver mApplicationsReceiver = new ApplicationsIntentReceiver();
105 
106     private GridView mGrid;
107 
108     private LayoutAnimationController mShowLayoutAnimation;
109     private LayoutAnimationController mHideLayoutAnimation;
110 
111     private boolean mBlockAnimation;
112 
113     private boolean mHomeDown;
114     private boolean mBackDown;
115 
116     private View mShowApplications;
117     private CheckBox mShowApplicationsCheck;
118 
119     private ApplicationsStackLayout mApplicationsStack;
120 
121     private Animation mGridEntry;
122     private Animation mGridExit;
123 
124     @Override
onCreate(Bundle icicle)125     public void onCreate(Bundle icicle) {
126         super.onCreate(icicle);
127 
128         setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
129 
130         setContentView(R.layout.home);
131 
132         registerIntentReceivers();
133 
134         setDefaultWallpaper();
135 
136         loadApplications(true);
137 
138         bindApplications();
139         bindFavorites(true);
140         bindRecents();
141         bindButtons();
142 
143         mGridEntry = AnimationUtils.loadAnimation(this, R.anim.grid_entry);
144         mGridExit = AnimationUtils.loadAnimation(this, R.anim.grid_exit);
145     }
146 
147     @Override
onNewIntent(Intent intent)148     protected void onNewIntent(Intent intent) {
149         super.onNewIntent(intent);
150 
151         // Close the menu
152         if (Intent.ACTION_MAIN.equals(intent.getAction())) {
153             getWindow().closeAllPanels();
154         }
155     }
156 
157     @Override
onDestroy()158     public void onDestroy() {
159         super.onDestroy();
160 
161         // Remove the callback for the cached drawables or we leak
162         // the previous Home screen on orientation change
163         final int count = mApplications.size();
164         for (int i = 0; i < count; i++) {
165             mApplications.get(i).icon.setCallback(null);
166         }
167 
168         unregisterReceiver(mWallpaperReceiver);
169         unregisterReceiver(mApplicationsReceiver);
170     }
171 
172     @Override
onResume()173     protected void onResume() {
174         super.onResume();
175         bindRecents();
176     }
177 
178     @Override
onRestoreInstanceState(Bundle state)179     protected void onRestoreInstanceState(Bundle state) {
180         super.onRestoreInstanceState(state);
181         final boolean opened = state.getBoolean(KEY_SAVE_GRID_OPENED, false);
182         if (opened) {
183             showApplications(false);
184         }
185     }
186 
187     @Override
onSaveInstanceState(Bundle outState)188     protected void onSaveInstanceState(Bundle outState) {
189         super.onSaveInstanceState(outState);
190         outState.putBoolean(KEY_SAVE_GRID_OPENED, mGrid.getVisibility() == View.VISIBLE);
191     }
192 
193     /**
194      * Registers various intent receivers. The current implementation registers
195      * only a wallpaper intent receiver to let other applications change the
196      * wallpaper.
197      */
registerIntentReceivers()198     private void registerIntentReceivers() {
199         IntentFilter filter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED);
200         registerReceiver(mWallpaperReceiver, filter);
201 
202         filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
203         filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
204         filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
205         filter.addDataScheme("package");
206         registerReceiver(mApplicationsReceiver, filter);
207     }
208 
209     /**
210      * Creates a new appplications adapter for the grid view and registers it.
211      */
bindApplications()212     private void bindApplications() {
213         if (mGrid == null) {
214             mGrid = (GridView) findViewById(R.id.all_apps);
215         }
216         mGrid.setAdapter(new ApplicationsAdapter(this, mApplications));
217         mGrid.setSelection(0);
218 
219         if (mApplicationsStack == null) {
220             mApplicationsStack = (ApplicationsStackLayout) findViewById(R.id.faves_and_recents);
221         }
222     }
223 
224     /**
225      * Binds actions to the various buttons.
226      */
bindButtons()227     private void bindButtons() {
228         mShowApplications = findViewById(R.id.show_all_apps);
229         mShowApplications.setOnClickListener(new ShowApplications());
230         mShowApplicationsCheck = (CheckBox) findViewById(R.id.show_all_apps_check);
231 
232         mGrid.setOnItemClickListener(new ApplicationLauncher());
233     }
234 
235     /**
236      * When no wallpaper was manually set, a default wallpaper is used instead.
237      */
setDefaultWallpaper()238     private void setDefaultWallpaper() {
239         if (!mWallpaperChecked) {
240             Drawable wallpaper = peekWallpaper();
241             if (wallpaper == null) {
242                 try {
243                     clearWallpaper();
244                 } catch (IOException e) {
245                     Log.e(LOG_TAG, "Failed to clear wallpaper " + e);
246                 }
247             } else {
248                 getWindow().setBackgroundDrawable(new ClippedDrawable(wallpaper));
249             }
250             mWallpaperChecked = true;
251         }
252     }
253 
254     /**
255      * Refreshes the favorite applications stacked over the all apps button.
256      * The number of favorites depends on the user.
257      */
bindFavorites(boolean isLaunching)258     private void bindFavorites(boolean isLaunching) {
259         if (!isLaunching || mFavorites == null) {
260 
261             if (mFavorites == null) {
262                 mFavorites = new LinkedList<ApplicationInfo>();
263             } else {
264                 mFavorites.clear();
265             }
266             mApplicationsStack.setFavorites(mFavorites);
267 
268             FileReader favReader;
269 
270             // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system".
271             final File favFile = new File(Environment.getRootDirectory(), DEFAULT_FAVORITES_PATH);
272             try {
273                 favReader = new FileReader(favFile);
274             } catch (FileNotFoundException e) {
275                 Log.e(LOG_TAG, "Couldn't find or open favorites file " + favFile);
276                 return;
277             }
278 
279             final Intent intent = new Intent(Intent.ACTION_MAIN, null);
280             intent.addCategory(Intent.CATEGORY_LAUNCHER);
281 
282             final PackageManager packageManager = getPackageManager();
283 
284             try {
285                 final XmlPullParser parser = Xml.newPullParser();
286                 parser.setInput(favReader);
287 
288                 beginDocument(parser, TAG_FAVORITES);
289 
290                 ApplicationInfo info;
291 
292                 while (true) {
293                     nextElement(parser);
294 
295                     String name = parser.getName();
296                     if (!TAG_FAVORITE.equals(name)) {
297                         break;
298                     }
299 
300                     final String favoritePackage = parser.getAttributeValue(null, TAG_PACKAGE);
301                     final String favoriteClass = parser.getAttributeValue(null, TAG_CLASS);
302 
303                     final ComponentName cn = new ComponentName(favoritePackage, favoriteClass);
304                     intent.setComponent(cn);
305                     intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
306 
307                     info = getApplicationInfo(packageManager, intent);
308                     if (info != null) {
309                         info.intent = intent;
310                         mFavorites.addFirst(info);
311                     }
312                 }
313             } catch (XmlPullParserException e) {
314                 Log.w(LOG_TAG, "Got exception parsing favorites.", e);
315             } catch (IOException e) {
316                 Log.w(LOG_TAG, "Got exception parsing favorites.", e);
317             }
318         }
319 
320         mApplicationsStack.setFavorites(mFavorites);
321     }
322 
beginDocument(XmlPullParser parser, String firstElementName)323     private static void beginDocument(XmlPullParser parser, String firstElementName)
324             throws XmlPullParserException, IOException {
325 
326         int type;
327         while ((type = parser.next()) != XmlPullParser.START_TAG &&
328                 type != XmlPullParser.END_DOCUMENT) {
329             // Empty
330         }
331 
332         if (type != XmlPullParser.START_TAG) {
333             throw new XmlPullParserException("No start tag found");
334         }
335 
336         if (!parser.getName().equals(firstElementName)) {
337             throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
338                     ", expected " + firstElementName);
339         }
340     }
341 
nextElement(XmlPullParser parser)342     private static void nextElement(XmlPullParser parser) throws XmlPullParserException, IOException {
343         int type;
344         while ((type = parser.next()) != XmlPullParser.START_TAG &&
345                 type != XmlPullParser.END_DOCUMENT) {
346             // Empty
347         }
348     }
349 
350     /**
351      * Refreshes the recently launched applications stacked over the favorites. The number
352      * of recents depends on how many favorites are present.
353      */
bindRecents()354     private void bindRecents() {
355         final PackageManager manager = getPackageManager();
356         final ActivityManager tasksManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
357         final List<ActivityManager.RecentTaskInfo> recentTasks = tasksManager.getRecentTasks(
358                 MAX_RECENT_TASKS, 0);
359 
360         final int count = recentTasks.size();
361         final ArrayList<ApplicationInfo> recents = new ArrayList<ApplicationInfo>();
362 
363         for (int i = count - 1; i >= 0; i--) {
364             final Intent intent = recentTasks.get(i).baseIntent;
365 
366             if (Intent.ACTION_MAIN.equals(intent.getAction()) &&
367                     !intent.hasCategory(Intent.CATEGORY_HOME)) {
368 
369                 ApplicationInfo info = getApplicationInfo(manager, intent);
370                 if (info != null) {
371                     info.intent = intent;
372                     if (!mFavorites.contains(info)) {
373                         recents.add(info);
374                     }
375                 }
376             }
377         }
378 
379         mApplicationsStack.setRecents(recents);
380     }
381 
getApplicationInfo(PackageManager manager, Intent intent)382     private static ApplicationInfo getApplicationInfo(PackageManager manager, Intent intent) {
383         final ResolveInfo resolveInfo = manager.resolveActivity(intent, 0);
384 
385         if (resolveInfo == null) {
386             return null;
387         }
388 
389         final ApplicationInfo info = new ApplicationInfo();
390         final ActivityInfo activityInfo = resolveInfo.activityInfo;
391         info.icon = activityInfo.loadIcon(manager);
392         if (info.title == null || info.title.length() == 0) {
393             info.title = activityInfo.loadLabel(manager);
394         }
395         if (info.title == null) {
396             info.title = "";
397         }
398         return info;
399     }
400 
401     @Override
onWindowFocusChanged(boolean hasFocus)402     public void onWindowFocusChanged(boolean hasFocus) {
403         super.onWindowFocusChanged(hasFocus);
404         if (!hasFocus) {
405             mBackDown = mHomeDown = false;
406         }
407     }
408 
409     @Override
dispatchKeyEvent(KeyEvent event)410     public boolean dispatchKeyEvent(KeyEvent event) {
411         if (event.getAction() == KeyEvent.ACTION_DOWN) {
412             switch (event.getKeyCode()) {
413                 case KeyEvent.KEYCODE_BACK:
414                     mBackDown = true;
415                     return true;
416                 case KeyEvent.KEYCODE_HOME:
417                     mHomeDown = true;
418                     return true;
419             }
420         } else if (event.getAction() == KeyEvent.ACTION_UP) {
421             switch (event.getKeyCode()) {
422                 case KeyEvent.KEYCODE_BACK:
423                     if (!event.isCanceled()) {
424                         // Do BACK behavior.
425                     }
426                     mBackDown = true;
427                     return true;
428                 case KeyEvent.KEYCODE_HOME:
429                     if (!event.isCanceled()) {
430                         // Do HOME behavior.
431                     }
432                     mHomeDown = true;
433                     return true;
434             }
435         }
436 
437         return super.dispatchKeyEvent(event);
438     }
439 
440     @Override
onCreateOptionsMenu(Menu menu)441     public boolean onCreateOptionsMenu(Menu menu) {
442         super.onCreateOptionsMenu(menu);
443 
444         menu.add(0, MENU_WALLPAPER_SETTINGS, 0, R.string.menu_wallpaper)
445                  .setIcon(android.R.drawable.ic_menu_gallery)
446                  .setAlphabeticShortcut('W');
447         menu.add(0, MENU_SEARCH, 0, R.string.menu_search)
448                 .setIcon(android.R.drawable.ic_search_category_default)
449                 .setAlphabeticShortcut(SearchManager.MENU_KEY);
450         menu.add(0, MENU_SETTINGS, 0, R.string.menu_settings)
451                 .setIcon(android.R.drawable.ic_menu_preferences)
452                 .setIntent(new Intent(android.provider.Settings.ACTION_SETTINGS));
453 
454         return true;
455     }
456 
457     @Override
onOptionsItemSelected(MenuItem item)458     public boolean onOptionsItemSelected(MenuItem item) {
459         switch (item.getItemId()) {
460             case MENU_WALLPAPER_SETTINGS:
461                 startWallpaper();
462                 return true;
463             case MENU_SEARCH:
464                 onSearchRequested();
465                 return true;
466         }
467 
468         return super.onOptionsItemSelected(item);
469     }
470 
startWallpaper()471     private void startWallpaper() {
472         final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER);
473         startActivity(Intent.createChooser(pickWallpaper, getString(R.string.menu_wallpaper)));
474     }
475 
476     /**
477      * Loads the list of installed applications in mApplications.
478      */
loadApplications(boolean isLaunching)479     private void loadApplications(boolean isLaunching) {
480         if (isLaunching && mApplications != null) {
481             return;
482         }
483 
484         PackageManager manager = getPackageManager();
485 
486         Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
487         mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
488 
489         final List<ResolveInfo> apps = manager.queryIntentActivities(mainIntent, 0);
490         Collections.sort(apps, new ResolveInfo.DisplayNameComparator(manager));
491 
492         if (apps != null) {
493             final int count = apps.size();
494 
495             if (mApplications == null) {
496                 mApplications = new ArrayList<ApplicationInfo>(count);
497             }
498             mApplications.clear();
499 
500             for (int i = 0; i < count; i++) {
501                 ApplicationInfo application = new ApplicationInfo();
502                 ResolveInfo info = apps.get(i);
503 
504                 application.title = info.loadLabel(manager);
505                 application.setActivity(new ComponentName(
506                         info.activityInfo.applicationInfo.packageName,
507                         info.activityInfo.name),
508                         Intent.FLAG_ACTIVITY_NEW_TASK
509                         | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
510                 application.icon = info.activityInfo.loadIcon(manager);
511 
512                 mApplications.add(application);
513             }
514         }
515     }
516 
517     /**
518      * Shows all of the applications by playing an animation on the grid.
519      */
showApplications(boolean animate)520     private void showApplications(boolean animate) {
521         if (mBlockAnimation) {
522             return;
523         }
524         mBlockAnimation = true;
525 
526         mShowApplicationsCheck.toggle();
527 
528         if (mShowLayoutAnimation == null) {
529             mShowLayoutAnimation = AnimationUtils.loadLayoutAnimation(
530                     this, R.anim.show_applications);
531         }
532 
533         // This enables a layout animation; if you uncomment this code, you need to
534         // comment the line mGrid.startAnimation() below
535 //        mGrid.setLayoutAnimationListener(new ShowGrid());
536 //        mGrid.setLayoutAnimation(mShowLayoutAnimation);
537 //        mGrid.startLayoutAnimation();
538 
539         if (animate) {
540             mGridEntry.setAnimationListener(new ShowGrid());
541             mGrid.startAnimation(mGridEntry);
542         }
543 
544         mGrid.setVisibility(View.VISIBLE);
545 
546         if (!animate) {
547             mBlockAnimation = false;
548         }
549 
550         // ViewDebug.startHierarchyTracing("Home", mGrid);
551     }
552 
553     /**
554      * Hides all of the applications by playing an animation on the grid.
555      */
hideApplications()556     private void hideApplications() {
557         if (mBlockAnimation) {
558             return;
559         }
560         mBlockAnimation = true;
561 
562         mShowApplicationsCheck.toggle();
563 
564         if (mHideLayoutAnimation == null) {
565             mHideLayoutAnimation = AnimationUtils.loadLayoutAnimation(
566                     this, R.anim.hide_applications);
567         }
568 
569         mGridExit.setAnimationListener(new HideGrid());
570         mGrid.startAnimation(mGridExit);
571         mGrid.setVisibility(View.INVISIBLE);
572         mShowApplications.requestFocus();
573 
574         // This enables a layout animation; if you uncomment this code, you need to
575         // comment the line mGrid.startAnimation() above
576 //        mGrid.setLayoutAnimationListener(new HideGrid());
577 //        mGrid.setLayoutAnimation(mHideLayoutAnimation);
578 //        mGrid.startLayoutAnimation();
579     }
580 
581     /**
582      * Receives intents from other applications to change the wallpaper.
583      */
584     private class WallpaperIntentReceiver extends BroadcastReceiver {
585         @Override
onReceive(Context context, Intent intent)586         public void onReceive(Context context, Intent intent) {
587             getWindow().setBackgroundDrawable(new ClippedDrawable(getWallpaper()));
588         }
589     }
590 
591     /**
592      * Receives notifications when applications are added/removed.
593      */
594     private class ApplicationsIntentReceiver extends BroadcastReceiver {
595         @Override
onReceive(Context context, Intent intent)596         public void onReceive(Context context, Intent intent) {
597             loadApplications(false);
598             bindApplications();
599             bindRecents();
600             bindFavorites(false);
601         }
602     }
603 
604     /**
605      * GridView adapter to show the list of all installed applications.
606      */
607     private class ApplicationsAdapter extends ArrayAdapter<ApplicationInfo> {
608         private Rect mOldBounds = new Rect();
609 
ApplicationsAdapter(Context context, ArrayList<ApplicationInfo> apps)610         public ApplicationsAdapter(Context context, ArrayList<ApplicationInfo> apps) {
611             super(context, 0, apps);
612         }
613 
614         @Override
getView(int position, View convertView, ViewGroup parent)615         public View getView(int position, View convertView, ViewGroup parent) {
616             final ApplicationInfo info = mApplications.get(position);
617 
618             if (convertView == null) {
619                 final LayoutInflater inflater = getLayoutInflater();
620                 convertView = inflater.inflate(R.layout.application, parent, false);
621             }
622 
623             Drawable icon = info.icon;
624 
625             if (!info.filtered) {
626                 //final Resources resources = getContext().getResources();
627                 int width = 42;//(int) resources.getDimension(android.R.dimen.app_icon_size);
628                 int height = 42;//(int) resources.getDimension(android.R.dimen.app_icon_size);
629 
630                 final int iconWidth = icon.getIntrinsicWidth();
631                 final int iconHeight = icon.getIntrinsicHeight();
632 
633                 if (icon instanceof PaintDrawable) {
634                     PaintDrawable painter = (PaintDrawable) icon;
635                     painter.setIntrinsicWidth(width);
636                     painter.setIntrinsicHeight(height);
637                 }
638 
639                 if (width > 0 && height > 0 && (width < iconWidth || height < iconHeight)) {
640                     final float ratio = (float) iconWidth / iconHeight;
641 
642                     if (iconWidth > iconHeight) {
643                         height = (int) (width / ratio);
644                     } else if (iconHeight > iconWidth) {
645                         width = (int) (height * ratio);
646                     }
647 
648                     final Bitmap.Config c =
649                             icon.getOpacity() != PixelFormat.OPAQUE ?
650                                 Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
651                     final Bitmap thumb = Bitmap.createBitmap(width, height, c);
652                     final Canvas canvas = new Canvas(thumb);
653                     canvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG, 0));
654                     // Copy the old bounds to restore them later
655                     // If we were to do oldBounds = icon.getBounds(),
656                     // the call to setBounds() that follows would
657                     // change the same instance and we would lose the
658                     // old bounds
659                     mOldBounds.set(icon.getBounds());
660                     icon.setBounds(0, 0, width, height);
661                     icon.draw(canvas);
662                     icon.setBounds(mOldBounds);
663                     icon = info.icon = new BitmapDrawable(thumb);
664                     info.filtered = true;
665                 }
666             }
667 
668             final TextView textView = (TextView) convertView.findViewById(R.id.label);
669             textView.setCompoundDrawablesWithIntrinsicBounds(null, icon, null, null);
670             textView.setText(info.title);
671 
672             return convertView;
673         }
674     }
675 
676     /**
677      * Shows and hides the applications grid view.
678      */
679     private class ShowApplications implements View.OnClickListener {
onClick(View v)680         public void onClick(View v) {
681             if (mGrid.getVisibility() != View.VISIBLE) {
682                 showApplications(true);
683             } else {
684                 hideApplications();
685             }
686         }
687     }
688 
689     /**
690      * Hides the applications grid when the layout animation is over.
691      */
692     private class HideGrid implements Animation.AnimationListener {
onAnimationStart(Animation animation)693         public void onAnimationStart(Animation animation) {
694         }
695 
onAnimationEnd(Animation animation)696         public void onAnimationEnd(Animation animation) {
697             mBlockAnimation = false;
698         }
699 
onAnimationRepeat(Animation animation)700         public void onAnimationRepeat(Animation animation) {
701         }
702     }
703 
704     /**
705      * Shows the applications grid when the layout animation is over.
706      */
707     private class ShowGrid implements Animation.AnimationListener {
onAnimationStart(Animation animation)708         public void onAnimationStart(Animation animation) {
709         }
710 
onAnimationEnd(Animation animation)711         public void onAnimationEnd(Animation animation) {
712             mBlockAnimation = false;
713             // ViewDebug.stopHierarchyTracing();
714         }
715 
onAnimationRepeat(Animation animation)716         public void onAnimationRepeat(Animation animation) {
717         }
718     }
719 
720     /**
721      * Starts the selected activity/application in the grid view.
722      */
723     private class ApplicationLauncher implements AdapterView.OnItemClickListener {
onItemClick(AdapterView parent, View v, int position, long id)724         public void onItemClick(AdapterView parent, View v, int position, long id) {
725             ApplicationInfo app = (ApplicationInfo) parent.getItemAtPosition(position);
726             startActivity(app.intent);
727         }
728     }
729 
730     /**
731      * When a drawable is attached to a View, the View gives the Drawable its dimensions
732      * by calling Drawable.setBounds(). In this application, the View that draws the
733      * wallpaper has the same size as the screen. However, the wallpaper might be larger
734      * that the screen which means it will be automatically stretched. Because stretching
735      * a bitmap while drawing it is very expensive, we use a ClippedDrawable instead.
736      * This drawable simply draws another wallpaper but makes sure it is not stretched
737      * by always giving it its intrinsic dimensions. If the wallpaper is larger than the
738      * screen, it will simply get clipped but it won't impact performance.
739      */
740     private class ClippedDrawable extends Drawable {
741         private final Drawable mWallpaper;
742 
ClippedDrawable(Drawable wallpaper)743         public ClippedDrawable(Drawable wallpaper) {
744             mWallpaper = wallpaper;
745         }
746 
747         @Override
setBounds(int left, int top, int right, int bottom)748         public void setBounds(int left, int top, int right, int bottom) {
749             super.setBounds(left, top, right, bottom);
750             // Ensure the wallpaper is as large as it really is, to avoid stretching it
751             // at drawing time
752             mWallpaper.setBounds(left, top, left + mWallpaper.getIntrinsicWidth(),
753                     top + mWallpaper.getIntrinsicHeight());
754         }
755 
draw(Canvas canvas)756         public void draw(Canvas canvas) {
757             mWallpaper.draw(canvas);
758         }
759 
setAlpha(int alpha)760         public void setAlpha(int alpha) {
761             mWallpaper.setAlpha(alpha);
762         }
763 
setColorFilter(ColorFilter cf)764         public void setColorFilter(ColorFilter cf) {
765             mWallpaper.setColorFilter(cf);
766         }
767 
getOpacity()768         public int getOpacity() {
769             return mWallpaper.getOpacity();
770         }
771     }
772 }
773