BackgroundManager.java revision 86c973d53a08fdd1081be12c10c86e06e0172cd3
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14package android.support.v17.leanback.app; 15 16import android.support.v17.leanback.R; 17import android.animation.Animator; 18import android.animation.ValueAnimator; 19import android.app.Activity; 20import android.content.Context; 21import android.content.res.Resources; 22import android.content.res.TypedArray; 23import android.graphics.Bitmap; 24import android.graphics.Canvas; 25import android.graphics.Color; 26import android.graphics.ColorFilter; 27import android.graphics.Matrix; 28import android.graphics.Paint; 29import android.graphics.drawable.ColorDrawable; 30import android.graphics.drawable.Drawable; 31import android.graphics.drawable.LayerDrawable; 32import android.os.Handler; 33import android.util.Log; 34import android.view.LayoutInflater; 35import android.view.View; 36import android.view.ViewGroup; 37import android.view.Window; 38import android.view.WindowManager; 39import android.view.animation.Interpolator; 40import android.view.animation.LinearInterpolator; 41 42/** 43 * Supports background image continuity between multiple Activities. 44 * 45 * <p>An Activity should instantiate a BackgroundManager and {@link #attach} 46 * to the Activity's window. When the Activity is started, the background is 47 * initialized to the current background values stored in a continuity service. 48 * The background continuity service is updated as the background is updated. 49 * 50 * <p>At some point, for example when it is stopped, the Activity may release 51 * its background state. 52 * 53 * <p>When an Activity is resumed, if the BackgroundManager has not been 54 * released, the continuity service is updated from the BackgroundManager state. 55 * If the BackgroundManager was released, the BackgroundManager inherits the 56 * current state from the continuity service. 57 * 58 * <p>When the last Activity is destroyed, the background state is reset. 59 * 60 * <p>Backgrounds consist of several layers, from back to front: 61 * <ul> 62 * <li>the background Drawable of the theme</li> 63 * <li>a solid color (set via {@link #setColor})</li> 64 * <li>two Drawables, previous and current (set via {@link #setBitmap} or 65 * {@link #setDrawable}), which may be in transition</li> 66 * </ul> 67 * 68 * <p>BackgroundManager holds references to potentially large bitmap Drawables. 69 * Call {@link #release} to release these references when the Activity is not 70 * visible. 71 */ 72// TODO: support for multiple app processes requires a proper android service 73// instead of the shared memory "service" implemented here. Such a service could 74// support continuity between fragments of different applications if desired. 75public final class BackgroundManager { 76 private static final String TAG = "BackgroundManager"; 77 private static final boolean DEBUG = false; 78 79 private static final int FULL_ALPHA = 255; 80 private static final int DIM_ALPHA_ON_SOLID = (int) (0.8f * FULL_ALPHA); 81 private static final int CHANGE_BG_DELAY_MS = 500; 82 private static final int FADE_DURATION = 500; 83 84 /** 85 * Using a separate window for backgrounds can improve graphics performance by 86 * leveraging hardware display layers. 87 * TODO: support a leanback configuration option. 88 */ 89 private static final boolean USE_SEPARATE_WINDOW = false; 90 91 private static final String WINDOW_NAME = "BackgroundManager"; 92 private static final String FRAGMENT_TAG = BackgroundManager.class.getCanonicalName(); 93 94 private Context mContext; 95 private Handler mHandler; 96 private Window mWindow; 97 private WindowManager mWindowManager; 98 private View mBgView; 99 private BackgroundContinuityService mService; 100 private int mThemeDrawableResourceId; 101 102 private int mHeightPx; 103 private int mWidthPx; 104 private Drawable mBackgroundDrawable; 105 private int mBackgroundColor; 106 private boolean mAttached; 107 108 private static class BitmapDrawable extends Drawable { 109 110 static class ConstantState extends Drawable.ConstantState { 111 Bitmap mBitmap; 112 Matrix mMatrix; 113 Paint mPaint; 114 115 @Override 116 public Drawable newDrawable() { 117 return new BitmapDrawable(null, mBitmap, mMatrix); 118 } 119 120 @Override 121 public int getChangingConfigurations() { 122 return 0; 123 } 124 } 125 126 private ConstantState mState = new ConstantState(); 127 128 BitmapDrawable(Resources resources, Bitmap bitmap) { 129 this(resources, bitmap, null); 130 } 131 132 BitmapDrawable(Resources resources, Bitmap bitmap, Matrix matrix) { 133 mState.mBitmap = bitmap; 134 mState.mMatrix = matrix != null ? matrix : new Matrix(); 135 mState.mPaint = new Paint(); 136 mState.mPaint.setFilterBitmap(true); 137 } 138 139 Bitmap getBitmap() { 140 return mState.mBitmap; 141 } 142 143 @Override 144 public void draw(Canvas canvas) { 145 if (mState.mBitmap == null) { 146 return; 147 } 148 canvas.drawBitmap(mState.mBitmap, mState.mMatrix, mState.mPaint); 149 } 150 151 @Override 152 public int getOpacity() { 153 return android.graphics.PixelFormat.OPAQUE; 154 } 155 156 @Override 157 public void setAlpha(int alpha) { 158 if (mState.mPaint.getAlpha() != alpha) { 159 mState.mPaint.setAlpha(alpha); 160 invalidateSelf(); 161 } 162 } 163 164 @Override 165 public void setColorFilter(ColorFilter cf) { 166 // Abstract in Drawable, not implemented 167 } 168 169 @Override 170 public ConstantState getConstantState() { 171 return mState; 172 } 173 } 174 175 private static class DrawableWrapper { 176 protected int mAlpha; 177 protected Drawable mDrawable; 178 protected ValueAnimator mAnimator; 179 protected boolean mAnimationPending; 180 181 private final Interpolator mInterpolator = new LinearInterpolator(); 182 private final ValueAnimator.AnimatorUpdateListener mAnimationUpdateListener = 183 new ValueAnimator.AnimatorUpdateListener() { 184 @Override 185 public void onAnimationUpdate(ValueAnimator animation) { 186 setAlpha((Integer) animation.getAnimatedValue()); 187 } 188 }; 189 190 public DrawableWrapper(Drawable drawable) { 191 mDrawable = drawable; 192 setAlpha(FULL_ALPHA); 193 } 194 195 public Drawable getDrawable() { 196 return mDrawable; 197 } 198 public void setAlpha(int alpha) { 199 mAlpha = alpha; 200 mDrawable.setAlpha(alpha); 201 } 202 public int getAlpha() { 203 return mAlpha; 204 } 205 public void setColor(int color) { 206 ((ColorDrawable) mDrawable).setColor(color); 207 } 208 public void fadeIn(int durationMs, int delayMs) { 209 fade(durationMs, delayMs, FULL_ALPHA); 210 } 211 public void fadeOut(int durationMs) { 212 fade(durationMs, 0, 0); 213 } 214 public void fade(int durationMs, int delayMs, int alpha) { 215 if (mAnimator != null && mAnimator.isStarted()) { 216 mAnimator.cancel(); 217 } 218 mAnimator = ValueAnimator.ofInt(getAlpha(), alpha); 219 mAnimator.addUpdateListener(mAnimationUpdateListener); 220 mAnimator.setInterpolator(mInterpolator); 221 mAnimator.setDuration(durationMs); 222 mAnimator.setStartDelay(delayMs); 223 mAnimationPending = true; 224 } 225 public boolean isAnimationPending() { 226 return mAnimationPending; 227 } 228 public boolean isAnimationStarted() { 229 return mAnimator != null && mAnimator.isStarted(); 230 } 231 public void startAnimation() { 232 startAnimation(null); 233 } 234 public void startAnimation(Animator.AnimatorListener listener) { 235 if (listener != null) { 236 mAnimator.addListener(listener); 237 } 238 mAnimator.start(); 239 mAnimationPending = false; 240 } 241 } 242 243 private LayerDrawable mLayerDrawable; 244 private DrawableWrapper mLayerWrapper; 245 private DrawableWrapper mImageInWrapper; 246 private DrawableWrapper mImageOutWrapper; 247 private DrawableWrapper mColorWrapper; 248 private DrawableWrapper mDimWrapper; 249 250 private Drawable mThemeDrawable; 251 private ChangeBackgroundRunnable mChangeRunnable; 252 253 /** 254 * Shared memory continuity service. 255 */ 256 private static class BackgroundContinuityService { 257 private static final String TAG = "BackgroundContinuityService"; 258 private static boolean DEBUG = BackgroundManager.DEBUG; 259 260 private static BackgroundContinuityService sService = new BackgroundContinuityService(); 261 262 private int mColor; 263 private Drawable mDrawable; 264 private int mCount; 265 266 private BackgroundContinuityService() { 267 reset(); 268 } 269 270 private void reset() { 271 mColor = Color.TRANSPARENT; 272 mDrawable = null; 273 } 274 275 public static BackgroundContinuityService getInstance() { 276 final int count = sService.mCount++; 277 if (DEBUG) Log.v(TAG, "Returning instance with new count " + count); 278 return sService; 279 } 280 281 public void unref() { 282 if (mCount <= 0) throw new IllegalStateException("Can't unref, count " + mCount); 283 if (--mCount == 0) { 284 if (DEBUG) Log.v(TAG, "mCount is zero, resetting"); 285 reset(); 286 } 287 } 288 public int getColor() { 289 return mColor; 290 } 291 public Drawable getDrawable() { 292 return mDrawable; 293 } 294 public void setColor(int color) { 295 mColor = color; 296 } 297 public void setDrawable(Drawable drawable) { 298 mDrawable = drawable; 299 } 300 } 301 302 private Drawable getThemeDrawable() { 303 Drawable drawable = null; 304 if (mThemeDrawableResourceId != -1) { 305 drawable = mContext.getResources().getDrawable(mThemeDrawableResourceId).mutate(); 306 } 307 if (drawable == null) { 308 drawable = createEmptyDrawable(); 309 } 310 return drawable; 311 } 312 313 /** 314 * Get the BackgroundManager associated with the Activity. 315 * <p> 316 * The BackgroundManager will be created on-demand for each individual 317 * Activity. Subsequent calls will return the same BackgroundManager created 318 * for this Activity. 319 */ 320 public static BackgroundManager getInstance(Activity activity) { 321 BackgroundFragment fragment = (BackgroundFragment) activity.getFragmentManager() 322 .findFragmentByTag(FRAGMENT_TAG); 323 if (fragment != null) { 324 BackgroundManager manager = fragment.getBackgroundManager(); 325 if (manager != null) { 326 return manager; 327 } 328 // manager is null: this is a fragment restored by FragmentManager, 329 // fall through to create a BackgroundManager attach to it. 330 } 331 return new BackgroundManager(activity); 332 } 333 334 private BackgroundManager(Activity activity) { 335 mContext = activity; 336 mService = BackgroundContinuityService.getInstance(); 337 mHeightPx = mContext.getResources().getDisplayMetrics().heightPixels; 338 mWidthPx = mContext.getResources().getDisplayMetrics().widthPixels; 339 mHandler = new Handler(); 340 341 TypedArray ta = activity.getTheme().obtainStyledAttributes(new int[] { 342 android.R.attr.windowBackground }); 343 mThemeDrawableResourceId = ta.getResourceId(0, -1); 344 if (mThemeDrawableResourceId < 0) { 345 if (DEBUG) Log.v(TAG, "BackgroundManager no window background resource!"); 346 } 347 ta.recycle(); 348 349 createFragment(activity); 350 } 351 352 private void createFragment(Activity activity) { 353 // Use a fragment to ensure the background manager gets detached properly. 354 BackgroundFragment fragment = (BackgroundFragment) activity.getFragmentManager() 355 .findFragmentByTag(FRAGMENT_TAG); 356 if (fragment == null) { 357 fragment = new BackgroundFragment(); 358 activity.getFragmentManager().beginTransaction().add(fragment, FRAGMENT_TAG).commit(); 359 } else { 360 if (fragment.getBackgroundManager() != null) { 361 throw new IllegalStateException("Created duplicated BackgroundManager for same " + 362 "activity, please use getInstance() instead"); 363 } 364 } 365 fragment.setBackgroundManager(this); 366 } 367 368 /** 369 * Synchronizes state when the owning Activity is resumed. 370 */ 371 void onActivityResume() { 372 if (mService == null) { 373 return; 374 } 375 if (mLayerDrawable == null) { 376 if (DEBUG) Log.v(TAG, "onActivityResume " + this + 377 " released state, syncing with service"); 378 syncWithService(); 379 } else { 380 if (DEBUG) Log.v(TAG, "onActivityResume " + this + " updating service color " 381 + mBackgroundColor + " drawable " + mBackgroundDrawable); 382 mService.setColor(mBackgroundColor); 383 mService.setDrawable(mBackgroundDrawable); 384 } 385 } 386 387 private void syncWithService() { 388 int color = mService.getColor(); 389 Drawable drawable = mService.getDrawable(); 390 391 if (DEBUG) Log.v(TAG, "syncWithService color " + Integer.toHexString(color) 392 + " drawable " + drawable); 393 394 mBackgroundColor = color; 395 mBackgroundDrawable = drawable == null ? null : 396 drawable.getConstantState().newDrawable().mutate(); 397 398 updateImmediate(); 399 } 400 401 private void lazyInit() { 402 if (mLayerDrawable != null) { 403 return; 404 } 405 406 mLayerDrawable = (LayerDrawable) mContext.getResources().getDrawable( 407 R.drawable.lb_background).mutate(); 408 mBgView.setBackground(mLayerDrawable); 409 410 mLayerDrawable.setDrawableByLayerId(R.id.background_imageout, createEmptyDrawable()); 411 412 mDimWrapper = new DrawableWrapper( 413 mLayerDrawable.findDrawableByLayerId(R.id.background_dim)); 414 415 mLayerWrapper = new DrawableWrapper(mLayerDrawable); 416 417 mColorWrapper = new DrawableWrapper( 418 mLayerDrawable.findDrawableByLayerId(R.id.background_color)); 419 } 420 421 /** 422 * Make the background visible on the given Window. 423 */ 424 public void attach(Window window) { 425 if (USE_SEPARATE_WINDOW) { 426 attachBehindWindow(window); 427 } else { 428 attachToView(window.getDecorView()); 429 } 430 } 431 432 private void attachBehindWindow(Window window) { 433 if (DEBUG) Log.v(TAG, "attachBehindWindow " + window); 434 mWindow = window; 435 mWindowManager = window.getWindowManager(); 436 437 WindowManager.LayoutParams params = new WindowManager.LayoutParams( 438 // Media window sits behind the main application window 439 WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA, 440 // Avoid default to software format RGBA 441 WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, 442 android.graphics.PixelFormat.TRANSLUCENT); 443 params.setTitle(WINDOW_NAME); 444 params.width = ViewGroup.LayoutParams.MATCH_PARENT; 445 params.height = ViewGroup.LayoutParams.MATCH_PARENT; 446 447 View backgroundView = LayoutInflater.from(mContext).inflate( 448 R.layout.lb_background_window, null); 449 mWindowManager.addView(backgroundView, params); 450 451 attachToView(backgroundView); 452 } 453 454 private void attachToView(View sceneRoot) { 455 mBgView = sceneRoot; 456 mAttached = true; 457 syncWithService(); 458 } 459 460 /** 461 * Release references to Drawables and put the BackgroundManager into the 462 * detached state. Called when the associated Activity is destroyed. 463 * @hide 464 */ 465 void detach() { 466 if (DEBUG) Log.v(TAG, "detach " + this); 467 release(); 468 469 if (mWindowManager != null && mBgView != null) { 470 mWindowManager.removeViewImmediate(mBgView); 471 } 472 473 mWindowManager = null; 474 mWindow = null; 475 mBgView = null; 476 mAttached = false; 477 478 if (mService != null) { 479 mService.unref(); 480 mService = null; 481 } 482 } 483 484 /** 485 * Release references to Drawables. Typically called to reduce memory 486 * overhead when not visible. 487 * <p> 488 * When an Activity is resumed, if the BackgroundManager has not been 489 * released, the continuity service is updated from the BackgroundManager 490 * state. If the BackgroundManager was released, the BackgroundManager 491 * inherits the current state from the continuity service. 492 */ 493 public void release() { 494 if (DEBUG) Log.v(TAG, "release " + this); 495 if (mLayerDrawable != null) { 496 mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, createEmptyDrawable()); 497 mLayerDrawable.setDrawableByLayerId(R.id.background_imageout, createEmptyDrawable()); 498 mLayerDrawable = null; 499 } 500 mLayerWrapper = null; 501 mImageInWrapper = null; 502 mImageOutWrapper = null; 503 mColorWrapper = null; 504 mDimWrapper = null; 505 mThemeDrawable = null; 506 if (mChangeRunnable != null) { 507 mChangeRunnable.cancel(); 508 mChangeRunnable = null; 509 } 510 releaseBackgroundBitmap(); 511 } 512 513 private void releaseBackgroundBitmap() { 514 mBackgroundDrawable = null; 515 } 516 517 private void updateImmediate() { 518 lazyInit(); 519 520 mColorWrapper.setColor(mBackgroundColor); 521 if (mDimWrapper != null) { 522 mDimWrapper.setAlpha(mBackgroundColor == Color.TRANSPARENT ? 0 : DIM_ALPHA_ON_SOLID); 523 } 524 showWallpaper(mBackgroundColor == Color.TRANSPARENT); 525 526 mThemeDrawable = getThemeDrawable(); 527 mLayerDrawable.setDrawableByLayerId(R.id.background_theme, mThemeDrawable); 528 529 if (mBackgroundDrawable == null) { 530 mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, createEmptyDrawable()); 531 } else { 532 if (DEBUG) Log.v(TAG, "Background drawable is available"); 533 mImageInWrapper = new DrawableWrapper(mBackgroundDrawable); 534 mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, mBackgroundDrawable); 535 if (mDimWrapper != null) { 536 mDimWrapper.setAlpha(FULL_ALPHA); 537 } 538 } 539 } 540 541 /** 542 * Set the background to the given color. The timing for when this becomes 543 * visible in the app is undefined and may take place after a small delay. 544 */ 545 public void setColor(int color) { 546 if (DEBUG) Log.v(TAG, "setColor " + Integer.toHexString(color)); 547 548 mBackgroundColor = color; 549 mService.setColor(mBackgroundColor); 550 551 if (mColorWrapper != null) { 552 mColorWrapper.setColor(mBackgroundColor); 553 } 554 } 555 556 /** 557 * Set the given drawable into the background. The provided Drawable will be 558 * used unmodified as the background, without any scaling or cropping 559 * applied to it. The timing for when this becomes visible in the app is 560 * undefined and may take place after a small delay. 561 */ 562 public void setDrawable(Drawable drawable) { 563 if (DEBUG) Log.v(TAG, "setBackgroundDrawable " + drawable); 564 setDrawableInternal(drawable); 565 } 566 567 private void setDrawableInternal(Drawable drawable) { 568 if (!mAttached) { 569 throw new IllegalStateException("Must attach before setting background drawable"); 570 } 571 572 if (mChangeRunnable != null) { 573 if (sameDrawable(drawable, mChangeRunnable.mDrawable)) { 574 if (DEBUG) Log.v(TAG, "setting same drawable"); 575 return; 576 } 577 mChangeRunnable.cancel(); 578 } 579 mChangeRunnable = new ChangeBackgroundRunnable(drawable); 580 581 if (mImageInWrapper != null && mImageInWrapper.isAnimationStarted()) { 582 if (DEBUG) Log.v(TAG, "animation in progress"); 583 } else { 584 mHandler.postDelayed(mChangeRunnable, CHANGE_BG_DELAY_MS); 585 } 586 } 587 588 /** 589 * Set the given bitmap into the background. When using setBitmap to set the 590 * background, the provided bitmap will be scaled and cropped to correctly 591 * fit within the dimensions of the view. The timing for when this becomes 592 * visible in the app is undefined and may take place after a small delay. 593 */ 594 public void setBitmap(Bitmap bitmap) { 595 if (DEBUG) { 596 Log.v(TAG, "setBitmap " + bitmap); 597 } 598 599 if (bitmap == null) { 600 setDrawableInternal(null); 601 return; 602 } 603 604 if (bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) { 605 if (DEBUG) { 606 Log.v(TAG, "invalid bitmap width or height"); 607 } 608 return; 609 } 610 611 Matrix matrix = null; 612 613 if ((bitmap.getWidth() != mWidthPx || bitmap.getHeight() != mHeightPx)) { 614 int dwidth = bitmap.getWidth(); 615 int dheight = bitmap.getHeight(); 616 float scale; 617 618 // Scale proportionately to fit width and height. 619 if (dwidth * mHeightPx > mWidthPx * dheight) { 620 scale = (float) mHeightPx / (float) dheight; 621 } else { 622 scale = (float) mWidthPx / (float) dwidth; 623 } 624 625 int subX = Math.min((int) (mWidthPx / scale), dwidth); 626 int dx = Math.max(0, (dwidth - subX) / 2); 627 628 matrix = new Matrix(); 629 matrix.setScale(scale, scale); 630 matrix.preTranslate(-dx, 0); 631 632 if (DEBUG) Log.v(TAG, "original image size " + bitmap.getWidth() + "x" + bitmap.getHeight() + 633 " scale " + scale + " dx " + dx); 634 } 635 636 BitmapDrawable bitmapDrawable = new BitmapDrawable(mContext.getResources(), bitmap, matrix); 637 638 setDrawableInternal(bitmapDrawable); 639 } 640 641 private void applyBackgroundChanges() { 642 if (!mAttached || mLayerWrapper == null) { 643 return; 644 } 645 646 if (DEBUG) Log.v(TAG, "applyBackgroundChanges drawable " + mBackgroundDrawable); 647 648 int dimAlpha = 0; 649 650 if (mImageOutWrapper != null && mImageOutWrapper.isAnimationPending()) { 651 if (DEBUG) Log.v(TAG, "mImageOutWrapper animation starting"); 652 mImageOutWrapper.startAnimation(); 653 mImageOutWrapper = null; 654 dimAlpha = DIM_ALPHA_ON_SOLID; 655 } 656 657 if (mImageInWrapper == null && mBackgroundDrawable != null) { 658 if (DEBUG) Log.v(TAG, "creating new imagein drawable"); 659 mImageInWrapper = new DrawableWrapper(mBackgroundDrawable); 660 mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, mBackgroundDrawable); 661 if (DEBUG) Log.v(TAG, "mImageInWrapper animation starting"); 662 mImageInWrapper.setAlpha(0); 663 mImageInWrapper.fadeIn(FADE_DURATION, 0); 664 mImageInWrapper.startAnimation(mImageInListener); 665 dimAlpha = FULL_ALPHA; 666 } 667 668 if (mDimWrapper != null && dimAlpha != 0) { 669 if (DEBUG) Log.v(TAG, "dimwrapper animation starting to " + dimAlpha); 670 mDimWrapper.fade(FADE_DURATION, 0, dimAlpha); 671 mDimWrapper.startAnimation(); 672 } 673 } 674 675 private final Animator.AnimatorListener mImageInListener = new Animator.AnimatorListener() { 676 @Override 677 public void onAnimationStart(Animator animation) { 678 } 679 @Override 680 public void onAnimationRepeat(Animator animation) { 681 } 682 @Override 683 public void onAnimationEnd(Animator animation) { 684 if (mChangeRunnable != null) { 685 if (DEBUG) Log.v(TAG, "animation ended, found change runnable"); 686 mChangeRunnable.run(); 687 } 688 } 689 @Override 690 public void onAnimationCancel(Animator animation) { 691 } 692 }; 693 694 /** 695 * Returns the current background color. 696 */ 697 public final int getColor() { 698 return mBackgroundColor; 699 } 700 701 /** 702 * Returns the current background {@link Drawable}. 703 */ 704 public Drawable getDrawable() { 705 return mBackgroundDrawable; 706 } 707 708 private boolean sameDrawable(Drawable first, Drawable second) { 709 if (first == null || second == null) { 710 return false; 711 } 712 if (first == second) { 713 return true; 714 } 715 if (first instanceof BitmapDrawable && second instanceof BitmapDrawable) { 716 if (((BitmapDrawable) first).getBitmap().sameAs(((BitmapDrawable) second).getBitmap())) { 717 return true; 718 } 719 } 720 return false; 721 } 722 723 /** 724 * Task which changes the background. 725 */ 726 class ChangeBackgroundRunnable implements Runnable { 727 private Drawable mDrawable; 728 private boolean mCancel; 729 730 ChangeBackgroundRunnable(Drawable drawable) { 731 mDrawable = drawable; 732 } 733 734 public void cancel() { 735 mCancel = true; 736 } 737 738 @Override 739 public void run() { 740 if (!mCancel) { 741 runTask(); 742 } 743 } 744 745 private void runTask() { 746 lazyInit(); 747 748 if (sameDrawable(mDrawable, mBackgroundDrawable)) { 749 if (DEBUG) Log.v(TAG, "same bitmap detected"); 750 return; 751 } 752 753 releaseBackgroundBitmap(); 754 755 if (mImageInWrapper != null) { 756 mImageOutWrapper = new DrawableWrapper(mImageInWrapper.getDrawable()); 757 mImageOutWrapper.setAlpha(mImageInWrapper.getAlpha()); 758 mImageOutWrapper.fadeOut(FADE_DURATION); 759 760 // Order is important! Setting a drawable "removes" the 761 // previous one from the view 762 mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, createEmptyDrawable()); 763 mLayerDrawable.setDrawableByLayerId(R.id.background_imageout, 764 mImageOutWrapper.getDrawable()); 765 mImageInWrapper.setAlpha(0); 766 mImageInWrapper = null; 767 } 768 769 mBackgroundDrawable = mDrawable; 770 mService.setDrawable(mBackgroundDrawable); 771 772 applyBackgroundChanges(); 773 774 mChangeRunnable = null; 775 } 776 } 777 778 private Drawable createEmptyDrawable() { 779 Bitmap bitmap = null; 780 return new BitmapDrawable(mContext.getResources(), bitmap); 781 } 782 783 private void showWallpaper(boolean show) { 784 if (mWindow == null) { 785 return; 786 } 787 788 WindowManager.LayoutParams layoutParams = mWindow.getAttributes(); 789 if (show) { 790 if ((layoutParams.flags & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) { 791 return; 792 } 793 if (DEBUG) Log.v(TAG, "showing wallpaper"); 794 layoutParams.flags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; 795 } else { 796 if ((layoutParams.flags & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) == 0) { 797 return; 798 } 799 if (DEBUG) Log.v(TAG, "hiding wallpaper"); 800 layoutParams.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; 801 } 802 803 mWindow.setAttributes(layoutParams); 804 } 805} 806