Snap for 10214038 from ed7b50efee to udc-release

Change-Id: I4df93d43a86d1608aa4e4c1329849c7b5074e542
This commit is contained in:
Android Build Coastguard Worker
2023-05-27 01:41:25 +00:00
22 changed files with 315 additions and 311 deletions
@@ -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"
@@ -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"
@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2023 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<View
xmlns:android="http://schemas.android.com/apk/res/android"
android:theme="@style/GestureTutorialActivity"
android:id="@+id/overview_gesture_tutorial_shape"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:background="?attr/onSurfaceOverview" />
@@ -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"
@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2023 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<View
xmlns:android="http://schemas.android.com/apk/res/android"
android:theme="@style/GestureTutorialActivity"
android:id="@+id/swipe_up_gesture_tutorial_shape"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/surfaceHome" />
@@ -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
@@ -2260,6 +2260,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
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<T extends StatefulActivity<S>,
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;
}
@@ -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 */,
@@ -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<Animator> 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.
@@ -136,7 +136,7 @@ final class BackGestureTutorialController extends TutorialController {
}
@Override
protected int getSwipeActionColor() {
protected int getFakeLauncherColor() {
return mTutorialFragment.mRootView.mColorSurfaceContainer;
}
@@ -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;
}
@@ -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();
}
}
@@ -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));
@@ -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());
}
}
}
@@ -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);
@@ -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,
@@ -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<Boolean> 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<InstanceId, com.android.launcher3.logging.InstanceId> 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<Boolean> 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<Boolean> 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) {
@@ -3260,7 +3260,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
true /* isStagedTask */);
pendingAnimation.addEndListener(animationSuccess ->
mSplitSelectStateController.launchSplitTasks(launchSuccess ->
mSplitSelectStateController.launchInitialAppFullscreen(launchSuccess ->
resetFromSplitSelectionState()));
pendingAnimation.buildAnim().start();
@@ -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),
@@ -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)
@@ -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) {
}
@@ -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<SafeCloseable>()
override fun apply(base: Statement, description: Description): Statement {
val testWatcherStatement = super.apply(base, description)
return object : Statement() {
override fun evaluate() {
val windowListenerCloseables = mutableListOf<SafeCloseable>()
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<Application>()) {
registerActivityLifecycleCallbacks(lifecycleCallbacks)
try {
testWatcherStatement.evaluate()
} finally {
unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
}
val application = ApplicationProvider.getApplicationContext<Application>()
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) }
}
}