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