1 /*
2  * Copyright (C) 2017 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.graphics.Bitmap;
20 import android.util.Log;
21 import android.util.SparseArray;
22 
23 import androidx.collection.LruCache;
24 import androidx.leanback.widget.PlaybackSeekDataProvider;
25 
26 import java.util.Iterator;
27 import java.util.Map;
28 
29 /**
30  *
31  * Base class that implements PlaybackSeekDataProvider using AsyncTask.THREAD_POOL_EXECUTOR with
32  * prefetching.
33  */
34 public abstract class PlaybackSeekAsyncDataProvider extends PlaybackSeekDataProvider {
35 
36     static final String TAG = "SeekAsyncProvider";
37 
38     long[] mSeekPositions;
39     // mCache is for the bitmap requested by user
40     final LruCache<Integer, Bitmap> mCache;
41     // mPrefetchCache is for the bitmap not requested by user but prefetched by heuristic
42     // estimation. We use a different LruCache so that items in mCache will not be evicted by
43     // prefeteched items.
44     final LruCache<Integer, Bitmap> mPrefetchCache;
45     final SparseArray<LoadBitmapTask> mRequests = new SparseArray<>();
46     int mLastRequestedIndex = -1;
47 
isCancelled(Object task)48     protected boolean isCancelled(Object task) {
49         return ((android.os.AsyncTask) task).isCancelled();
50     }
51 
doInBackground(Object task, int index, long position)52     protected abstract Bitmap doInBackground(Object task, int index, long position);
53 
54     class LoadBitmapTask extends android.os.AsyncTask<Object, Object, Bitmap> {
55 
56         int mIndex;
57         ResultCallback mResultCallback;
58 
LoadBitmapTask(int index, ResultCallback callback)59         LoadBitmapTask(int index, ResultCallback callback) {
60             mIndex = index;
61             mResultCallback = callback;
62         }
63 
64         @Override
doInBackground(Object... params)65         protected Bitmap doInBackground(Object... params) {
66             return PlaybackSeekAsyncDataProvider.this
67                     .doInBackground(this, mIndex, mSeekPositions[mIndex]);
68         }
69 
70         @Override
onPostExecute(Bitmap bitmap)71         protected void onPostExecute(Bitmap bitmap) {
72             mRequests.remove(mIndex);
73             Log.d(TAG, "thumb Loaded " + mIndex);
74             if (mResultCallback != null) {
75                 mCache.put(mIndex, bitmap);
76                 mResultCallback.onThumbnailLoaded(bitmap, mIndex);
77             } else {
78                 mPrefetchCache.put(mIndex, bitmap);
79             }
80         }
81 
82     }
83 
PlaybackSeekAsyncDataProvider()84     public PlaybackSeekAsyncDataProvider() {
85         this(16, 24);
86     }
87 
PlaybackSeekAsyncDataProvider(int cacheSize, int prefetchCacheSize)88     public PlaybackSeekAsyncDataProvider(int cacheSize, int prefetchCacheSize) {
89         mCache = new LruCache<Integer, Bitmap>(cacheSize);
90         mPrefetchCache = new LruCache<Integer, Bitmap>(prefetchCacheSize);
91     }
92 
setSeekPositions(long[] positions)93     public void setSeekPositions(long[] positions) {
94         mSeekPositions = positions;
95     }
96 
97     @Override
getSeekPositions()98     public long[] getSeekPositions() {
99         return mSeekPositions;
100     }
101 
102     @Override
103     @SuppressWarnings("deprecation") /* AsyncTask */
getThumbnail(int index, ResultCallback callback)104     public void getThumbnail(int index, ResultCallback callback) {
105         Integer key = index;
106         Bitmap bitmap = mCache.get(key);
107         if (bitmap != null) {
108             callback.onThumbnailLoaded(bitmap, index);
109         } else {
110             bitmap = mPrefetchCache.get(key);
111             if (bitmap != null) {
112                 mCache.put(key, bitmap);
113                 mPrefetchCache.remove(key);
114                 callback.onThumbnailLoaded(bitmap, index);
115             } else {
116                 LoadBitmapTask task = mRequests.get(index);
117                 if (task == null || task.isCancelled()) {
118                     // no normal task or prefetch for the position, create a new task
119                     task = new LoadBitmapTask(index, callback);
120                     mRequests.put(index, task);
121                     task.executeOnExecutor(android.os.AsyncTask.THREAD_POOL_EXECUTOR);
122                 } else {
123                     // update existing ResultCallback which might be normal task or prefetch
124                     task.mResultCallback = callback;
125                 }
126             }
127         }
128         if (mLastRequestedIndex != index) {
129             if (mLastRequestedIndex != -1) {
130                 prefetch(mLastRequestedIndex, index > mLastRequestedIndex);
131             }
132             mLastRequestedIndex = index;
133         }
134     }
135 
136     @SuppressWarnings("deprecation") /* AsyncTask */
prefetch(int hintIndex, boolean forward)137     protected void prefetch(int hintIndex, boolean forward) {
138         for (Iterator<Map.Entry<Integer, Bitmap>> it =
139                 mPrefetchCache.snapshot().entrySet().iterator(); it.hasNext(); ) {
140             Map.Entry<Integer, Bitmap> entry = it.next();
141             if (forward ? entry.getKey() < hintIndex : entry.getKey() > hintIndex) {
142                 mPrefetchCache.remove(entry.getKey());
143             }
144         }
145         int inc = forward ? 1 : -1;
146         for (int i = hintIndex; (mRequests.size() + mPrefetchCache.size()
147                 < mPrefetchCache.maxSize()) && (inc > 0 ? i < mSeekPositions.length : i >= 0);
148                 i += inc) {
149             Integer key = i;
150             if (mCache.get(key) == null && mPrefetchCache.get(key) == null) {
151                 LoadBitmapTask task = mRequests.get(i);
152                 if (task == null) {
153                     task = new LoadBitmapTask(key, null);
154                     mRequests.put(i, task);
155                     task.executeOnExecutor(android.os.AsyncTask.THREAD_POOL_EXECUTOR);
156                 }
157             }
158         }
159     }
160 
161     @Override
reset()162     public void reset() {
163         for (int i = 0; i < mRequests.size(); i++) {
164             LoadBitmapTask task = mRequests.valueAt(i);
165             task.cancel(true);
166         }
167         mRequests.clear();
168         mCache.evictAll();
169         mPrefetchCache.evictAll();
170         mLastRequestedIndex = -1;
171     }
172 
173     @Override
toString()174     public String toString() {
175         StringBuilder b = new StringBuilder();
176         b.append("Requests<");
177         for (int i = 0; i < mRequests.size(); i++) {
178             b.append(mRequests.keyAt(i));
179             b.append(",");
180         }
181         b.append("> Cache<");
182         for (Iterator<Integer> it = mCache.snapshot().keySet().iterator(); it.hasNext();) {
183             Integer key = it.next();
184             if (mCache.get(key) != null) {
185                 b.append(key);
186                 b.append(",");
187             }
188         }
189         b.append(">");
190         b.append("> PrefetchCache<");
191         for (Iterator<Integer> it = mPrefetchCache.snapshot().keySet().iterator(); it.hasNext();) {
192             Integer key = it.next();
193             if (mPrefetchCache.get(key) != null) {
194                 b.append(key);
195                 b.append(",");
196             }
197         }
198         b.append(">");
199         return b.toString();
200     }
201 }
202