13bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu/*
23bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu * Copyright (C) 2017 The Android Open Source Project
33bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu *
43bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu * Licensed under the Apache License, Version 2.0 (the "License");
53bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu * you may not use this file except in compliance with the License.
63bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu * You may obtain a copy of the License at
73bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu *
83bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu *      http://www.apache.org/licenses/LICENSE-2.0
93bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu *
103bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu * Unless required by applicable law or agreed to in writing, software
113bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu * distributed under the License is distributed on an "AS IS" BASIS,
123bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
133bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu * See the License for the specific language governing permissions and
143bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu * limitations under the License.
153bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu */
163bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu
173bcad88cbf4488e747d84893c35f2351b8f84afeDake Gupackage com.example.android.leanback;
183bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu
193bcad88cbf4488e747d84893c35f2351b8f84afeDake Guimport android.graphics.Bitmap;
203bcad88cbf4488e747d84893c35f2351b8f84afeDake Guimport android.os.AsyncTask;
213bcad88cbf4488e747d84893c35f2351b8f84afeDake Guimport android.util.Log;
223bcad88cbf4488e747d84893c35f2351b8f84afeDake Guimport android.util.SparseArray;
233bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu
24def582a5836579a3fadabfdbe4413cb1652bf098Aurimas Liutikasimport androidx.collection.LruCache;
25def582a5836579a3fadabfdbe4413cb1652bf098Aurimas Liutikasimport androidx.leanback.widget.PlaybackSeekDataProvider;
26def582a5836579a3fadabfdbe4413cb1652bf098Aurimas Liutikas
273bcad88cbf4488e747d84893c35f2351b8f84afeDake Guimport java.util.Iterator;
283bcad88cbf4488e747d84893c35f2351b8f84afeDake Guimport java.util.Map;
293bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu
303bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu/**
313bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu *
323bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu * Base class that implements PlaybackSeekDataProvider using AsyncTask.THREAD_POOL_EXECUTOR with
333bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu * prefetching.
343bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu */
353bcad88cbf4488e747d84893c35f2351b8f84afeDake Gupublic abstract class PlaybackSeekAsyncDataProvider extends PlaybackSeekDataProvider {
363bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu
373bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    static final String TAG = "SeekAsyncProvider";
383bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu
393bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    long[] mSeekPositions;
403bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    // mCache is for the bitmap requested by user
413bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    final LruCache<Integer, Bitmap> mCache;
423bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    // mPrefetchCache is for the bitmap not requested by user but prefetched by heuristic
433bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    // estimation. We use a different LruCache so that items in mCache will not be evicted by
443bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    // prefeteched items.
453bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    final LruCache<Integer, Bitmap> mPrefetchCache;
463bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    final SparseArray<LoadBitmapTask> mRequests = new SparseArray<>();
473bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    int mLastRequestedIndex = -1;
483bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu
493bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    protected boolean isCancelled(Object task) {
503bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        return ((AsyncTask) task).isCancelled();
513bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    }
523bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu
533bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    protected abstract Bitmap doInBackground(Object task, int index, long position);
543bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu
553bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    class LoadBitmapTask extends AsyncTask<Object, Object, Bitmap> {
563bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu
573bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        int mIndex;
583bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        ResultCallback mResultCallback;
593bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu
603bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        LoadBitmapTask(int index, ResultCallback callback) {
613bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            mIndex = index;
623bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            mResultCallback = callback;
633bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        }
643bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu
653bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        @Override
663bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        protected Bitmap doInBackground(Object[] params) {
673bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            return PlaybackSeekAsyncDataProvider.this
683bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                    .doInBackground(this, mIndex, mSeekPositions[mIndex]);
693bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        }
703bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu
713bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        @Override
723bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        protected void onPostExecute(Bitmap bitmap) {
733bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            mRequests.remove(mIndex);
743bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            Log.d(TAG, "thumb Loaded " + mIndex);
753bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            if (mResultCallback != null) {
763bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                mCache.put(mIndex, bitmap);
773bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                mResultCallback.onThumbnailLoaded(bitmap, mIndex);
783bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            } else {
793bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                mPrefetchCache.put(mIndex, bitmap);
803bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            }
813bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        }
823bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu
833bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    }
843bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu
853bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    public PlaybackSeekAsyncDataProvider() {
863bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        this(16, 24);
873bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    }
883bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu
893bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    public PlaybackSeekAsyncDataProvider(int cacheSize, int prefetchCacheSize) {
903bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        mCache = new LruCache<Integer, Bitmap>(cacheSize);
913bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        mPrefetchCache = new LruCache<Integer, Bitmap>(prefetchCacheSize);
923bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    }
933bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu
943bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    public void setSeekPositions(long[] positions) {
953bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        mSeekPositions = positions;
963bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    }
973bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu
983bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    @Override
993bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    public long[] getSeekPositions() {
1003bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        return mSeekPositions;
1013bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    }
1023bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu
1033bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    @Override
1043bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    public void getThumbnail(int index, ResultCallback callback) {
1053bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        Integer key = index;
1063bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        Bitmap bitmap = mCache.get(key);
1073bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        if (bitmap != null) {
1083bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            callback.onThumbnailLoaded(bitmap, index);
1093bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        } else {
1103bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            bitmap = mPrefetchCache.get(key);
1113bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            if (bitmap != null) {
1123bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                mCache.put(key, bitmap);
1133bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                mPrefetchCache.remove(key);
1143bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                callback.onThumbnailLoaded(bitmap, index);
1153bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            } else {
1163bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                LoadBitmapTask task = mRequests.get(index);
1173bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                if (task == null || task.isCancelled()) {
1183bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                    // no normal task or prefetch for the position, create a new task
1193bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                    task = new LoadBitmapTask(index, callback);
1203bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                    mRequests.put(index, task);
1213bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                    task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
1223bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                } else {
1233bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                    // update existing ResultCallback which might be normal task or prefetch
1243bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                    task.mResultCallback = callback;
1253bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                }
1263bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            }
1273bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        }
1283bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        if (mLastRequestedIndex != index) {
1293bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            if (mLastRequestedIndex != -1) {
1303bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                prefetch(mLastRequestedIndex, index > mLastRequestedIndex);
1313bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            }
1323bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            mLastRequestedIndex = index;
1333bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        }
1343bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    }
1353bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu
1363bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    protected void prefetch(int hintIndex, boolean forward) {
1373bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        for (Iterator<Map.Entry<Integer, Bitmap>> it =
1383bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                mPrefetchCache.snapshot().entrySet().iterator(); it.hasNext(); ) {
1393bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            Map.Entry<Integer, Bitmap> entry = it.next();
1403bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            if (forward ? entry.getKey() < hintIndex : entry.getKey() > hintIndex) {
1413bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                mPrefetchCache.remove(entry.getKey());
1423bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            }
1433bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        }
1443bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        int inc = forward ? 1 : -1;
1453bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        for (int i = hintIndex; (mRequests.size() + mPrefetchCache.size()
1463bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                < mPrefetchCache.maxSize()) && (inc > 0 ? i < mSeekPositions.length : i >= 0);
1473bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                i += inc) {
1483bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            Integer key = i;
1493bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            if (mCache.get(key) == null && mPrefetchCache.get(key) == null) {
1503bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                LoadBitmapTask task = mRequests.get(i);
1513bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                if (task == null) {
1523bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                    task = new LoadBitmapTask(key, null);
1533bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                    mRequests.put(i, task);
1543bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                    task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
1553bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                }
1563bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            }
1573bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        }
1583bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    }
1593bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu
1603bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    @Override
1613bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    public void reset() {
1623bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        for (int i = 0; i < mRequests.size(); i++) {
1633bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            LoadBitmapTask task = mRequests.valueAt(i);
1643bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            task.cancel(true);
1653bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        }
1663bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        mRequests.clear();
1673bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        mCache.evictAll();
1683bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        mPrefetchCache.evictAll();
1693bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        mLastRequestedIndex = -1;
1703bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    }
1713bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu
1723bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    @Override
1733bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    public String toString() {
1743bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        StringBuilder b = new StringBuilder();
1753bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        b.append("Requests<");
1763bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        for (int i = 0; i < mRequests.size(); i++) {
1773bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            b.append(mRequests.keyAt(i));
1783bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            b.append(",");
1793bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        }
1803bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        b.append("> Cache<");
1813bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        for (Iterator<Integer> it = mCache.snapshot().keySet().iterator(); it.hasNext();) {
1823bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            Integer key = it.next();
1833bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            if (mCache.get(key) != null) {
1843bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                b.append(key);
1853bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                b.append(",");
1863bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            }
1873bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        }
1883bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        b.append(">");
1893bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        b.append("> PrefetchCache<");
1903bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        for (Iterator<Integer> it = mPrefetchCache.snapshot().keySet().iterator(); it.hasNext();) {
1913bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            Integer key = it.next();
1923bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            if (mPrefetchCache.get(key) != null) {
1933bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                b.append(key);
1943bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu                b.append(",");
1953bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu            }
1963bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        }
1973bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        b.append(">");
1983bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu        return b.toString();
1993bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu    }
2003bcad88cbf4488e747d84893c35f2351b8f84afeDake Gu}
201