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
17package com.example.android.home;
18
19import android.app.Activity;
20import android.app.ActivityManager;
21import android.app.SearchManager;
22import android.content.BroadcastReceiver;
23import android.content.ComponentName;
24import android.content.Context;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.content.pm.ActivityInfo;
28import android.content.pm.PackageManager;
29import android.content.pm.ResolveInfo;
30import android.graphics.Bitmap;
31import android.graphics.Canvas;
32import android.graphics.Paint;
33import android.graphics.PaintFlagsDrawFilter;
34import android.graphics.PixelFormat;
35import android.graphics.Rect;
36import android.graphics.ColorFilter;
37import android.graphics.drawable.BitmapDrawable;
38import android.graphics.drawable.Drawable;
39import android.graphics.drawable.PaintDrawable;
40import android.os.Bundle;
41import android.os.Environment;
42import android.util.Log;
43import android.util.Xml;
44import android.view.KeyEvent;
45import android.view.LayoutInflater;
46import android.view.Menu;
47import android.view.MenuItem;
48import android.view.View;
49import android.view.ViewGroup;
50import android.view.animation.Animation;
51import android.view.animation.AnimationUtils;
52import android.view.animation.LayoutAnimationController;
53import android.widget.AdapterView;
54import android.widget.ArrayAdapter;
55import android.widget.CheckBox;
56import android.widget.GridView;
57import android.widget.TextView;
58
59import java.io.IOException;
60import java.io.FileReader;
61import java.io.File;
62import java.io.FileNotFoundException;
63import java.util.ArrayList;
64import java.util.Collections;
65import java.util.LinkedList;
66import java.util.List;
67
68import org.xmlpull.v1.XmlPullParser;
69import org.xmlpull.v1.XmlPullParserException;
70
71public 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
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
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
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
173    protected void onResume() {
174        super.onResume();
175        bindRecents();
176    }
177
178    @Override
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
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     */
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     */
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     */
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     */
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     */
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
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
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     */
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
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
402    public void onWindowFocusChanged(boolean hasFocus) {
403        super.onWindowFocusChanged(hasFocus);
404        if (!hasFocus) {
405            mBackDown = mHomeDown = false;
406        }
407    }
408
409    @Override
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
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
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
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     */
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     */
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     */
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
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
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
610        public ApplicationsAdapter(Context context, ArrayList<ApplicationInfo> apps) {
611            super(context, 0, apps);
612        }
613
614        @Override
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 {
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 {
693        public void onAnimationStart(Animation animation) {
694        }
695
696        public void onAnimationEnd(Animation animation) {
697            mBlockAnimation = false;
698        }
699
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 {
708        public void onAnimationStart(Animation animation) {
709        }
710
711        public void onAnimationEnd(Animation animation) {
712            mBlockAnimation = false;
713            // ViewDebug.stopHierarchyTracing();
714        }
715
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 {
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
743        public ClippedDrawable(Drawable wallpaper) {
744            mWallpaper = wallpaper;
745        }
746
747        @Override
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
756        public void draw(Canvas canvas) {
757            mWallpaper.draw(canvas);
758        }
759
760        public void setAlpha(int alpha) {
761            mWallpaper.setAlpha(alpha);
762        }
763
764        public void setColorFilter(ColorFilter cf) {
765            mWallpaper.setColorFilter(cf);
766        }
767
768        public int getOpacity() {
769            return mWallpaper.getOpacity();
770        }
771    }
772}
773