blob: 5e0195806a9c296e3b422aa684cc6c1e9c8f8b02 [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.uioverrides.RecentsUiFactory.GO_LOW_RAM_RECENTS_ENABLED;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.util.LruCache;
import android.view.accessibility.AccessibilityManager;
import com.android.launcher3.MainThreadExecutor;
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.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskKey;
import com.android.systemui.shared.recents.model.TaskKeyLruCache;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import java.util.Map;
import java.util.function.Consumer;
/**
* Manages the caching of task icons and related data.
* TODO(b/138944598): This class should later be merged into IconCache.
*/
public class TaskIconCache {
private final Handler mBackgroundHandler;
private final MainThreadExecutor mMainThreadExecutor;
private final AccessibilityManager mAccessibilityManager;
private final NormalizedIconLoader mIconLoader;
private final TaskKeyLruCache<Drawable> mIconCache;
private final TaskKeyLruCache<String> mContentDescriptionCache;
private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache;
private TaskKeyLruCache.EvictionCallback mClearActivityInfoOnEviction =
new TaskKeyLruCache.EvictionCallback() {
@Override
public void onEntryEvicted(Task.TaskKey key) {
if (key != null) {
mActivityInfoCache.remove(key.getComponent());
}
}
};
public TaskIconCache(Context context, Looper backgroundLooper) {
mBackgroundHandler = new Handler(backgroundLooper);
mMainThreadExecutor = new MainThreadExecutor();
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
Resources res = context.getResources();
int cacheSize = res.getInteger(R.integer.recentsIconCacheSize);
mIconCache = new TaskKeyLruCache<>(cacheSize, mClearActivityInfoOnEviction);
mContentDescriptionCache = new TaskKeyLruCache<>(cacheSize, mClearActivityInfoOnEviction);
mActivityInfoCache = new LruCache<>(cacheSize);
mIconLoader = new NormalizedIconLoader(context, mIconCache, mActivityInfoCache,
true /* disableColorExtraction */);
}
/**
* Asynchronously fetches the icon and other task data.
*
* @param task The task to fetch the data for
* @param callback The callback to receive the task after its data has been populated.
* @return A cancelable handle to the request
*/
public IconLoadRequest updateIconInBackground(Task task, Consumer<Task> callback) {
Preconditions.assertUIThread();
if (task.icon != null) {
// Nothing to load, the icon is already loaded
callback.accept(task);
return null;
}
IconLoadRequest request = new IconLoadRequest(mBackgroundHandler) {
@Override
public void run() {
Drawable icon = mIconLoader.getIcon(task);
String contentDescription = loadContentDescriptionInBackground(task);
if (isCanceled()) {
// We don't call back to the provided callback in this case
return;
}
mMainThreadExecutor.execute(() -> {
task.icon = icon;
task.titleDescription = contentDescription;
callback.accept(task);
onEnd();
});
}
};
Utilities.postAsyncCallback(mBackgroundHandler, request);
return request;
}
public void clear() {
mIconCache.evictAll();
mContentDescriptionCache.evictAll();
}
/**
* Loads the content description for the given {@param task}.
*/
private String loadContentDescriptionInBackground(Task task) {
// Return the cached content description if it exists
String label = mContentDescriptionCache.getAndInvalidateIfModified(task.key);
if (label != null) {
return label;
}
// Skip loading content descriptions if accessibility is disabled unless low RAM recents
// is enabled.
if (!GO_LOW_RAM_RECENTS_ENABLED && !mAccessibilityManager.isEnabled()) {
return "";
}
// Skip loading the content description if the activity no longer exists
ActivityInfo activityInfo = mIconLoader.getAndUpdateActivityInfo(task.key);
if (activityInfo == null) {
return "";
}
// Load the label otherwise
label = ActivityManagerWrapper.getInstance().getBadgedContentDescription(activityInfo,
task.key.userId, task.taskDescription);
mContentDescriptionCache.put(task.key, label);
return label;
}
void onTaskRemoved(TaskKey taskKey) {
mIconCache.remove(taskKey);
}
void invalidatePackage(String packageName) {
// TODO(b/138944598): Merge this class into IconCache so we can do this at the base level
Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot();
for (ComponentName cn : activityInfoCache.keySet()) {
if (cn.getPackageName().equals(packageName)) {
mActivityInfoCache.remove(cn);
}
}
}
public static abstract class IconLoadRequest extends HandlerRunnable {
IconLoadRequest(Handler handler) {
super(handler, null);
}
}
}