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