From 1463f86a38af8b8f317c4689fcfb5f6ddbd17ecc Mon Sep 17 00:00:00 2001 From: Schneider Victor-tulias Date: Thu, 13 Jul 2023 10:50:09 -0700 Subject: [PATCH 1/3] Fix null pointer in createRecentsWindowAnimator. Flag: not needed Fixes: 289715966 Test: performed several gestures Change-Id: Ib18dd0ffbfbc99220e1c7f1970259f139e21f67f --- .../src/com/android/quickstep/RecentsActivity.java | 14 +++++++++++--- .../src/com/android/quickstep/TaskViewUtils.java | 9 +++++---- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java index 8a9c3c826a..c785a9ab04 100644 --- a/quickstep/src/com/android/quickstep/RecentsActivity.java +++ b/quickstep/src/com/android/quickstep/RecentsActivity.java @@ -45,6 +45,7 @@ import android.view.View; import android.window.RemoteTransition; import android.window.SplashScreen; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.app.animation.Interpolators; @@ -239,6 +240,11 @@ public final class RecentsActivity extends StatefulActivity { } final TaskView taskView = (TaskView) v; + final RecentsView recentsView = taskView.getRecentsView(); + if (recentsView == null) { + return super.getActivityLaunchOptions(v, item); + } + RunnableList onEndCallback = new RunnableList(); mActivityLaunchAnimationRunner = new RemoteAnimationFactory() { @@ -247,7 +253,7 @@ public final class RecentsActivity extends StatefulActivity { RemoteAnimationTarget[] wallpaperTargets, RemoteAnimationTarget[] nonAppTargets, AnimationResult result) { mHandler.removeCallbacks(mAnimationStartTimeoutRunnable); - AnimatorSet anim = composeRecentsLaunchAnimator(taskView, appTargets, + AnimatorSet anim = composeRecentsLaunchAnimator(recentsView, taskView, appTargets, wallpaperTargets, nonAppTargets); anim.addListener(resetStateListener()); result.setAnimation(anim, RecentsActivity.this, onEndCallback::executeAllAndDestroy, @@ -282,14 +288,16 @@ public final class RecentsActivity extends StatefulActivity { /** * Composes the animations for a launch from the recents list if possible. */ - private AnimatorSet composeRecentsLaunchAnimator(TaskView taskView, + private AnimatorSet composeRecentsLaunchAnimator( + @NonNull RecentsView recentsView, + @NonNull TaskView taskView, RemoteAnimationTarget[] appTargets, RemoteAnimationTarget[] wallpaperTargets, RemoteAnimationTarget[] nonAppTargets) { AnimatorSet target = new AnimatorSet(); boolean activityClosing = taskIsATargetWithMode(appTargets, getTaskId(), MODE_CLOSING); PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION); - createRecentsWindowAnimator(taskView, !activityClosing, appTargets, + createRecentsWindowAnimator(recentsView, taskView, !activityClosing, appTargets, wallpaperTargets, nonAppTargets, null /* depthController */, pa); target.play(pa.buildAnim()); diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java index c9bad38546..538aba9e21 100644 --- a/quickstep/src/com/android/quickstep/TaskViewUtils.java +++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java @@ -160,13 +160,14 @@ public final class TaskViewUtils { } public static void createRecentsWindowAnimator( - @NonNull TaskView v, boolean skipViewChanges, + @NonNull RecentsView recentsView, + @NonNull TaskView v, + boolean skipViewChanges, @NonNull RemoteAnimationTarget[] appTargets, @NonNull RemoteAnimationTarget[] wallpaperTargets, @NonNull RemoteAnimationTarget[] nonAppTargets, @Nullable DepthController depthController, PendingAnimation out) { - RecentsView recentsView = v.getRecentsView(); boolean isQuickSwitch = v.isEndQuickswitchCuj(); v.setEndQuickswitchCuj(false); @@ -606,8 +607,8 @@ public final class TaskViewUtils { TaskView taskView = findTaskViewToLaunch(recentsView, v, appTargets); PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION); - createRecentsWindowAnimator(taskView, skipLauncherChanges, appTargets, wallpaperTargets, - nonAppTargets, depthController, pa); + createRecentsWindowAnimator(recentsView, taskView, skipLauncherChanges, appTargets, + wallpaperTargets, nonAppTargets, depthController, pa); if (launcherClosing) { // TODO(b/182592057): differentiate between "restore split" vs "launch fullscreen app" TaskViewUtils.createSplitAuxiliarySurfacesAnimator(nonAppTargets, true /*shown*/, From 5f09091583e6031766ac0934801f8380df10144a Mon Sep 17 00:00:00 2001 From: Vinit Nayak Date: Thu, 13 Jul 2023 14:22:39 -0700 Subject: [PATCH 2/3] Show split select instructions toast when starting split on workspace * Used same animation properties that RecentsView uses to show the instructions view unfolding * TODOs: * UI polish (correct asset, animation timings, etc) * Hook into existing animations (if possible) * Migrate recentsView splitInstructionsView to use same/similar code path; remove RecentsView#safeRemoveDragLayerView Bug: 276361926 Test: Create split w/ and w/o flag, works as expected Flag: ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE Change-Id: I7a51b71c358902399ad73b650287cdbf1dad07c6 --- .../res/layout/split_instructions_view.xml | 5 +- quickstep/res/values/strings.xml | 1 + .../uioverrides/QuickstepLauncher.java | 16 ++-- .../util/SplitAnimationController.kt | 57 +++++++++++--- .../views/SplitInstructionsView.java | 77 ++++++++++++++++++- res/values/dimens.xml | 1 + 6 files changed, 130 insertions(+), 27 deletions(-) diff --git a/quickstep/res/layout/split_instructions_view.xml b/quickstep/res/layout/split_instructions_view.xml index 91fb05cd0a..5f037f8971 100644 --- a/quickstep/res/layout/split_instructions_view.xml +++ b/quickstep/res/layout/split_instructions_view.xml @@ -24,12 +24,15 @@ android:paddingTop="@dimen/split_instructions_vertical_padding" android:paddingBottom="@dimen/split_instructions_vertical_padding" android:elevation="@dimen/split_instructions_elevation" - android:visibility="gone"> + android:visibility="gone" + android:importantForAccessibility="yes"> \ No newline at end of file diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml index 77799e6e7b..dd9fc6371b 100644 --- a/quickstep/res/values/strings.xml +++ b/quickstep/res/values/strings.xml @@ -230,6 +230,7 @@ Split Tap another app to use split screen + Exit split screen selection Choose another app to use split screen diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index fbe0a8f722..6c73a2de81 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -544,17 +544,9 @@ public class QuickstepLauncher extends Launcher { ArrayList list = new ArrayList<>(); list.add(getDragController()); - Consumer splitAnimator = animatorSet -> { - AnimatorSet anim = mSplitSelectStateController.getSplitAnimationController() - .createPlaceholderDismissAnim(QuickstepLauncher.this); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mSplitSelectStateController.resetState(); - } - }); - animatorSet.play(anim); - }; + Consumer splitAnimator = animatorSet -> + animatorSet.play(mSplitSelectStateController.getSplitAnimationController() + .createPlaceholderDismissAnim(this)); switch (mode) { case NO_BUTTON: list.add(new NoButtonQuickSwitchTouchController(this)); @@ -673,6 +665,8 @@ public class QuickstepLauncher extends Launcher { mSplitSelectStateController.resetState(); } }); + anim.add(mSplitSelectStateController.getSplitAnimationController() + .getShowSplitInstructionsAnim(this).buildAnim()); anim.buildAnim().start(); } diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt index 5740991a4a..56d68570ac 100644 --- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt +++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt @@ -26,15 +26,17 @@ import android.graphics.Rect import android.graphics.RectF import android.graphics.drawable.Drawable import android.view.View +import com.android.app.animation.Interpolators import com.android.launcher3.DeviceProfile -import com.android.launcher3.Launcher import com.android.launcher3.Utilities import com.android.launcher3.anim.PendingAnimation -import com.android.launcher3.dragndrop.DragLayer +import com.android.launcher3.statemanager.StatefulActivity import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource +import com.android.launcher3.views.BaseDragLayer import com.android.quickstep.views.FloatingTaskView import com.android.quickstep.views.IconView import com.android.quickstep.views.RecentsView +import com.android.quickstep.views.SplitInstructionsView import com.android.quickstep.views.TaskThumbnailView import com.android.quickstep.views.TaskView import com.android.quickstep.views.TaskView.TaskIdAttributeContainer @@ -58,6 +60,8 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC ) } + var splitInstructionsView: SplitInstructionsView? = null + /** * Returns different elements to animate for the initial split selection animation * depending on the state of the surface from which the split was initiated @@ -188,31 +192,26 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC } /** Does not play any animation if user is not currently in split selection state. */ - fun playPlaceholderDismissAnim(launcher: Launcher) { + fun playPlaceholderDismissAnim(launcher: StatefulActivity<*>) { if (!splitSelectStateController.isSplitSelectActive) { return } val anim = createPlaceholderDismissAnim(launcher) - anim.addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - splitSelectStateController.resetState() - } - }) anim.start() } /** Returns [AnimatorSet] which slides initial split placeholder view offscreen. */ - fun createPlaceholderDismissAnim(launcher: Launcher) : AnimatorSet { + fun createPlaceholderDismissAnim(launcher: StatefulActivity<*>) : AnimatorSet { val animatorSet = AnimatorSet() val recentsView : RecentsView<*, *> = launcher.getOverviewPanel() val floatingTask: FloatingTaskView = splitSelectStateController.firstFloatingTaskView ?: return animatorSet // We are in split selection state currently, transitioning to another state - val dragLayer: DragLayer = launcher.dragLayer + val dragLayer: BaseDragLayer<*> = launcher.dragLayer val onScreenRectF = RectF() - Utilities.getBoundsForViewInDragLayer(launcher.dragLayer, floatingTask, + Utilities.getBoundsForViewInDragLayer(dragLayer, floatingTask, Rect(0, 0, floatingTask.width, floatingTask.height), false, null, onScreenRectF) // Get the part of the floatingTask that intersects with the DragLayer (i.e. the @@ -233,6 +232,42 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC floatingTask.stagePosition, launcher.deviceProfile ))) + animatorSet.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + splitSelectStateController.resetState() + safeRemoveViewFromDragLayer(launcher, splitInstructionsView) + } + }) return animatorSet } + + /** + * Returns a [PendingAnimation] to animate in the chip to instruct a user to select a second + * app for splitscreen + */ + fun getShowSplitInstructionsAnim(launcher: StatefulActivity<*>) : PendingAnimation { + safeRemoveViewFromDragLayer(launcher, splitInstructionsView) + splitInstructionsView = SplitInstructionsView.getSplitInstructionsView(launcher) + val timings = AnimUtils.getDeviceOverviewToSplitTimings(launcher.deviceProfile.isTablet) + val anim = PendingAnimation(100 /*duration */) + anim.setViewAlpha(splitInstructionsView, 1f, + Interpolators.clampToProgress(Interpolators.LINEAR, + timings.instructionsContainerFadeInStartOffset, + timings.instructionsContainerFadeInEndOffset)) + anim.setViewAlpha(splitInstructionsView!!.textView, 1f, + Interpolators.clampToProgress(Interpolators.LINEAR, + timings.instructionsTextFadeInStartOffset, + timings.instructionsTextFadeInEndOffset)) + anim.addFloat(splitInstructionsView, SplitInstructionsView.UNFOLD, 0.1f, 1f, + Interpolators.clampToProgress(Interpolators.EMPHASIZED_DECELERATE, + timings.instructionsUnfoldStartOffset, + timings.instructionsUnfoldEndOffset)) + return anim + } + + private fun safeRemoveViewFromDragLayer(launcher: StatefulActivity<*>, view: View?) { + if (view != null) { + launcher.dragLayer.removeView(view) + } + } } diff --git a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java index 0d9e41243b..4ca02e0a13 100644 --- a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java +++ b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java @@ -17,15 +17,21 @@ package com.android.quickstep.views; import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Bundle; import android.util.AttributeSet; import android.util.FloatProperty; +import android.view.MotionEvent; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import androidx.annotation.Nullable; import androidx.appcompat.widget.AppCompatTextView; +import com.android.launcher3.LauncherState; import com.android.launcher3.R; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.statemanager.StatefulActivity; /** @@ -65,7 +71,7 @@ public class SplitInstructionsView extends FrameLayout { mLauncher = (StatefulActivity) context; } - static SplitInstructionsView getSplitInstructionsView(StatefulActivity launcher) { + public static SplitInstructionsView getSplitInstructionsView(StatefulActivity launcher) { ViewGroup dragLayer = launcher.getDragLayer(); final SplitInstructionsView splitInstructionsView = (SplitInstructionsView) launcher.getLayoutInflater().inflate( @@ -73,9 +79,7 @@ public class SplitInstructionsView extends FrameLayout { dragLayer, false ); - - splitInstructionsView.mTextView = splitInstructionsView.findViewById( - R.id.split_instructions_text); + splitInstructionsView.init(); // Since textview overlays base view, and we sometimes manipulate the alpha of each // simultaneously, force overlapping rendering to false prevents redrawing of pixels, @@ -92,6 +96,71 @@ public class SplitInstructionsView extends FrameLayout { ensureProperRotation(); } + private void init() { + mTextView = findViewById(R.id.split_instructions_text); + + if (!FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) { + mTextView.setCompoundDrawables(null, null, null, null); + return; + } + + mTextView.setOnTouchListener((v, event) -> { + if (isTouchInsideRightCompoundDrawable(event)) { + if (event.getAction() == MotionEvent.ACTION_UP) { + exitSplitSelection(); + } + return true; + } + return false; + }); + } + + private void exitSplitSelection() { + ((RecentsView) mLauncher.getOverviewPanel()).getSplitSelectController() + .getSplitAnimationController().playPlaceholderDismissAnim(mLauncher); + mLauncher.getStateManager().goToState(LauncherState.NORMAL); + } + + private boolean isTouchInsideRightCompoundDrawable(MotionEvent event) { + // Get the right compound drawable of the TextView. + Drawable rightDrawable = mTextView.getCompoundDrawablesRelative()[2]; + + // Check if the touch event intersects with the drawable's bounds. + if (rightDrawable != null) { + // We can get away w/o caring about the Y bounds since it's such a small view, if it's + // above/below the drawable just assume they meant to touch it. ¯\_(ツ)_/¯ + return event.getX() >= (mTextView.getWidth() - rightDrawable.getBounds().width()); + } else { + return false; + } + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + if (!FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) { + return; + } + + info.addAction(new AccessibilityNodeInfo.AccessibilityAction( + R.string.toast_split_select_cont_desc, + getResources().getString(R.string.toast_split_select_cont_desc) + )); + } + + @Override + public boolean performAccessibilityAction(int action, Bundle arguments) { + if (!FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) { + return super.performAccessibilityAction(action, arguments); + } + + if (action == R.string.toast_split_select_cont_desc) { + exitSplitSelection(); + return true; + } + return super.performAccessibilityAction(action, arguments); + } + void ensureProperRotation() { ((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler() .setSplitInstructionsParams( diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 6796d4b6f5..1079e0000d 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -411,6 +411,7 @@ 1dp 24dp 12dp + 10dp 24dp 60dp From e7e7a6e5222f47b1a492454f5cf475939211466f Mon Sep 17 00:00:00 2001 From: Vinit Nayak Date: Tue, 20 Jun 2023 17:21:22 -0700 Subject: [PATCH 3/3] Keep track of multiple TasksIDs and Targets in GestureState * Quickswitching between fullscreen and split tasks breaks split, need that to be fixed to further test these code changes Bug: 236226779 Change-Id: I332ad6e2d98760ec1d691dae76e8e3ab8b839c75 --- .../android/quickstep/AbsSwipeUpHandler.java | 84 ++++++++++++------- .../com/android/quickstep/GestureState.java | 76 +++++++++++++---- .../quickstep/RecentsAnimationController.java | 5 +- .../quickstep/TaskAnimationManager.java | 32 ++++--- .../com/android/quickstep/TopTaskTracker.java | 2 +- .../quickstep/TouchInteractionService.java | 4 +- .../quickstep/interaction/AllSetActivity.java | 2 +- .../android/quickstep/views/RecentsView.java | 3 +- 8 files changed, 143 insertions(+), 65 deletions(-) diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java index 25909ac331..ff757b1410 100644 --- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java +++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java @@ -134,6 +134,7 @@ import com.android.quickstep.util.TaskViewSimulator; import com.android.quickstep.views.DesktopTaskView; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; +import com.android.quickstep.views.TaskView.TaskIdAttributeContainer; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -150,6 +151,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Objects; import java.util.Optional; +import java.util.OptionalInt; import java.util.function.Consumer; /** @@ -656,11 +658,12 @@ public abstract class AbsSwipeUpHandler, protected void notifyGestureAnimationStartToRecents() { Task[] runningTasks; + TopTaskTracker.CachedTaskInfo cachedTaskInfo = mGestureState.getRunningTask(); if (mIsSwipeForSplit) { int[] splitTaskIds = TopTaskTracker.INSTANCE.get(mContext).getRunningSplitTaskIds(); - runningTasks = mGestureState.getRunningTask().getPlaceholderTasks(splitTaskIds); + runningTasks = cachedTaskInfo.getPlaceholderTasks(splitTaskIds); } else { - runningTasks = mGestureState.getRunningTask().getPlaceholderTasks(); + runningTasks = cachedTaskInfo.getPlaceholderTasks(); } // Safeguard against any null tasks being sent to recents view, happens when quickswitching @@ -733,8 +736,11 @@ public abstract class AbsSwipeUpHandler, || mRecentsView == null) { return; } + // looking at single target is fine here since either app of a split pair would + // have their "isInRecents" field set? (that's what this is used for below) RemoteAnimationTarget runningTaskTarget = mRecentsAnimationTargets != null - ? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId()) + ? mRecentsAnimationTargets + .findTask(mGestureState.getTopRunningTaskId()) : null; final boolean recentsAttachedToAppWindow; if (mIsInAllAppsRegion) { @@ -1190,7 +1196,7 @@ public abstract class AbsSwipeUpHandler, return false; } boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTarget).anyMatch( - targetCompat -> targetCompat.taskId == mGestureState.getLastStartedTaskId()); + mGestureState.mLastStartedTaskIdPredicate); if (mStateCallback.hasStates(STATE_START_NEW_TASK) && hasStartedTaskBefore) { reset(); return true; @@ -1456,9 +1462,12 @@ public abstract class AbsSwipeUpHandler, @Override public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) { - if (task.taskId == mGestureState.getRunningTaskId() - && task.configuration.windowConfiguration.getActivityType() - != ACTIVITY_TYPE_HOME) { + boolean taskRunningAndNotHome = Arrays.stream(mGestureState + .getRunningTaskIds(true /*getMultipleTasks*/)) + .anyMatch(taskId -> task.taskId == taskId + && task.configuration.windowConfiguration.getActivityType() + != ACTIVITY_TYPE_HOME); + if (taskRunningAndNotHome) { // Since this is an edge case, just cancel and relaunch with default activity // options (since we don't know if there's an associated app icon to launch from) endRunningWindowAnim(true /* cancel */); @@ -1500,8 +1509,12 @@ public abstract class AbsSwipeUpHandler, if (mGestureState.getEndTarget() == HOME) { getOrientationHandler().adjustFloatingIconStartVelocity(velocityPxPerMs); + // Take first task ID, if there are multiple we don't have any special home + // animation so doesn't matter for splitscreen.. though the "allowEnterPip" might change + // depending on which task it is.. final RemoteAnimationTarget runningTaskTarget = mRecentsAnimationTargets != null - ? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId()) + ? mRecentsAnimationTargets + .findTask(mGestureState.getTopRunningTaskId()) : null; final ArrayList cookies = runningTaskTarget != null ? runningTaskTarget.taskInfo.launchCookies @@ -1530,7 +1543,8 @@ public abstract class AbsSwipeUpHandler, // grab a screenshot before the PipContentOverlay gets parented on top of the task UI_HELPER_EXECUTOR.execute(() -> { - final int taskId = mGestureState.getRunningTaskId(); + // Directly use top task, split to pip handled on shell side + final int taskId = mGestureState.getTopRunningTaskId(); mTaskSnapshotCache.put(taskId, mRecentsAnimationController.screenshotTask(taskId)); }); @@ -1994,13 +2008,10 @@ public abstract class AbsSwipeUpHandler, // If there are no targets, then we don't need to capture anything mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); } else { - final int runningTaskId = mGestureState.getRunningTaskId(); boolean finishTransitionPosted = false; // If we already have cached screenshot(s) from running tasks, skip update boolean shouldUpdate = false; - int[] runningTaskIds = mIsSwipeForSplit - ? TopTaskTracker.INSTANCE.get(mContext).getRunningSplitTaskIds() - : new int[]{runningTaskId}; + int[] runningTaskIds = mGestureState.getRunningTaskIds(mIsSwipeForSplit); for (int id : runningTaskIds) { if (!mTaskSnapshotCache.containsKey(id)) { shouldUpdate = true; @@ -2205,16 +2216,27 @@ public abstract class AbsSwipeUpHandler, if (!mCanceled) { TaskView nextTask = mRecentsView == null ? null : mRecentsView.getNextPageTaskView(); if (nextTask != null) { - Task.TaskKey nextTaskKey = nextTask.getTask().key; - int taskId = nextTaskKey.id; - mGestureState.updateLastStartedTaskId(taskId); - boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds() - .contains(taskId); + int[] taskIds = nextTask.getTaskIds(); + StringBuilder nextTaskLog = new StringBuilder(); + for (TaskIdAttributeContainer c : nextTask.getTaskIdAttributeContainers()) { + if (c == null) { + continue; + } + nextTaskLog + .append("[id: ") + .append(c.getTask().key.id) + .append(", pkg: ") + .append(c.getTask().key.getPackageName()) + .append("] | "); + } + mGestureState.updateLastStartedTaskIds(taskIds); + boolean hasTaskPreviouslyAppeared = Arrays.stream(taskIds).anyMatch( + taskId -> mGestureState.getPreviouslyAppearedTaskIds() + .contains(taskId)); if (!hasTaskPreviouslyAppeared) { ActiveGestureLog.INSTANCE.trackEvent(EXPECTING_TASK_APPEARED); } - ActiveGestureLog.INSTANCE.addLog("Launching task: id=" + taskId - + " pkg=" + nextTaskKey.getPackageName()); + ActiveGestureLog.INSTANCE.addLog("Launching task: " + nextTaskLog); nextTask.launchTask(success -> { resultCallback.accept(success); if (success) { @@ -2284,7 +2306,7 @@ public abstract class AbsSwipeUpHandler, public void onTasksAppeared(RemoteAnimationTarget[] appearedTaskTargets) { if (mRecentsAnimationController != null) { boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTargets).anyMatch( - targetCompat -> targetCompat.taskId == mGestureState.getLastStartedTaskId()); + mGestureState.mLastStartedTaskIdPredicate); if (!mStateCallback.hasStates(STATE_GESTURE_COMPLETED) && !hasStartedTaskBefore) { // This is a special case, if a task is started mid-gesture that wasn't a part of a // previous quickswitch task launch, then cancel the animation back to the app @@ -2297,8 +2319,7 @@ public abstract class AbsSwipeUpHandler, } else if (handleTaskAppeared(appearedTaskTargets)) { Optional taskTargetOptional = Arrays.stream(appearedTaskTargets) - .filter(targetCompat -> - targetCompat.taskId == mGestureState.getLastStartedTaskId()) + .filter(mGestureState.mLastStartedTaskIdPredicate) .findFirst(); if (!taskTargetOptional.isPresent()) { ActiveGestureLog.INSTANCE.addLog("No appeared task matching started task id"); @@ -2367,11 +2388,16 @@ public abstract class AbsSwipeUpHandler, * resume if we finish the controller. */ protected int getLastAppearedTaskIndex() { - return mRecentsView == null - ? -1 - : mGestureState.getLastAppearedTaskId() != -1 - ? mRecentsView.getTaskIndexForId(mGestureState.getLastAppearedTaskId()) - : mRecentsView.getRunningTaskIndex(); + if (mRecentsView == null) { + return -1; + } + + OptionalInt firstValidTaskId = Arrays.stream(mGestureState.getLastAppearedTaskIds()) + .filter(i -> i != -1) + .findFirst(); + return firstValidTaskId.isPresent() + ? mRecentsView.getTaskIndexForId(firstValidTaskId.getAsInt()) + : mRecentsView.getRunningTaskIndex(); } /** @@ -2379,7 +2405,7 @@ public abstract class AbsSwipeUpHandler, * but before that task appeared. */ protected boolean hasStartedNewTask() { - return mGestureState.getLastStartedTaskId() != -1; + return mGestureState.getLastStartedTaskIds()[0] != -1; } /** diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java index c7df18f6fb..c2d8c6214e 100644 --- a/quickstep/src/com/android/quickstep/GestureState.java +++ b/quickstep/src/com/android/quickstep/GestureState.java @@ -15,8 +15,9 @@ */ package com.android.quickstep; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; + import static com.android.launcher3.MotionEventsUtils.isTrackpadFourFingerSwipe; -import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe; import static com.android.launcher3.MotionEventsUtils.isTrackpadThreeFingerSwipe; import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS; import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND; @@ -44,10 +45,12 @@ import com.android.systemui.shared.recents.model.ThumbnailData; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Predicate; /** * Manages the state for an active system gesture, listens for events from the system and Launcher, @@ -56,6 +59,18 @@ import java.util.Set; @TargetApi(Build.VERSION_CODES.R) public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationListener { + final Predicate mLastStartedTaskIdPredicate = new Predicate<>() { + @Override + public boolean test(RemoteAnimationTarget targetCompat) { + for (int taskId : mLastStartedTaskId) { + if (targetCompat.taskId == taskId) { + return true; + } + } + return false; + } + }; + /** * Defines the end targets of a gesture and the associated state. */ @@ -161,9 +176,9 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL private TrackpadGestureType mTrackpadGestureType = TrackpadGestureType.NONE; private CachedTaskInfo mRunningTask; private GestureEndTarget mEndTarget; - private RemoteAnimationTarget mLastAppearedTaskTarget; + private RemoteAnimationTarget[] mLastAppearedTaskTargets; private Set mPreviouslyAppearedTaskIds = new HashSet<>(); - private int mLastStartedTaskId = -1; + private int[] mLastStartedTaskId = new int[]{INVALID_TASK_ID, INVALID_TASK_ID}; private RecentsAnimationController mRecentsAnimationController; private HashMap mRecentsAnimationCanceledSnapshots; @@ -189,7 +204,7 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL mGestureId = other.mGestureId; mRunningTask = other.mRunningTask; mEndTarget = other.mEndTarget; - mLastAppearedTaskTarget = other.mLastAppearedTaskTarget; + mLastAppearedTaskTargets = other.mLastAppearedTaskTargets; mPreviouslyAppearedTaskIds = other.mPreviouslyAppearedTaskIds; mLastStartedTaskId = other.mLastStartedTaskId; } @@ -293,10 +308,29 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL } /** - * @return the running task id for this gesture. + * @param getMultipleTasks Whether multiple tasks or not are to be returned (for split) + * @return the running task ids for this gesture. */ - public int getRunningTaskId() { - return mRunningTask != null ? mRunningTask.getTaskId() : -1; + public int[] getRunningTaskIds(boolean getMultipleTasks) { + if (mRunningTask == null) { + return new int[]{INVALID_TASK_ID, INVALID_TASK_ID}; + } else { + int cachedTasksSize = mRunningTask.mAllCachedTasks.size(); + int count = Math.min(cachedTasksSize, getMultipleTasks ? 2 : 1); + int[] runningTaskIds = new int[count]; + for (int i = 0; i < count; i++) { + runningTaskIds[i] = mRunningTask.mAllCachedTasks.get(i).taskId; + } + return runningTaskIds; + } + } + + /** + * @see #getRunningTaskIds(boolean) + * @return the single top-most running taskId for this gesture + */ + public int getTopRunningTaskId() { + return getRunningTaskIds(false /*getMultipleTasks*/)[0]; } /** @@ -309,18 +343,26 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL /** * Updates the last task that appeared during this gesture. */ - public void updateLastAppearedTaskTarget(RemoteAnimationTarget lastAppearedTaskTarget) { - mLastAppearedTaskTarget = lastAppearedTaskTarget; - if (lastAppearedTaskTarget != null) { - mPreviouslyAppearedTaskIds.add(lastAppearedTaskTarget.taskId); + public void updateLastAppearedTaskTargets(RemoteAnimationTarget[] lastAppearedTaskTargets) { + mLastAppearedTaskTargets = lastAppearedTaskTargets; + for (RemoteAnimationTarget target : lastAppearedTaskTargets) { + if (target == null) { + continue; + } + mPreviouslyAppearedTaskIds.add(target.taskId); } } /** * @return The id of the task that appeared during this gesture. */ - public int getLastAppearedTaskId() { - return mLastAppearedTaskTarget != null ? mLastAppearedTaskTarget.taskId : -1; + public int[] getLastAppearedTaskIds() { + if (mLastAppearedTaskTargets == null) { + return new int[]{INVALID_TASK_ID, INVALID_TASK_ID}; + } else { + return Arrays.stream(mLastAppearedTaskTargets) + .mapToInt(target -> target != null ? target.taskId : INVALID_TASK_ID).toArray(); + } } public void updatePreviouslyAppearedTaskIds(Set previouslyAppearedTaskIds) { @@ -334,7 +376,7 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL /** * Updates the last task that we started via startActivityFromRecents() during this gesture. */ - public void updateLastStartedTaskId(int lastStartedTaskId) { + public void updateLastStartedTaskIds(int[] lastStartedTaskId) { mLastStartedTaskId = lastStartedTaskId; } @@ -342,7 +384,7 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL * @return The id of the task that was most recently started during this gesture, or -1 if * no task has been started yet (i.e. we haven't settled on a new task). */ - public int getLastStartedTaskId() { + public int[] getLastStartedTaskIds() { return mLastStartedTaskId; } @@ -478,8 +520,8 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL pw.println(" gestureID=" + mGestureId); pw.println(" runningTask=" + mRunningTask); pw.println(" endTarget=" + mEndTarget); - pw.println(" lastAppearedTaskTargetId=" + getLastAppearedTaskId()); - pw.println(" lastStartedTaskId=" + mLastStartedTaskId); + pw.println(" lastAppearedTaskTargetId=" + Arrays.toString(mLastAppearedTaskTargets)); + pw.println(" lastStartedTaskId=" + Arrays.toString(mLastStartedTaskId)); pw.println(" isRecentsAnimationRunning=" + isRecentsAnimationRunning()); } } diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java index f8e09e1e88..8972dc84c1 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java @@ -115,8 +115,8 @@ public class RecentsAnimationController { * {@link RecentsAnimationCallbacks#onTasksAppeared}}. */ @UiThread - public void removeTaskTarget(@NonNull RemoteAnimationTarget target) { - UI_HELPER_EXECUTOR.execute(() -> mController.removeTask(target.taskId)); + public void removeTaskTarget(int targetTaskId) { + UI_HELPER_EXECUTOR.execute(() -> mController.removeTask(targetTaskId)); } @UiThread @@ -167,7 +167,6 @@ public class RecentsAnimationController { /* event= */ "finishRecentsAnimation", /* extras= */ toRecents, /* gestureEvent= */ FINISH_RECENTS_ANIMATION); - // Finish not yet requested mFinishRequested = true; mFinishTargetIsLauncher = toRecents; diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java index 410ba21866..739f10eb6a 100644 --- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java +++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java @@ -59,7 +59,7 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn private RecentsAnimationTargets mTargets; // Temporary until we can hook into gesture state events private GestureState mLastGestureState; - private RemoteAnimationTarget mLastAppearedTaskTarget; + private RemoteAnimationTarget[] mLastAppearedTaskTargets; private Runnable mLiveTileCleanUpHandler; private Context mCtx; @@ -141,8 +141,15 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn } mController = controller; mTargets = targets; - mLastAppearedTaskTarget = mTargets.findTask(mLastGestureState.getRunningTaskId()); - mLastGestureState.updateLastAppearedTaskTarget(mLastAppearedTaskTarget); + // TODO(b/236226779): We can probably get away w/ setting mLastAppearedTaskTargets + // to all appeared targets directly vs just looking at running ones + int[] runningTaskIds = mLastGestureState.getRunningTaskIds(targets.apps.length > 1); + mLastAppearedTaskTargets = new RemoteAnimationTarget[runningTaskIds.length]; + for (int i = 0; i < runningTaskIds.length; i++) { + RemoteAnimationTarget task = mTargets.findTask(runningTaskIds[i]); + mLastAppearedTaskTargets[i] = task; + } + mLastGestureState.updateLastAppearedTaskTargets(mLastAppearedTaskTargets); } @Override @@ -196,14 +203,17 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn true /*shown*/, null /* animatorHandler */); } if (mController != null) { - if (mLastAppearedTaskTarget == null - || appearedTaskTarget.taskId != mLastAppearedTaskTarget.taskId) { - if (mLastAppearedTaskTarget != null) { - mController.removeTaskTarget(mLastAppearedTaskTarget); + if (mLastAppearedTaskTargets != null) { + for (RemoteAnimationTarget lastTarget : mLastAppearedTaskTargets) { + for (RemoteAnimationTarget appearedTarget : appearedTaskTargets) { + if (appearedTarget.taskId != lastTarget.taskId) { + mController.removeTaskTarget(lastTarget.taskId); + } + } } - mLastAppearedTaskTarget = appearedTaskTarget; - mLastGestureState.updateLastAppearedTaskTarget(mLastAppearedTaskTarget); } + mLastAppearedTaskTargets = appearedTaskTargets; + mLastGestureState.updateLastAppearedTaskTargets(mLastAppearedTaskTargets); } } @@ -268,7 +278,7 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn mCallbacks.addListener(gestureState); gestureState.setState(STATE_RECENTS_ANIMATION_INITIALIZED | STATE_RECENTS_ANIMATION_STARTED); - gestureState.updateLastAppearedTaskTarget(mLastAppearedTaskTarget); + gestureState.updateLastAppearedTaskTargets(mLastAppearedTaskTargets); return mCallbacks; } @@ -369,7 +379,7 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn mCallbacks = null; mTargets = null; mLastGestureState = null; - mLastAppearedTaskTarget = null; + mLastAppearedTaskTargets = null; } @Nullable diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java index d34cddf584..01baed36fc 100644 --- a/quickstep/src/com/android/quickstep/TopTaskTracker.java +++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java @@ -210,7 +210,7 @@ public class TopTaskTracker extends ISplitScreenListener.Stub implements TaskSta @Nullable private final RunningTaskInfo mTopTask; - private final List mAllCachedTasks; + public final List mAllCachedTasks; CachedTaskInfo(List allCachedTasks) { mAllCachedTasks = allCachedTasks; diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index a1ecaeb33a..bb12356b8c 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -41,7 +41,6 @@ import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNF import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED; -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED; import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION; import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BUBBLES; import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE; @@ -792,7 +791,7 @@ public class TouchInteractionService extends Service { ActiveGestureLog.INSTANCE.getLogId()); taskInfo = previousGestureState.getRunningTask(); gestureState.updateRunningTask(taskInfo); - gestureState.updateLastStartedTaskId(previousGestureState.getLastStartedTaskId()); + gestureState.updateLastStartedTaskIds(previousGestureState.getLastStartedTaskIds()); gestureState.updatePreviouslyAppearedTaskIds( previousGestureState.getPreviouslyAppearedTaskIds()); } else { @@ -1005,6 +1004,7 @@ public class TouchInteractionService extends Service { } reasonString.append(SUBSTRING_PREFIX).append("keyguard is not showing occluded"); + // Use overview input consumer for sharesheets on top of home. boolean forceOverviewInputConsumer = gestureState.getActivityInterface().isStarted() && gestureState.getRunningTask() != null diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java index 9f6119c5a5..f11bc81e70 100644 --- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java +++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java @@ -302,7 +302,7 @@ public class AllSetActivity extends Activity { } private AnimatedFloat createSwipeUpProxy(GestureState state) { - if (state.getRunningTaskId() != getTaskId()) { + if (state.getTopRunningTaskId() != getTaskId()) { return null; } mSwipeProgress.updateValue(0); diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 6f16f41e2c..21da7cff3d 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -5233,7 +5233,8 @@ public abstract class RecentsView