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/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
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 */,
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 extends TutorialController> 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);
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();
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) }
+ }
}