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