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