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