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