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