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  */
14 package android.support.v17.leanback.app;
15 
16 import android.animation.Animator;
17 import android.animation.ValueAnimator;
18 import android.app.Activity;
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.content.res.TypedArray;
22 import android.graphics.Bitmap;
23 import android.graphics.Canvas;
24 import android.graphics.Color;
25 import android.graphics.ColorFilter;
26 import android.graphics.Matrix;
27 import android.graphics.Paint;
28 import android.graphics.PixelFormat;
29 import android.graphics.drawable.ColorDrawable;
30 import android.graphics.drawable.Drawable;
31 import android.graphics.drawable.LayerDrawable;
32 import android.os.Build;
33 import android.os.Handler;
34 import android.support.annotation.ColorInt;
35 import android.support.annotation.NonNull;
36 import android.support.v17.leanback.R;
37 import android.support.v17.leanback.widget.BackgroundHelper;
38 import android.support.v4.content.ContextCompat;
39 import android.support.v4.graphics.drawable.DrawableCompat;
40 import android.support.v4.os.BuildCompat;
41 import android.support.v4.view.animation.FastOutLinearInInterpolator;
42 import android.util.Log;
43 import android.view.View;
44 import android.view.Window;
45 import android.view.animation.AnimationUtils;
46 import android.view.animation.Interpolator;
47 
48 import java.lang.ref.WeakReference;
49 
50 /**
51  * Supports background image continuity between multiple Activities.
52  *
53  * <p>An Activity should instantiate a BackgroundManager and {@link #attach}
54  * to the Activity's window.  When the Activity is started, the background is
55  * initialized to the current background values stored in a continuity service.
56  * The background continuity service is updated as the background is updated.
57  *
58  * <p>At some point, for example when it is stopped, the Activity may release
59  * its background state.
60  *
61  * <p>When an Activity is resumed, if the BackgroundManager has not been
62  * released, the continuity service is updated from the BackgroundManager state.
63  * If the BackgroundManager was released, the BackgroundManager inherits the
64  * current state from the continuity service.
65  *
66  * <p>When the last Activity is destroyed, the background state is reset.
67  *
68  * <p>Backgrounds consist of several layers, from back to front:
69  * <ul>
70  *   <li>the background Drawable of the theme</li>
71  *   <li>a solid color (set via {@link #setColor})</li>
72  *   <li>two Drawables, previous and current (set via {@link #setBitmap} or
73  *   {@link #setDrawable}), which may be in transition</li>
74  * </ul>
75  *
76  * <p>BackgroundManager holds references to potentially large bitmap Drawables.
77  * Call {@link #release} to release these references when the Activity is not
78  * visible.
79  */
80 // TODO: support for multiple app processes requires a proper android service
81 // instead of the shared memory "service" implemented here. Such a service could
82 // support continuity between fragments of different applications if desired.
83 public final class BackgroundManager {
84 
85     static final String TAG = "BackgroundManager";
86     static final boolean DEBUG = false;
87 
88     static final int FULL_ALPHA = 255;
89     private static final int CHANGE_BG_DELAY_MS = 500;
90     private static final int FADE_DURATION = 500;
91 
92     private static final String FRAGMENT_TAG = BackgroundManager.class.getCanonicalName();
93 
94     Activity mContext;
95     Handler mHandler;
96     private View mBgView;
97     private BackgroundContinuityService mService;
98     private int mThemeDrawableResourceId;
99     private BackgroundFragment mFragmentState;
100     private boolean mAutoReleaseOnStop = true;
101 
102     private int mHeightPx;
103     private int mWidthPx;
104     int mBackgroundColor;
105     Drawable mBackgroundDrawable;
106     private boolean mAttached;
107     private long mLastSetTime;
108 
109     private final Interpolator mAccelerateInterpolator;
110     private final Interpolator mDecelerateInterpolator;
111     final ValueAnimator mAnimator;
112 
113     static class BitmapDrawable extends Drawable {
114 
115         static final class ConstantState extends Drawable.ConstantState {
116             final Bitmap mBitmap;
117             final Matrix mMatrix;
118             final Paint mPaint = new Paint();
119 
ConstantState(Bitmap bitmap, Matrix matrix)120             ConstantState(Bitmap bitmap, Matrix matrix) {
121                 mBitmap = bitmap;
122                 mMatrix = matrix != null ? matrix : new Matrix();
123                 mPaint.setFilterBitmap(true);
124             }
125 
ConstantState(ConstantState copyFrom)126             ConstantState(ConstantState copyFrom) {
127                 mBitmap = copyFrom.mBitmap;
128                 mMatrix = copyFrom.mMatrix != null ? new Matrix(copyFrom.mMatrix) : new Matrix();
129                 if (copyFrom.mPaint.getAlpha() != FULL_ALPHA) {
130                     mPaint.setAlpha(copyFrom.mPaint.getAlpha());
131                 }
132                 if (copyFrom.mPaint.getColorFilter() != null) {
133                     mPaint.setColorFilter(copyFrom.mPaint.getColorFilter());
134                 }
135                 mPaint.setFilterBitmap(true);
136             }
137 
138             @Override
newDrawable()139             public Drawable newDrawable() {
140                 return new BitmapDrawable(this);
141             }
142 
143             @Override
getChangingConfigurations()144             public int getChangingConfigurations() {
145                 return 0;
146             }
147         }
148 
149         ConstantState mState;
150         boolean mMutated;
151 
BitmapDrawable(Resources resources, Bitmap bitmap)152         BitmapDrawable(Resources resources, Bitmap bitmap) {
153             this(resources, bitmap, null);
154         }
155 
BitmapDrawable(Resources resources, Bitmap bitmap, Matrix matrix)156         BitmapDrawable(Resources resources, Bitmap bitmap, Matrix matrix) {
157             mState = new ConstantState(bitmap, matrix);
158         }
159 
BitmapDrawable(ConstantState state)160         BitmapDrawable(ConstantState state) {
161             mState = state;
162         }
163 
getBitmap()164         Bitmap getBitmap() {
165             return mState.mBitmap;
166         }
167 
168         @Override
draw(Canvas canvas)169         public void draw(Canvas canvas) {
170             if (mState.mBitmap == null) {
171                 return;
172             }
173             if (mState.mPaint.getAlpha() < FULL_ALPHA && mState.mPaint.getColorFilter() != null) {
174                 throw new IllegalStateException("Can't draw with translucent alpha and color filter");
175             }
176             canvas.drawBitmap(mState.mBitmap, mState.mMatrix, mState.mPaint);
177         }
178 
179         @Override
getOpacity()180         public int getOpacity() {
181             return android.graphics.PixelFormat.TRANSLUCENT;
182         }
183 
184         @Override
setAlpha(int alpha)185         public void setAlpha(int alpha) {
186             mutate();
187             if (mState.mPaint.getAlpha() != alpha) {
188                 mState.mPaint.setAlpha(alpha);
189                 invalidateSelf();
190             }
191         }
192 
193         /**
194          * Does not invalidateSelf to avoid recursion issues.
195          * Caller must ensure appropriate invalidation.
196          */
197         @Override
setColorFilter(ColorFilter cf)198         public void setColorFilter(ColorFilter cf) {
199             mutate();
200             mState.mPaint.setColorFilter(cf);
201             invalidateSelf();
202         }
203 
204         @Override
getColorFilter()205         public ColorFilter getColorFilter() {
206             return mState.mPaint.getColorFilter();
207         }
208 
209         @Override
getConstantState()210         public ConstantState getConstantState() {
211             return mState;
212         }
213 
214         @NonNull
215         @Override
mutate()216         public Drawable mutate() {
217             if (!mMutated) {
218                 mMutated = true;
219                 mState = new ConstantState(mState);
220             }
221             return this;
222         }
223     }
224 
225     static final class DrawableWrapper {
226         int mAlpha = FULL_ALPHA;
227         final Drawable mDrawable;
228 
DrawableWrapper(Drawable drawable)229         public DrawableWrapper(Drawable drawable) {
230             mDrawable = drawable;
231         }
DrawableWrapper(DrawableWrapper wrapper, Drawable drawable)232         public DrawableWrapper(DrawableWrapper wrapper, Drawable drawable) {
233             mDrawable = drawable;
234             mAlpha = wrapper.mAlpha;
235         }
236 
getDrawable()237         public Drawable getDrawable() {
238             return mDrawable;
239         }
240 
setColor(int color)241         public void setColor(int color) {
242             ((ColorDrawable) mDrawable).setColor(color);
243         }
244     }
245 
246     static final class TranslucentLayerDrawable extends LayerDrawable {
247         DrawableWrapper[] mWrapper;
248         int mAlpha = FULL_ALPHA;
249         boolean mSuspendInvalidation;
250         WeakReference<BackgroundManager> mManagerWeakReference;
251 
TranslucentLayerDrawable(BackgroundManager manager, Drawable[] drawables)252         TranslucentLayerDrawable(BackgroundManager manager, Drawable[] drawables) {
253             super(drawables);
254             mManagerWeakReference = new WeakReference(manager);
255             int count = drawables.length;
256             mWrapper = new DrawableWrapper[count];
257             for (int i = 0; i < count; i++) {
258                 mWrapper[i] = new DrawableWrapper(drawables[i]);
259             }
260         }
261 
262         @Override
setAlpha(int alpha)263         public void setAlpha(int alpha) {
264             if (mAlpha != alpha) {
265                 mAlpha = alpha;
266                 invalidateSelf();
267                 BackgroundManager manager = mManagerWeakReference.get();
268                 if (manager != null) {
269                     manager.postChangeRunnable();
270                 }
271             }
272         }
273 
setWrapperAlpha(int wrapperIndex, int alpha)274         void setWrapperAlpha(int wrapperIndex, int alpha) {
275             if (mWrapper[wrapperIndex] != null) {
276                 mWrapper[wrapperIndex].mAlpha = alpha;
277                 invalidateSelf();
278             }
279         }
280 
281         // Queried by system transitions
282         @Override
getAlpha()283         public int getAlpha() {
284             return mAlpha;
285         }
286 
287         @Override
mutate()288         public Drawable mutate() {
289             Drawable drawable = super.mutate();
290             int count = getNumberOfLayers();
291             for (int i = 0; i < count; i++) {
292                 if (mWrapper[i] != null) {
293                     mWrapper[i] = new DrawableWrapper(mWrapper[i], getDrawable(i));
294                 }
295             }
296             return drawable;
297         }
298 
299         @Override
getOpacity()300         public int getOpacity() {
301             return PixelFormat.TRANSLUCENT;
302         }
303 
304         @Override
setDrawableByLayerId(int id, Drawable drawable)305         public boolean setDrawableByLayerId(int id, Drawable drawable) {
306             return updateDrawable(id, drawable) != null;
307         }
308 
updateDrawable(int id, Drawable drawable)309         public DrawableWrapper updateDrawable(int id, Drawable drawable) {
310             super.setDrawableByLayerId(id, drawable);
311             for (int i = 0; i < getNumberOfLayers(); i++) {
312                 if (getId(i) == id) {
313                     mWrapper[i] = new DrawableWrapper(drawable);
314                     // Must come after mWrapper was updated so it can be seen by updateColorFilter
315                     invalidateSelf();
316                     return mWrapper[i];
317                 }
318             }
319             return null;
320         }
321 
clearDrawable(int id, Context context)322         public void clearDrawable(int id, Context context) {
323             for (int i = 0; i < getNumberOfLayers(); i++) {
324                 if (getId(i) == id) {
325                     mWrapper[i] = null;
326                     if (!(getDrawable(i) instanceof EmptyDrawable)) {
327                         super.setDrawableByLayerId(id, createEmptyDrawable(context));
328                     }
329                     break;
330                 }
331             }
332         }
333 
findWrapperIndexById(int id)334         public int findWrapperIndexById(int id) {
335             for (int i = 0; i < getNumberOfLayers(); i++) {
336                 if (getId(i) == id) {
337                     return i;
338                 }
339             }
340             return -1;
341         }
342 
343         @Override
invalidateDrawable(Drawable who)344         public void invalidateDrawable(Drawable who) {
345             // Prevent invalidate when temporarily change child drawable's alpha in draw()
346             if (!mSuspendInvalidation) {
347                 super.invalidateDrawable(who);
348             }
349         }
350 
351         @Override
draw(Canvas canvas)352         public void draw(Canvas canvas) {
353             for (int i = 0; i < mWrapper.length; i++) {
354                 final Drawable d;
355                 // For each child drawable, we multiple Wrapper's alpha and LayerDrawable's alpha
356                 // temporarily using mSuspendInvalidation to suppress invalidate event.
357                 if (mWrapper[i] != null && (d = mWrapper[i].getDrawable()) != null) {
358                     int alpha = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
359                             ? DrawableCompat.getAlpha(d) : FULL_ALPHA;
360                     final int savedAlpha = alpha;
361                     int multiple = 0;
362                     if (mAlpha < FULL_ALPHA) {
363                         alpha = alpha * mAlpha;
364                         multiple++;
365                     }
366                     if (mWrapper[i].mAlpha < FULL_ALPHA) {
367                         alpha = alpha * mWrapper[i].mAlpha;
368                         multiple++;
369                     }
370                     if (multiple == 0) {
371                         d.draw(canvas);
372                     } else {
373                         if (multiple == 1) {
374                             alpha = alpha / FULL_ALPHA;
375                         } else if (multiple == 2) {
376                             alpha = alpha / (FULL_ALPHA * FULL_ALPHA);
377                         }
378                         try {
379                             mSuspendInvalidation = true;
380                             d.setAlpha(alpha);
381                             d.draw(canvas);
382                             d.setAlpha(savedAlpha);
383                         } finally {
384                             mSuspendInvalidation = false;
385                         }
386                     }
387                 }
388             }
389         }
390     }
391 
createTranslucentLayerDrawable( LayerDrawable layerDrawable)392     TranslucentLayerDrawable createTranslucentLayerDrawable(
393             LayerDrawable layerDrawable) {
394         int numChildren = layerDrawable.getNumberOfLayers();
395         Drawable[] drawables = new Drawable[numChildren];
396         for (int i = 0; i < numChildren; i++) {
397             drawables[i] = layerDrawable.getDrawable(i);
398         }
399         TranslucentLayerDrawable result = new TranslucentLayerDrawable(this, drawables);
400         for (int i = 0; i < numChildren; i++) {
401             result.setId(i, layerDrawable.getId(i));
402         }
403         return result;
404     }
405 
406     TranslucentLayerDrawable mLayerDrawable;
407     int mImageInWrapperIndex;
408     int mImageOutWrapperIndex;
409     ChangeBackgroundRunnable mChangeRunnable;
410     private boolean mChangeRunnablePending;
411 
412     private final Animator.AnimatorListener mAnimationListener = new Animator.AnimatorListener() {
413         final Runnable mRunnable = new Runnable() {
414             @Override
415             public void run() {
416                 postChangeRunnable();
417             }
418         };
419 
420         @Override
421         public void onAnimationStart(Animator animation) {
422         }
423         @Override
424         public void onAnimationRepeat(Animator animation) {
425         }
426         @Override
427         public void onAnimationEnd(Animator animation) {
428             if (mLayerDrawable != null) {
429                 mLayerDrawable.clearDrawable(R.id.background_imageout, mContext);
430             }
431             mHandler.post(mRunnable);
432         }
433         @Override
434         public void onAnimationCancel(Animator animation) {
435         }
436     };
437 
438     private final ValueAnimator.AnimatorUpdateListener mAnimationUpdateListener =
439             new ValueAnimator.AnimatorUpdateListener() {
440         @Override
441         public void onAnimationUpdate(ValueAnimator animation) {
442             int fadeInAlpha = (Integer) animation.getAnimatedValue();
443             if (mImageInWrapperIndex != -1) {
444                 mLayerDrawable.setWrapperAlpha(mImageInWrapperIndex, fadeInAlpha);
445             }
446         }
447     };
448 
449     /**
450      * Shared memory continuity service.
451      */
452     private static class BackgroundContinuityService {
453         private static final String TAG = "BackgroundContinuity";
454         private static boolean DEBUG = BackgroundManager.DEBUG;
455 
456         private static BackgroundContinuityService sService = new BackgroundContinuityService();
457 
458         private int mColor;
459         private Drawable mDrawable;
460         private int mCount;
461 
462         /** Single cache of theme drawable */
463         private int mLastThemeDrawableId;
464         private WeakReference<Drawable.ConstantState> mLastThemeDrawableState;
465 
BackgroundContinuityService()466         private BackgroundContinuityService() {
467             reset();
468         }
469 
reset()470         private void reset() {
471             mColor = Color.TRANSPARENT;
472             mDrawable = null;
473         }
474 
getInstance()475         public static BackgroundContinuityService getInstance() {
476             final int count = sService.mCount++;
477             if (DEBUG) Log.v(TAG, "Returning instance with new count " + count);
478             return sService;
479         }
480 
unref()481         public void unref() {
482             if (mCount <= 0) throw new IllegalStateException("Can't unref, count " + mCount);
483             if (--mCount == 0) {
484                 if (DEBUG) Log.v(TAG, "mCount is zero, resetting");
485                 reset();
486             }
487         }
getColor()488         public int getColor() {
489             return mColor;
490         }
getDrawable()491         public Drawable getDrawable() {
492             return mDrawable;
493         }
setColor(int color)494         public void setColor(int color) {
495             mColor = color;
496             mDrawable = null;
497         }
setDrawable(Drawable drawable)498         public void setDrawable(Drawable drawable) {
499             mDrawable = drawable;
500         }
getThemeDrawable(Context context, int themeDrawableId)501         public Drawable getThemeDrawable(Context context, int themeDrawableId) {
502             Drawable drawable = null;
503             if (mLastThemeDrawableState != null && mLastThemeDrawableId == themeDrawableId) {
504                 Drawable.ConstantState drawableState = mLastThemeDrawableState.get();
505                 if (DEBUG) Log.v(TAG, "got cached theme drawable state " + drawableState);
506                 if (drawableState != null) {
507                     drawable = drawableState.newDrawable();
508                 }
509             }
510             if (drawable == null) {
511                 drawable = ContextCompat.getDrawable(context, themeDrawableId);
512                 if (DEBUG) Log.v(TAG, "loaded theme drawable " + drawable);
513                 mLastThemeDrawableState = new WeakReference<Drawable.ConstantState>(
514                         drawable.getConstantState());
515                 mLastThemeDrawableId = themeDrawableId;
516             }
517             // No mutate required because this drawable is never manipulated.
518             return drawable;
519         }
520     }
521 
getDefaultDrawable()522     Drawable getDefaultDrawable() {
523         if (mBackgroundColor != Color.TRANSPARENT) {
524             return new ColorDrawable(mBackgroundColor);
525         } else {
526             return getThemeDrawable();
527         }
528     }
529 
getThemeDrawable()530     private Drawable getThemeDrawable() {
531         Drawable drawable = null;
532         if (mThemeDrawableResourceId != -1) {
533             drawable = mService.getThemeDrawable(mContext, mThemeDrawableResourceId);
534         }
535         if (drawable == null) {
536             drawable = createEmptyDrawable(mContext);
537         }
538         return drawable;
539     }
540 
541     /**
542      * Returns the BackgroundManager associated with the given Activity.
543      * <p>
544      * The BackgroundManager will be created on-demand for each individual
545      * Activity. Subsequent calls will return the same BackgroundManager created
546      * for this Activity.
547      */
getInstance(Activity activity)548     public static BackgroundManager getInstance(Activity activity) {
549         BackgroundFragment fragment = (BackgroundFragment) activity.getFragmentManager()
550                 .findFragmentByTag(FRAGMENT_TAG);
551         if (fragment != null) {
552             BackgroundManager manager = fragment.getBackgroundManager();
553             if (manager != null) {
554                 return manager;
555             }
556             // manager is null: this is a fragment restored by FragmentManager,
557             // fall through to create a BackgroundManager attach to it.
558         }
559         return new BackgroundManager(activity);
560     }
561 
BackgroundManager(Activity activity)562     private BackgroundManager(Activity activity) {
563         mContext = activity;
564         mService = BackgroundContinuityService.getInstance();
565         mHeightPx = mContext.getResources().getDisplayMetrics().heightPixels;
566         mWidthPx = mContext.getResources().getDisplayMetrics().widthPixels;
567         mHandler = new Handler();
568 
569         Interpolator defaultInterpolator = new FastOutLinearInInterpolator();
570         mAccelerateInterpolator = AnimationUtils.loadInterpolator(mContext,
571                 android.R.anim.accelerate_interpolator);
572         mDecelerateInterpolator = AnimationUtils.loadInterpolator(mContext,
573                 android.R.anim.decelerate_interpolator);
574 
575         mAnimator = ValueAnimator.ofInt(0, FULL_ALPHA);
576         mAnimator.addListener(mAnimationListener);
577         mAnimator.addUpdateListener(mAnimationUpdateListener);
578         mAnimator.setInterpolator(defaultInterpolator);
579 
580         TypedArray ta = activity.getTheme().obtainStyledAttributes(new int[] {
581                 android.R.attr.windowBackground });
582         mThemeDrawableResourceId = ta.getResourceId(0, -1);
583         if (mThemeDrawableResourceId < 0) {
584             if (DEBUG) Log.v(TAG, "BackgroundManager no window background resource!");
585         }
586         ta.recycle();
587 
588         createFragment(activity);
589     }
590 
createFragment(Activity activity)591     private void createFragment(Activity activity) {
592         // Use a fragment to ensure the background manager gets detached properly.
593         BackgroundFragment fragment = (BackgroundFragment) activity.getFragmentManager()
594                 .findFragmentByTag(FRAGMENT_TAG);
595         if (fragment == null) {
596             fragment = new BackgroundFragment();
597             activity.getFragmentManager().beginTransaction().add(fragment, FRAGMENT_TAG).commit();
598         } else {
599             if (fragment.getBackgroundManager() != null) {
600                 throw new IllegalStateException("Created duplicated BackgroundManager for same "
601                         + "activity, please use getInstance() instead");
602             }
603         }
604         fragment.setBackgroundManager(this);
605         mFragmentState = fragment;
606     }
607 
getImageInWrapper()608     DrawableWrapper getImageInWrapper() {
609         return mLayerDrawable == null
610                 ? null : mLayerDrawable.mWrapper[mImageInWrapperIndex];
611     }
612 
getImageOutWrapper()613     DrawableWrapper getImageOutWrapper() {
614         return mLayerDrawable == null
615                 ? null : mLayerDrawable.mWrapper[mImageOutWrapperIndex];
616     }
617 
618     /**
619      * Synchronizes state when the owning Activity is started.
620      * At that point the view becomes visible.
621      */
onActivityStart()622     void onActivityStart() {
623         updateImmediate();
624     }
625 
onStop()626     void onStop() {
627         if (isAutoReleaseOnStop()) {
628             release();
629         }
630     }
631 
onResume()632     void onResume() {
633         if (DEBUG) Log.v(TAG, "onResume " + this);
634         postChangeRunnable();
635     }
636 
syncWithService()637     private void syncWithService() {
638         int color = mService.getColor();
639         Drawable drawable = mService.getDrawable();
640 
641         if (DEBUG) Log.v(TAG, "syncWithService color " + Integer.toHexString(color)
642                 + " drawable " + drawable);
643 
644         mBackgroundColor = color;
645         mBackgroundDrawable = drawable == null ? null :
646             drawable.getConstantState().newDrawable().mutate();
647 
648         updateImmediate();
649     }
650 
651     /**
652      * Makes the background visible on the given Window. The background manager must be attached
653      * when the background is set.
654      */
attach(Window window)655     public void attach(Window window) {
656         attachToViewInternal(window.getDecorView());
657     }
658 
659     /**
660      * Sets the resource id for the drawable to be shown when there is no background set.
661      * Overrides the window background drawable from the theme. This should
662      * be called before attaching.
663      */
setThemeDrawableResourceId(int resourceId)664     public void setThemeDrawableResourceId(int resourceId) {
665         mThemeDrawableResourceId = resourceId;
666     }
667 
668     /**
669      * Adds the composite drawable to the given view.
670      */
attachToView(View sceneRoot)671     public void attachToView(View sceneRoot) {
672         attachToViewInternal(sceneRoot);
673         // clear background to reduce overdraw since the View will act as background.
674         // Activity transition below O has ghost effect for null window background where we
675         // need set a transparent background to force redraw the whole window.
676         mContext.getWindow().getDecorView().setBackground(
677                 BuildCompat.isAtLeastO() ? null : new ColorDrawable(Color.TRANSPARENT));
678     }
679 
attachToViewInternal(View sceneRoot)680     void attachToViewInternal(View sceneRoot) {
681         if (mAttached) {
682             throw new IllegalStateException("Already attached to " + mBgView);
683         }
684         mBgView = sceneRoot;
685         mAttached = true;
686         syncWithService();
687     }
688 
689     /**
690      * Returns true if the background manager is currently attached; false otherwise.
691      */
isAttached()692     public boolean isAttached() {
693         return mAttached;
694     }
695 
696     /**
697      * Release references to Drawables and put the BackgroundManager into the
698      * detached state. Called when the associated Activity is destroyed.
699      */
detach()700     void detach() {
701         if (DEBUG) Log.v(TAG, "detach " + this);
702         release();
703 
704         mBgView = null;
705         mAttached = false;
706 
707         if (mService != null) {
708             mService.unref();
709             mService = null;
710         }
711     }
712 
713     /**
714      * Release references to Drawable/Bitmap. Typically called in Activity onStop() to reduce memory
715      * overhead when not visible. It's app's responsibility to restore the drawable/bitmap in
716      * Activity onStart(). The method is automatically called in onStop() when
717      * {@link #isAutoReleaseOnStop()} is true.
718      * @see #setAutoReleaseOnStop(boolean)
719      */
release()720     public void release() {
721         if (DEBUG) Log.v(TAG, "release " + this);
722         if (mChangeRunnable != null) {
723             mHandler.removeCallbacks(mChangeRunnable);
724             mChangeRunnable = null;
725         }
726         if (mAnimator.isStarted()) {
727             mAnimator.cancel();
728         }
729         if (mLayerDrawable != null) {
730             mLayerDrawable.clearDrawable(R.id.background_imagein, mContext);
731             mLayerDrawable.clearDrawable(R.id.background_imageout, mContext);
732             mLayerDrawable = null;
733         }
734         mBackgroundDrawable = null;
735     }
736 
737     /**
738      * Sets the drawable used as a dim layer.
739      * @deprecated No longer support dim layer.
740      */
741     @Deprecated
setDimLayer(Drawable drawable)742     public void setDimLayer(Drawable drawable) {
743     }
744 
745     /**
746      * Returns the drawable used as a dim layer.
747      * @deprecated No longer support dim layer.
748      */
749     @Deprecated
getDimLayer()750     public Drawable getDimLayer() {
751         return null;
752     }
753 
754     /**
755      * Returns the default drawable used as a dim layer.
756      * @deprecated No longer support dim layer.
757      */
758     @Deprecated
getDefaultDimLayer()759     public Drawable getDefaultDimLayer() {
760         return ContextCompat.getDrawable(mContext, R.color.lb_background_protection);
761     }
762 
postChangeRunnable()763     void postChangeRunnable() {
764         if (mChangeRunnable == null || !mChangeRunnablePending) {
765             return;
766         }
767 
768         // Postpone a pending change runnable until: no existing change animation in progress &&
769         // activity is resumed (in the foreground) && layerdrawable fully opaque.
770         // If the layerdrawable is translucent then an activity transition is in progress
771         // and we want to use the optimized drawing path for performance reasons (see
772         // OptimizedTranslucentLayerDrawable).
773         if (mAnimator.isStarted()) {
774             if (DEBUG) Log.v(TAG, "animation in progress");
775         } else if (!mFragmentState.isResumed()) {
776             if (DEBUG) Log.v(TAG, "not resumed");
777         } else if (mLayerDrawable.getAlpha() < FULL_ALPHA) {
778             if (DEBUG) Log.v(TAG, "in transition, alpha " + mLayerDrawable.getAlpha());
779         } else {
780             long delayMs = getRunnableDelay();
781             if (DEBUG) Log.v(TAG, "posting runnable delayMs " + delayMs);
782             mLastSetTime = System.currentTimeMillis();
783             mHandler.postDelayed(mChangeRunnable, delayMs);
784             mChangeRunnablePending = false;
785         }
786     }
787 
lazyInit()788     private void lazyInit() {
789         if (mLayerDrawable != null) {
790             return;
791         }
792 
793         LayerDrawable layerDrawable = (LayerDrawable)
794                 ContextCompat.getDrawable(mContext, R.drawable.lb_background).mutate();
795         mLayerDrawable = createTranslucentLayerDrawable(layerDrawable);
796         mImageInWrapperIndex = mLayerDrawable.findWrapperIndexById(R.id.background_imagein);
797         mImageOutWrapperIndex = mLayerDrawable.findWrapperIndexById(R.id.background_imageout);
798         BackgroundHelper.setBackgroundPreservingAlpha(mBgView, mLayerDrawable);
799     }
800 
updateImmediate()801     private void updateImmediate() {
802         if (!mAttached) {
803             return;
804         }
805         lazyInit();
806 
807         if (mBackgroundDrawable == null) {
808             if (DEBUG) Log.v(TAG, "Use defefault background");
809             mLayerDrawable.updateDrawable(R.id.background_imagein, getDefaultDrawable());
810         } else {
811             if (DEBUG) Log.v(TAG, "Background drawable is available " + mBackgroundDrawable);
812             mLayerDrawable.updateDrawable(R.id.background_imagein, mBackgroundDrawable);
813         }
814         mLayerDrawable.clearDrawable(R.id.background_imageout, mContext);
815     }
816 
817     /**
818      * Sets the background to the given color. The timing for when this becomes
819      * visible in the app is undefined and may take place after a small delay.
820      */
setColor(@olorInt int color)821     public void setColor(@ColorInt int color) {
822         if (DEBUG) Log.v(TAG, "setColor " + Integer.toHexString(color));
823 
824         mService.setColor(color);
825         mBackgroundColor = color;
826         mBackgroundDrawable = null;
827         if (mLayerDrawable == null) {
828             return;
829         }
830         setDrawableInternal(getDefaultDrawable());
831     }
832 
833     /**
834      * Sets the given drawable into the background. The provided Drawable will be
835      * used unmodified as the background, without any scaling or cropping
836      * applied to it. The timing for when this becomes visible in the app is
837      * undefined and may take place after a small delay.
838      */
setDrawable(Drawable drawable)839     public void setDrawable(Drawable drawable) {
840         if (DEBUG) Log.v(TAG, "setBackgroundDrawable " + drawable);
841 
842         mService.setDrawable(drawable);
843         mBackgroundDrawable = drawable;
844         if (mLayerDrawable == null) {
845             return;
846         }
847         if (drawable == null) {
848             setDrawableInternal(getDefaultDrawable());
849         } else {
850             setDrawableInternal(drawable);
851         }
852     }
853 
854     /**
855      * Clears the Drawable set by {@link #setDrawable(Drawable)} or {@link #setBitmap(Bitmap)}.
856      * BackgroundManager will show a solid color set by {@link #setColor(int)} or theme drawable
857      * if color is not provided.
858      */
clearDrawable()859     public void clearDrawable() {
860         setDrawable(null);
861     }
862 
setDrawableInternal(Drawable drawable)863     private void setDrawableInternal(Drawable drawable) {
864         if (!mAttached) {
865             throw new IllegalStateException("Must attach before setting background drawable");
866         }
867 
868         if (mChangeRunnable != null) {
869             if (sameDrawable(drawable, mChangeRunnable.mDrawable)) {
870                 if (DEBUG) Log.v(TAG, "new drawable same as pending");
871                 return;
872             }
873             mHandler.removeCallbacks(mChangeRunnable);
874             mChangeRunnable = null;
875         }
876 
877         mChangeRunnable = new ChangeBackgroundRunnable(drawable);
878         mChangeRunnablePending = true;
879 
880         postChangeRunnable();
881     }
882 
getRunnableDelay()883     private long getRunnableDelay() {
884         return Math.max(0, mLastSetTime + CHANGE_BG_DELAY_MS - System.currentTimeMillis());
885     }
886 
887     /**
888      * Sets the given bitmap into the background. When using setCoverImageBitmap to set the
889      * background, the provided bitmap will be scaled and cropped to correctly
890      * fit within the dimensions of the view. The timing for when this becomes
891      * visible in the app is undefined and may take place after a small delay.
892      */
setBitmap(Bitmap bitmap)893     public void setBitmap(Bitmap bitmap) {
894         if (DEBUG) {
895             Log.v(TAG, "setCoverImageBitmap " + bitmap);
896         }
897 
898         if (bitmap == null) {
899             setDrawable(null);
900             return;
901         }
902 
903         if (bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
904             if (DEBUG) {
905                 Log.v(TAG, "invalid bitmap width or height");
906             }
907             return;
908         }
909 
910         Matrix matrix = null;
911 
912         if ((bitmap.getWidth() != mWidthPx || bitmap.getHeight() != mHeightPx)) {
913             int dwidth = bitmap.getWidth();
914             int dheight = bitmap.getHeight();
915             float scale;
916 
917             // Scale proportionately to fit width and height.
918             if (dwidth * mHeightPx > mWidthPx * dheight) {
919                 scale = (float) mHeightPx / (float) dheight;
920             } else {
921                 scale = (float) mWidthPx / (float) dwidth;
922             }
923 
924             int subX = Math.min((int) (mWidthPx / scale), dwidth);
925             int dx = Math.max(0, (dwidth - subX) / 2);
926 
927             matrix = new Matrix();
928             matrix.setScale(scale, scale);
929             matrix.preTranslate(-dx, 0);
930 
931             if (DEBUG) {
932                 Log.v(TAG, "original image size " + bitmap.getWidth() + "x" + bitmap.getHeight()
933                         + " scale " + scale + " dx " + dx);
934             }
935         }
936 
937         BitmapDrawable bitmapDrawable = new BitmapDrawable(mContext.getResources(), bitmap, matrix);
938 
939         setDrawable(bitmapDrawable);
940     }
941 
942     /**
943      * Enable or disable call release() in Activity onStop(). Default is true.
944      * @param autoReleaseOnStop True to call release() in Activity onStop(), false otherwise.
945      */
setAutoReleaseOnStop(boolean autoReleaseOnStop)946     public void setAutoReleaseOnStop(boolean autoReleaseOnStop) {
947         mAutoReleaseOnStop = autoReleaseOnStop;
948     }
949 
950     /**
951      * @return True if release() in Activity.onStop(), false otherwise.
952      */
isAutoReleaseOnStop()953     public boolean isAutoReleaseOnStop() {
954         return mAutoReleaseOnStop;
955     }
956 
957     /**
958      * Returns the current background color.
959      */
960     @ColorInt
getColor()961     public final int getColor() {
962         return mBackgroundColor;
963     }
964 
965     /**
966      * Returns the current background {@link Drawable}.
967      */
getDrawable()968     public Drawable getDrawable() {
969         return mBackgroundDrawable;
970     }
971 
sameDrawable(Drawable first, Drawable second)972     boolean sameDrawable(Drawable first, Drawable second) {
973         if (first == null || second == null) {
974             return false;
975         }
976         if (first == second) {
977             return true;
978         }
979         if (first instanceof BitmapDrawable && second instanceof BitmapDrawable) {
980             if (((BitmapDrawable) first).getBitmap().sameAs(((BitmapDrawable) second).getBitmap())) {
981                 return true;
982             }
983         }
984         if (first instanceof ColorDrawable && second instanceof ColorDrawable) {
985             if (((ColorDrawable) first).getColor() == ((ColorDrawable) second).getColor()) {
986                 return true;
987             }
988         }
989         return false;
990     }
991 
992     /**
993      * Task which changes the background.
994      */
995     final class ChangeBackgroundRunnable implements Runnable {
996         final Drawable mDrawable;
997 
ChangeBackgroundRunnable(Drawable drawable)998         ChangeBackgroundRunnable(Drawable drawable) {
999             mDrawable = drawable;
1000         }
1001 
1002         @Override
run()1003         public void run() {
1004             runTask();
1005             mChangeRunnable = null;
1006         }
1007 
runTask()1008         private void runTask() {
1009             if (mLayerDrawable == null) {
1010                 if (DEBUG) Log.v(TAG, "runTask while released - should not happen");
1011                 return;
1012             }
1013 
1014             DrawableWrapper imageInWrapper = getImageInWrapper();
1015             if (imageInWrapper != null) {
1016                 if (sameDrawable(mDrawable, imageInWrapper.getDrawable())) {
1017                     if (DEBUG) Log.v(TAG, "new drawable same as current");
1018                     return;
1019                 }
1020 
1021                 if (DEBUG) Log.v(TAG, "moving image in to image out");
1022                 // Order is important! Setting a drawable "removes" the
1023                 // previous one from the view
1024                 mLayerDrawable.clearDrawable(R.id.background_imagein, mContext);
1025                 mLayerDrawable.updateDrawable(R.id.background_imageout,
1026                         imageInWrapper.getDrawable());
1027             }
1028 
1029             applyBackgroundChanges();
1030         }
1031 
applyBackgroundChanges()1032         void applyBackgroundChanges() {
1033             if (!mAttached) {
1034                 return;
1035             }
1036 
1037             if (DEBUG) Log.v(TAG, "applyBackgroundChanges drawable " + mDrawable);
1038 
1039             DrawableWrapper imageInWrapper = getImageInWrapper();
1040             if (imageInWrapper == null && mDrawable != null) {
1041                 if (DEBUG) Log.v(TAG, "creating new imagein drawable");
1042                 imageInWrapper = mLayerDrawable.updateDrawable(
1043                         R.id.background_imagein, mDrawable);
1044                 if (DEBUG) Log.v(TAG, "imageInWrapper animation starting");
1045                 mLayerDrawable.setWrapperAlpha(mImageInWrapperIndex, 0);
1046             }
1047 
1048             mAnimator.setDuration(FADE_DURATION);
1049             mAnimator.start();
1050 
1051         }
1052 
1053     }
1054 
1055     static class EmptyDrawable extends BitmapDrawable {
EmptyDrawable(Resources res)1056         EmptyDrawable(Resources res) {
1057             super(res, (Bitmap) null);
1058         }
1059     }
1060 
createEmptyDrawable(Context context)1061     static Drawable createEmptyDrawable(Context context) {
1062         return new EmptyDrawable(context.getResources());
1063     }
1064 
1065 }
1066