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 Preconditions.assertUIThread(); 108 // Fetch the thumbnail for this task and put it in the cache 109 if (task.thumbnail == null) { 110 updateThumbnailInBackground(task.key, true /* lowResolution */, 111 t -> task.thumbnail = t); 112 } 113 } 114 115 /** 116 * Synchronously updates the thumbnail in the cache if it is already there. 117 */ updateTaskSnapShot(int taskId, ThumbnailData thumbnail)118 public void updateTaskSnapShot(int taskId, ThumbnailData thumbnail) { 119 Preconditions.assertUIThread(); 120 mCache.updateIfAlreadyInCache(taskId, thumbnail); 121 } 122 123 /** 124 * Asynchronously fetches the icon and other task data for the given {@param task}. 125 * 126 * @param callback The callback to receive the task after its data has been populated. 127 * @return A cancelable handle to the request 128 */ updateThumbnailInBackground( Task task, Consumer<ThumbnailData> callback)129 public CancellableTask updateThumbnailInBackground( 130 Task task, Consumer<ThumbnailData> callback) { 131 Preconditions.assertUIThread(); 132 133 boolean lowResolution = !mHighResLoadingState.isEnabled(); 134 if (task.thumbnail != null && (!task.thumbnail.reducedResolution || lowResolution)) { 135 // Nothing to load, the thumbnail is already high-resolution or matches what the 136 // request, so just callback 137 callback.accept(task.thumbnail); 138 return null; 139 } 140 141 return updateThumbnailInBackground(task.key, !mHighResLoadingState.isEnabled(), t -> { 142 task.thumbnail = t; 143 callback.accept(t); 144 }); 145 } 146 updateThumbnailInBackground(TaskKey key, boolean lowResolution, Consumer<ThumbnailData> callback)147 private CancellableTask updateThumbnailInBackground(TaskKey key, boolean lowResolution, 148 Consumer<ThumbnailData> callback) { 149 Preconditions.assertUIThread(); 150 151 ThumbnailData cachedThumbnail = mCache.getAndInvalidateIfModified(key); 152 if (cachedThumbnail != null && (!cachedThumbnail.reducedResolution || lowResolution)) { 153 // Already cached, lets use that thumbnail 154 callback.accept(cachedThumbnail); 155 return null; 156 } 157 158 CancellableTask<ThumbnailData> request = new CancellableTask<ThumbnailData>() { 159 @Override 160 public ThumbnailData getResultOnBg() { 161 return ActivityManagerWrapper.getInstance().getTaskThumbnail( 162 key.id, lowResolution); 163 } 164 165 @Override 166 public void handleResult(ThumbnailData result) { 167 mCache.put(key, result); 168 callback.accept(result); 169 } 170 }; 171 mBgExecutor.execute(request); 172 return request; 173 } 174 175 /** 176 * Clears the cache. 177 */ clear()178 public void clear() { 179 mCache.evictAll(); 180 } 181 182 /** 183 * Removes the cached thumbnail for the given task. 184 */ remove(Task.TaskKey key)185 public void remove(Task.TaskKey key) { 186 mCache.remove(key); 187 } 188 189 /** 190 * @return The cache size. 191 */ getCacheSize()192 public int getCacheSize() { 193 return mCacheSize; 194 } 195 196 /** 197 * @return The mutable high-res loading state. 198 */ getHighResLoadingState()199 public HighResLoadingState getHighResLoadingState() { 200 return mHighResLoadingState; 201 } 202 203 /** 204 * @return Whether to enable background preloading of task thumbnails. 205 */ isPreloadingEnabled()206 public boolean isPreloadingEnabled() { 207 return mEnableTaskSnapshotPreloading && mHighResLoadingState.mVisible; 208 } 209 210 /** 211 * @return Whether device supports low-res thumbnails. Low-res files are an optimization 212 * for faster load times of snapshots. Devices can optionally disable low-res files so that 213 * they only store snapshots at high-res scale. The actual scale can be configured in 214 * frameworks/base config overlay. 215 */ supportsLowResThumbnails()216 private static boolean supportsLowResThumbnails() { 217 Resources res = Resources.getSystem(); 218 int resId = res.getIdentifier("config_lowResTaskSnapshotScale", "dimen", "android"); 219 if (resId != 0) { 220 return 0 < res.getFloat(resId); 221 } 222 return true; 223 } 224 225 } 226