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