diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java index e1b44740eb..cedd952b27 100644 --- a/quickstep/src/com/android/quickstep/RecentTasksList.java +++ b/quickstep/src/com/android/quickstep/RecentTasksList.java @@ -44,6 +44,7 @@ public class RecentTasksList extends TaskStackChangeListener { private final KeyguardManagerCompat mKeyguardManager; private final MainThreadExecutor mMainThreadExecutor; private final BackgroundExecutor mBgThreadExecutor; + private final TaskListStabilizer mStabilizer = new TaskListStabilizer(); // The list change id, increments as the task list changes in the system private int mChangeId; @@ -83,7 +84,7 @@ public class RecentTasksList extends TaskStackChangeListener { final int requestLoadId = mChangeId; Runnable resultCallback = callback == null ? () -> { } - : () -> callback.accept(mTasks); + : () -> callback.accept(mStabilizer.reorder(mTasks)); if (mLastLoadedId == mChangeId && (!mLastLoadHadKeysOnly || loadKeysOnly)) { // The list is up to date, callback with the same list diff --git a/quickstep/src/com/android/quickstep/TaskListStabilizer.java b/quickstep/src/com/android/quickstep/TaskListStabilizer.java new file mode 100644 index 0000000000..0d239731aa --- /dev/null +++ b/quickstep/src/com/android/quickstep/TaskListStabilizer.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2019 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.config.FeatureFlags.ENABLE_TASK_STABILIZER; + +import android.os.SystemClock; +import android.util.SparseArray; + +import com.android.launcher3.util.IntArray; +import com.android.launcher3.util.IntSet; +import com.android.systemui.shared.recents.model.Task; + +import java.util.ArrayList; + +public class TaskListStabilizer { + + private static final long TASK_CACHE_TIMEOUT_MS = 5000; + + private final SparseArray mTempMap = new SparseArray<>(); + private final IntArray mTempArray = new IntArray(); + private final IntSet mTempSet = new IntSet(); + + private final IntArray mLastStableOrder = new IntArray(); + private final IntSet mLastSet = new IntSet(); + private final IntArray mLastUnstableOrder = new IntArray(); + + private long mLastReorderTime; + + public ArrayList reorder(ArrayList tasks) { + if (!ENABLE_TASK_STABILIZER.get()) { + return tasks; + } + + // Create task id array + int count = tasks.size(); + mTempArray.clear(); + mTempSet.clear(); + mTempMap.clear(); + + for (int i = 0; i < count; i++) { + Task t = tasks.get(i); + mTempMap.put(t.key.id, t); + + mTempSet.add(t.key.id); + mTempArray.add(t.key.id); + } + + if (mLastSet.equals(mTempSet) && isStabilizationQuickEnough()) { + if (mLastStableOrder.equals(mTempArray)) { + // Everything is same + return tasks; + } + + if (!mLastUnstableOrder.equals(mTempArray)) { + // Fast reordering, record the current time. + mLastUnstableOrder.copyFrom(mTempArray); + mLastReorderTime = SystemClock.uptimeMillis(); + } + + // Reorder the tasks based on the last stable order. + ArrayList sorted = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + sorted.add(mTempMap.get(mLastStableOrder.get(i))); + } + return sorted; + } + + // Cache the data + mLastStableOrder.copyFrom(mTempArray); + mLastUnstableOrder.copyFrom(mTempArray); + mLastSet.copyFrom(mTempSet); + + mLastReorderTime = SystemClock.uptimeMillis(); + + return tasks; + } + + private boolean isStabilizationQuickEnough() { + return (SystemClock.uptimeMillis() - mLastReorderTime) < TASK_CACHE_TIMEOUT_MS; + } +} diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 1ffb20d29d..6861bc11f9 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -751,7 +751,8 @@ public abstract class RecentsView extends PagedView impl setRunningTaskIconScaledDown(runningTaskIconScaledDown); setRunningTaskHidden(runningTaskTileHidden); - setCurrentPage(0); + TaskView tv = getRunningTaskView(); + setCurrentPage(tv == null ? 0 : indexOfChild(tv)); // Load the tasks (if the loading is already mTaskListChangeId = mModel.getTasks(this::applyLoadPlan); diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java index 23ad91212b..3f420fa272 100644 --- a/src/com/android/launcher3/config/BaseFlags.java +++ b/src/com/android/launcher3/config/BaseFlags.java @@ -95,6 +95,9 @@ abstract class BaseFlags { public static final TogglableFlag APPLY_CONFIG_AT_RUNTIME = new TogglableFlag( "APPLY_CONFIG_AT_RUNTIME", false, "Apply display changes dynamically"); + public static final TogglableFlag ENABLE_TASK_STABILIZER = new TogglableFlag( + "ENABLE_TASK_STABILIZER", true, "Stable task list across fast task switches"); + public static void initialize(Context context) { // Avoid the disk read for user builds if (Utilities.IS_DEBUG_DEVICE) { diff --git a/src/com/android/launcher3/util/IntArray.java b/src/com/android/launcher3/util/IntArray.java index b2fb32a6cf..d2a551f454 100644 --- a/src/com/android/launcher3/util/IntArray.java +++ b/src/com/android/launcher3/util/IntArray.java @@ -99,6 +99,14 @@ public class IntArray implements Cloneable { mSize += count; } + /** + * Sets the array to be same as {@param other} + */ + public void copyFrom(IntArray other) { + clear(); + addAll(other); + } + /** * Ensures capacity to append at least count values. */ @@ -127,6 +135,25 @@ public class IntArray implements Cloneable { return wrap(toArray()); } + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof IntArray) { + IntArray arr = (IntArray) obj; + if (mSize == arr.mSize) { + for (int i = 0; i < mSize; i++) { + if (arr.mValues[i] != mValues[i]) { + return false; + } + } + return true; + } + } + return false; + } + /** * Returns the value at the specified position in this array. */ diff --git a/src/com/android/launcher3/util/IntSet.java b/src/com/android/launcher3/util/IntSet.java index 2459fad327..851f129a51 100644 --- a/src/com/android/launcher3/util/IntSet.java +++ b/src/com/android/launcher3/util/IntSet.java @@ -49,10 +49,29 @@ public class IntSet { return mArray.size(); } + public void clear() { + mArray.clear(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + return (obj instanceof IntSet) && ((IntSet) obj).mArray.equals(mArray); + } + public IntArray getArray() { return mArray; } + /** + * Sets this set to be same as {@param other} + */ + public void copyFrom(IntSet other) { + mArray.copyFrom(other.mArray); + } + public static IntSet wrap(IntArray array) { IntSet set = new IntSet(); set.mArray.addAll(array);