BackgroundHelper.java revision 0908efd712e79f77e0cf9307bd5c32753c855561
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
17package com.example.android.leanback;
18
19import android.app.Activity;
20import android.graphics.Bitmap;
21import android.graphics.drawable.BitmapDrawable;
22import android.graphics.drawable.Drawable;
23import android.os.AsyncTask;
24import android.os.Handler;
25import android.support.v17.leanback.app.BackgroundManager;
26import android.support.v4.content.ContextCompat;
27import android.util.Log;
28import 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 */
35public 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