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 
BitmapCache()56         private BitmapCache() {
57         }
58 
59         /**
60          * Get cached bitmap by token, returns null if missing cache.
61          */
getCache(Object token)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          */
putCache(Object token, Bitmap bitmap)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          */
getInstance()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          */
onBitmapLoaded(Bitmap bitmap)94         public abstract void onBitmapLoaded(Bitmap bitmap);
95     }
96 
97     static class Request {
98         Object mImageToken;
99         Bitmap mResult;
100 
Request(Object imageToken)101         Request(Object imageToken) {
102             mImageToken = imageToken;
103         }
104     }
105 
BackgroundHelper(Activity activity)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 
LoadBackgroundRunnable(Object imageToken)114         LoadBackgroundRunnable(Object imageToken) {
115             mRequest = new Request(imageToken);
116         }
117 
118         @Override
run()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
doInBackground(Request... params)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
onPostExecute(Request request)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
onCancelled(Request request)145         protected void onCancelled(Request request) {
146             if (DEBUG) Log.v(TAG, "onCancelled");
147         }
148 
loadBitmap(Object imageToken)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
onPostExecute(Request request)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 
LoadBitmapCallbackTask(BitmapLoadCallback callback)173         LoadBitmapCallbackTask(BitmapLoadCallback callback) {
174             mCallback = callback;
175         }
176 
177         @Override
onPostExecute(Request request)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 
createBackgroundManagerIfNeeded()194     void createBackgroundManagerIfNeeded() {
195         if (mBackgroundManager == null) {
196             mBackgroundManager = BackgroundManager.getInstance(mActivity);
197         }
198     }
199 
200     /**
201      * Attach BackgroundManager to activity window.
202      */
attachToWindow()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      */
attachToView(View backgroundView)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      */
setBackground(Object imageToken)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      */
clearDrawable()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      */
setDrawable(Drawable drawable)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      */
loadBitmap(Object imageToken, BitmapLoadCallback callback)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