blob: 2b7a8ec25073a1ac2748fce8c363fe1e49f9b5a3 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.content.Context;
import android.content.res.Resources;
import android.os.Handler;
import android.os.Looper;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.icons.cache.HandlerRunnable;
import com.android.launcher3.util.Preconditions;
import com.android.quickstep.util.TaskKeyLruCache;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskKey;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import java.util.ArrayList;
import java.util.function.Consumer;
public class TaskThumbnailCache {
private final Handler mBackgroundHandler;
private final int mCacheSize;
private final TaskKeyLruCache<ThumbnailData> mCache;
private final HighResLoadingState mHighResLoadingState;
private final boolean mEnableTaskSnapshotPreloading;
public static class HighResLoadingState {
private boolean mForceHighResThumbnails;
private boolean mVisible;
private boolean mFlingingFast;
private boolean mHighResLoadingEnabled;
private ArrayList<HighResLoadingStateChangedCallback> mCallbacks = new ArrayList<>();
public interface HighResLoadingStateChangedCallback {
void onHighResLoadingStateChanged(boolean enabled);
}
private HighResLoadingState(Context context) {
// If the device does not support low-res thumbnails, only attempt to load high-res
// thumbnails
mForceHighResThumbnails = !supportsLowResThumbnails();
}
public void addCallback(HighResLoadingStateChangedCallback callback) {
mCallbacks.add(callback);
}
public void removeCallback(HighResLoadingStateChangedCallback callback) {
mCallbacks.remove(callback);
}
public void setVisible(boolean visible) {
mVisible = visible;
updateState();
}
public void setFlingingFast(boolean flingingFast) {
mFlingingFast = flingingFast;
updateState();
}
public boolean isEnabled() {
return mHighResLoadingEnabled;
}
private void updateState() {
boolean prevState = mHighResLoadingEnabled;
mHighResLoadingEnabled = mForceHighResThumbnails || (mVisible && !mFlingingFast);
if (prevState != mHighResLoadingEnabled) {
for (int i = mCallbacks.size() - 1; i >= 0; i--) {
mCallbacks.get(i).onHighResLoadingStateChanged(mHighResLoadingEnabled);
}
}
}
}
public TaskThumbnailCache(Context context, Looper backgroundLooper) {
mBackgroundHandler = new Handler(backgroundLooper);
mHighResLoadingState = new HighResLoadingState(context);
Resources res = context.getResources();
mCacheSize = res.getInteger(R.integer.recentsThumbnailCacheSize);
mEnableTaskSnapshotPreloading = res.getBoolean(R.bool.config_enableTaskSnapshotPreloading);
mCache = new TaskKeyLruCache<>(mCacheSize);
}
/**
* Synchronously fetches the thumbnail for the given {@param task} and puts it in the cache.
*/
public void updateThumbnailInCache(Task task) {
Preconditions.assertUIThread();
// Fetch the thumbnail for this task and put it in the cache
if (task.thumbnail == null) {
updateThumbnailInBackground(task.key, true /* lowResolution */,
t -> task.thumbnail = t);
}
}
/**
* Synchronously updates the thumbnail in the cache if it is already there.
*/
public void updateTaskSnapShot(int taskId, ThumbnailData thumbnail) {
Preconditions.assertUIThread();
mCache.updateIfAlreadyInCache(taskId, thumbnail);
}
/**
* Asynchronously fetches the icon and other task data for the given {@param task}.
*
* @param callback The callback to receive the task after its data has been populated.
* @return A cancelable handle to the request
*/
public ThumbnailLoadRequest updateThumbnailInBackground(
Task task, Consumer<ThumbnailData> callback) {
Preconditions.assertUIThread();
boolean lowResolution = !mHighResLoadingState.isEnabled();
if (task.thumbnail != null && (!task.thumbnail.reducedResolution || lowResolution)) {
// Nothing to load, the thumbnail is already high-resolution or matches what the
// request, so just callback
callback.accept(task.thumbnail);
return null;
}
return updateThumbnailInBackground(task.key, !mHighResLoadingState.isEnabled(), t -> {
task.thumbnail = t;
callback.accept(t);
});
}
private ThumbnailLoadRequest updateThumbnailInBackground(TaskKey key, boolean lowResolution,
Consumer<ThumbnailData> callback) {
Preconditions.assertUIThread();
ThumbnailData cachedThumbnail = mCache.getAndInvalidateIfModified(key);
if (cachedThumbnail != null && (!cachedThumbnail.reducedResolution || lowResolution)) {
// Already cached, lets use that thumbnail
callback.accept(cachedThumbnail);
return null;
}
ThumbnailLoadRequest request = new ThumbnailLoadRequest(mBackgroundHandler,
lowResolution) {
@Override
public void run() {
ThumbnailData thumbnail = ActivityManagerWrapper.getInstance().getTaskThumbnail(
key.id, lowResolution);
MAIN_EXECUTOR.execute(() -> {
if (isCanceled()) {
// We don't call back to the provided callback in this case
return;
}
mCache.put(key, thumbnail);
callback.accept(thumbnail);
onEnd();
});
}
};
Utilities.postAsyncCallback(mBackgroundHandler, request);
return request;
}
/**
* Clears the cache.
*/
public void clear() {
mCache.evictAll();
}
/**
* Removes the cached thumbnail for the given task.
*/
public void remove(Task.TaskKey key) {
mCache.remove(key);
}
/**
* @return The cache size.
*/
public int getCacheSize() {
return mCacheSize;
}
/**
* @return The mutable high-res loading state.
*/
public HighResLoadingState getHighResLoadingState() {
return mHighResLoadingState;
}
/**
* @return Whether to enable background preloading of task thumbnails.
*/
public boolean isPreloadingEnabled() {
return mEnableTaskSnapshotPreloading && mHighResLoadingState.mVisible;
}
public static abstract class ThumbnailLoadRequest extends HandlerRunnable {
public final boolean mLowResolution;
ThumbnailLoadRequest(Handler handler, boolean lowResolution) {
super(handler, null);
mLowResolution = lowResolution;
}
}
/**
* @return Whether device supports low-res thumbnails. Low-res files are an optimization
* for faster load times of snapshots. Devices can optionally disable low-res files so that
* they only store snapshots at high-res scale. The actual scale can be configured in
* frameworks/base config overlay.
*/
private static boolean supportsLowResThumbnails() {
Resources res = Resources.getSystem();
int resId = res.getIdentifier("config_lowResTaskSnapshotScale", "dimen", "android");
if (resId != 0) {
return 0 < res.getFloat(resId);
}
return true;
}
}