Home | History | Annotate | Download | only in leanback
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.example.android.leanback;
     18 
     19 import android.app.Activity;
     20 import android.graphics.Bitmap;
     21 import android.graphics.drawable.BitmapDrawable;
     22 import android.graphics.drawable.Drawable;
     23 import android.os.AsyncTask;
     24 import android.os.Handler;
     25 import android.util.Log;
     26 import android.view.View;
     27 
     28 import androidx.core.content.ContextCompat;
     29 import androidx.leanback.app.BackgroundManager;
     30 
     31 /**
     32  * App uses BackgroundHelper for each Activity, it wraps BackgroundManager and provides:
     33  * 1. AsyncTask to load bitmap in background thread.
     34  * 2. Using a BitmapCache to cache loaded bitmaps.
     35  */
     36 public class BackgroundHelper {
     37 
     38     private static final String TAG = "BackgroundHelper";
     39     private static final boolean DEBUG = false;
     40     private static final boolean ENABLED = true;
     41 
     42     // Background delay serves to avoid kicking off expensive bitmap loading
     43     // in case multiple backgrounds are set in quick succession.
     44     private static final int SET_BACKGROUND_DELAY_MS = 100;
     45 
     46     /**
     47      * An very simple example of BitmapCache.
     48      */
     49     public static class BitmapCache {
     50         Bitmap mLastBitmap;
     51         Object mLastToken;
     52 
     53         // Singleton BitmapCache shared by multiple activities/backgroundHelper.
     54         static BitmapCache sInstance = new BitmapCache();
     55 
     56         private BitmapCache() {
     57         }
     58 
     59         /**
     60          * Get cached bitmap by token, returns null if missing cache.
     61          */
     62         public Bitmap getCache(Object token) {
     63             if (token == null ? mLastToken == null : token.equals(mLastToken)) {
     64                 if (DEBUG) Log.v(TAG, "hitCache token:" + token + " " + mLastBitmap);
     65                 return mLastBitmap;
     66             }
     67             return null;
     68         }
     69 
     70         /**
     71          * Add cached bitmap.
     72          */
     73         public void putCache(Object token, Bitmap bitmap) {
     74             if (DEBUG) Log.v(TAG, "putCache token:" + token + " " + bitmap);
     75             mLastToken = token;
     76             mLastBitmap = bitmap;
     77         }
     78 
     79         /**
     80          * Add singleton of BitmapCache shared across activities.
     81          */
     82         public static BitmapCache getInstance() {
     83             return sInstance;
     84         }
     85     }
     86 
     87     /**
     88      * Callback class to perform task after bitmap is loaded.
     89      */
     90     public abstract static class BitmapLoadCallback {
     91         /**
     92          * Called when Bitmap is loaded.
     93          */
     94         public abstract void onBitmapLoaded(Bitmap bitmap);
     95     }
     96 
     97     static class Request {
     98         Object mImageToken;
     99         Bitmap mResult;
    100 
    101         Request(Object imageToken) {
    102             mImageToken = imageToken;
    103         }
    104     }
    105 
    106     public BackgroundHelper(Activity activity) {
    107         if (DEBUG && !ENABLED) Log.v(TAG, "BackgroundHelper: disabled");
    108         mActivity = activity;
    109     }
    110 
    111     class LoadBackgroundRunnable implements Runnable {
    112         Request mRequest;
    113 
    114         LoadBackgroundRunnable(Object imageToken) {
    115             mRequest = new Request(imageToken);
    116         }
    117 
    118         @Override
    119         public void run() {
    120             if (DEBUG) Log.v(TAG, "Executing task");
    121             new LoadBitmapIntoBackgroundManagerTask().execute(mRequest);
    122             mRunnable = null;
    123         }
    124     }
    125 
    126     class LoadBitmapTaskBase extends AsyncTask<Request, Object, Request> {
    127         @Override
    128         protected Request doInBackground(Request... params) {
    129             boolean cancelled = isCancelled();
    130             if (DEBUG) Log.v(TAG, "doInBackground cancelled " + cancelled);
    131             Request request = params[0];
    132             if (!cancelled) {
    133                 request.mResult = loadBitmap(request.mImageToken);
    134             }
    135             return request;
    136         }
    137 
    138         @Override
    139         protected void onPostExecute(Request request) {
    140             if (DEBUG) Log.v(TAG, "onPostExecute");
    141             BitmapCache.getInstance().putCache(request.mImageToken, request.mResult);
    142         }
    143 
    144         @Override
    145         protected void onCancelled(Request request) {
    146             if (DEBUG) Log.v(TAG, "onCancelled");
    147         }
    148 
    149         private Bitmap loadBitmap(Object imageToken) {
    150             if (imageToken instanceof Integer) {
    151                 final int resourceId = (Integer) imageToken;
    152                 if (DEBUG) Log.v(TAG, "load resourceId " + resourceId);
    153                 Drawable drawable = ContextCompat.getDrawable(mActivity, resourceId);
    154                 if (drawable instanceof BitmapDrawable) {
    155                     return ((BitmapDrawable) drawable).getBitmap();
    156                 }
    157             }
    158             return null;
    159         }
    160     }
    161 
    162     class LoadBitmapIntoBackgroundManagerTask extends LoadBitmapTaskBase {
    163         @Override
    164         protected void onPostExecute(Request request) {
    165             super.onPostExecute(request);
    166             mBackgroundManager.setBitmap(request.mResult);
    167         }
    168     }
    169 
    170     class LoadBitmapCallbackTask extends LoadBitmapTaskBase {
    171         BitmapLoadCallback mCallback;
    172 
    173         LoadBitmapCallbackTask(BitmapLoadCallback callback) {
    174             mCallback = callback;
    175         }
    176 
    177         @Override
    178         protected void onPostExecute(Request request) {
    179             super.onPostExecute(request);
    180             if (mCallback != null) {
    181                 mCallback.onBitmapLoaded(request.mResult);
    182             }
    183         }
    184     }
    185 
    186     final Activity mActivity;
    187     BackgroundManager mBackgroundManager;
    188     LoadBackgroundRunnable mRunnable;
    189 
    190     // Allocate a dedicated handler because there may be no view available
    191     // when setBackground is invoked.
    192     static Handler sHandler = new Handler();
    193 
    194     void createBackgroundManagerIfNeeded() {
    195         if (mBackgroundManager == null) {
    196             mBackgroundManager = BackgroundManager.getInstance(mActivity);
    197         }
    198     }
    199 
    200     /**
    201      * Attach BackgroundManager to activity window.
    202      */
    203     public void attachToWindow() {
    204         if (!ENABLED) {
    205             return;
    206         }
    207         if (DEBUG) Log.v(TAG, "attachToWindow " + mActivity);
    208         createBackgroundManagerIfNeeded();
    209         mBackgroundManager.attach(mActivity.getWindow());
    210     }
    211 
    212     /**
    213      * Attach BackgroundManager to a view inside activity.
    214      */
    215     public void attachToView(View backgroundView) {
    216         if (!ENABLED) {
    217             return;
    218         }
    219         if (DEBUG) Log.v(TAG, "attachToView " + mActivity + " " + backgroundView);
    220         createBackgroundManagerIfNeeded();
    221         mBackgroundManager.attachToView(backgroundView);
    222     }
    223 
    224     /**
    225      * Sets a background bitmap. It will look up the cache first if missing, an AsyncTask will
    226      * will be launched to load the bitmap.
    227      */
    228     public void setBackground(Object imageToken) {
    229         if (!ENABLED) {
    230             return;
    231         }
    232         if (DEBUG) Log.v(TAG, "set imageToken " + imageToken + " to " + mActivity);
    233         createBackgroundManagerIfNeeded();
    234         if (imageToken == null) {
    235             mBackgroundManager.setDrawable(null);
    236             return;
    237         }
    238         Bitmap cachedBitmap = BitmapCache.getInstance().getCache(imageToken);
    239         if (cachedBitmap != null) {
    240             mBackgroundManager.setBitmap(cachedBitmap);
    241             return;
    242         }
    243         if (mRunnable != null) {
    244             sHandler.removeCallbacks(mRunnable);
    245         }
    246         mRunnable = new LoadBackgroundRunnable(imageToken);
    247         sHandler.postDelayed(mRunnable, SET_BACKGROUND_DELAY_MS);
    248     }
    249 
    250     /**
    251      * Clear Drawable.
    252      */
    253     public void clearDrawable() {
    254         if (!ENABLED) {
    255             return;
    256         }
    257         if (DEBUG) Log.v(TAG, "clearDrawable to " + mActivity);
    258         createBackgroundManagerIfNeeded();
    259         mBackgroundManager.clearDrawable();
    260     }
    261 
    262     /**
    263      * Directly sets a Drawable as background.
    264      */
    265     public void setDrawable(Drawable drawable) {
    266         if (!ENABLED) {
    267             return;
    268         }
    269         if (DEBUG) Log.v(TAG, "setDrawable " + drawable + " to " + mActivity);
    270         createBackgroundManagerIfNeeded();
    271         mBackgroundManager.setDrawable(drawable);
    272     }
    273 
    274     /**
    275      * Load bitmap in background and pass result to BitmapLoadCallback.
    276      */
    277     public void loadBitmap(Object imageToken, BitmapLoadCallback callback) {
    278         Bitmap cachedBitmap = BitmapCache.getInstance().getCache(imageToken);
    279         if (cachedBitmap != null) {
    280             if (callback != null) {
    281                 callback.onBitmapLoaded(cachedBitmap);
    282                 return;
    283             }
    284         }
    285         new LoadBitmapCallbackTask(callback).execute(new Request(imageToken));
    286     }
    287 }
    288