From 69ee1b9caf9d8ff5b999f0fea0699ba2ebd12a87 Mon Sep 17 00:00:00 2001 From: Vinit Nayak Date: Mon, 22 May 2023 16:30:04 -0700 Subject: [PATCH 1/5] Handle fullscreen app launchs in split init flow for SplitSelectDataHolder * SystemUi split APIs are overloaded to launch fullscreen tasks as well, break that out into a separate method for clarity * Have SplitSelectDataHolder also special case fullscreen launches Bug: 283045822 Test: Launched various combinations of split tasks, also tapped on initial view to see fullscreen launch with tasks and intents Flag: none Change-Id: If3de9784b72e0f5f27f1b6d97cbf835b8f5391de --- .../quickstep/util/SplitSelectDataHolder.kt | 27 ++++- .../util/SplitSelectStateController.java | 114 +++++++++++++----- .../android/quickstep/views/RecentsView.java | 2 +- 3 files changed, 110 insertions(+), 33 deletions(-) diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt b/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt index 614dfe82d6..8e6415b303 100644 --- a/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt +++ b/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt @@ -61,7 +61,8 @@ class SplitSelectDataHolder( */ companion object { @IntDef(SPLIT_TASK_TASK, SPLIT_TASK_PENDINGINTENT, SPLIT_TASK_SHORTCUT, - SPLIT_PENDINGINTENT_TASK, SPLIT_PENDINGINTENT_PENDINGINTENT, SPLIT_SHORTCUT_TASK) + SPLIT_PENDINGINTENT_TASK, SPLIT_PENDINGINTENT_PENDINGINTENT, SPLIT_SHORTCUT_TASK, + SPLIT_SINGLE_TASK_FULLSCREEN, SPLIT_SINGLE_INTENT_FULLSCREEN) @Retention(AnnotationRetention.SOURCE) annotation class SplitLaunchType @@ -71,6 +72,10 @@ class SplitSelectDataHolder( const val SPLIT_PENDINGINTENT_TASK = 3 const val SPLIT_SHORTCUT_TASK = 4 const val SPLIT_PENDINGINTENT_PENDINGINTENT = 5 + + // Non-split edge case of launching the initial selected task as a fullscreen task + const val SPLIT_SINGLE_TASK_FULLSCREEN = 6 + const val SPLIT_SINGLE_INTENT_FULLSCREEN = 7 } @@ -190,7 +195,7 @@ class SplitSelectDataHolder( /** * @return [SplitLaunchData] with the necessary fields populated as determined by - * [SplitLaunchData.splitLaunchType] + * [SplitLaunchData.splitLaunchType]. This is to be used for launching splitscreen */ fun getSplitLaunchData() : SplitLaunchData { // Convert all intents to shortcut infos to see if determine if we launch shortcut or intent @@ -201,6 +206,24 @@ class SplitSelectDataHolder( initialStagePosition = getOppositeStagePosition(initialStagePosition) } + return generateSplitLaunchData(splitLaunchType) + } + + /** + * @return [SplitLaunchData] with the necessary fields populated as determined by + * [SplitLaunchData.splitLaunchType]. This is to be used for launching an initially selected + * split task in fullscreen + */ + fun getFullscreenLaunchData() : SplitLaunchData { + // Convert all intents to shortcut infos to see if determine if we launch shortcut or intent + convertIntentsToFinalTypes() + val splitLaunchType = if (initialTaskId != INVALID_TASK_ID) SPLIT_SINGLE_TASK_FULLSCREEN + else SPLIT_SINGLE_INTENT_FULLSCREEN + + return generateSplitLaunchData(splitLaunchType) + } + + private fun generateSplitLaunchData(@SplitLaunchType splitLaunchType: Int) : SplitLaunchData { return SplitLaunchData( splitLaunchType, initialTaskId, diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java index ee51af7a5d..ec8be89ecb 100644 --- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java +++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java @@ -27,6 +27,8 @@ import static com.android.launcher3.util.SplitConfigurationOptions.getOppositeSt import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_PENDINGINTENT_PENDINGINTENT; import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_PENDINGINTENT_TASK; import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_SHORTCUT_TASK; +import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_SINGLE_INTENT_FULLSCREEN; +import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_SINGLE_TASK_FULLSCREEN; import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_PENDINGINTENT; import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_SHORTCUT; import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_TASK; @@ -324,11 +326,8 @@ public class SplitSelectStateController { } boolean hasSecondaryPendingIntent = mSecondPendingIntent != null; if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) { - final RemoteSplitLaunchTransitionRunner animationRunner = - new RemoteSplitLaunchTransitionRunner(taskId1, taskId2, callback); - final RemoteTransition remoteTransition = new RemoteTransition(animationRunner, - ActivityThread.currentActivityThread().getApplicationThread(), - "LaunchSplitPair"); + final RemoteTransition remoteTransition = getShellRemoteTransition(taskId1, taskId2, + callback); if (intent1 == null && (intent2 == null && !hasSecondaryPendingIntent)) { mSystemUiProxy.startTasks(taskId1, options1.toBundle(), taskId2, null /* options2 */, stagePosition, splitRatio, remoteTransition, @@ -351,11 +350,8 @@ public class SplitSelectStateController { shellInstanceId); } } else { - final RemoteSplitLaunchAnimationRunner animationRunner = - new RemoteSplitLaunchAnimationRunner(taskId1, taskId2, callback); - final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter( - animationRunner, 300, 150, - ActivityThread.currentActivityThread().getApplicationThread()); + final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(taskId1, taskId2, + callback); if (intent1 == null && (intent2 == null && !hasSecondaryPendingIntent)) { mSystemUiProxy.startTasksWithLegacyTransition(taskId1, options1.toBundle(), @@ -402,11 +398,8 @@ public class SplitSelectStateController { Bundle optionsBundle = options1.toBundle(); if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) { - final RemoteSplitLaunchTransitionRunner animationRunner = - new RemoteSplitLaunchTransitionRunner(firstTaskId, secondTaskId, callback); - final RemoteTransition remoteTransition = new RemoteTransition(animationRunner, - ActivityThread.currentActivityThread().getApplicationThread(), - "LaunchSplitPair"); + final RemoteTransition remoteTransition = getShellRemoteTransition(firstTaskId, + secondTaskId, callback); switch (launchData.getSplitLaunchType()) { case SPLIT_TASK_TASK -> mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId, @@ -440,11 +433,8 @@ public class SplitSelectStateController { remoteTransition, shellInstanceId); } } else { - final RemoteSplitLaunchAnimationRunner animationRunner = - new RemoteSplitLaunchAnimationRunner(firstTaskId, secondTaskId, callback); - final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter( - animationRunner, 300, 150, - ActivityThread.currentActivityThread().getApplicationThread()); + final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(firstTaskId, secondTaskId, + callback); switch (launchData.getSplitLaunchType()) { case SPLIT_TASK_TASK -> mSystemUiProxy.startTasksWithLegacyTransition(firstTaskId, optionsBundle, @@ -501,26 +491,90 @@ public class SplitSelectStateController { Bundle optionsBundle = options1.toBundle(); if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) { - final RemoteSplitLaunchTransitionRunner animationRunner = - new RemoteSplitLaunchTransitionRunner(firstTaskId, secondTaskId, callback); - final RemoteTransition remoteTransition = new RemoteTransition(animationRunner, - ActivityThread.currentActivityThread().getApplicationThread(), - "LaunchSplitPair"); + final RemoteTransition remoteTransition = getShellRemoteTransition(firstTaskId, + secondTaskId, callback); mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId, null /* options2 */, stagePosition, splitRatio, remoteTransition, null /*shellInstanceId*/); } else { - final RemoteSplitLaunchAnimationRunner animationRunner = - new RemoteSplitLaunchAnimationRunner(firstTaskId, secondTaskId, callback); - final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter( - animationRunner, 300, 150, - ActivityThread.currentActivityThread().getApplicationThread()); + final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(firstTaskId, + secondTaskId, callback); mSystemUiProxy.startTasksWithLegacyTransition(firstTaskId, optionsBundle, secondTaskId, null /* options2 */, stagePosition, splitRatio, adapter, null /*shellInstanceId*/); } } + /** + * Launches the initially selected task/intent in fullscreen (note the same SystemUi APIs are + * used as {@link #launchSplitTasks(Consumer)} because they are overloaded to launch both + * split and fullscreen tasks) + */ + public void launchInitialAppFullscreen(Consumer callback) { + if (!FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) { + launchSplitTasks(callback); + return; + } + + final ActivityOptions options1 = ActivityOptions.makeBasic(); + SplitSelectDataHolder.SplitLaunchData launchData = + mSplitSelectDataHolder.getFullscreenLaunchData(); + int firstTaskId = launchData.getInitialTaskId(); + int secondTaskId = launchData.getSecondTaskId(); + PendingIntent firstPI = launchData.getInitialPendingIntent(); + int firstUserId = launchData.getInitialUserId(); + int initialStagePosition = launchData.getInitialStagePosition(); + Bundle optionsBundle = options1.toBundle(); + + final RemoteSplitLaunchTransitionRunner animationRunner = + new RemoteSplitLaunchTransitionRunner(firstTaskId, secondTaskId, callback); + final RemoteTransition remoteTransition = new RemoteTransition(animationRunner, + ActivityThread.currentActivityThread().getApplicationThread(), + "LaunchSplitPair"); + Pair instanceIds = + LogUtils.getShellShareableInstanceId(); + if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) { + switch (launchData.getSplitLaunchType()) { + case SPLIT_SINGLE_TASK_FULLSCREEN -> mSystemUiProxy.startTasks(firstTaskId, + optionsBundle, secondTaskId, null /* options2 */, initialStagePosition, + DEFAULT_SPLIT_RATIO, remoteTransition, instanceIds.first); + case SPLIT_SINGLE_INTENT_FULLSCREEN -> mSystemUiProxy.startIntentAndTask(firstPI, + firstUserId, optionsBundle, secondTaskId, null /*options2*/, + initialStagePosition, DEFAULT_SPLIT_RATIO, remoteTransition, + instanceIds.first); + } + } else { + final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(firstTaskId, + secondTaskId, callback); + switch (launchData.getSplitLaunchType()) { + case SPLIT_SINGLE_TASK_FULLSCREEN -> mSystemUiProxy.startTasksWithLegacyTransition( + firstTaskId, optionsBundle, secondTaskId, null /* options2 */, + initialStagePosition, DEFAULT_SPLIT_RATIO, adapter, instanceIds.first); + case SPLIT_SINGLE_INTENT_FULLSCREEN -> + mSystemUiProxy.startIntentAndTaskWithLegacyTransition(firstPI, firstUserId, + optionsBundle, secondTaskId, null /*options2*/, + initialStagePosition, DEFAULT_SPLIT_RATIO, adapter, + instanceIds.first); + } + } + } + + private RemoteTransition getShellRemoteTransition(int firstTaskId, int secondTaskId, + Consumer callback) { + final RemoteSplitLaunchTransitionRunner animationRunner = + new RemoteSplitLaunchTransitionRunner(firstTaskId, secondTaskId, callback); + return new RemoteTransition(animationRunner, + ActivityThread.currentActivityThread().getApplicationThread(), "LaunchSplitPair"); + } + + private RemoteAnimationAdapter getLegacyRemoteAdapter(int firstTaskId, int secondTaskId, + Consumer callback) { + final RemoteSplitLaunchAnimationRunner animationRunner = + new RemoteSplitLaunchAnimationRunner(firstTaskId, secondTaskId, callback); + return new RemoteAnimationAdapter(animationRunner, 300, 150, + ActivityThread.currentActivityThread().getApplicationThread()); + } + private void launchIntentOrShortcut(Intent intent, UserHandle user, ActivityOptions options1, int taskId, @StagePosition int stagePosition, float splitRatio, RemoteTransition remoteTransition, @Nullable InstanceId shellInstanceId) { diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index d8fe32db85..f740d9c082 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -3260,7 +3260,7 @@ public abstract class RecentsView - mSplitSelectStateController.launchSplitTasks(launchSuccess -> + mSplitSelectStateController.launchInitialAppFullscreen(launchSuccess -> resetFromSplitSelectionState())); pendingAnimation.buildAnim().start(); From 8f26e477368837083cb7628019e3682ee2e5a07d Mon Sep 17 00:00:00 2001 From: Schneider Victor-tulias Date: Wed, 24 May 2023 10:04:05 -0700 Subject: [PATCH 2/5] Fix keyboard quick switch d-pad left/right traversal direction Flag: ENABLE_KEYBOARD_QUICK_SWITCH Fixes: 284156698 Test: tried keyboard quick switching with d-pad left/right in rtl/ltr Change-Id: Idb7466b316cb7cbd9f592c5949d2c95c3278d85f --- .../launcher3/taskbar/KeyboardQuickSwitchViewController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java index 7bd8898dfc..3230c661d5 100644 --- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java @@ -185,8 +185,8 @@ public class KeyboardQuickSwitchViewController { return false; } boolean traverseBackwards = (keyCode == KeyEvent.KEYCODE_TAB && event.isShiftPressed()) - || (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT && !isRTL) - || (keyCode == KeyEvent.KEYCODE_DPAD_LEFT && isRTL); + || (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT && isRTL) + || (keyCode == KeyEvent.KEYCODE_DPAD_LEFT && !isRTL); int taskCount = mControllerCallbacks.getTaskCount(); int toIndex = mCurrentFocusIndex == -1 // Focus the second-most recent app if possible From 9a7de100f47e79d39bf0496e9de927a0cee9a317 Mon Sep 17 00:00:00 2001 From: Schneider Victor-tulias Date: Tue, 23 May 2023 14:45:38 -0700 Subject: [PATCH 3/5] Update gesture nav edu motion and colors - Updated gesture nav edu overview step post-success motion and colors - Updated gesture nav edu menu done button color and legacy action button color - Switching light/dark mode when a step is complete started the demonstration animation. fixed this jank Flag: ENABLE_NEW_GESTURE_NAV_TUTORIAL Fixes: 281764891 Fixes: 283964958 Bug: 279823249 Test: ran full tutorial with ENABLE_NEW_GESTURE_NAV_TUTORIAL enabled and disabled Change-Id: I9adc05947267ec038b6f374b2a29a5499468883b --- .../gesture_tutorial_step_menu.xml | 1 + .../res/layout/gesture_tutorial_fragment.xml | 1 + .../gesture_tutorial_mock_task_view.xml | 21 ---- .../res/layout/gesture_tutorial_step_menu.xml | 1 + .../swipe_up_gesture_tutorial_shape.xml | 20 --- .../interaction/AnimatedTaskView.java | 117 +----------------- .../BackGestureTutorialController.java | 2 +- .../HomeGestureTutorialController.java | 23 ++-- .../OverviewGestureTutorialController.java | 73 +++++++---- .../SwipeUpGestureTutorialController.java | 42 ++++--- .../interaction/TutorialController.java | 16 ++- .../interaction/TutorialFragment.java | 7 ++ 12 files changed, 113 insertions(+), 211 deletions(-) delete mode 100644 quickstep/res/layout/gesture_tutorial_mock_task_view.xml delete mode 100644 quickstep/res/layout/swipe_up_gesture_tutorial_shape.xml diff --git a/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml b/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml index b13e8dc0ba..225b4cdc39 100644 --- a/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml +++ b/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml @@ -157,6 +157,7 @@ android:layout_marginVertical="16dp" android:text="@string/gesture_tutorial_action_button_label" android:background="@drawable/gesture_tutorial_action_button_background" + android:backgroundTint="?androidprv:attr/materialColorPrimary" android:stateListAnimator="@null" app:layout_constraintTop_toBottomOf="@id/guideline" diff --git a/quickstep/res/layout/gesture_tutorial_fragment.xml b/quickstep/res/layout/gesture_tutorial_fragment.xml index 3bd0df0349..64ad1f7f4e 100644 --- a/quickstep/res/layout/gesture_tutorial_fragment.xml +++ b/quickstep/res/layout/gesture_tutorial_fragment.xml @@ -175,6 +175,7 @@ android:paddingEnd="26dp" android:text="@string/gesture_tutorial_action_button_label" android:background="@drawable/gesture_tutorial_action_button_background" + android:backgroundTint="?android:attr/colorAccent" android:stateListAnimator="@null" android:visibility="invisible" diff --git a/quickstep/res/layout/gesture_tutorial_mock_task_view.xml b/quickstep/res/layout/gesture_tutorial_mock_task_view.xml deleted file mode 100644 index 047fdb1071..0000000000 --- a/quickstep/res/layout/gesture_tutorial_mock_task_view.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - \ No newline at end of file diff --git a/quickstep/res/layout/gesture_tutorial_step_menu.xml b/quickstep/res/layout/gesture_tutorial_step_menu.xml index 7a302f437b..cf78b1b016 100644 --- a/quickstep/res/layout/gesture_tutorial_step_menu.xml +++ b/quickstep/res/layout/gesture_tutorial_step_menu.xml @@ -156,6 +156,7 @@ android:layout_marginVertical="16dp" android:text="@string/gesture_tutorial_action_button_label" android:background="@drawable/gesture_tutorial_action_button_background" + android:backgroundTint="?androidprv:attr/materialColorPrimary" android:stateListAnimator="@null" app:layout_constraintTop_toBottomOf="@id/guideline" diff --git a/quickstep/res/layout/swipe_up_gesture_tutorial_shape.xml b/quickstep/res/layout/swipe_up_gesture_tutorial_shape.xml deleted file mode 100644 index d60e84f043..0000000000 --- a/quickstep/res/layout/swipe_up_gesture_tutorial_shape.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - diff --git a/quickstep/src/com/android/quickstep/interaction/AnimatedTaskView.java b/quickstep/src/com/android/quickstep/interaction/AnimatedTaskView.java index 44b3d625e6..742b0fc7e8 100644 --- a/quickstep/src/com/android/quickstep/interaction/AnimatedTaskView.java +++ b/quickstep/src/com/android/quickstep/interaction/AnimatedTaskView.java @@ -15,10 +15,6 @@ */ package com.android.quickstep.interaction; -import static com.android.launcher3.QuickstepTransitionManager.ANIMATION_NAV_FADE_OUT_DURATION; -import static com.android.launcher3.QuickstepTransitionManager.NAV_FADE_OUT_INTERPOLATOR; -import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; -import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.config.FeatureFlags.ENABLE_NEW_GESTURE_NAV_TUTORIAL; import android.animation.Animator; @@ -28,30 +24,19 @@ import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.ColorInt; import android.content.Context; -import android.graphics.Matrix; import android.graphics.Outline; import android.graphics.Rect; import android.util.AttributeSet; -import android.util.Log; -import android.view.Display; -import android.view.RoundedCorner; import android.view.View; import android.view.ViewOutlineProvider; -import android.view.animation.ScaleAnimation; -import android.view.animation.TranslateAnimation; -import android.widget.ViewAnimator; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.constraintlayout.widget.ConstraintLayout; import com.android.launcher3.R; -import com.android.launcher3.Utilities; -import com.android.launcher3.anim.Interpolators; -import com.android.quickstep.util.MultiValueUpdateListener; import java.util.ArrayList; -import java.util.Arrays; /** * Helper View for the gesture tutorial mock previous app task view. @@ -61,8 +46,6 @@ import java.util.Arrays; */ public class AnimatedTaskView extends ConstraintLayout { - private static final long ANIMATE_TO_FULL_SCREEN_DURATION = 300; - private View mFullTaskView; private View mTopTaskView; private View mBottomTaskView; @@ -72,17 +55,16 @@ public class AnimatedTaskView extends ConstraintLayout { private float mTaskViewAnimatedRadius; public AnimatedTaskView(@NonNull Context context) { - super(context); + this(context, null); } - public AnimatedTaskView(@NonNull Context context, - @Nullable AttributeSet attrs) { - super(context, attrs); + public AnimatedTaskView(@NonNull Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); } public AnimatedTaskView( @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); + this(context, attrs, defStyleAttr, 0); } public AnimatedTaskView( @@ -104,97 +86,6 @@ public class AnimatedTaskView extends ConstraintLayout { setToSingleRowLayout(false); } - void animateToFillScreen(@Nullable Runnable onAnimationEndCallback) { - if (mTaskViewOutlineProvider == null) { - // This is an illegal state. - return; - } - // calculate start and end corner radius - Outline startOutline = new Outline(); - mTaskViewOutlineProvider.getOutline(this, startOutline); - Rect outlineStartRect = new Rect(); - startOutline.getRect(outlineStartRect); - float outlineStartRadius = startOutline.getRadius(); - - final Display display = mContext.getDisplay();; - RoundedCorner corner = display.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT); - float outlineEndRadius = corner.getRadius(); - - // create animation - AnimatorSet set = new AnimatorSet(); - ArrayList animations = new ArrayList<>(); - - // center view - animations.add(ObjectAnimator.ofFloat(this, TRANSLATION_X, 0)); - - // retrieve start animation matrix to scale off of - Matrix matrix = getAnimationMatrix(); - if (matrix == null) { - // This is an illegal state. - return; - } - - float[] matrixValues = new float[9]; - matrix.getValues(matrixValues); - float[] newValues = matrixValues.clone(); - - ValueAnimator transformAnimation = ValueAnimator.ofFloat(0, 1); - - MultiValueUpdateListener listener = new MultiValueUpdateListener() { - Matrix currentMatrix = new Matrix(); - - FloatProp mOutlineRadius = new FloatProp(outlineStartRadius, outlineEndRadius, 0, - ANIMATE_TO_FULL_SCREEN_DURATION, LINEAR); - FloatProp mTransX = new FloatProp(matrixValues[Matrix.MTRANS_X], 0f, 0, - ANIMATE_TO_FULL_SCREEN_DURATION, LINEAR); - FloatProp mTransY = new FloatProp(matrixValues[Matrix.MTRANS_Y], 0f, 0, - ANIMATE_TO_FULL_SCREEN_DURATION, LINEAR); - FloatProp mScaleX = new FloatProp(matrixValues[Matrix.MSCALE_X], 1f, 0, - ANIMATE_TO_FULL_SCREEN_DURATION, LINEAR); - FloatProp mScaleY = new FloatProp(matrixValues[Matrix.MSCALE_Y], 1f, 0, - ANIMATE_TO_FULL_SCREEN_DURATION, LINEAR); - - @Override - public void onUpdate(float percent, boolean initOnly) { - // scale corner radius to match display radius - mTaskViewAnimatedRadius = mOutlineRadius.value; - mFullTaskView.invalidateOutline(); - - // translate to center, ends at translation x:0, y:0 - newValues[Matrix.MTRANS_X] = mTransX.value; - newValues[Matrix.MTRANS_Y] = mTransY.value; - - // scale to full size, ends at scale 1 - newValues[Matrix.MSCALE_X] = mScaleX.value; - newValues[Matrix.MSCALE_Y] = mScaleY.value; - - // create and set new animation matrix - currentMatrix.setValues(newValues); - setAnimationMatrix(currentMatrix); - } - }; - - transformAnimation.addUpdateListener(listener); - animations.add(transformAnimation); - set.playSequentially(animations); - set.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - super.onAnimationStart(animation); - addAnimatedOutlineProvider(mFullTaskView, outlineStartRect, outlineStartRadius); - } - - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - if (onAnimationEndCallback != null) { - onAnimationEndCallback.run(); - } - } - }); - set.start(); - } - AnimatorSet createAnimationToMultiRowLayout() { if (mTaskViewOutlineProvider == null) { // This is an illegal state. diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java index 17e57bf84b..54c441b59e 100644 --- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java @@ -136,7 +136,7 @@ final class BackGestureTutorialController extends TutorialController { } @Override - protected int getSwipeActionColor() { + protected int getFakeLauncherColor() { return mTutorialFragment.mRootView.mColorSurfaceContainer; } diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java index 0012d47057..891f20e560 100644 --- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java @@ -104,11 +104,9 @@ final class HomeGestureTutorialController extends SwipeUpGestureTutorialControll @Override protected int getMockAppTaskLayoutResId() { - return ENABLE_NEW_GESTURE_NAV_TUTORIAL.get() - ? R.layout.swipe_up_gesture_tutorial_shape - : mTutorialFragment.isLargeScreen() - ? R.layout.gesture_tutorial_tablet_mock_webpage - : R.layout.gesture_tutorial_mock_webpage; + return mTutorialFragment.isLargeScreen() + ? R.layout.gesture_tutorial_tablet_mock_webpage + : R.layout.gesture_tutorial_mock_webpage; } @Override @@ -121,7 +119,12 @@ final class HomeGestureTutorialController extends SwipeUpGestureTutorialControll } @Override - protected int getSwipeActionColor() { + protected int getFakeTaskViewColor() { + return isGestureCompleted() ? getFakeLauncherColor() : getExitingAppColor(); + } + + @Override + protected int getFakeLauncherColor() { return mTutorialFragment.mRootView.mColorSurfaceContainer; } @@ -148,7 +151,7 @@ final class HomeGestureTutorialController extends SwipeUpGestureTutorialControll case BACK_CANCELLED_FROM_LEFT: case BACK_CANCELLED_FROM_RIGHT: case BACK_NOT_STARTED_TOO_FAR_FROM_EDGE: - resetTaskView(); + resetTaskViews(); showFeedback(R.string.home_gesture_feedback_swipe_too_far_from_edge); break; } @@ -178,18 +181,18 @@ final class HomeGestureTutorialController extends SwipeUpGestureTutorialControll } case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE: case OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE: - resetTaskView(); + resetTaskViews(); showFeedback(R.string.home_gesture_feedback_swipe_too_far_from_edge); break; case OVERVIEW_GESTURE_COMPLETED: - fadeOutFakeTaskView(true, true, () -> { + fadeOutFakeTaskView(true, () -> { showFeedback(R.string.home_gesture_feedback_overview_detected); showFakeTaskbar(/* animateFromHotseat= */ false); }); break; case HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION: case HOME_OR_OVERVIEW_CANCELLED: - fadeOutFakeTaskView(false, true, null); + fadeOutFakeTaskView(false, null); showFeedback(R.string.home_gesture_feedback_wrong_swipe_direction); break; } diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java index 593e6d9a9f..667fe4dfad 100644 --- a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java @@ -25,7 +25,9 @@ import android.annotation.TargetApi; import android.graphics.PointF; import android.os.Build; import android.os.Handler; -import android.view.View; + +import androidx.annotation.ColorInt; +import androidx.core.graphics.ColorUtils; import com.android.launcher3.R; import com.android.launcher3.Utilities; @@ -43,6 +45,8 @@ import java.util.Map; @TargetApi(Build.VERSION_CODES.R) final class OverviewGestureTutorialController extends SwipeUpGestureTutorialController { + private static final float LAUNCHER_COLOR_BLENDING_RATIO = 0.4f; + OverviewGestureTutorialController(OverviewGestureTutorialFragment fragment, TutorialType tutorialType) { super(fragment, tutorialType); @@ -112,11 +116,9 @@ final class OverviewGestureTutorialController extends SwipeUpGestureTutorialCont @Override protected int getMockAppTaskLayoutResId() { - return ENABLE_NEW_GESTURE_NAV_TUTORIAL.get() - ? R.layout.gesture_tutorial_mock_task_view - : mTutorialFragment.isLargeScreen() - ? R.layout.gesture_tutorial_tablet_mock_conversation_list - : R.layout.gesture_tutorial_mock_conversation_list; + return mTutorialFragment.isLargeScreen() + ? R.layout.gesture_tutorial_tablet_mock_conversation_list + : R.layout.gesture_tutorial_mock_conversation_list; } @Override @@ -128,11 +130,31 @@ final class OverviewGestureTutorialController extends SwipeUpGestureTutorialCont : R.raw.overview_gesture_tutorial_animation; } - @Override - protected int getSwipeActionColor() { + @ColorInt + private int getFakeTaskViewStartColor() { return mTutorialFragment.mRootView.mColorSurfaceOverview; } + @ColorInt + private int getFakeTaskViewEndColor() { + return getMockPreviousAppTaskThumbnailColor(); + } + + @Override + protected int getFakeTaskViewColor() { + return isGestureCompleted() + ? getFakeTaskViewEndColor() + : getFakeTaskViewStartColor(); + } + + @Override + protected int getFakeLauncherColor() { + return ColorUtils.blendARGB( + mTutorialFragment.mRootView.mColorSurfaceContainer, + mTutorialFragment.mRootView.mColorOnSurfaceOverview, + LAUNCHER_COLOR_BLENDING_RATIO); + } + @Override protected int getHotseatIconColor() { return mTutorialFragment.mRootView.mColorOnSurfaceOverview; @@ -159,7 +181,7 @@ final class OverviewGestureTutorialController extends SwipeUpGestureTutorialCont case BACK_CANCELLED_FROM_LEFT: case BACK_CANCELLED_FROM_RIGHT: case BACK_NOT_STARTED_TOO_FAR_FROM_EDGE: - resetTaskView(); + resetTaskViews(); showFeedback(R.string.overview_gesture_feedback_swipe_too_far_from_edge); break; } @@ -190,7 +212,7 @@ final class OverviewGestureTutorialController extends SwipeUpGestureTutorialCont } case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE: case OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE: - resetTaskView(); + resetTaskViews(); showFeedback(R.string.overview_gesture_feedback_swipe_too_far_from_edge); break; case OVERVIEW_GESTURE_COMPLETED: @@ -204,7 +226,7 @@ final class OverviewGestureTutorialController extends SwipeUpGestureTutorialCont break; case HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION: case HOME_OR_OVERVIEW_CANCELLED: - fadeOutFakeTaskView(false, true, null); + fadeOutFakeTaskView(false, null); showFeedback(R.string.overview_gesture_feedback_wrong_swipe_direction); break; } @@ -229,15 +251,21 @@ final class OverviewGestureTutorialController extends SwipeUpGestureTutorialCont anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animator) { - new Handler().postDelayed(() -> { - mFakeTaskView.setVisibility(View.INVISIBLE); - if (!mTutorialFragment.isLargeScreen()) { - mFakePreviousTaskView.animateToFillScreen( - () -> onSuccessAnimationComplete()); - } else { - onSuccessAnimationComplete(); - } - }, TASK_VIEW_FILL_SCREEN_ANIMATION_DELAY_MILLIS); + new Handler().postDelayed( + () -> fadeOutFakeTaskView( + /* toOverviewFirst= */ true, + /* animatePreviousTask= */ false, + /* resetViews= */ false, + /* updateListener= */ v -> mFakeTaskView.setBackgroundColor( + ColorUtils.blendARGB( + getFakeTaskViewStartColor(), + getFakeTaskViewEndColor(), + v.getAnimatedFraction())), + /* onEndRunnable= */ () -> { + showSuccessFeedback(); + resetTaskViews(); + }), + TASK_VIEW_FILL_SCREEN_ANIMATION_DELAY_MILLIS); } }); } @@ -259,9 +287,4 @@ final class OverviewGestureTutorialController extends SwipeUpGestureTutorialCont animset.start(); mRunningWindowAnim = SwipeUpAnimationLogic.RunningWindowAnim.wrap(animset); } - - private void onSuccessAnimationComplete() { - mFakeLauncherView.setBackgroundColor(getMockPreviousAppTaskThumbnailColor()); - showSuccessFeedback(); - } } diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java index 66c659aa2f..0bbf373023 100644 --- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java @@ -78,7 +78,7 @@ abstract class SwipeUpGestureTutorialController extends TutorialController { private final AnimatorListenerAdapter mResetTaskView = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - resetTaskView(); + resetTaskViews(); } }; @@ -122,7 +122,7 @@ abstract class SwipeUpGestureTutorialController extends TutorialController { mRunningWindowAnim = null; } - void resetTaskView() { + void resetTaskViews() { mFakeHotseatView.setVisibility(View.INVISIBLE); mFakeIconView.setVisibility(View.INVISIBLE); if (mTutorialFragment.getActivity() != null) { @@ -141,10 +141,21 @@ abstract class SwipeUpGestureTutorialController extends TutorialController { mShowPreviousTasks = false; mRunningWindowAnim = null; } + void fadeOutFakeTaskView(boolean toOverviewFirst, @Nullable Runnable onEndRunnable) { + fadeOutFakeTaskView( + toOverviewFirst, + /* animatePreviousTask= */ true, + /* resetViews= */ true, + /* updateListener= */ null, + onEndRunnable); + } /** Fades the task view, optionally after animating to a fake Overview. */ - void fadeOutFakeTaskView(boolean toOverviewFirst, boolean reset, - @Nullable Runnable onEndRunnable) { + void fadeOutFakeTaskView(boolean toOverviewFirst, + boolean animatePreviousTask, + boolean resetViews, + @Nullable ValueAnimator.AnimatorUpdateListener updateListener, + @Nullable Runnable onEndRunnable) { cancelRunningAnimation(); PendingAnimation anim = new PendingAnimation(300); if (toOverviewFirst) { @@ -155,20 +166,20 @@ abstract class SwipeUpGestureTutorialController extends TutorialController { public void onAnimationEnd(Animator animation, boolean isReverse) { PendingAnimation fadeAnim = new PendingAnimation(TASK_VIEW_END_ANIMATION_DURATION_MILLIS); - if (reset) { - fadeAnim.setFloat(mTaskViewSwipeUpAnimation - .getCurrentShift(), AnimatedFloat.VALUE, 0, ACCEL); + fadeAnim.setFloat(mTaskViewSwipeUpAnimation + .getCurrentShift(), AnimatedFloat.VALUE, 0, ACCEL); + if (resetViews) { fadeAnim.addListener(mResetTaskView); - } else { - fadeAnim.setViewAlpha(mFakeTaskView, 0, ACCEL); - fadeAnim.setViewAlpha(mFakePreviousTaskView, 0, ACCEL); } if (onEndRunnable != null) { fadeAnim.addListener(AnimatorListeners.forSuccessCallback(onEndRunnable)); } + if (updateListener != null) { + fadeAnim.addOnFrameListener(updateListener); + } AnimatorSet animset = fadeAnim.buildAnim(); - if (reset && mTutorialFragment.isLargeScreen()) { + if (animatePreviousTask && mTutorialFragment.isLargeScreen()) { animset.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { @@ -190,13 +201,10 @@ abstract class SwipeUpGestureTutorialController extends TutorialController { } }); } else { - if (reset) { - anim.setFloat(mTaskViewSwipeUpAnimation - .getCurrentShift(), AnimatedFloat.VALUE, 0, ACCEL); + anim.setFloat(mTaskViewSwipeUpAnimation + .getCurrentShift(), AnimatedFloat.VALUE, 0, ACCEL); + if (resetViews) { anim.addListener(mResetTaskView); - } else { - anim.setViewAlpha(mFakeTaskView, 0, ACCEL); - anim.setViewAlpha(mFakePreviousTaskView, 0, ACCEL); } if (onEndRunnable != null) { anim.addListener(AnimatorListeners.forSuccessCallback(onEndRunnable)); diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java index 36655d2b01..084f8c1298 100644 --- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java @@ -268,7 +268,12 @@ abstract class TutorialController implements BackGestureAttemptCallback, } @ColorInt - protected abstract int getSwipeActionColor(); + protected int getFakeTaskViewColor() { + return Color.TRANSPARENT; + } + + @ColorInt + protected abstract int getFakeLauncherColor(); @ColorInt protected int getExitingAppColor() { @@ -445,6 +450,7 @@ abstract class TutorialController implements BackGestureAttemptCallback, } private void showSuccessPage() { + pauseAndHideLottieAnimation(); mCheckmarkAnimation.setVisibility(View.VISIBLE); mCheckmarkAnimation.playAnimation(); mFeedbackTitleView.setTextAppearance(mContext, getSuccessTitleTextAppearance()); @@ -591,7 +597,7 @@ abstract class TutorialController implements BackGestureAttemptCallback, protected void resetViewsForBackGesture() { mFakeTaskView.setVisibility(View.VISIBLE); - mFakeTaskView.setBackgroundColor(getSwipeActionColor()); + mFakeTaskView.setBackgroundColor(getFakeTaskViewColor()); mExitingAppView.setVisibility(View.VISIBLE); // reset the exiting app's dimensions @@ -690,11 +696,10 @@ abstract class TutorialController implements BackGestureAttemptCallback, mContext, getMockWallpaperResId())); mTutorialFragment.updateFeedbackAnimation(); mFakeLauncherView.setBackgroundColor(ENABLE_NEW_GESTURE_NAV_TUTORIAL.get() - ? getSwipeActionColor() + ? getFakeLauncherColor() : mContext.getColor(R.color.gesture_tutorial_fake_wallpaper_color)); updateFakeViewLayout(mFakeHotseatView, getMockHotseatResId()); mHotseatIconView = mFakeHotseatView.findViewById(R.id.hotseat_icon_1); - updateFakeViewLayout(mFakeTaskView, getMockAppTaskLayoutResId()); mFakeTaskView.animate().alpha(1).setListener( AnimatorListeners.forSuccessCallback(() -> mFakeTaskView.animate().cancel())); mFakePreviousTaskView.setFakeTaskViewFillColor(getMockPreviousAppTaskThumbnailColor()); @@ -703,12 +708,15 @@ abstract class TutorialController implements BackGestureAttemptCallback, if (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) { mExitingAppView.setBackgroundColor(getExitingAppColor()); + mFakeTaskView.setBackgroundColor(getFakeTaskViewColor()); updateHotseatChildViewColor(mFakeIconView); updateHotseatChildViewColor(mFakeHotseatView.findViewById(R.id.hotseat_icon_2)); updateHotseatChildViewColor(mFakeHotseatView.findViewById(R.id.hotseat_icon_3)); updateHotseatChildViewColor(mFakeHotseatView.findViewById(R.id.hotseat_icon_4)); updateHotseatChildViewColor(mFakeHotseatView.findViewById(R.id.hotseat_icon_5)); updateHotseatChildViewColor(mFakeHotseatView.findViewById(R.id.hotseat_search_bar)); + } else { + updateFakeViewLayout(mFakeTaskView, getMockAppTaskLayoutResId()); } } } diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java index 9f15e19ce4..bfad64308c 100644 --- a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java +++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java @@ -143,6 +143,7 @@ abstract class TutorialFragment extends GestureSandboxFragment implements OnTouc return null; } + @NonNull abstract TutorialController createController(TutorialType type); abstract Class getControllerClass(); @@ -374,9 +375,15 @@ abstract class TutorialFragment extends GestureSandboxFragment implements OnTouc void changeController(TutorialType tutorialType) { if (getControllerClass().isInstance(mTutorialController)) { mTutorialController.setTutorialType(tutorialType); + if (isGestureComplete()) { + mTutorialController.setGestureCompleted(); + } mTutorialController.fadeTaskViewAndRun(mTutorialController::transitToController); } else { mTutorialController = createController(tutorialType); + if (isGestureComplete()) { + mTutorialController.setGestureCompleted(); + } mTutorialController.transitToController(); } mEdgeBackGestureHandler.registerBackGestureAttemptCallback(mTutorialController); From 98fbe25da9ec04bfe67501dea49736358cf91ce5 Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Thu, 25 May 2023 05:56:05 +0000 Subject: [PATCH 4/5] Try to handle side-task launches even if not fully transitioned to overview - The launcher state does not transition from Background -> Overview until the overview animation finishes, and if a side task is launched before that happens, then we receive onTaskAppeared() but isInLiveTileMode() is false, which results in a state where no animation of the side task surface is run, and the recents animation is not finished. In these cases, if we've already calculated the end target is RECENTS, then we can still animate the side task - Adding some more gesture logs to indicate side task launched (or didn't) Bug: 279114961 Test: Go to overview and launch next task Change-Id: I23eac8721da801f14cc95fc6781a0ef9f0355cc0 --- .../src/com/android/quickstep/AbsSwipeUpHandler.java | 2 ++ .../src/com/android/quickstep/TaskAnimationManager.java | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java index 928910d3ab..f5202b7ebe 100644 --- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java +++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java @@ -2260,6 +2260,7 @@ public abstract class AbsSwipeUpHandler, targetCompat.taskId == mGestureState.getLastStartedTaskId()) .findFirst(); if (!taskTargetOptional.isPresent()) { + ActiveGestureLog.INSTANCE.addLog("No appeared task matching started task id"); finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */); return; } @@ -2267,6 +2268,7 @@ public abstract class AbsSwipeUpHandler, TaskView taskView = mRecentsView == null ? null : mRecentsView.getTaskViewByTaskId(taskTarget.taskId); if (taskView == null || !taskView.getThumbnail().shouldShowSplashView()) { + ActiveGestureLog.INSTANCE.addLog("Invalid task view splash state"); finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */); return; } diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java index 4c4b9b4095..410ba21866 100644 --- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java +++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java @@ -19,6 +19,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; +import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS; import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_INITIALIZED; import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_STARTED; import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.START_RECENTS_ANIMATION; @@ -174,16 +175,21 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn if (nonAppTargets == null) { nonAppTargets = new RemoteAnimationTarget[0]; } - if (activityInterface.isInLiveTileMode() + if ((activityInterface.isInLiveTileMode() + || mLastGestureState.getEndTarget() == RECENTS) && activityInterface.getCreatedActivity() != null) { RecentsView recentsView = activityInterface.getCreatedActivity().getOverviewPanel(); if (recentsView != null) { + ActiveGestureLog.INSTANCE.addLog("Launching side task id=" + + appearedTaskTarget.taskId); recentsView.launchSideTaskInLiveTileMode(appearedTaskTarget.taskId, appearedTaskTargets, new RemoteAnimationTarget[0] /* wallpaper */, nonAppTargets /* nonApps */); return; + } else { + ActiveGestureLog.INSTANCE.addLog("Unable to launch side task (no recents)"); } } else if (nonAppTargets.length > 0) { TaskViewUtils.createSplitAuxiliarySurfacesAnimator(nonAppTargets /* nonApps */, From eec7a9d90f920bbea38d8001c1a8fe01d0917af3 Mon Sep 17 00:00:00 2001 From: Stefan Andonian Date: Tue, 23 May 2023 23:44:39 +0000 Subject: [PATCH 5/5] Keep ViewCaptureRule logic self-contained. This will make it easier for other apps / processes to integrate the ViewCapture logic into their integrated testing frameworks. Bug: 270158224 Test: Verified that a zip file was generated properly and was able to be loaded into go/web-hv properly. Change-Id: Ib3e4a0b60497937b750126590071884882b22917 --- .../quickstep/FallbackRecentsTest.java | 5 +- .../launcher3/ui/AbstractLauncherUiTest.java | 5 +- .../launcher3/util/rule/FailureWatcher.java | 24 +--- .../launcher3/util/rule/ViewCaptureRule.kt | 111 ++++++++++++------ 4 files changed, 81 insertions(+), 64 deletions(-) diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java index 97e34c5f10..dbe4402812 100644 --- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java +++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java @@ -116,12 +116,11 @@ public class FallbackRecentsTest { Utilities.enableRunningInTestHarnessForTests(); } - final ViewCaptureRule viewCaptureRule = new ViewCaptureRule(); mOrderSensitiveRules = RuleChain .outerRule(new SamplerRule()) .around(new NavigationModeSwitchRule(mLauncher)) - .around(viewCaptureRule) - .around(new FailureWatcher(mDevice, mLauncher, viewCaptureRule.getViewCapture())); + .around(new ViewCaptureRule()) + .around(new FailureWatcher(mDevice, mLauncher)); mOtherLauncherActivity = context.getPackageManager().queryIntentActivities( getHomeIntentInPackage(context), diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index d7c4ae3857..5bd28d85a3 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -216,11 +216,10 @@ public abstract class AbstractLauncherUiTest { } protected TestRule getRulesInsideActivityMonitor() { - final ViewCaptureRule viewCaptureRule = new ViewCaptureRule(); final RuleChain inner = RuleChain .outerRule(new PortraitLandscapeRunner(this)) - .around(viewCaptureRule) - .around(new FailureWatcher(mDevice, mLauncher, viewCaptureRule.getViewCapture())); + .around(new ViewCaptureRule()) + .around(new FailureWatcher(mDevice, mLauncher)); return TestHelpers.isInLauncherProcess() ? RuleChain.outerRule(ShellCommandRule.setDefaultLauncher()).around(inner) diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java index 7ca6a06ed2..6b11fd6af4 100644 --- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java +++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java @@ -6,12 +6,8 @@ import android.os.FileUtils; import android.os.ParcelFileDescriptor.AutoCloseInputStream; import android.util.Log; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.test.core.app.ApplicationProvider; import androidx.test.uiautomator.UiDevice; -import com.android.app.viewcapture.ViewCapture; import com.android.launcher3.tapl.LauncherInstrumentation; import com.android.launcher3.ui.AbstractLauncherUiTest; @@ -32,14 +28,10 @@ public class FailureWatcher extends TestWatcher { private static boolean sSavedBugreport = false; final private UiDevice mDevice; private final LauncherInstrumentation mLauncher; - @NonNull - private final ViewCapture mViewCapture; - public FailureWatcher(UiDevice device, LauncherInstrumentation launcher, - @NonNull ViewCapture viewCapture) { + public FailureWatcher(UiDevice device, LauncherInstrumentation launcher) { mDevice = device; mLauncher = launcher; - mViewCapture = viewCapture; } @Override @@ -71,7 +63,7 @@ public class FailureWatcher extends TestWatcher { @Override protected void failed(Throwable e, Description description) { - onError(mLauncher, description, e, mViewCapture); + onError(mLauncher, description, e); } static File diagFile(Description description, String prefix, String ext) { @@ -82,12 +74,6 @@ public class FailureWatcher extends TestWatcher { public static void onError(LauncherInstrumentation launcher, Description description, Throwable e) { - onError(launcher, description, e, null); - } - - private static void onError(LauncherInstrumentation launcher, Description description, - Throwable e, @Nullable ViewCapture viewCapture) { - final File sceenshot = diagFile(description, "TestScreenshot", "png"); final File hierarchy = diagFile(description, "Hierarchy", "zip"); @@ -102,12 +88,6 @@ public class FailureWatcher extends TestWatcher { out.putNextEntry(new ZipEntry("visible_windows.zip")); dumpCommand("cmd window dump-visible-window-views", out); out.closeEntry(); - - if (viewCapture != null) { - out.putNextEntry(new ZipEntry("FS/data/misc/wmtrace/failed_test.vc")); - viewCapture.dumpTo(out, ApplicationProvider.getApplicationContext()); - out.closeEntry(); - } } catch (Exception ignored) { } diff --git a/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt b/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt index 0c6553998d..f3fff35c90 100644 --- a/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt +++ b/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt @@ -19,62 +19,101 @@ import android.app.Activity import android.app.Application import android.media.permission.SafeCloseable import android.os.Bundle +import android.util.Log +import androidx.annotation.AnyThread import androidx.test.core.app.ApplicationProvider import com.android.app.viewcapture.SimpleViewCapture import com.android.app.viewcapture.ViewCapture.MAIN_EXECUTOR import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter -import org.junit.rules.TestRule +import java.io.File +import java.io.FileOutputStream +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream +import org.junit.rules.TestWatcher import org.junit.runner.Description import org.junit.runners.model.Statement +private const val TAG = "ViewCaptureRule" + /** * This JUnit TestRule registers a listener for activity lifecycle events to attach a ViewCapture * instance that other test rules use to dump the timelapse hierarchy upon an error during a test. * * This rule will not work in OOP tests that don't have access to the activity under test. */ -class ViewCaptureRule : TestRule { - val viewCapture = SimpleViewCapture("test-view-capture") +class ViewCaptureRule : TestWatcher() { + private val viewCapture = SimpleViewCapture("test-view-capture") + private val windowListenerCloseables = mutableListOf() override fun apply(base: Statement, description: Description): Statement { + val testWatcherStatement = super.apply(base, description) + return object : Statement() { override fun evaluate() { - val windowListenerCloseables = mutableListOf() - - val lifecycleCallbacks = - object : ActivityLifecycleCallbacksAdapter { - override fun onActivityCreated(activity: Activity, bundle: Bundle?) { - super.onActivityCreated(activity, bundle) - windowListenerCloseables.add( - viewCapture.startCapture( - activity.window.decorView, - "${description.testClass?.simpleName}.${description.methodName}" - ) - ) - } - - override fun onActivityDestroyed(activity: Activity) { - super.onActivityDestroyed(activity) - viewCapture.stopCapture(activity.window.decorView) - } + val lifecycleCallbacks = createLifecycleCallbacks(description) + with(ApplicationProvider.getApplicationContext()) { + registerActivityLifecycleCallbacks(lifecycleCallbacks) + try { + testWatcherStatement.evaluate() + } finally { + unregisterActivityLifecycleCallbacks(lifecycleCallbacks) } - - val application = ApplicationProvider.getApplicationContext() - application.registerActivityLifecycleCallbacks(lifecycleCallbacks) - - try { - base.evaluate() - } finally { - application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks) - - // Clean up ViewCapture references here rather than in onActivityDestroyed so - // test code can access view hierarchy capture. onActivityDestroyed would delete - // view capture data before FailureWatcher could output it as a test artifact. - // This is on the main thread to avoid a race condition where the onDrawListener - // is removed while onDraw is running, resulting in an IllegalStateException. - MAIN_EXECUTOR.execute { windowListenerCloseables.onEach(SafeCloseable::close) } } } } } + + private fun createLifecycleCallbacks(description: Description) = + object : ActivityLifecycleCallbacksAdapter { + override fun onActivityCreated(activity: Activity, bundle: Bundle?) { + super.onActivityCreated(activity, bundle) + windowListenerCloseables.add( + viewCapture.startCapture( + activity.window.decorView, + "${description.testClass?.simpleName}.${description.methodName}" + ) + ) + } + + override fun onActivityDestroyed(activity: Activity) { + super.onActivityDestroyed(activity) + viewCapture.stopCapture(activity.window.decorView) + } + } + + override fun succeeded(description: Description) = cleanup() + + /** If the test fails, this function will output the ViewCapture information. */ + override fun failed(e: Throwable, description: Description) { + super.failed(e, description) + + val testName = "${description.testClass.simpleName}.${description.methodName}" + val application: Application = ApplicationProvider.getApplicationContext() + val zip = File(application.filesDir, "ViewCapture-$testName.zip") + + ZipOutputStream(FileOutputStream(zip)).use { + it.putNextEntry(ZipEntry("FS/data/misc/wmtrace/failed_test.vc")) + viewCapture.dumpTo(it, ApplicationProvider.getApplicationContext()) + it.closeEntry() + } + cleanup() + + Log.d( + TAG, + "Failed $testName due to ${e::class.java.simpleName}.\n" + + "\tUse go/web-hv to open dump file: \n\t\t${zip.absolutePath}" + ) + } + + /** + * Clean up ViewCapture references can't happen in onActivityDestroyed otherwise view + * hierarchies would be erased before they could be outputted. + * + * This is on the main thread to avoid a race condition where the onDrawListener is removed + * while onDraw is running, resulting in an IllegalStateException. + */ + @AnyThread + private fun cleanup() { + MAIN_EXECUTOR.execute { windowListenerCloseables.onEach(SafeCloseable::close) } + } }