From 706ff85fe5ff2aff700278c428d7febb38809066 Mon Sep 17 00:00:00 2001 From: Vinit Nayak Date: Tue, 16 Mar 2021 18:27:32 -0700 Subject: [PATCH] Prevent setting task thumbnail during swipe to overview Previously we were setting the incorrect thumbnail when swiping to overview, then taking a task screenshot right when the animation ends and replacing the first thumbnail. This sometimes caused a flicker of the old thumbnail, now we avoid setting the thumbnail at all for the taskview that is being swiped up. Another edge case handled here is when switching nav modes and then immediately entering overview, Recents receives an onConfigChange for changing task icon size. That would cause all taskviews to null out both their existing icon and thumbnail, now only the icon gets nulled out. Existing issue where switching to 3 button nav and then entering overview shows blank icon, doesn't register for receiving the updated task snapshot fast enough. Fixes: 179307265 Test: Ask assistant for weather, swipe to overview Ask assistant for time, swipe to overview, no flicker Switch nav modes to gesture, swipe to overview. There's a flicker for config change, but thumbnail is correct Change-Id: I300b29f999f6d6876f82bc0189b44f4c10ae33fe --- .../RecentsViewStateController.java | 3 +- .../FallbackRecentsStateController.java | 3 +- .../android/quickstep/views/RecentsView.java | 36 ++++++---- .../com/android/quickstep/views/TaskView.java | 68 +++++++++++++++---- 4 files changed, 84 insertions(+), 26 deletions(-) diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java index 750f673625..e1456b17ad 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java @@ -25,6 +25,7 @@ import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA; import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS; import static com.android.quickstep.views.RecentsView.TASK_MODALNESS; import static com.android.quickstep.views.SplitPlaceholderView.ALPHA_FLOAT; +import static com.android.quickstep.views.TaskView.FLAG_UPDATE_ALL; import android.annotation.TargetApi; import android.os.Build; @@ -73,7 +74,7 @@ public final class RecentsViewStateController extends if (toState.overviewUi) { // While animating into recents, update the visible task data as needed - builder.addOnFrameCallback(mRecentsView::loadVisibleTaskData); + builder.addOnFrameCallback(() -> mRecentsView.loadVisibleTaskData(FLAG_UPDATE_ALL)); mRecentsView.updateEmptyMessage(); } else { builder.addListener( diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java index 54f6ce6dc6..82bfa9b9a9 100644 --- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java +++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java @@ -29,6 +29,7 @@ import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS; import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY; import static com.android.quickstep.views.RecentsView.TASK_MODALNESS; import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION; +import static com.android.quickstep.views.TaskView.FLAG_UPDATE_ALL; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.anim.PropertySetter; @@ -70,7 +71,7 @@ public class FallbackRecentsStateController implements StateHandler mRecentsView.loadVisibleTaskData(FLAG_UPDATE_ALL)); mRecentsView.updateEmptyMessage(); setProperties(toState, config, setter); diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index f216985a94..4d8176c7e5 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -315,6 +315,10 @@ public abstract class RecentsView extends PagedView private final ClearAllButton mClearAllButton; private final Rect mClearAllButtonDeadZoneRect = new Rect(); private final Rect mTaskViewDeadZoneRect = new Rect(); + /** + * Reflects if Recents is currently in the middle of a gesture + */ + private boolean mGestureActive; private final ScrollState mScrollState = new ScrollState(); // Keeps track of the previously known visible tasks for purposes of loading/unloading task data @@ -624,8 +628,8 @@ public abstract class RecentsView extends PagedView return; } mModel.getIconCache().clear(); - unloadVisibleTaskData(); - loadVisibleTaskData(); + unloadVisibleTaskData(TaskView.FLAG_UPDATE_ICON); + loadVisibleTaskData(TaskView.FLAG_UPDATE_ICON); } public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) { @@ -908,7 +912,7 @@ public abstract class RecentsView extends PagedView } // Unload existing visible task data - unloadVisibleTaskData(); + unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL); TaskView ignoreResetTaskView = mIgnoreResetTaskId == -1 ? null : getTaskView(mIgnoreResetTaskId); @@ -1031,7 +1035,7 @@ public abstract class RecentsView extends PagedView updateCurveProperties(); // Update the set of visible task's data - loadVisibleTaskData(); + loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL); setTaskModalness(0); } @@ -1147,7 +1151,7 @@ public abstract class RecentsView extends PagedView } // After scrolling, update the visible task's data - loadVisibleTaskData(); + loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL); } // Update the high res thumbnail loader state @@ -1210,7 +1214,7 @@ public abstract class RecentsView extends PagedView * Iterates through all the tasks, and loads the associated task data for newly visible tasks, * and unloads the associated task data for tasks that are no longer visible. */ - public void loadVisibleTaskData() { + public void loadVisibleTaskData(@TaskView.TaskDataChanges int dataChanges) { 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) @@ -1252,12 +1256,18 @@ public abstract class RecentsView extends PagedView continue; } if (!mHasVisibleTaskData.get(task.key.id)) { - taskView.onTaskListVisibilityChanged(true /* visible */); + // Ignore thumbnail update if it's current running task during the gesture + // We snapshot at end of gesture, it will update then + int changes = dataChanges; + if (taskView == getRunningTaskView() && mGestureActive) { + changes &= ~TaskView.FLAG_UPDATE_THUMBNAIL; + } + taskView.onTaskListVisibilityChanged(true /* visible */, changes); } mHasVisibleTaskData.put(task.key.id, visible); } else { if (mHasVisibleTaskData.get(task.key.id)) { - taskView.onTaskListVisibilityChanged(false /* visible */); + taskView.onTaskListVisibilityChanged(false /* visible */, dataChanges); } mHasVisibleTaskData.delete(task.key.id); } @@ -1267,12 +1277,12 @@ public abstract class RecentsView extends PagedView /** * Unloads any associated data from the currently visible tasks */ - private void unloadVisibleTaskData() { + private void unloadVisibleTaskData(@TaskView.TaskDataChanges int dataChanges) { for (int i = 0; i < mHasVisibleTaskData.size(); i++) { if (mHasVisibleTaskData.valueAt(i)) { TaskView taskView = getTaskView(mHasVisibleTaskData.keyAt(i)); if (taskView != null) { - taskView.onTaskListVisibilityChanged(false /* visible */); + taskView.onTaskListVisibilityChanged(false /* visible */, dataChanges); } } } @@ -1310,7 +1320,7 @@ public abstract class RecentsView extends PagedView mRecentsAnimationController = null; mLiveTileParams.setTargetSet(null); - unloadVisibleTaskData(); + unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL); setCurrentPage(0); mDwbToastShown = false; mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, 0); @@ -1358,6 +1368,7 @@ public abstract class RecentsView extends PagedView * Called when a gesture from an app is starting. */ public void onGestureAnimationStart(RunningTaskInfo runningTaskInfo) { + mGestureActive = true; // This needs to be called before the other states are set since it can create the task view if (mOrientationState.setGestureActive(true)) { updateOrientationHandler(); @@ -1428,6 +1439,7 @@ public abstract class RecentsView extends PagedView * Called when a gesture from an app has finished, and the animation to the target has ended. */ public void onGestureAnimationEnd() { + mGestureActive = false; if (mOrientationState.setGestureActive(false)) { updateOrientationHandler(); } @@ -2733,7 +2745,7 @@ public abstract class RecentsView extends PagedView @Override protected void notifyPageSwitchListener(int prevPage) { super.notifyPageSwitchListener(prevPage); - loadVisibleTaskData(); + loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL); updateEnabledOverlays(); } diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index 2b7e6fd059..a2acab880f 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -42,6 +42,8 @@ import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE; +import static java.lang.annotation.RetentionPolicy.SOURCE; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; @@ -67,6 +69,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import android.widget.Toast; +import androidx.annotation.IntDef; import androidx.annotation.NonNull; import com.android.launcher3.DeviceProfile; @@ -106,6 +109,7 @@ import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.ActivityOptionsCompat; import com.android.systemui.shared.system.QuickStepContract; +import java.lang.annotation.Retention; import java.util.Collections; import java.util.List; import java.util.function.Consumer; @@ -117,6 +121,19 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { private static final String TAG = TaskView.class.getSimpleName(); + public static final int FLAG_UPDATE_ICON = 1; + public static final int FLAG_UPDATE_THUMBNAIL = FLAG_UPDATE_ICON << 1; + + public static final int FLAG_UPDATE_ALL = FLAG_UPDATE_ICON | FLAG_UPDATE_THUMBNAIL; + + /** + * Used in conjunction with {@link #onTaskListVisibilityChanged(boolean, int)}, providing more + * granularity on which components of this task require an update + */ + @Retention(SOURCE) + @IntDef({FLAG_UPDATE_ALL, FLAG_UPDATE_ICON, FLAG_UPDATE_THUMBNAIL}) + public @interface TaskDataChanges {} + /** * The alpha of a black scrim on a page in the carousel as it leaves the screen. * In the resting position of the carousel, the adjacent pages have about half this scrim. @@ -557,7 +574,19 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { } } + /** + * See {@link TaskDataChanges} + * @param visible If this task view will be visible to the user in overview or hidden + */ public void onTaskListVisibilityChanged(boolean visible) { + onTaskListVisibilityChanged(visible, FLAG_UPDATE_ALL); + } + + /** + * See {@link TaskDataChanges} + * @param visible If this task view will be visible to the user in overview or hidden + */ + public void onTaskListVisibilityChanged(boolean visible, @TaskDataChanges int changes) { if (mTask == null) { return; } @@ -568,22 +597,37 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { RecentsModel model = RecentsModel.INSTANCE.get(getContext()); TaskThumbnailCache thumbnailCache = model.getThumbnailCache(); TaskIconCache iconCache = model.getIconCache(); - mThumbnailLoadRequest = thumbnailCache.updateThumbnailInBackground( - mTask, thumbnail -> mSnapshotView.setThumbnail(mTask, thumbnail)); - mIconLoadRequest = iconCache.updateIconInBackground(mTask, - (task) -> { - setIcon(task.icon); - mDigitalWellBeingToast.initialize(mTask); - }); + + if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) { + mThumbnailLoadRequest = thumbnailCache.updateThumbnailInBackground( + mTask, thumbnail -> { + mSnapshotView.setThumbnail(mTask, thumbnail); + }); + } + if (needsUpdate(changes, FLAG_UPDATE_ICON)) { + mIconLoadRequest = iconCache.updateIconInBackground(mTask, + (task) -> { + setIcon(task.icon); + mDigitalWellBeingToast.initialize(mTask); + }); + } } else { - mSnapshotView.setThumbnail(null, null); - setIcon(null); - // Reset the task thumbnail reference as well (it will be fetched from the cache or - // reloaded next time we need it) - mTask.thumbnail = null; + if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) { + mSnapshotView.setThumbnail(null, null); + // Reset the task thumbnail reference as well (it will be fetched from the cache or + // reloaded next time we need it) + mTask.thumbnail = null; + } + if (needsUpdate(changes, FLAG_UPDATE_ICON)) { + setIcon(null); + } } } + private boolean needsUpdate(@TaskDataChanges int dataChange, @TaskDataChanges int flag) { + return (dataChange & flag) == flag; + } + private void cancelPendingLoadTasks() { if (mThumbnailLoadRequest != null) { mThumbnailLoadRequest.cancel();