Home | History | Annotate | Download | only in app
      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 androidx.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.util.Log;
     35 import android.view.View;
     36 import android.view.Window;
     37 import android.view.animation.AnimationUtils;
     38 import android.view.animation.Interpolator;
     39 
     40 import androidx.annotation.ColorInt;
     41 import androidx.annotation.NonNull;
     42 import androidx.core.content.ContextCompat;
     43 import androidx.core.graphics.drawable.DrawableCompat;
     44 import androidx.interpolator.view.animation.FastOutLinearInInterpolator;
     45 import androidx.leanback.R;
     46 import androidx.leanback.widget.BackgroundHelper;
     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 
    120             ConstantState(Bitmap bitmap, Matrix matrix) {
    121                 mBitmap = bitmap;
    122                 mMatrix = matrix != null ? matrix : new Matrix();
    123                 mPaint.setFilterBitmap(true);
    124             }
    125 
    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
    139             public Drawable newDrawable() {
    140                 return new BitmapDrawable(this);
    141             }
    142 
    143             @Override
    144             public int getChangingConfigurations() {
    145                 return 0;
    146             }
    147         }
    148 
    149         ConstantState mState;
    150         boolean mMutated;
    151 
    152         BitmapDrawable(Resources resources, Bitmap bitmap) {
    153             this(resources, bitmap, null);
    154         }
    155 
    156         BitmapDrawable(Resources resources, Bitmap bitmap, Matrix matrix) {
    157             mState = new ConstantState(bitmap, matrix);
    158         }
    159 
    160         BitmapDrawable(ConstantState state) {
    161             mState = state;
    162         }
    163 
    164         Bitmap getBitmap() {
    165             return mState.mBitmap;
    166         }
    167 
    168         @Override
    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
    180         public int getOpacity() {
    181             return android.graphics.PixelFormat.TRANSLUCENT;
    182         }
    183 
    184         @Override
    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
    198         public void setColorFilter(ColorFilter cf) {
    199             mutate();
    200             mState.mPaint.setColorFilter(cf);
    201             invalidateSelf();
    202         }
    203 
    204         @Override
    205         public ColorFilter getColorFilter() {
    206             return mState.mPaint.getColorFilter();
    207         }
    208 
    209         @Override
    210         public ConstantState getConstantState() {
    211             return mState;
    212         }
    213 
    214         @NonNull
    215         @Override
    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 
    229         public DrawableWrapper(Drawable drawable) {
    230             mDrawable = drawable;
    231         }
    232         public DrawableWrapper(DrawableWrapper wrapper, Drawable drawable) {
    233             mDrawable = drawable;
    234             mAlpha = wrapper.mAlpha;
    235         }
    236 
    237         public Drawable getDrawable() {
    238             return mDrawable;
    239         }
    240 
    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 
    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
    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 
    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
    283         public int getAlpha() {
    284             return mAlpha;
    285         }
    286 
    287         @Override
    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
    300         public int getOpacity() {
    301             return PixelFormat.TRANSLUCENT;
    302         }
    303 
    304         @Override
    305         public boolean setDrawableByLayerId(int id, Drawable drawable) {
    306             return updateDrawable(id, drawable) != null;
    307         }
    308 
    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 
    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 
    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
    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
    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 
    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 final 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 
    466         private BackgroundContinuityService() {
    467             reset();
    468         }
    469 
    470         private void reset() {
    471             mColor = Color.TRANSPARENT;
    472             mDrawable = null;
    473         }
    474 
    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 
    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         }
    488         public int getColor() {
    489             return mColor;
    490         }
    491         public Drawable getDrawable() {
    492             return mDrawable;
    493         }
    494         public void setColor(int color) {
    495             mColor = color;
    496             mDrawable = null;
    497         }
    498         public void setDrawable(Drawable drawable) {
    499             mDrawable = drawable;
    500         }
    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 
    522     Drawable getDefaultDrawable() {
    523         if (mBackgroundColor != Color.TRANSPARENT) {
    524             return new ColorDrawable(mBackgroundColor);
    525         } else {
    526             return getThemeDrawable();
    527         }
    528     }
    529 
    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      */
    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 
    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 
    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 
    608     DrawableWrapper getImageInWrapper() {
    609         return mLayerDrawable == null
    610                 ? null : mLayerDrawable.mWrapper[mImageInWrapperIndex];
    611     }
    612 
    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      */
    622     void onActivityStart() {
    623         updateImmediate();
    624     }
    625 
    626     void onStop() {
    627         if (isAutoReleaseOnStop()) {
    628             release();
    629         }
    630     }
    631 
    632     void onResume() {
    633         if (DEBUG) Log.v(TAG, "onResume " + this);
    634         postChangeRunnable();
    635     }
    636 
    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      */
    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      */
    664     public void setThemeDrawableResourceId(int resourceId) {
    665         mThemeDrawableResourceId = resourceId;
    666     }
    667 
    668     /**
    669      * Adds the composite drawable to the given view.
    670      */
    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                 Build.VERSION.SDK_INT >= 26 ? null : new ColorDrawable(Color.TRANSPARENT));
    678     }
    679 
    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      */
    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      */
    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      */
    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
    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
    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
    759     public Drawable getDefaultDimLayer() {
    760         return ContextCompat.getDrawable(mContext, R.color.lb_background_protection);
    761     }
    762 
    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 
    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 
    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      */
    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      */
    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      */
    859     public void clearDrawable() {
    860         setDrawable(null);
    861     }
    862 
    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 
    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      */
    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      */
    946     public void setAutoReleaseOnStop(boolean autoReleaseOnStop) {
    947         mAutoReleaseOnStop = autoReleaseOnStop;
    948     }
    949 
    950     /**
    951      * @return True if release() in Activity.onStop(), false otherwise.
    952      */
    953     public boolean isAutoReleaseOnStop() {
    954         return mAutoReleaseOnStop;
    955     }
    956 
    957     /**
    958      * Returns the current background color.
    959      */
    960     @ColorInt
    961     public final int getColor() {
    962         return mBackgroundColor;
    963     }
    964 
    965     /**
    966      * Returns the current background {@link Drawable}.
    967      */
    968     public Drawable getDrawable() {
    969         return mBackgroundDrawable;
    970     }
    971 
    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 
    998         ChangeBackgroundRunnable(Drawable drawable) {
    999             mDrawable = drawable;
   1000         }
   1001 
   1002         @Override
   1003         public void run() {
   1004             runTask();
   1005             mChangeRunnable = null;
   1006         }
   1007 
   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 
   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 {
   1056         EmptyDrawable(Resources res) {
   1057             super(res, (Bitmap) null);
   1058         }
   1059     }
   1060 
   1061     static Drawable createEmptyDrawable(Context context) {
   1062         return new EmptyDrawable(context.getResources());
   1063     }
   1064 
   1065 }
   1066