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