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