1 /*
2  * Copyright (C) 2018 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 package com.android.quickstep;
17 
18 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
19 
20 import android.content.Context;
21 import android.content.res.Resources;
22 import android.os.Handler;
23 import android.os.Looper;
24 
25 import com.android.launcher3.R;
26 import com.android.launcher3.Utilities;
27 import com.android.launcher3.icons.cache.HandlerRunnable;
28 import com.android.launcher3.util.Preconditions;
29 import com.android.quickstep.util.TaskKeyLruCache;
30 import com.android.systemui.shared.recents.model.Task;
31 import com.android.systemui.shared.recents.model.Task.TaskKey;
32 import com.android.systemui.shared.recents.model.ThumbnailData;
33 import com.android.systemui.shared.system.ActivityManagerWrapper;
34 
35 import java.util.ArrayList;
36 import java.util.function.Consumer;
37 
38 public class TaskThumbnailCache {
39 
40     private final Handler mBackgroundHandler;
41 
42     private final int mCacheSize;
43     private final TaskKeyLruCache<ThumbnailData> mCache;
44     private final HighResLoadingState mHighResLoadingState;
45     private final boolean mEnableTaskSnapshotPreloading;
46 
47     public static class HighResLoadingState {
48         private boolean mForceHighResThumbnails;
49         private boolean mVisible;
50         private boolean mFlingingFast;
51         private boolean mHighResLoadingEnabled;
52         private ArrayList<HighResLoadingStateChangedCallback> mCallbacks = new ArrayList<>();
53 
54         public interface HighResLoadingStateChangedCallback {
onHighResLoadingStateChanged(boolean enabled)55             void onHighResLoadingStateChanged(boolean enabled);
56         }
57 
HighResLoadingState(Context context)58         private HighResLoadingState(Context context) {
59             // If the device does not support low-res thumbnails, only attempt to load high-res
60             // thumbnails
61             mForceHighResThumbnails = !supportsLowResThumbnails();
62         }
63 
addCallback(HighResLoadingStateChangedCallback callback)64         public void addCallback(HighResLoadingStateChangedCallback callback) {
65             mCallbacks.add(callback);
66         }
67 
removeCallback(HighResLoadingStateChangedCallback callback)68         public void removeCallback(HighResLoadingStateChangedCallback callback) {
69             mCallbacks.remove(callback);
70         }
71 
setVisible(boolean visible)72         public void setVisible(boolean visible) {
73             mVisible = visible;
74             updateState();
75         }
76 
setFlingingFast(boolean flingingFast)77         public void setFlingingFast(boolean flingingFast) {
78             mFlingingFast = flingingFast;
79             updateState();
80         }
81 
isEnabled()82         public boolean isEnabled() {
83             return mHighResLoadingEnabled;
84         }
85 
updateState()86         private void updateState() {
87             boolean prevState = mHighResLoadingEnabled;
88             mHighResLoadingEnabled = mForceHighResThumbnails || (mVisible && !mFlingingFast);
89             if (prevState != mHighResLoadingEnabled) {
90                 for (int i = mCallbacks.size() - 1; i >= 0; i--) {
91                     mCallbacks.get(i).onHighResLoadingStateChanged(mHighResLoadingEnabled);
92                 }
93             }
94         }
95     }
96 
TaskThumbnailCache(Context context, Looper backgroundLooper)97     public TaskThumbnailCache(Context context, Looper backgroundLooper) {
98         mBackgroundHandler = new Handler(backgroundLooper);
99         mHighResLoadingState = new HighResLoadingState(context);
100 
101         Resources res = context.getResources();
102         mCacheSize = res.getInteger(R.integer.recentsThumbnailCacheSize);
103         mEnableTaskSnapshotPreloading = res.getBoolean(R.bool.config_enableTaskSnapshotPreloading);
104         mCache = new TaskKeyLruCache<>(mCacheSize);
105     }
106 
107     /**
108      * Synchronously fetches the thumbnail for the given {@param task} and puts it in the cache.
109      */
updateThumbnailInCache(Task task)110     public void updateThumbnailInCache(Task task) {
111         Preconditions.assertUIThread();
112         // Fetch the thumbnail for this task and put it in the cache
113         if (task.thumbnail == null) {
114             updateThumbnailInBackground(task.key, true /* lowResolution */,
115                     t -> task.thumbnail = t);
116         }
117     }
118 
119     /**
120      * Synchronously updates the thumbnail in the cache if it is already there.
121      */
updateTaskSnapShot(int taskId, ThumbnailData thumbnail)122     public void updateTaskSnapShot(int taskId, ThumbnailData thumbnail) {
123         Preconditions.assertUIThread();
124         mCache.updateIfAlreadyInCache(taskId, thumbnail);
125     }
126 
127     /**
128      * Asynchronously fetches the icon and other task data for the given {@param task}.
129      *
130      * @param callback The callback to receive the task after its data has been populated.
131      * @return A cancelable handle to the request
132      */
updateThumbnailInBackground( Task task, Consumer<ThumbnailData> callback)133     public ThumbnailLoadRequest updateThumbnailInBackground(
134             Task task, Consumer<ThumbnailData> callback) {
135         Preconditions.assertUIThread();
136 
137         boolean lowResolution = !mHighResLoadingState.isEnabled();
138         if (task.thumbnail != null && (!task.thumbnail.reducedResolution || lowResolution)) {
139             // Nothing to load, the thumbnail is already high-resolution or matches what the
140             // request, so just callback
141             callback.accept(task.thumbnail);
142             return null;
143         }
144 
145 
146         return updateThumbnailInBackground(task.key, !mHighResLoadingState.isEnabled(), t -> {
147             task.thumbnail = t;
148             callback.accept(t);
149         });
150     }
151 
updateThumbnailInBackground(TaskKey key, boolean lowResolution, Consumer<ThumbnailData> callback)152     private ThumbnailLoadRequest updateThumbnailInBackground(TaskKey key, boolean lowResolution,
153             Consumer<ThumbnailData> callback) {
154         Preconditions.assertUIThread();
155 
156         ThumbnailData cachedThumbnail = mCache.getAndInvalidateIfModified(key);
157         if (cachedThumbnail != null && (!cachedThumbnail.reducedResolution || lowResolution)) {
158             // Already cached, lets use that thumbnail
159             callback.accept(cachedThumbnail);
160             return null;
161         }
162 
163         ThumbnailLoadRequest request = new ThumbnailLoadRequest(mBackgroundHandler,
164                 lowResolution) {
165             @Override
166             public void run() {
167                 ThumbnailData thumbnail = ActivityManagerWrapper.getInstance().getTaskThumbnail(
168                         key.id, lowResolution);
169 
170                 MAIN_EXECUTOR.execute(() -> {
171                     if (isCanceled()) {
172                         // We don't call back to the provided callback in this case
173                         return;
174                     }
175 
176                     mCache.put(key, thumbnail);
177                     callback.accept(thumbnail);
178                     onEnd();
179                 });
180             }
181         };
182         Utilities.postAsyncCallback(mBackgroundHandler, request);
183         return request;
184     }
185 
186     /**
187      * Clears the cache.
188      */
clear()189     public void clear() {
190         mCache.evictAll();
191     }
192 
193     /**
194      * Removes the cached thumbnail for the given task.
195      */
remove(Task.TaskKey key)196     public void remove(Task.TaskKey key) {
197         mCache.remove(key);
198     }
199 
200     /**
201      * @return The cache size.
202      */
getCacheSize()203     public int getCacheSize() {
204         return mCacheSize;
205     }
206 
207     /**
208      * @return The mutable high-res loading state.
209      */
getHighResLoadingState()210     public HighResLoadingState getHighResLoadingState() {
211         return mHighResLoadingState;
212     }
213 
214     /**
215      * @return Whether to enable background preloading of task thumbnails.
216      */
isPreloadingEnabled()217     public boolean isPreloadingEnabled() {
218         return mEnableTaskSnapshotPreloading && mHighResLoadingState.mVisible;
219     }
220 
221     public static abstract class ThumbnailLoadRequest extends HandlerRunnable {
222         public final boolean mLowResolution;
223 
ThumbnailLoadRequest(Handler handler, boolean lowResolution)224         ThumbnailLoadRequest(Handler handler, boolean lowResolution) {
225             super(handler, null);
226             mLowResolution = lowResolution;
227         }
228     }
229 
230     /**
231      * @return Whether device supports low-res thumbnails. Low-res files are an optimization
232      * for faster load times of snapshots. Devices can optionally disable low-res files so that
233      * they only store snapshots at high-res scale. The actual scale can be configured in
234      * frameworks/base config overlay.
235      */
supportsLowResThumbnails()236     private static boolean supportsLowResThumbnails() {
237         Resources res = Resources.getSystem();
238         int resId = res.getIdentifier("config_lowResTaskSnapshotScale", "dimen", "android");
239         if (resId != 0) {
240             return 0 < res.getFloat(resId);
241         }
242         return true;
243     }
244 
245 }
246