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