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 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