BackgroundManager.java revision 7e22f555da71f49a32420965817c760522b95963
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); 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 /** 335 * Construct a BackgroundManager instance. The Initial background is set 336 * from the continuity service. 337 * @deprecated Use getInstance(Activity). 338 */ 339 @Deprecated 340 public BackgroundManager(Activity activity) { 341 mContext = activity; 342 mService = BackgroundContinuityService.getInstance(); 343 mHeightPx = mContext.getResources().getDisplayMetrics().heightPixels; 344 mWidthPx = mContext.getResources().getDisplayMetrics().widthPixels; 345 mHandler = new Handler(); 346 347 TypedArray ta = activity.getTheme().obtainStyledAttributes(new int[] { 348 android.R.attr.windowBackground }); 349 mThemeDrawableResourceId = ta.getResourceId(0, -1); 350 if (mThemeDrawableResourceId < 0) { 351 if (DEBUG) Log.v(TAG, "BackgroundManager no window background resource!"); 352 } 353 ta.recycle(); 354 355 createFragment(activity); 356 } 357 358 private void createFragment(Activity activity) { 359 // Use a fragment to ensure the background manager gets detached properly. 360 BackgroundFragment fragment = (BackgroundFragment) activity.getFragmentManager() 361 .findFragmentByTag(FRAGMENT_TAG); 362 if (fragment == null) { 363 fragment = new BackgroundFragment(); 364 activity.getFragmentManager().beginTransaction().add(fragment, FRAGMENT_TAG).commit(); 365 } else { 366 if (fragment.getBackgroundManager() != null) { 367 throw new IllegalStateException("Created duplicated BackgroundManager for same " + 368 "activity, please use getInstance() instead"); 369 } 370 } 371 fragment.setBackgroundManager(this); 372 } 373 374 /** 375 * Synchronizes state when the owning Activity is resumed. 376 */ 377 void onActivityResume() { 378 if (mService == null) { 379 return; 380 } 381 if (mLayerDrawable == null) { 382 if (DEBUG) Log.v(TAG, "onActivityResume " + this + 383 " released state, syncing with service"); 384 syncWithService(); 385 } else { 386 if (DEBUG) Log.v(TAG, "onActivityResume " + this + " updating service color " 387 + mBackgroundColor + " drawable " + mBackgroundDrawable); 388 mService.setColor(mBackgroundColor); 389 mService.setDrawable(mBackgroundDrawable); 390 } 391 } 392 393 private void syncWithService() { 394 int color = mService.getColor(); 395 Drawable drawable = mService.getDrawable(); 396 397 if (DEBUG) Log.v(TAG, "syncWithService color " + Integer.toHexString(color) 398 + " drawable " + drawable); 399 400 mBackgroundColor = color; 401 mBackgroundDrawable = drawable == null ? null : 402 drawable.getConstantState().newDrawable().mutate(); 403 404 updateImmediate(); 405 } 406 407 private void lazyInit() { 408 if (mLayerDrawable != null) { 409 return; 410 } 411 412 mLayerDrawable = (LayerDrawable) mContext.getResources().getDrawable( 413 R.drawable.lb_background).mutate(); 414 mBgView.setBackground(mLayerDrawable); 415 416 mLayerDrawable.setDrawableByLayerId(R.id.background_imageout, createEmptyDrawable()); 417 418 mDimWrapper = new DrawableWrapper( 419 mLayerDrawable.findDrawableByLayerId(R.id.background_dim)); 420 421 mLayerWrapper = new DrawableWrapper(mLayerDrawable); 422 423 mColorWrapper = new DrawableWrapper( 424 mLayerDrawable.findDrawableByLayerId(R.id.background_color)); 425 } 426 427 /** 428 * Make the background visible on the given Window. 429 */ 430 public void attach(Window window) { 431 if (USE_SEPARATE_WINDOW) { 432 attachBehindWindow(window); 433 } else { 434 attachToView(window.getDecorView()); 435 } 436 } 437 438 private void attachBehindWindow(Window window) { 439 if (DEBUG) Log.v(TAG, "attachBehindWindow " + window); 440 mWindow = window; 441 mWindowManager = window.getWindowManager(); 442 443 WindowManager.LayoutParams params = new WindowManager.LayoutParams( 444 // Media window sits behind the main application window 445 WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA, 446 // Avoid default to software format RGBA 447 WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, 448 android.graphics.PixelFormat.TRANSLUCENT); 449 params.setTitle(WINDOW_NAME); 450 params.width = ViewGroup.LayoutParams.MATCH_PARENT; 451 params.height = ViewGroup.LayoutParams.MATCH_PARENT; 452 453 View backgroundView = LayoutInflater.from(mContext).inflate( 454 R.layout.lb_background_window, null); 455 mWindowManager.addView(backgroundView, params); 456 457 attachToView(backgroundView); 458 } 459 460 private void attachToView(View sceneRoot) { 461 mBgView = sceneRoot; 462 mAttached = true; 463 syncWithService(); 464 } 465 466 /** 467 * Release references to Drawables and put the BackgroundManager into the 468 * detached state. Called when the associated Activity is destroyed. 469 * @hide 470 */ 471 void detach() { 472 if (DEBUG) Log.v(TAG, "detach " + this); 473 release(); 474 475 if (mWindowManager != null && mBgView != null) { 476 mWindowManager.removeViewImmediate(mBgView); 477 } 478 479 mWindowManager = null; 480 mWindow = null; 481 mBgView = null; 482 mAttached = false; 483 484 if (mService != null) { 485 mService.unref(); 486 mService = null; 487 } 488 } 489 490 /** 491 * Release references to Drawables. Typically called to reduce memory 492 * overhead when not visible. 493 * <p> 494 * When an Activity is resumed, if the BackgroundManager has not been 495 * released, the continuity service is updated from the BackgroundManager 496 * state. If the BackgroundManager was released, the BackgroundManager 497 * inherits the current state from the continuity service. 498 */ 499 public void release() { 500 if (DEBUG) Log.v(TAG, "release " + this); 501 if (mLayerDrawable != null) { 502 mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, createEmptyDrawable()); 503 mLayerDrawable.setDrawableByLayerId(R.id.background_imageout, createEmptyDrawable()); 504 mLayerDrawable = null; 505 } 506 mLayerWrapper = null; 507 mImageInWrapper = null; 508 mImageOutWrapper = null; 509 mColorWrapper = null; 510 mDimWrapper = null; 511 mThemeDrawable = null; 512 if (mChangeRunnable != null) { 513 mChangeRunnable.cancel(); 514 mChangeRunnable = null; 515 } 516 releaseBackgroundBitmap(); 517 } 518 519 private void releaseBackgroundBitmap() { 520 mBackgroundDrawable = null; 521 } 522 523 private void updateImmediate() { 524 lazyInit(); 525 526 mColorWrapper.setColor(mBackgroundColor); 527 if (mDimWrapper != null) { 528 mDimWrapper.setAlpha(mBackgroundColor == Color.TRANSPARENT ? 0 : DIM_ALPHA_ON_SOLID); 529 } 530 showWallpaper(mBackgroundColor == Color.TRANSPARENT); 531 532 mThemeDrawable = getThemeDrawable(); 533 mLayerDrawable.setDrawableByLayerId(R.id.background_theme, mThemeDrawable); 534 535 if (mBackgroundDrawable == null) { 536 mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, createEmptyDrawable()); 537 } else { 538 if (DEBUG) Log.v(TAG, "Background drawable is available"); 539 mImageInWrapper = new DrawableWrapper(mBackgroundDrawable); 540 mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, mBackgroundDrawable); 541 if (mDimWrapper != null) { 542 mDimWrapper.setAlpha(FULL_ALPHA); 543 } 544 } 545 } 546 547 /** 548 * Set the background to the given color. The timing for when this becomes 549 * visible in the app is undefined and may take place after a small delay. 550 */ 551 public void setColor(int color) { 552 if (DEBUG) Log.v(TAG, "setColor " + Integer.toHexString(color)); 553 554 mBackgroundColor = color; 555 mService.setColor(mBackgroundColor); 556 557 if (mColorWrapper != null) { 558 mColorWrapper.setColor(mBackgroundColor); 559 } 560 } 561 562 /** 563 * Set the given drawable into the background. The provided Drawable will be 564 * used unmodified as the background, without any scaling or cropping 565 * applied to it. The timing for when this becomes visible in the app is 566 * undefined and may take place after a small delay. 567 */ 568 public void setDrawable(Drawable drawable) { 569 if (DEBUG) Log.v(TAG, "setBackgroundDrawable " + drawable); 570 setDrawableInternal(drawable); 571 } 572 573 private void setDrawableInternal(Drawable drawable) { 574 if (!mAttached) { 575 throw new IllegalStateException("Must attach before setting background drawable"); 576 } 577 578 if (mChangeRunnable != null) { 579 mChangeRunnable.cancel(); 580 } 581 mChangeRunnable = new ChangeBackgroundRunnable(drawable); 582 583 if (mImageInWrapper != null && mImageInWrapper.isAnimationStarted()) { 584 if (DEBUG) Log.v(TAG, "animation in progress"); 585 } else { 586 mHandler.postDelayed(mChangeRunnable, CHANGE_BG_DELAY_MS); 587 } 588 } 589 590 /** 591 * Set the given bitmap into the background. When using setBitmap to set the 592 * background, the provided bitmap will be scaled and cropped to correctly 593 * fit within the dimensions of the view. The timing for when this becomes 594 * visible in the app is undefined and may take place after a small delay. 595 */ 596 public void setBitmap(Bitmap bitmap) { 597 if (DEBUG) { 598 Log.v(TAG, "setBitmap " + bitmap); 599 } 600 601 if (bitmap == null) { 602 setDrawableInternal(null); 603 return; 604 } 605 606 if (bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) { 607 if (DEBUG) { 608 Log.v(TAG, "invalid bitmap width or height"); 609 } 610 return; 611 } 612 613 Matrix matrix = null; 614 615 if ((bitmap.getWidth() != mWidthPx || bitmap.getHeight() != mHeightPx)) { 616 int dwidth = bitmap.getWidth(); 617 int dheight = bitmap.getHeight(); 618 float scale; 619 620 // Scale proportionately to fit width and height. 621 if (dwidth * mHeightPx > mWidthPx * dheight) { 622 scale = (float) mHeightPx / (float) dheight; 623 } else { 624 scale = (float) mWidthPx / (float) dwidth; 625 } 626 627 int subX = Math.min((int) (mWidthPx / scale), dwidth); 628 int dx = Math.max(0, (dwidth - subX) / 2); 629 630 matrix = new Matrix(); 631 matrix.setScale(scale, scale); 632 matrix.preTranslate(-dx, 0); 633 634 if (DEBUG) Log.v(TAG, "original image size " + bitmap.getWidth() + "x" + bitmap.getHeight() + 635 " scale " + scale + " dx " + dx); 636 } 637 638 BitmapDrawable bitmapDrawable = new BitmapDrawable(mContext.getResources(), bitmap, matrix); 639 640 setDrawableInternal(bitmapDrawable); 641 } 642 643 private void applyBackgroundChanges() { 644 if (!mAttached || mLayerWrapper == null) { 645 return; 646 } 647 648 if (DEBUG) Log.v(TAG, "applyBackgroundChanges drawable " + mBackgroundDrawable); 649 650 int dimAlpha = 0; 651 652 if (mImageOutWrapper != null && mImageOutWrapper.isAnimationPending()) { 653 if (DEBUG) Log.v(TAG, "mImageOutWrapper animation starting"); 654 mImageOutWrapper.startAnimation(); 655 mImageOutWrapper = null; 656 dimAlpha = DIM_ALPHA_ON_SOLID; 657 } 658 659 if (mImageInWrapper == null && mBackgroundDrawable != null) { 660 if (DEBUG) Log.v(TAG, "creating new imagein drawable"); 661 mImageInWrapper = new DrawableWrapper(mBackgroundDrawable); 662 mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, mBackgroundDrawable); 663 if (DEBUG) Log.v(TAG, "mImageInWrapper animation starting"); 664 mImageInWrapper.setAlpha(0); 665 mImageInWrapper.fadeIn(FADE_DURATION, 0); 666 mImageInWrapper.startAnimation(mImageInListener); 667 dimAlpha = FULL_ALPHA; 668 } 669 670 if (mDimWrapper != null && dimAlpha != 0) { 671 if (DEBUG) Log.v(TAG, "dimwrapper animation starting to " + dimAlpha); 672 mDimWrapper.fade(FADE_DURATION, 0, dimAlpha); 673 mDimWrapper.startAnimation(); 674 } 675 } 676 677 private final Animator.AnimatorListener mImageInListener = new Animator.AnimatorListener() { 678 @Override 679 public void onAnimationStart(Animator animation) { 680 } 681 @Override 682 public void onAnimationRepeat(Animator animation) { 683 } 684 @Override 685 public void onAnimationEnd(Animator animation) { 686 if (mChangeRunnable != null) { 687 if (DEBUG) Log.v(TAG, "animation ended, found change runnable"); 688 mChangeRunnable.run(); 689 } 690 } 691 @Override 692 public void onAnimationCancel(Animator animation) { 693 } 694 }; 695 696 /** 697 * Returns the current background color. 698 */ 699 public final int getColor() { 700 return mBackgroundColor; 701 } 702 703 /** 704 * Returns the current background {@link Drawable}. 705 */ 706 public Drawable getDrawable() { 707 return mBackgroundDrawable; 708 } 709 710 private boolean sameDrawable(Drawable first, Drawable second) { 711 if (first == null || second == null) { 712 return false; 713 } 714 if (first == second) { 715 return true; 716 } 717 if (first instanceof BitmapDrawable && second instanceof BitmapDrawable) { 718 if (((BitmapDrawable) first).getBitmap().sameAs(((BitmapDrawable) second).getBitmap())) { 719 return true; 720 } 721 } 722 return false; 723 } 724 725 /** 726 * Task which changes the background. 727 */ 728 class ChangeBackgroundRunnable implements Runnable { 729 private Drawable mDrawable; 730 private boolean mCancel; 731 732 ChangeBackgroundRunnable(Drawable drawable) { 733 mDrawable = drawable; 734 } 735 736 public void cancel() { 737 mCancel = true; 738 } 739 740 @Override 741 public void run() { 742 if (!mCancel) { 743 runTask(); 744 } 745 } 746 747 private void runTask() { 748 lazyInit(); 749 750 if (sameDrawable(mDrawable, mBackgroundDrawable)) { 751 if (DEBUG) Log.v(TAG, "same bitmap detected"); 752 return; 753 } 754 755 releaseBackgroundBitmap(); 756 757 if (mImageInWrapper != null) { 758 mImageOutWrapper = new DrawableWrapper(mImageInWrapper.getDrawable()); 759 mImageOutWrapper.setAlpha(mImageInWrapper.getAlpha()); 760 mImageOutWrapper.fadeOut(FADE_DURATION); 761 762 // Order is important! Setting a drawable "removes" the 763 // previous one from the view 764 mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, createEmptyDrawable()); 765 mLayerDrawable.setDrawableByLayerId(R.id.background_imageout, 766 mImageOutWrapper.getDrawable()); 767 mImageInWrapper.setAlpha(0); 768 mImageInWrapper = null; 769 } 770 771 mBackgroundDrawable = mDrawable; 772 mService.setDrawable(mBackgroundDrawable); 773 774 applyBackgroundChanges(); 775 776 mChangeRunnable = null; 777 } 778 } 779 780 private Drawable createEmptyDrawable() { 781 Bitmap bitmap = null; 782 return new BitmapDrawable(mContext.getResources(), bitmap); 783 } 784 785 private void showWallpaper(boolean show) { 786 if (mWindow == null) { 787 return; 788 } 789 790 WindowManager.LayoutParams layoutParams = mWindow.getAttributes(); 791 if (show) { 792 if ((layoutParams.flags & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) { 793 return; 794 } 795 if (DEBUG) Log.v(TAG, "showing wallpaper"); 796 layoutParams.flags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; 797 } else { 798 if ((layoutParams.flags & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) == 0) { 799 return; 800 } 801 if (DEBUG) Log.v(TAG, "hiding wallpaper"); 802 layoutParams.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; 803 } 804 805 mWindow.setAttributes(layoutParams); 806 } 807} 808