Snap for 10214038 from ed7b50efee to udc-release
Change-Id: I4df93d43a86d1608aa4e4c1329849c7b5074e542
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+13
-10
@@ -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;
|
||||
}
|
||||
|
||||
+48
-25
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+25
-17
@@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user