Merge "Increase the touch slop for noti shade pull down. Bug: 116879058" into ub-launcher3-master
diff --git a/Android.bp b/Android.bp
index 4b32702..e3dd5e5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -16,7 +16,8 @@
name: "launcher-aosp-tapl",
static_libs: [
"androidx.annotation_annotation",
- "androidx-test",
+ "androidx.test.runner",
+ "androidx.test.rules",
"androidx.test.uiautomator_uiautomator",
"SystemUISharedLib",
],
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index c5a7c05..6f3fa4b 100644
--- a/quickstep/libs/sysui_shared.jar
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index c294376..d8ca1c4 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -19,4 +19,9 @@
<string name="overview_callbacks_class" translatable="false"></string>
<string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherExtension</string>
+
+ <!-- The number of thumbnails and icons to keep in the cache. The thumbnail cache size also
+ determines how many thumbnails will be fetched in the background. -->
+ <integer name="recentsThumbnailCacheSize">3</integer>
+ <integer name="recentsIconCacheSize">12</integer>
</resources>
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index b406b30..4e79fed 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -163,19 +163,12 @@
}
}
- public static void onStart(Context context) {
- RecentsModel model = RecentsModel.INSTANCE.get(context);
- if (model != null) {
- model.onStart();
- }
- }
-
public static void onEnterAnimationComplete(Context context) {
// After the transition to home, enable the high-res thumbnail loader if it wasn't enabled
// as a part of quickstep/scrub, so that high-res thumbnails can load the next time we
// enter overview
- RecentsModel.INSTANCE.get(context).getRecentsTaskLoader()
- .getHighResThumbnailLoader().setVisible(true);
+ RecentsModel.INSTANCE.get(context).getThumbnailCache()
+ .getHighResLoadingState().setVisible(true);
}
public static void onLauncherStateOrResumeChanged(Launcher launcher) {
diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index 94ec69a..b11260e 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -232,7 +232,7 @@
mInputConsumer, mTouchInteractionLog);
// Preload the plan
- mRecentsModel.loadTasks(mRunningTask.id, null);
+ mRecentsModel.getTasks(null);
mInteractionHandler = handler;
handler.setGestureEndCallback(mEventQueue::reset);
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index d9626c4..f8f0905 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -227,10 +227,10 @@
public RecentsActivityCommand() {
mHelper = getActivityControlHelper();
mCreateTime = SystemClock.elapsedRealtime();
- mRunningTaskId = mAM.getRunningTask().id;
+ mRunningTaskId = RecentsModel.getRunningTaskId();
// Preload the plan
- mRecentsModel.loadTasks(mRunningTaskId, null);
+ mRecentsModel.getTasks(null);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
new file mode 100644
index 0000000..fec38bf
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2014 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 android.app.ActivityManager;
+import android.content.Context;
+import android.os.Process;
+import android.util.SparseBooleanArray;
+import com.android.launcher3.MainThreadExecutor;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.BackgroundExecutor;
+import com.android.systemui.shared.system.KeyguardManagerCompat;
+import com.android.systemui.shared.system.RecentTaskInfoCompat;
+import com.android.systemui.shared.system.TaskDescriptionCompat;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Manages the recent task list from the system, caching it as necessary.
+ */
+public class RecentTasksList extends TaskStackChangeListener {
+
+ private final KeyguardManagerCompat mKeyguardManager;
+ private final MainThreadExecutor mMainThreadExecutor;
+ private final BackgroundExecutor mBgThreadExecutor;
+
+ // The list change id, increments as the task list changes in the system
+ private int mChangeId;
+ // The last change id when the list was last loaded completely, must be <= the list change id
+ private int mLastLoadedId;
+
+ ArrayList<Task> mTasks = new ArrayList<>();
+
+ public RecentTasksList(Context context) {
+ mMainThreadExecutor = new MainThreadExecutor();
+ mBgThreadExecutor = BackgroundExecutor.get();
+ mKeyguardManager = new KeyguardManagerCompat(context);
+ mChangeId = 1;
+ }
+
+ /**
+ * Asynchronously fetches the list of recent tasks.
+ *
+ * @param numTasks The maximum number of tasks to fetch
+ * @param loadKeysOnly Whether to load other associated task data, or just the key
+ * @param callback The callback to receive the list of recent tasks
+ * @return The change id of the current task list
+ */
+ public synchronized int getTasks(int numTasks, boolean loadKeysOnly,
+ Consumer<ArrayList<Task>> callback) {
+ final int requestLoadId = mChangeId;
+ final int numLoadTasks = numTasks > 0
+ ? numTasks
+ : Integer.MAX_VALUE;
+
+ if (mLastLoadedId == mChangeId) {
+ // The list is up to date, callback with the same list
+ mMainThreadExecutor.execute(() -> {
+ if (callback != null) {
+ callback.accept(mTasks);
+ }
+ });
+ }
+
+ // Kick off task loading in the background
+ mBgThreadExecutor.submit(() -> {
+ ArrayList<Task> tasks = loadTasksInBackground(numLoadTasks,
+ loadKeysOnly);
+
+ mMainThreadExecutor.execute(() -> {
+ mTasks = tasks;
+ mLastLoadedId = requestLoadId;
+
+ if (callback != null) {
+ callback.accept(tasks);
+ }
+ });
+ });
+
+ return requestLoadId;
+ }
+
+ /**
+ * @return Whether the provided {@param changeId} is the latest recent tasks list id.
+ */
+ public synchronized boolean isTaskListValid(int changeId) {
+ return mChangeId == changeId;
+ }
+
+ @Override
+ public synchronized void onTaskStackChanged() {
+ mChangeId++;
+ }
+
+ @Override
+ public synchronized void onActivityPinned(String packageName, int userId, int taskId,
+ int stackId) {
+ mChangeId++;
+ }
+
+ @Override
+ public synchronized void onActivityUnpinned() {
+ mChangeId++;
+ }
+
+ /**
+ * Loads and creates a list of all the recent tasks.
+ */
+ private ArrayList<Task> loadTasksInBackground(int numTasks,
+ boolean loadKeysOnly) {
+ int currentUserId = Process.myUserHandle().getIdentifier();
+ ArrayList<Task> allTasks = new ArrayList<>();
+ List<ActivityManager.RecentTaskInfo> rawTasks =
+ ActivityManagerWrapper.getInstance().getRecentTasks(numTasks, currentUserId);
+ // The raw tasks are given in most-recent to least-recent order, we need to reverse it
+ Collections.reverse(rawTasks);
+
+ SparseBooleanArray tmpLockedUsers = new SparseBooleanArray() {
+ @Override
+ public boolean get(int key) {
+ if (indexOfKey(key) < 0) {
+ // Fill the cached locked state as we fetch
+ put(key, mKeyguardManager.isDeviceLocked(key));
+ }
+ return super.get(key);
+ }
+ };
+
+ int taskCount = rawTasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ ActivityManager.RecentTaskInfo rawTask = rawTasks.get(i);
+ RecentTaskInfoCompat t = new RecentTaskInfoCompat(rawTask);
+ Task.TaskKey taskKey = new Task.TaskKey(rawTask);
+ Task task;
+ if (!loadKeysOnly) {
+ ActivityManager.TaskDescription rawTd = t.getTaskDescription();
+ TaskDescriptionCompat td = new TaskDescriptionCompat(rawTd);
+ boolean isLocked = tmpLockedUsers.get(t.getUserId());
+ task = new Task(taskKey, td.getPrimaryColor(), td.getBackgroundColor(),
+ t.supportsSplitScreenMultiWindow(), isLocked, rawTd, t.getTopActivity());
+ } else {
+ task = new Task(taskKey);
+ }
+ allTasks.add(task);
+ }
+
+ return allTasks;
+ }
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index b93a54b..ef735e1 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -219,7 +219,6 @@
// onActivityStart callback.
mFallbackRecentsView.setContentAlpha(1);
super.onStart();
- UiFactory.onStart(this);
mFallbackRecentsView.resetTaskVisuals();
}
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 517f759..e184a9b 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -20,36 +20,21 @@
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.content.ComponentCallbacks2;
-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.Build;
import android.os.Bundle;
-import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.util.Log;
-import android.util.LruCache;
import android.util.SparseArray;
-import android.view.accessibility.AccessibilityManager;
-
import com.android.launcher3.MainThreadExecutor;
-import com.android.launcher3.R;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.UiThreadHelper;
import com.android.systemui.shared.recents.ISystemUiProxy;
-import com.android.systemui.shared.recents.model.IconLoader;
-import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
-import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.PreloadOptions;
-import com.android.systemui.shared.recents.model.RecentsTaskLoader;
-import com.android.systemui.shared.recents.model.TaskKeyLruCache;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.BackgroundExecutor;
import com.android.systemui.shared.system.TaskStackChangeListener;
-
import java.util.ArrayList;
import java.util.function.Consumer;
@@ -68,110 +53,111 @@
private final ArrayList<AssistDataListener> mAssistDataListeners = new ArrayList<>();
private final Context mContext;
- private final RecentsTaskLoader mRecentsTaskLoader;
private final MainThreadExecutor mMainThreadExecutor;
- private final Handler mBgHandler;
- private RecentsTaskLoadPlan mLastLoadPlan;
- private int mLastLoadPlanId;
- private int mTaskChangeId;
private ISystemUiProxy mSystemUiProxy;
private boolean mClearAssistCacheOnStackChange = true;
- private final boolean mIsLowRamDevice;
- private boolean mPreloadTasksInBackground;
- private final AccessibilityManager mAccessibilityManager;
+
+ private final RecentTasksList mTaskList;
+ private final TaskIconCache mIconCache;
+ private final TaskThumbnailCache mThumbnailCache;
private RecentsModel(Context context) {
mContext = context;
- ActivityManager activityManager =
- (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
- mIsLowRamDevice = activityManager.isLowRamDevice();
mMainThreadExecutor = new MainThreadExecutor();
- mBgHandler = new Handler(UiThreadHelper.getBackgroundLooper());
- Resources res = context.getResources();
- mRecentsTaskLoader = new RecentsTaskLoader(mContext,
- res.getInteger(R.integer.config_recentsMaxThumbnailCacheSize),
- res.getInteger(R.integer.config_recentsMaxIconCacheSize), 0) {
-
- @Override
- protected IconLoader createNewIconLoader(Context context,
- TaskKeyLruCache<Drawable> iconCache,
- LruCache<ComponentName, ActivityInfo> activityInfoCache) {
- // Disable finding the dominant color since we don't need to use it
- return new NormalizedIconLoader(context, iconCache, activityInfoCache,
- true /* disableColorExtraction */);
- }
- };
- mRecentsTaskLoader.startLoader(mContext);
+ HandlerThread loaderThread = new HandlerThread("TaskThumbnailIconCache",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ loaderThread.start();
+ mTaskList = new RecentTasksList(context);
+ mIconCache = new TaskIconCache(context, loaderThread.getLooper());
+ mThumbnailCache = new TaskThumbnailCache(context, loaderThread.getLooper());
ActivityManagerWrapper.getInstance().registerTaskStackListener(this);
-
- mTaskChangeId = 1;
- loadTasks(-1, null);
- mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
}
- public RecentsTaskLoader getRecentsTaskLoader() {
- return mRecentsTaskLoader;
+ public TaskIconCache getIconCache() {
+ return mIconCache;
+ }
+
+ public TaskThumbnailCache getThumbnailCache() {
+ return mThumbnailCache;
}
/**
- * Preloads the task plan
- * @param taskId The running task id or -1
+ * Fetches the list of recent tasks.
+ *
* @param callback The callback to receive the task plan once its complete or null. This is
* always called on the UI thread.
* @return the request id associated with this call.
*/
- public int loadTasks(int taskId, Consumer<RecentsTaskLoadPlan> callback) {
- final int requestId = mTaskChangeId;
+ public int getTasks(Consumer<ArrayList<Task>> callback) {
+ return mTaskList.getTasks(-1, false /* loadKeysOnly */, callback);
+ }
- // Fail fast if nothing has changed.
- if (mLastLoadPlanId == mTaskChangeId) {
- if (callback != null) {
- final RecentsTaskLoadPlan plan = mLastLoadPlan;
- mMainThreadExecutor.execute(() -> callback.accept(plan));
+ /**
+ * @return The task id of the running task, or -1 if there is no current running task.
+ */
+ public static int getRunningTaskId() {
+ ActivityManager.RunningTaskInfo runningTask =
+ ActivityManagerWrapper.getInstance().getRunningTask();
+ return runningTask != null ? runningTask.id : -1;
+ }
+
+ /**
+ * @return Whether the provided {@param changeId} is the latest recent tasks list id.
+ */
+ public boolean isTaskListValid(int changeId) {
+ return mTaskList.isTaskListValid(changeId);
+ }
+
+ /**
+ * Finds and returns the task key associated with the given task id.
+ *
+ * @param callback The callback to receive the task key if it is found or null. This is always
+ * called on the UI thread.
+ */
+ public void findTaskWithId(int taskId, Consumer<Task.TaskKey> callback) {
+ mTaskList.getTasks(-1, true /* loadKeysOnly */, (tasks) -> {
+ for (Task task : tasks) {
+ if (task.key.id == taskId) {
+ callback.accept(task.key);
+ return;
+ }
}
- return requestId;
+ callback.accept(null);
+ });
+ }
+
+ @Override
+ public void onTaskStackChangedBackground() {
+ if (!mThumbnailCache.isPreloadingEnabled()) {
+ // Skip if we aren't preloading
+ return;
}
- BackgroundExecutor.get().submit(() -> {
- // Preload the plan
- RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(mContext);
- PreloadOptions opts = new PreloadOptions();
- opts.loadTitles = mAccessibilityManager.isEnabled();
- loadPlan.preloadPlan(opts, mRecentsTaskLoader, taskId, UserHandle.myUserId());
- // Set the load plan on UI thread
- mMainThreadExecutor.execute(() -> {
- mLastLoadPlan = loadPlan;
- mLastLoadPlanId = requestId;
+ int currentUserId = Process.myUserHandle().getIdentifier();
+ if (!checkCurrentOrManagedUserId(currentUserId, mContext)) {
+ // Skip if we are not the current user
+ return;
+ }
- if (callback != null) {
- callback.accept(loadPlan);
+ // Keep the cache up to date with the latest thumbnails
+ mTaskList.getTasks(mThumbnailCache.getCacheSize(), true /* keysOnly */, (tasks) -> {
+ int runningTaskId = RecentsModel.getRunningTaskId();
+ for (Task task : tasks) {
+ if (task.key.id == runningTaskId) {
+ // Skip the running task, it's not going to have an up-to-date snapshot by the
+ // time the user next enters overview
+ continue;
}
- });
+ mThumbnailCache.updateThumbnailInCache(task);
+ }
});
- return requestId;
- }
-
- public void setPreloadTasksInBackground(boolean preloadTasksInBackground) {
- mPreloadTasksInBackground = preloadTasksInBackground && !mIsLowRamDevice;
- }
-
- @Override
- public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
- mTaskChangeId++;
- }
-
- @Override
- public void onActivityUnpinned() {
- mTaskChangeId++;
}
@Override
public void onTaskStackChanged() {
- mTaskChangeId++;
-
Preconditions.assertUIThread();
if (mClearAssistCacheOnStackChange) {
mCachedAssistData.clear();
@@ -180,39 +166,6 @@
}
}
- @Override
- public void onTaskStackChangedBackground() {
- int userId = UserHandle.myUserId();
- if (!mPreloadTasksInBackground || !checkCurrentOrManagedUserId(userId, mContext)) {
- // TODO: Only register this for the current user
- return;
- }
-
- // Preload a fixed number of task icons/thumbnails in the background
- ActivityManager.RunningTaskInfo runningTaskInfo =
- ActivityManagerWrapper.getInstance().getRunningTask();
- RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
- RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
- launchOpts.runningTaskId = runningTaskInfo != null ? runningTaskInfo.id : -1;
- launchOpts.numVisibleTasks = 2;
- launchOpts.numVisibleTaskThumbnails = 2;
- launchOpts.onlyLoadForCache = true;
- launchOpts.onlyLoadPausedActivities = true;
- launchOpts.loadThumbnails = true;
- PreloadOptions preloadOpts = new PreloadOptions();
- preloadOpts.loadTitles = mAccessibilityManager.isEnabled();
- plan.preloadPlan(preloadOpts, mRecentsTaskLoader, -1, userId);
- mRecentsTaskLoader.loadTasks(plan, launchOpts);
- }
-
- public boolean isLoadPlanValid(int resultId) {
- return mTaskChangeId == resultId;
- }
-
- public RecentsTaskLoadPlan getLastLoadPlan() {
- return mLastLoadPlan;
- }
-
public void setSystemUiProxy(ISystemUiProxy systemUiProxy) {
mSystemUiProxy = systemUiProxy;
}
@@ -221,16 +174,15 @@
return mSystemUiProxy;
}
- public void onStart() {
- mRecentsTaskLoader.startLoader(mContext);
- }
-
public void onTrimMemory(int level) {
if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
- // We already stop the loader in UI_HIDDEN, so stop the high res loader as well
- mRecentsTaskLoader.getHighResThumbnailLoader().setVisible(false);
+ mThumbnailCache.getHighResLoadingState().setVisible(false);
}
- mBgHandler.post(() -> mRecentsTaskLoader.onTrimMemory(level));
+ if (level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
+ // Clear everything once we reach a low-mem situation
+ mThumbnailCache.clear();
+ mIconCache.clear();
+ }
}
public void onOverviewShown(boolean fromHome, String tag) {
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
new file mode 100644
index 0000000..afa58fa
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.java
@@ -0,0 +1,151 @@
+/*
+ * 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 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.HandlerRunnable;
+import com.android.launcher3.util.Preconditions;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskKeyLruCache;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import java.util.function.Consumer;
+
+/**
+ * Manages the caching of task icons and related data.
+ * TODO: 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 not enabled
+ if (!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;
+ }
+
+ public static abstract class IconLoadRequest extends HandlerRunnable {
+ IconLoadRequest(Handler handler) {
+ super(handler, null);
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
new file mode 100644
index 0000000..c47101b
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
@@ -0,0 +1,198 @@
+/*
+ * 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 android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.Looper;
+import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.icons.HandlerRunnable;
+import com.android.launcher3.util.Preconditions;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskKeyLruCache;
+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 MainThreadExecutor mMainThreadExecutor;
+
+ private final int mCacheSize;
+ private final TaskKeyLruCache<ThumbnailData> mCache;
+ private final HighResLoadingState mHighResLoadingState;
+
+ public static class HighResLoadingState {
+ private boolean mIsLowRamDevice;
+ 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) {
+ ActivityManager activityManager =
+ (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ mIsLowRamDevice = activityManager.isLowRamDevice();
+ }
+
+ 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 = !mIsLowRamDevice && 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);
+ mMainThreadExecutor = new MainThreadExecutor();
+ mHighResLoadingState = new HighResLoadingState(context);
+
+ Resources res = context.getResources();
+ mCacheSize = res.getInteger(R.integer.recentsThumbnailCacheSize);
+ 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
+ mCache.put(task.key, ActivityManagerWrapper.getInstance().getTaskThumbnail(
+ task.key.id, true /* reducedResolution */));
+ }
+
+
+ /**
+ * 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, boolean reducedResolution,
+ Consumer<Task> callback) {
+ Preconditions.assertUIThread();
+
+ if (task.thumbnail != null && (!task.thumbnail.reducedResolution || reducedResolution)) {
+ // Nothing to load, the thumbnail is already high-resolution or matches what the
+ // request, so just callback
+ callback.accept(task);
+ return null;
+ }
+
+ ThumbnailData cachedThumbnail = mCache.getAndInvalidateIfModified(task.key);
+ if (cachedThumbnail != null && (!cachedThumbnail.reducedResolution || reducedResolution)) {
+ // Already cached, lets use that thumbnail
+ task.thumbnail = cachedThumbnail;
+ callback.accept(task);
+ return null;
+ }
+
+ ThumbnailLoadRequest request = new ThumbnailLoadRequest(mBackgroundHandler,
+ reducedResolution) {
+ @Override
+ public void run() {
+ ThumbnailData thumbnail = ActivityManagerWrapper.getInstance().getTaskThumbnail(
+ task.key.id, reducedResolution);
+ if (isCanceled()) {
+ // We don't call back to the provided callback in this case
+ return;
+ }
+ mMainThreadExecutor.execute(() -> {
+ task.thumbnail = thumbnail;
+ callback.accept(task);
+ onEnd();
+ });
+ }
+ };
+ Utilities.postAsyncCallback(mBackgroundHandler, request);
+ return request;
+ }
+
+ /**
+ * Clears the cache.
+ */
+ public void clear() {
+ mCache.evictAll();
+ }
+
+ /**
+ * @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 !mHighResLoadingState.mIsLowRamDevice && mHighResLoadingState.mVisible;
+ }
+
+ public static abstract class ThumbnailLoadRequest extends HandlerRunnable {
+ public final boolean reducedResolution;
+
+ ThumbnailLoadRequest(Handler handler, boolean reducedResolution) {
+ super(handler, null);
+ this.reducedResolution = reducedResolution;
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 9371a4c..b1a214d 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -196,7 +196,6 @@
super.onCreate();
mAM = ActivityManagerWrapper.getInstance();
mRecentsModel = RecentsModel.INSTANCE.get(this);
- mRecentsModel.setPreloadTasksInBackground(true);
mMainThreadExecutor = new MainThreadExecutor();
mOverviewCommandHelper = new OverviewCommandHelper(this);
mMainThreadChoreographer = Choreographer.getInstance();
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 1c79f44..6908b89 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -504,8 +504,8 @@
// This method is only called when STATE_GESTURE_STARTED_QUICKSTEP/
// STATE_GESTURE_STARTED_QUICKSCRUB is set, so we can enable the high-res thumbnail loader
// here once we are sure that we will end up in an overview state
- RecentsModel.INSTANCE.get(mContext).getRecentsTaskLoader()
- .getHighResThumbnailLoader().setVisible(true);
+ RecentsModel.INSTANCE.get(mContext).getThumbnailCache()
+ .getHighResLoadingState().setVisible(true);
}
private void shiftAnimationDestinationForQuickscrub() {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 1205bdc..cbbd181 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -43,7 +43,6 @@
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
-import android.os.UserHandle;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
@@ -78,13 +77,11 @@
import com.android.quickstep.OverviewCallbacks;
import com.android.quickstep.QuickScrubController;
import com.android.quickstep.RecentsModel;
+import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.util.ClipAnimationHelper;
import com.android.quickstep.util.TaskViewDrawable;
-import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
-import com.android.systemui.shared.recents.model.RecentsTaskLoader;
import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.TaskStack;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.BackgroundExecutor;
@@ -100,7 +97,8 @@
* A list of recent tasks.
*/
@TargetApi(Build.VERSION_CODES.P)
-public abstract class RecentsView<T extends BaseActivity> extends PagedView implements Insettable {
+public abstract class RecentsView<T extends BaseActivity> extends PagedView implements Insettable,
+ TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback {
private static final String TAG = RecentsView.class.getSimpleName();
@@ -206,17 +204,13 @@
handler.post(() ->
dismissTask(taskView, true /* animate */, false /* removeTask */));
} else {
- RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(getContext());
- RecentsTaskLoadPlan.PreloadOptions opts =
- new RecentsTaskLoadPlan.PreloadOptions();
- opts.loadTitles = false;
- loadPlan.preloadPlan(opts, mModel.getRecentsTaskLoader(), -1,
- UserHandle.myUserId());
- if (loadPlan.getTaskStack().findTaskWithId(taskId) == null) {
- // The task was removed from the recents list
- handler.post(() ->
- dismissTask(taskView, true /* animate */, false /* removeTask */));
- }
+ mModel.findTaskWithId(taskKey.id, (key) -> {
+ if (key == null) {
+ // The task was removed from the recents list
+ handler.post(() -> dismissTask(taskView, true /* animate */,
+ false /* removeTask */));
+ }
+ });
}
});
}
@@ -229,9 +223,9 @@
}
};
- // Used to keep track of the last requested load plan id, so that we do not request to load the
+ // Used to keep track of the last requested task list id, so that we do not request to load the
// tasks again if we have already requested it and the task list has not changed
- private int mRequestedLoadPlanId = -1;
+ private int mTaskListChangeId = -1;
// Only valid until the launcher state changes to NORMAL
private int mRunningTaskId = -1;
@@ -285,7 +279,6 @@
mActivity = (T) BaseActivity.fromContext(context);
mQuickScrubController = new QuickScrubController(mActivity, this);
mModel = RecentsModel.INSTANCE.get(context);
-
mClearAllButton = (ClearAllButton) LayoutInflater.from(context)
.inflate(R.layout.overview_clear_all_button, this, false);
mClearAllButton.setOnClickListener(this::dismissAllTasks);
@@ -316,7 +309,7 @@
public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) {
TaskView taskView = getTaskView(taskId);
if (taskView != null) {
- taskView.onTaskDataLoaded(taskView.getTask(), thumbnailData);
+ taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData);
}
return taskView;
}
@@ -331,6 +324,7 @@
protected void onAttachedToWindow() {
super.onAttachedToWindow();
updateTaskStackListenerState();
+ mModel.getThumbnailCache().getHighResLoadingState().addCallback(this);
mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
}
@@ -339,6 +333,7 @@
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
updateTaskStackListenerState();
+ mModel.getThumbnailCache().getHighResLoadingState().removeCallback(this);
mActivity.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
}
@@ -349,12 +344,11 @@
// Clear the task data for the removed child if it was visible
if (child != mClearAllButton) {
- Task task = ((TaskView) child).getTask();
+ TaskView taskView = (TaskView) child;
+ Task task = taskView.getTask();
if (mHasVisibleTaskData.get(task.key.id)) {
mHasVisibleTaskData.delete(task.key.id);
- RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
- loader.unloadTaskData(task);
- loader.getHighResThumbnailLoader().onTaskInvisible(task);
+ taskView.onTaskListVisibilityChanged(false /* visible */);
}
}
}
@@ -444,14 +438,13 @@
return true;
}
- private void applyLoadPlan(RecentsTaskLoadPlan loadPlan) {
+ private void applyLoadPlan(ArrayList<Task> tasks) {
if (mPendingAnimation != null) {
- mPendingAnimation.addEndListener((onEndListener) -> applyLoadPlan(loadPlan));
+ mPendingAnimation.addEndListener((onEndListener) -> applyLoadPlan(tasks));
return;
}
- TaskStack stack = loadPlan != null ? loadPlan.getTaskStack() : null;
- if (stack == null) {
+ if (tasks == null || tasks.isEmpty()) {
removeAllViews();
onTaskStackUpdated();
return;
@@ -462,7 +455,6 @@
// Ensure there are as many views as there are tasks in the stack (adding and trimming as
// necessary)
final LayoutInflater inflater = LayoutInflater.from(getContext());
- final ArrayList<Task> tasks = new ArrayList<>(stack.getTasks());
// Unload existing visible task data
unloadVisibleTaskData();
@@ -581,9 +573,8 @@
loadVisibleTaskData();
}
- // Update the high res thumbnail loader
- RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
- loader.getHighResThumbnailLoader().setFlingingFast(isFlingingFast);
+ // Update the high res thumbnail loader state
+ mModel.getThumbnailCache().getHighResLoadingState().setFlingingFast(isFlingingFast);
return scrolling;
}
@@ -618,13 +609,12 @@
* and unloads the associated task data for tasks that are no longer visible.
*/
public void loadVisibleTaskData() {
- if (!mOverviewStateEnabled || mRequestedLoadPlanId == -1) {
+ if (!mOverviewStateEnabled || mTaskListChangeId == -1) {
// Skip loading visible task data if we've already left the overview state, or if the
// task list hasn't been loaded yet (the task views will not reflect the task list)
return;
}
- RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
int centerPageIndex = getPageNearestToCenterOfScreen();
int numChildren = getTaskViewCount();
int lower = Math.max(0, centerPageIndex - 2);
@@ -641,14 +631,12 @@
continue;
}
if (!mHasVisibleTaskData.get(task.key.id)) {
- loader.loadTaskData(task);
- loader.getHighResThumbnailLoader().onTaskVisible(task);
+ taskView.onTaskListVisibilityChanged(true /* visible */);
}
mHasVisibleTaskData.put(task.key.id, visible);
} else {
if (mHasVisibleTaskData.get(task.key.id)) {
- loader.unloadTaskData(task);
- loader.getHighResThumbnailLoader().onTaskInvisible(task);
+ taskView.onTaskListVisibilityChanged(false /* visible */);
}
mHasVisibleTaskData.delete(task.key.id);
}
@@ -659,27 +647,40 @@
* Unloads any associated data from the currently visible tasks
*/
private void unloadVisibleTaskData() {
- RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
for (int i = 0; i < mHasVisibleTaskData.size(); i++) {
if (mHasVisibleTaskData.valueAt(i)) {
TaskView taskView = getTaskView(mHasVisibleTaskData.keyAt(i));
if (taskView != null) {
- Task task = taskView.getTask();
- loader.unloadTaskData(task);
- loader.getHighResThumbnailLoader().onTaskInvisible(task);
+ taskView.onTaskListVisibilityChanged(false /* visible */);
}
}
}
mHasVisibleTaskData.clear();
}
+ @Override
+ public void onHighResLoadingStateChanged(boolean enabled) {
+ // Whenever the high res loading state changes, poke each of the visible tasks to see if
+ // they want to updated their thumbnail state
+ for (int i = 0; i < mHasVisibleTaskData.size(); i++) {
+ if (mHasVisibleTaskData.valueAt(i)) {
+ TaskView taskView = getTaskView(mHasVisibleTaskData.keyAt(i));
+ if (taskView != null) {
+ // Poke the view again, which will trigger it to load high res if the state
+ // is enabled
+ taskView.onTaskListVisibilityChanged(true /* visible */);
+ }
+ }
+ }
+ }
+
protected abstract void startHome();
public void reset() {
mRunningTaskId = -1;
mRunningTaskTileHidden = false;
mIgnoreResetTaskId = -1;
- mRequestedLoadPlanId = -1;
+ mTaskListChangeId = -1;
unloadVisibleTaskData();
setCurrentPage(0);
@@ -691,8 +692,8 @@
* Reloads the view if anything in recents changed.
*/
public void reloadIfNeeded() {
- if (!mModel.isLoadPlanValid(mRequestedLoadPlanId)) {
- mRequestedLoadPlanId = mModel.loadTasks(mRunningTaskId, this::applyLoadPlan);
+ if (!mModel.isTaskListValid(mTaskListChangeId)) {
+ mTaskListChangeId = mModel.getTasks(this::applyLoadPlan);
}
}
@@ -753,8 +754,8 @@
setCurrentPage(0);
- // Load the tasks
- reloadIfNeeded();
+ // Load the tasks (if the loading is already
+ mTaskListChangeId = mModel.getTasks(this::applyLoadPlan);
}
public void showNextTask() {
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 56074f0..da5b79a 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -17,7 +17,6 @@
package com.android.quickstep.views;
import static android.widget.Toast.LENGTH_SHORT;
-
import static com.android.launcher3.BaseActivity.fromContext;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -30,6 +29,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Outline;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.util.AttributeSet;
@@ -47,14 +47,15 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.quickstep.RecentsModel;
+import com.android.quickstep.TaskIconCache;
import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskSystemShortcut;
+import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.views.RecentsView.PageCallbacks;
import com.android.quickstep.views.RecentsView.ScrollState;
import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.Task.TaskCallbacks;
-import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.ActivityOptionsCompat;
@@ -64,7 +65,7 @@
/**
* A task in the Recents view.
*/
-public class TaskView extends FrameLayout implements TaskCallbacks, PageCallbacks {
+public class TaskView extends FrameLayout implements PageCallbacks {
private static final String TAG = TaskView.class.getSimpleName();
@@ -137,6 +138,10 @@
private Animator mIconAndDimAnimator;
private float mFocusTransitionProgress = 1;
+ // The current background requests to load the task thumbnail and icon
+ private TaskThumbnailCache.ThumbnailLoadRequest mThumbnailLoadRequest;
+ private TaskIconCache.IconLoadRequest mIconLoadRequest;
+
public TaskView(Context context) {
this(context, null);
}
@@ -170,13 +175,8 @@
* Updates this task view to the given {@param task}.
*/
public void bind(Task task) {
- if (mTask != null) {
- mTask.removeCallback(this);
- }
mTask = task;
mSnapshotView.bind();
- task.addCallback(this);
- setContentDescription(task.titleDescription);
}
public Task getTask() {
@@ -233,15 +233,34 @@
}
}
- @Override
- public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData) {
- mSnapshotView.setThumbnail(task, thumbnailData);
- mIconView.setDrawable(task.icon);
- mIconView.setOnClickListener(icon -> showTaskMenu());
- mIconView.setOnLongClickListener(icon -> {
- requestDisallowInterceptTouchEvent(true);
- return showTaskMenu();
- });
+ public void onTaskListVisibilityChanged(boolean visible) {
+ if (mTask == null) {
+ return;
+ }
+ if (visible) {
+ // These calls are no-ops if the data is already loaded, try and load the high
+ // resolution thumbnail if the state permits
+ RecentsModel model = RecentsModel.INSTANCE.get(getContext());
+ TaskThumbnailCache thumbnailCache = model.getThumbnailCache();
+ TaskIconCache iconCache = model.getIconCache();
+ mThumbnailLoadRequest = thumbnailCache.updateThumbnailInBackground(mTask,
+ !thumbnailCache.getHighResLoadingState().isEnabled() /* reducedResolution */,
+ (task) -> mSnapshotView.setThumbnail(task, task.thumbnail));
+ mIconLoadRequest = iconCache.updateIconInBackground(mTask,
+ (task) -> {
+ setContentDescription(task.titleDescription);
+ setIcon(task.icon);
+ });
+ } else {
+ if (mThumbnailLoadRequest != null) {
+ mThumbnailLoadRequest.cancel();
+ }
+ if (mIconLoadRequest != null) {
+ mIconLoadRequest.cancel();
+ }
+ mSnapshotView.setThumbnail(null, null);
+ setIcon(null);
+ }
}
private boolean showTaskMenu() {
@@ -253,16 +272,18 @@
return mMenuView != null;
}
- @Override
- public void onTaskDataUnloaded() {
- mSnapshotView.setThumbnail(null, null);
- mIconView.setDrawable(null);
- mIconView.setOnLongClickListener(null);
- }
-
- @Override
- public void onTaskWindowingModeChanged() {
- // Do nothing
+ private void setIcon(Drawable icon) {
+ if (icon != null) {
+ mIconView.setDrawable(icon);
+ mIconView.setOnClickListener(v -> showTaskMenu());
+ mIconView.setOnLongClickListener(v -> {
+ requestDisallowInterceptTouchEvent(true);
+ return showTaskMenu();
+ });
+ } else {
+ mIconView.setDrawable(null);
+ mIconView.setOnLongClickListener(null);
+ }
}
private void setIconAndDimTransitionProgress(float progress) {
diff --git a/res/values/config.xml b/res/values/config.xml
index 85c2e65..946afec 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -132,6 +132,4 @@
<!-- Recents -->
<item type="id" name="overview_panel"/>
- <integer name="config_recentsMaxThumbnailCacheSize">6</integer>
- <integer name="config_recentsMaxIconCacheSize">12</integer>
</resources>
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index f3f2238..0b2f4d9 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -16,6 +16,8 @@
package com.android.launcher3;
+import static com.android.launcher3.config.FeatureFlags.APPLY_CONFIG_AT_RUNTIME;
+
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Configuration;
@@ -23,6 +25,7 @@
import android.content.res.XmlResourceParser;
import android.graphics.Point;
import android.util.DisplayMetrics;
+import android.util.Log;
import android.util.Xml;
import android.view.Display;
import android.view.WindowManager;
@@ -45,13 +48,13 @@
// We do not need any synchronization for this variable as its only written on UI thread.
public static final MainThreadInitializedObject<InvariantDeviceProfile> INSTANCE =
- new MainThreadInitializedObject<>((c) -> {
- new ConfigMonitor(c).register();
- return new InvariantDeviceProfile(c);
- });
+ new MainThreadInitializedObject<>(InvariantDeviceProfile::new);
private static final float ICON_SIZE_DEFINED_IN_APP_DP = 48;
+ public static final int CHANGE_FLAG_GRID = 1 << 0;
+ public static final int CHANGE_FLAG_ICON_SIZE = 1 << 1;
+
// Constants that affects the interpolation curve between statically defined device profile
// buckets.
private static float KNEARESTNEIGHBOR = 3;
@@ -61,9 +64,9 @@
private static float WEIGHT_EFFICIENT = 100000f;
// Profile-defining invariant properties
- String name;
- float minWidthDps;
- float minHeightDps;
+ private String name;
+ private float minWidthDps;
+ private float minHeightDps;
/**
* Number of icons per row and column in the workspace.
@@ -95,9 +98,11 @@
public Point defaultWallpaperSize;
+ private final ArrayList<OnIDPChangeListener> mChangeListeners = new ArrayList<>();
+ private ConfigMonitor mConfigMonitor;
+
@VisibleForTesting
- public InvariantDeviceProfile() {
- }
+ public InvariantDeviceProfile() {}
private InvariantDeviceProfile(InvariantDeviceProfile p) {
this(p.name, p.minWidthDps, p.minHeightDps, p.numRows, p.numColumns,
@@ -125,6 +130,12 @@
@TargetApi(23)
private InvariantDeviceProfile(Context context) {
+ initGrid(context);
+ mConfigMonitor = new ConfigMonitor(context,
+ APPLY_CONFIG_AT_RUNTIME.get() ? this::onConfigChanged : this::killProcess);
+ }
+
+ private void initGrid(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
DisplayMetrics dm = new DisplayMetrics();
@@ -185,6 +196,44 @@
}
}
+ public void addOnChangeListener(OnIDPChangeListener listener) {
+ mChangeListeners.add(listener);
+ }
+
+ private void killProcess(Context context) {
+ Log.e("ConfigMonitor", "restarting launcher");
+ android.os.Process.killProcess(android.os.Process.myPid());
+ }
+
+ private void onConfigChanged(Context context) {
+ // Config changes, what shall we do?
+ InvariantDeviceProfile oldProfile = new InvariantDeviceProfile(this);
+
+ // Re-init grid
+ initGrid(context);
+
+ int changeFlags = 0;
+ if (numRows != oldProfile.numRows ||
+ numColumns != oldProfile.numColumns ||
+ numFolderColumns != oldProfile.numFolderColumns ||
+ numFolderRows != oldProfile.numFolderRows ||
+ numHotseatIcons != oldProfile.numHotseatIcons) {
+ changeFlags |= CHANGE_FLAG_GRID;
+ }
+
+ if (iconSize != oldProfile.iconSize || iconBitmapSize != oldProfile.iconBitmapSize) {
+ changeFlags |= CHANGE_FLAG_ICON_SIZE;
+ }
+
+ // Create a new config monitor
+ mConfigMonitor.unregister();
+ mConfigMonitor = new ConfigMonitor(context, this::onConfigChanged);
+
+ for (OnIDPChangeListener listener : mChangeListeners) {
+ listener.onIdpChanged(changeFlags, this);
+ }
+ }
+
ArrayList<InvariantDeviceProfile> getPredefinedDeviceProfiles(Context context) {
ArrayList<InvariantDeviceProfile> profiles = new ArrayList<>();
try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
@@ -356,4 +405,8 @@
return x * aspectRatio + y;
}
+ public interface OnIDPChangeListener {
+
+ void onIdpChanged(int changeFlags, InvariantDeviceProfile profile);
+ }
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index e714a0b..3ae9a49 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -768,7 +768,6 @@
}
mAppWidgetHost.setListenIfResumed(true);
NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
- UiFactory.onStart(this);
}
private void logOnDelayedResume() {
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 6bf5812..338c20b 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -17,6 +17,7 @@
package com.android.launcher3;
import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
+import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_SIZE;
import android.content.ComponentName;
import android.content.ContentProviderClient;
@@ -30,6 +31,7 @@
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Preconditions;
@@ -94,6 +96,7 @@
mContext.registerReceiver(mModel, filter);
UserManagerCompat.getInstance(mContext).enableAndResetCache();
+ mInvariantDeviceProfile.addOnChangeListener(this::onIdpChanged);
if (!mContext.getResources().getBoolean(R.bool.notification_badging_enabled)) {
mNotificationBadgingObserver = null;
@@ -113,6 +116,19 @@
}
}
+ private void onIdpChanged(int changeFlags, InvariantDeviceProfile idp) {
+ if (changeFlags == 0) {
+ return;
+ }
+
+ if ((changeFlags & CHANGE_FLAG_ICON_SIZE) != 0) {
+ LauncherIcons.clearPool();
+ mIconCache.updateIconParams(idp.fillResIconDpi, idp.iconBitmapSize);
+ }
+
+ mModel.forceReload();
+ }
+
/**
* Call from Application.onTerminate(), which is not guaranteed to ever be called.
*/
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index fa4ff75..64b5652 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -87,6 +87,12 @@
// trying to make them fit the orientation the device is in.
public static final boolean OVERVIEW_USE_SCREENSHOT_ORIENTATION = true;
+ /**
+ * Feature flag to handle define config changes dynamically instead of killing the process.
+ */
+ public static final TogglableFlag APPLY_CONFIG_AT_RUNTIME = new TogglableFlag(
+ "APPLY_CONFIG_AT_RUNTIME", false, "Apply display changes dynamically");
+
public static void initialize(Context context) {
// Avoid the disk read for user builds
if (Utilities.IS_DEBUG_DEVICE) {
@@ -191,5 +197,4 @@
return h$;
}
}
-
}
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index b6a8b50..8f223a3 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -232,7 +232,7 @@
nDr = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK), null);
}
Utilities.scaleRectAboutCenter(bounds,
- li.getNormalizer().getScale(nDr, null, null, null));
+ li.getNormalizer().getScale(nDr, null));
}
AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) dr;
diff --git a/src/com/android/launcher3/icons/BaseIconCache.java b/src/com/android/launcher3/icons/BaseIconCache.java
index 9198c24..2afe713 100644
--- a/src/com/android/launcher3/icons/BaseIconCache.java
+++ b/src/com/android/launcher3/icons/BaseIconCache.java
@@ -90,11 +90,11 @@
private final HashMap<ComponentKey, CacheEntry> mCache =
new HashMap<>(INITIAL_ICON_CACHE_CAPACITY);
private final InstantAppResolver mInstantAppResolver;
- final int mIconDpi;
-
- final IconDB mIconDb;
final Handler mWorkerHandler;
+ int mIconDpi;
+ IconDB mIconDb;
+
private final BitmapFactory.Options mDecodeOptions;
public BaseIconCache(Context context, int iconDpi, int iconPixelSize) {
@@ -103,8 +103,6 @@
mUserManager = UserManagerCompat.getInstance(mContext);
mLauncherApps = LauncherAppsCompat.getInstance(mContext);
mInstantAppResolver = InstantAppResolver.newInstance(mContext);
- mIconDpi = iconDpi;
- mIconDb = new IconDB(context, iconPixelSize);
mIconProvider = IconProvider.newInstance(context);
mWorkerHandler = new Handler(LauncherModel.getWorkerLooper());
@@ -115,6 +113,22 @@
} else {
mDecodeOptions = null;
}
+
+ mIconDpi = iconDpi;
+ mIconDb = new IconDB(context, iconPixelSize);
+ }
+
+ public void updateIconParams(int iconDpi, int iconPixelSize) {
+ mWorkerHandler.post(() -> updateIconParamsBg(iconDpi, iconPixelSize));
+ }
+
+ private synchronized void updateIconParamsBg(int iconDpi, int iconPixelSize) {
+ mIconDpi = iconDpi;
+ mDefaultIcons.clear();
+
+ mIconDb.close();
+ mIconDb = new IconDB(mContext, iconPixelSize);
+ mCache.clear();
}
private Drawable getFullResDefaultActivityIcon() {
diff --git a/src/com/android/launcher3/icons/BaseIconFactory.java b/src/com/android/launcher3/icons/BaseIconFactory.java
index c8c9618..cd60de5 100644
--- a/src/com/android/launcher3/icons/BaseIconFactory.java
+++ b/src/com/android/launcher3/icons/BaseIconFactory.java
@@ -192,18 +192,18 @@
}
AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon;
dr.setBounds(0, 0, 1, 1);
- scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape);
- if (ATLEAST_OREO && !outShape[0] && !(icon instanceof AdaptiveIconDrawable)) {
+ scale = getNormalizer().getScale(icon, outIconBounds);
+ if (ATLEAST_OREO && !(icon instanceof AdaptiveIconDrawable)) {
FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground());
fsd.setDrawable(icon);
fsd.setScale(scale);
icon = dr;
- scale = getNormalizer().getScale(icon, outIconBounds, null, null);
+ scale = getNormalizer().getScale(icon, outIconBounds);
((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
}
} else {
- scale = getNormalizer().getScale(icon, outIconBounds, null, null);
+ scale = getNormalizer().getScale(icon, outIconBounds);
}
outScale[0] = scale;
diff --git a/src/com/android/launcher3/icons/HandlerRunnable.java b/src/com/android/launcher3/icons/HandlerRunnable.java
new file mode 100644
index 0000000..e7132cd
--- /dev/null
+++ b/src/com/android/launcher3/icons/HandlerRunnable.java
@@ -0,0 +1,67 @@
+/*
+ * 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.launcher3.icons;
+
+import android.os.Handler;
+
+/**
+ * A runnable that can be posted to a {@link Handler} which can be canceled.
+ */
+public abstract class HandlerRunnable implements Runnable {
+
+ private final Handler mHandler;
+ private final Runnable mEndRunnable;
+
+ private boolean mEnded = false;
+ private boolean mCanceled = false;
+
+ public HandlerRunnable(Handler handler, Runnable endRunnable) {
+ mHandler = handler;
+ mEndRunnable = endRunnable;
+ }
+
+ /**
+ * Cancels this runnable from being run, only if it has not already run.
+ */
+ public void cancel() {
+ mHandler.removeCallbacks(this);
+ // TODO: This can actually cause onEnd to be called twice if the handler is already running
+ // this runnable
+ // NOTE: This is currently run on whichever thread the caller is run on.
+ mCanceled = true;
+ onEnd();
+ }
+
+ /**
+ * @return whether this runnable was canceled.
+ */
+ protected boolean isCanceled() {
+ return mCanceled;
+ }
+
+ /**
+ * To be called by the implemention of this runnable. The end callback is done on whichever
+ * thread the caller is calling from.
+ */
+ public void onEnd() {
+ if (!mEnded) {
+ mEnded = true;
+ if (mEndRunnable != null) {
+ mEndRunnable.run();
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 41a53e5..6e2ca28 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -169,27 +169,9 @@
applyCacheEntry(entry, infoInOut);
}
- public static abstract class IconLoadRequest implements Runnable {
- private final Handler mHandler;
- private final Runnable mEndRunnable;
-
- private boolean mEnded = false;
-
+ public static abstract class IconLoadRequest extends HandlerRunnable {
IconLoadRequest(Handler handler, Runnable endRunnable) {
- mHandler = handler;
- mEndRunnable = endRunnable;
- }
-
- public void cancel() {
- mHandler.removeCallbacks(this);
- onEnd();
- }
-
- public void onEnd() {
- if (!mEnded) {
- mEnded = true;
- mEndRunnable.run();
- }
+ super(handler, endRunnable);
}
}
diff --git a/src/com/android/launcher3/icons/IconNormalizer.java b/src/com/android/launcher3/icons/IconNormalizer.java
index 4052a55..8eb8252 100644
--- a/src/com/android/launcher3/icons/IconNormalizer.java
+++ b/src/com/android/launcher3/icons/IconNormalizer.java
@@ -20,16 +20,13 @@
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.Matrix;
import android.graphics.Paint;
-import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.Drawable;
-import android.util.Log;
import java.nio.ByteBuffer;
@@ -53,9 +50,6 @@
private static final int MIN_VISIBLE_ALPHA = 40;
- // Shape detection related constants
- private static final float BOUND_RATIO_MARGIN = .05f;
- private static final float PIXEL_DIFF_PERCENTAGE_THRESHOLD = 0.005f;
private static final float SCALE_NOT_INITIALIZED = 0;
// Ratio of the diameter of an normalized circular icon to the actual icon size.
@@ -64,8 +58,6 @@
private final int mMaxSize;
private final Bitmap mBitmap;
private final Canvas mCanvas;
- private final Paint mPaintMaskShape;
- private final Paint mPaintMaskShapeOutline;
private final byte[] mPixels;
private final Rect mAdaptiveIconBounds;
@@ -75,8 +67,6 @@
private final float[] mLeftBorder;
private final float[] mRightBorder;
private final Rect mBounds;
- private final Path mShapePath;
- private final Matrix mMatrix;
/** package private **/
IconNormalizer(Context context, int iconBitmapSize) {
@@ -90,89 +80,10 @@
mBounds = new Rect();
mAdaptiveIconBounds = new Rect();
- mPaintMaskShape = new Paint();
- mPaintMaskShape.setColor(Color.RED);
- mPaintMaskShape.setStyle(Paint.Style.FILL);
- mPaintMaskShape.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
-
- mPaintMaskShapeOutline = new Paint();
- mPaintMaskShapeOutline.setStrokeWidth(2 * context.getResources().getDisplayMetrics().density);
- mPaintMaskShapeOutline.setStyle(Paint.Style.STROKE);
- mPaintMaskShapeOutline.setColor(Color.BLACK);
- mPaintMaskShapeOutline.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
-
- mShapePath = new Path();
- mMatrix = new Matrix();
mAdaptiveIconScale = SCALE_NOT_INITIALIZED;
}
/**
- * Returns if the shape of the icon is same as the path.
- * For this method to work, the shape path bounds should be in [0,1]x[0,1] bounds.
- */
- private boolean isShape(Path maskPath) {
- // Condition1:
- // If width and height of the path not close to a square, then the icon shape is
- // not same as the mask shape.
- float iconRatio = ((float) mBounds.width()) / mBounds.height();
- if (Math.abs(iconRatio - 1) > BOUND_RATIO_MARGIN) {
- if (DEBUG) {
- Log.d(TAG, "Not same as mask shape because width != height. " + iconRatio);
- }
- return false;
- }
-
- // Condition 2:
- // Actual icon (white) and the fitted shape (e.g., circle)(red) XOR operation
- // should generate transparent image, if the actual icon is equivalent to the shape.
-
- // Fit the shape within the icon's bounding box
- mMatrix.reset();
- mMatrix.setScale(mBounds.width(), mBounds.height());
- mMatrix.postTranslate(mBounds.left, mBounds.top);
- maskPath.transform(mMatrix, mShapePath);
-
- // XOR operation
- mCanvas.drawPath(mShapePath, mPaintMaskShape);
-
- // DST_OUT operation around the mask path outline
- mCanvas.drawPath(mShapePath, mPaintMaskShapeOutline);
-
- // Check if the result is almost transparent
- return isTransparentBitmap();
- }
-
- /**
- * Used to determine if certain the bitmap is transparent.
- */
- private boolean isTransparentBitmap() {
- ByteBuffer buffer = ByteBuffer.wrap(mPixels);
- buffer.rewind();
- mBitmap.copyPixelsToBuffer(buffer);
-
- int y = mBounds.top;
- // buffer position
- int index = y * mMaxSize;
- // buffer shift after every row, width of buffer = mMaxSize
- int rowSizeDiff = mMaxSize - mBounds.right;
-
- int sum = 0;
- for (; y < mBounds.bottom; y++) {
- index += mBounds.left;
- for (int x = mBounds.left; x < mBounds.right; x++) {
- if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) {
- sum++;
- }
- index++;
- }
- index += rowSizeDiff;
- }
-
- float percentageDiffPixels = ((float) sum) / (mBounds.width() * mBounds.height());
- return percentageDiffPixels < PIXEL_DIFF_PERCENTAGE_THRESHOLD;
- }
-
- /**
* Returns the amount by which the {@param d} should be scaled (in both dimensions) so that it
* matches the design guidelines for a launcher icon.
*
@@ -186,8 +97,7 @@
*
* @param outBounds optional rect to receive the fraction distance from each edge.
*/
- public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds,
- @Nullable Path path, @Nullable boolean[] outMaskShape) {
+ public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds) {
if (BaseIconFactory.ATLEAST_OREO && d instanceof AdaptiveIconDrawable) {
if (mAdaptiveIconScale != SCALE_NOT_INITIALIZED) {
if (outBounds != null) {
@@ -298,10 +208,6 @@
1 - ((float) mBounds.right) / width,
1 - ((float) mBounds.bottom) / height);
}
-
- if (outMaskShape != null && outMaskShape.length > 0) {
- outMaskShape[0] = isShape(path);
- }
float areaScale = area / (width * height);
// Use sqrt of the final ratio as the images is scaled across both width and height.
float scale = areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1;
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
index 69614a3..c96d35d 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -27,11 +27,11 @@
import com.android.launcher3.AppInfo;
import com.android.launcher3.FastBitmapDrawable;
-import com.android.launcher3.graphics.BitmapRenderer;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.Utilities;
+import com.android.launcher3.graphics.BitmapRenderer;
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutInfoCompat;
@@ -48,13 +48,14 @@
private static final Object sPoolSync = new Object();
private static LauncherIcons sPool;
- private LauncherIcons next;
+ private static int sPoolId = 0;
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static LauncherIcons obtain(Context context) {
+ int poolId;
synchronized (sPoolSync) {
if (sPool != null) {
LauncherIcons m = sPool;
@@ -62,9 +63,33 @@
m.next = null;
return m;
}
+ poolId = sPoolId;
}
+
InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
- return new LauncherIcons(context, idp.fillResIconDpi, idp.iconBitmapSize);
+ return new LauncherIcons(context, idp.fillResIconDpi, idp.iconBitmapSize, poolId);
+ }
+
+ public static void clearPool() {
+ synchronized (sPoolSync) {
+ sPool = null;
+ sPoolId++;
+ }
+ }
+
+ private final Context mContext;
+ private final int mFillResIconDpi;
+ private final int mIconBitmapSize;
+ private final int mPoolId;
+
+ private LauncherIcons next;
+
+ private LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize, int poolId) {
+ super(context, fillResIconDpi, iconBitmapSize);
+ mContext = context.getApplicationContext();
+ mFillResIconDpi = fillResIconDpi;
+ mIconBitmapSize = iconBitmapSize;
+ mPoolId = poolId;
}
/**
@@ -72,6 +97,9 @@
*/
public void recycle() {
synchronized (sPoolSync) {
+ if (sPoolId != mPoolId) {
+ return;
+ }
// Clear any temporary state variables
clear();
@@ -85,17 +113,6 @@
recycle();
}
- private final Context mContext;
- private final int mFillResIconDpi;
- private final int mIconBitmapSize;
-
- private LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize) {
- super(context, fillResIconDpi, iconBitmapSize);
- mContext = context.getApplicationContext();
- mFillResIconDpi = fillResIconDpi;
- mIconBitmapSize = iconBitmapSize;
- }
-
public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
int iconAppTargetSdk) {
return createBadgedIconBitmap(icon, user, iconAppTargetSdk, false);
diff --git a/src/com/android/launcher3/popup/RemoteActionShortcut.java b/src/com/android/launcher3/popup/RemoteActionShortcut.java
index af0d3da..c76fb96 100644
--- a/src/com/android/launcher3/popup/RemoteActionShortcut.java
+++ b/src/com/android/launcher3/popup/RemoteActionShortcut.java
@@ -18,6 +18,7 @@
import android.app.PendingIntent;
import android.app.RemoteAction;
+import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
@@ -48,14 +49,20 @@
AbstractFloatingView.closeAllOpenViews(launcher);
try {
- mAction.getActionIntent().send(0,
+ mAction.getActionIntent().send(
+ launcher,
+ 0,
+ new Intent().putExtra(
+ Intent.EXTRA_PACKAGE_NAME,
+ itemInfo.getTargetComponent().getPackageName()),
(pendingIntent, intent, resultCode, resultData, resultExtras) -> {
if (resultData != null && !resultData.isEmpty()) {
Log.e(TAG, "Remote action returned result: " + mAction.getTitle()
+ " : " + resultData);
Toast.makeText(launcher, resultData, Toast.LENGTH_SHORT).show();
}
- }, new Handler(Looper.getMainLooper()));
+ },
+ new Handler(Looper.getMainLooper()));
} catch (PendingIntent.CanceledException e) {
Log.e(TAG, "Remote action canceled: " + mAction.getTitle(), e);
Toast.makeText(launcher, launcher.getString(
diff --git a/src/com/android/launcher3/util/ConfigMonitor.java b/src/com/android/launcher3/util/ConfigMonitor.java
index 5dd0d08..717acdc 100644
--- a/src/com/android/launcher3/util/ConfigMonitor.java
+++ b/src/com/android/launcher3/util/ConfigMonitor.java
@@ -29,9 +29,12 @@
import android.view.Display;
import android.view.WindowManager;
+import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.Utilities.Consumer;
+
/**
* {@link BroadcastReceiver} which watches configuration changes and
- * restarts the process in case changes which affect the device profile occur.
+ * notifies the callback in case changes which affect the device profile occur.
*/
public class ConfigMonitor extends BroadcastReceiver implements DisplayListener {
@@ -48,7 +51,9 @@
private final Point mRealSize;
private final Point mSmallestSize, mLargestSize;
- public ConfigMonitor(Context context) {
+ private Consumer<Context> mCallback;
+
+ public ConfigMonitor(Context context, Consumer<Context> callback) {
mContext = context;
Configuration config = context.getResources().getConfiguration();
@@ -64,6 +69,12 @@
mSmallestSize = new Point();
mLargestSize = new Point();
display.getCurrentSizeRange(mSmallestSize, mLargestSize);
+
+ mCallback = callback;
+
+ mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED));
+ mContext.getSystemService(DisplayManager.class)
+ .registerDisplayListener(this, new Handler(UiThreadHelper.getBackgroundLooper()));
}
@Override
@@ -71,16 +82,10 @@
Configuration config = context.getResources().getConfiguration();
if (mFontScale != config.fontScale || mDensity != config.densityDpi) {
Log.d(TAG, "Configuration changed");
- killProcess();
+ notifyChange();
}
}
- public void register() {
- mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED));
- mContext.getSystemService(DisplayManager.class)
- .registerDisplayListener(this, new Handler(UiThreadHelper.getBackgroundLooper()));
- }
-
@Override
public void onDisplayAdded(int displayId) { }
@@ -97,7 +102,7 @@
if (!mRealSize.equals(mTmpPoint1) && !mRealSize.equals(mTmpPoint1.y, mTmpPoint1.x)) {
Log.d(TAG, String.format("Display size changed from %s to %s", mRealSize, mTmpPoint1));
- killProcess();
+ notifyChange();
return;
}
@@ -105,22 +110,28 @@
if (!mSmallestSize.equals(mTmpPoint1) || !mLargestSize.equals(mTmpPoint2)) {
Log.d(TAG, String.format("Available size changed from [%s, %s] to [%s, %s]",
mSmallestSize, mLargestSize, mTmpPoint1, mTmpPoint2));
- killProcess();
+ notifyChange();
}
}
- private void killProcess() {
- Log.d(TAG, "restarting launcher");
- try {
- mContext.unregisterReceiver(this);
- mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
- } catch (Exception e) {
- // We are going to die anyway, ignore any error die to race condition in registering.
+ private synchronized void notifyChange() {
+ if (mCallback != null) {
+ Consumer<Context> callback = mCallback;
+ mCallback = null;
+ new MainThreadExecutor().execute(() -> callback.accept(mContext));
}
- android.os.Process.killProcess(android.os.Process.myPid());
}
private Display getDefaultDisplay(Context context) {
return context.getSystemService(WindowManager.class).getDefaultDisplay();
}
+
+ public void unregister() {
+ try {
+ mContext.unregisterReceiver(this);
+ mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to unregister config monitor", e);
+ }
+ }
}
diff --git a/src/com/android/launcher3/util/SQLiteCacheHelper.java b/src/com/android/launcher3/util/SQLiteCacheHelper.java
index 44c1762..3faf070 100644
--- a/src/com/android/launcher3/util/SQLiteCacheHelper.java
+++ b/src/com/android/launcher3/util/SQLiteCacheHelper.java
@@ -87,6 +87,10 @@
mOpenHelper.clearDB(mOpenHelper.getWritableDatabase());
}
+ public void close() {
+ mOpenHelper.close();
+ }
+
protected abstract void onCreateTable(SQLiteDatabase db);
/**
diff --git a/tests/Android.mk b/tests/Android.mk
index d808873..a787537 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -21,7 +21,8 @@
include $(CLEAR_VARS)
LOCAL_STATIC_JAVA_LIBRARIES := \
androidx.annotation_annotation \
- androidx-test \
+ androidx.test.runner \
+ androidx.test.rules \
androidx.test.uiautomator_uiautomator \
libSharedSystemUI
@@ -42,7 +43,8 @@
LOCAL_MODULE_TAGS := tests
LOCAL_STATIC_JAVA_LIBRARIES := \
- androidx-test \
+ androidx.test.runner \
+ androidx.test.rules \
androidx.test.uiautomator_uiautomator \
mockito-target-minus-junit4