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