Merge "[Toast] Fix ANR when tapping on rich card chip." into main
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
index 3e1a6ae..a9d50b9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
@@ -40,6 +40,8 @@
import java.util.function.Consumer;
+import kotlin.Unit;
+
/**
* A view that displays a recent task during a keyboard quick switch.
*/
@@ -96,17 +98,18 @@
Resources resources = mContext.getResources();
Preconditions.assertNotNull(mContent);
- mBorderAnimator = new BorderAnimator(
+ mBorderAnimator = BorderAnimator.createScalingBorderAnimator(
/* borderRadiusPx= */ resources.getDimensionPixelSize(
R.dimen.keyboard_quick_switch_task_view_radius),
- /* borderColor= */ mBorderColor,
- /* borderAnimationParams= */ new BorderAnimator.ScalingParams(
- /* borderWidthPx= */ resources.getDimensionPixelSize(
+ /* borderWidthPx= */ resources.getDimensionPixelSize(
R.dimen.keyboard_quick_switch_border_width),
- /* boundsBuilder= */ bounds -> bounds.set(
- 0, 0, getWidth(), getHeight()),
- /* targetView= */ this,
- /* contentView= */ mContent));
+ /* boundsBuilder= */ bounds -> {
+ bounds.set(0, 0, getWidth(), getHeight());
+ return Unit.INSTANCE;
+ },
+ /* targetView= */ this,
+ /* contentView= */ mContent,
+ /* borderColor= */ mBorderColor);
}
@Nullable
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
index c54bb7e..5182a32 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
@@ -89,7 +89,8 @@
mAppsModelFlags = flags;
mPackageUserKeytoUidMap = map;
if (mAppsView != null) {
- mAppsView.getAppsStore().setApps(mApps, mAppsModelFlags, mPackageUserKeytoUidMap);
+ mAppsView.getAppsStore().setApps(
+ mApps, mAppsModelFlags, mPackageUserKeytoUidMap, false);
}
}
@@ -190,7 +191,7 @@
viewController.show(animate);
mAppsView = mOverlayContext.getAppsView();
- mAppsView.getAppsStore().setApps(mApps, mAppsModelFlags, mPackageUserKeytoUidMap);
+ mAppsView.getAppsStore().setApps(mApps, mAppsModelFlags, mPackageUserKeytoUidMap, false);
mAppsView.getFloatingHeaderView()
.findFixedRowByType(PredictionRowView.class)
.setPredictedApps(mPredictedApps);
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 3429df1..419824a 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -137,6 +137,7 @@
private ISplitSelectListener mSplitSelectListener;
private IStartingWindowListener mStartingWindowListener;
private ILauncherUnlockAnimationController mLauncherUnlockAnimationController;
+ private String mLauncherActivityClass;
private IRecentTasksListener mRecentTasksListener;
private IUnfoldTransitionListener mUnfoldAnimationListener;
private IDesktopTaskListener mDesktopTaskListener;
@@ -248,7 +249,8 @@
registerSplitScreenListener(mSplitScreenListener);
registerSplitSelectListener(mSplitSelectListener);
setStartingWindowListener(mStartingWindowListener);
- setLauncherUnlockAnimationController(mLauncherUnlockAnimationController);
+ setLauncherUnlockAnimationController(
+ mLauncherActivityClass, mLauncherUnlockAnimationController);
new LinkedHashMap<>(mRemoteTransitions).forEach(this::registerRemoteTransition);
setupTransactionQueue();
registerRecentTasksListener(mRecentTasksListener);
@@ -1109,11 +1111,11 @@
* changes).
*/
public void setLauncherUnlockAnimationController(
- ILauncherUnlockAnimationController controller) {
+ String activityClass, ILauncherUnlockAnimationController controller) {
if (mSysuiUnlockAnimationController != null) {
try {
- mSysuiUnlockAnimationController.setLauncherUnlockController(controller);
-
+ mSysuiUnlockAnimationController.setLauncherUnlockController(
+ activityClass, controller);
if (controller != null) {
controller.dispatchSmartspaceStateToSysui();
}
@@ -1121,7 +1123,7 @@
Log.w(TAG, "Failed call setLauncherUnlockAnimationController", e);
}
}
-
+ mLauncherActivityClass = activityClass;
mLauncherUnlockAnimationController = controller;
}
diff --git a/quickstep/src/com/android/quickstep/util/BorderAnimator.java b/quickstep/src/com/android/quickstep/util/BorderAnimator.java
deleted file mode 100644
index 7563187..0000000
--- a/quickstep/src/com/android/quickstep/util/BorderAnimator.java
+++ /dev/null
@@ -1,352 +0,0 @@
-/*
- * 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.
- */
-package com.android.quickstep.util;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.annotation.ColorInt;
-import android.annotation.Nullable;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.view.View;
-import android.view.animation.Interpolator;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Px;
-
-import com.android.app.animation.Interpolators;
-import com.android.launcher3.anim.AnimatedFloat;
-import com.android.launcher3.anim.AnimatorListeners;
-
-/**
- * Utility class for drawing a rounded-rect border around a view.
- * <p>
- * To use this class:
- * 1. Create an instance in the target view. NOTE: The border will animate outwards from the
- * provided border bounds. See {@link SimpleParams} and {@link ScalingParams} to determine
- * which would be best for your target view.
- * 2. Override the target view's {@link android.view.View#draw(Canvas)} method and call
- * {@link BorderAnimator#drawBorder(Canvas)} after {@code super.draw(canvas)}.
- * 3. Call {@link BorderAnimator#buildAnimator(boolean)} and start the animation or call
- * {@link BorderAnimator#setBorderVisible(boolean)} where appropriate.
- */
-public final class BorderAnimator {
-
- public static final int DEFAULT_BORDER_COLOR = Color.WHITE;
-
- private static final long DEFAULT_APPEARANCE_ANIMATION_DURATION_MS = 300;
- private static final long DEFAULT_DISAPPEARANCE_ANIMATION_DURATION_MS = 133;
- private static final Interpolator DEFAULT_INTERPOLATOR = Interpolators.EMPHASIZED_DECELERATE;
-
- @NonNull private final AnimatedFloat mBorderAnimationProgress = new AnimatedFloat(
- this::updateOutline);
- @Px private final int mBorderRadiusPx;
- @NonNull private final BorderAnimationParams mBorderAnimationParams;
- private final long mAppearanceDurationMs;
- private final long mDisappearanceDurationMs;
- @NonNull private final Interpolator mInterpolator;
- @NonNull private final Paint mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-
- @Nullable private Animator mRunningBorderAnimation;
-
- public BorderAnimator(
- @Px int borderRadiusPx,
- @ColorInt int borderColor,
- @NonNull BorderAnimationParams borderAnimationParams) {
- this(borderRadiusPx,
- borderColor,
- borderAnimationParams,
- DEFAULT_APPEARANCE_ANIMATION_DURATION_MS,
- DEFAULT_DISAPPEARANCE_ANIMATION_DURATION_MS,
- DEFAULT_INTERPOLATOR);
- }
-
- /**
- * @param borderRadiusPx the radius of the border's corners, in pixels
- * @param borderColor the border's color
- * @param borderAnimationParams params for handling different target view layout situation.
- * @param appearanceDurationMs appearance animation duration, in milliseconds
- * @param disappearanceDurationMs disappearance animation duration, in milliseconds
- * @param interpolator animation interpolator
- */
- public BorderAnimator(
- @Px int borderRadiusPx,
- @ColorInt int borderColor,
- @NonNull BorderAnimationParams borderAnimationParams,
- long appearanceDurationMs,
- long disappearanceDurationMs,
- @NonNull Interpolator interpolator) {
- mBorderRadiusPx = borderRadiusPx;
- mBorderAnimationParams = borderAnimationParams;
- mAppearanceDurationMs = appearanceDurationMs;
- mDisappearanceDurationMs = disappearanceDurationMs;
- mInterpolator = interpolator;
-
- mBorderPaint.setColor(borderColor);
- mBorderPaint.setStyle(Paint.Style.STROKE);
- mBorderPaint.setAlpha(0);
- }
-
- private void updateOutline() {
- float interpolatedProgress = mInterpolator.getInterpolation(
- mBorderAnimationProgress.value);
-
- mBorderAnimationParams.setProgress(interpolatedProgress);
- mBorderPaint.setAlpha(Math.round(255 * interpolatedProgress));
- mBorderPaint.setStrokeWidth(mBorderAnimationParams.getBorderWidth());
- mBorderAnimationParams.mTargetView.invalidate();
- }
-
- /**
- * Draws the border on the given canvas.
- * <p>
- * Call this method in the target view's {@link android.view.View#draw(Canvas)} method after
- * calling super.
- */
- public void drawBorder(Canvas canvas) {
- float alignmentAdjustment = mBorderAnimationParams.getAlignmentAdjustment();
- canvas.drawRoundRect(
- /* left= */ mBorderAnimationParams.mBorderBounds.left + alignmentAdjustment,
- /* top= */ mBorderAnimationParams.mBorderBounds.top + alignmentAdjustment,
- /* right= */ mBorderAnimationParams.mBorderBounds.right - alignmentAdjustment,
- /* bottom= */ mBorderAnimationParams.mBorderBounds.bottom - alignmentAdjustment,
- /* rx= */ mBorderRadiusPx + mBorderAnimationParams.getRadiusAdjustment(),
- /* ry= */ mBorderRadiusPx + mBorderAnimationParams.getRadiusAdjustment(),
- /* paint= */ mBorderPaint);
- }
-
- /**
- * Builds the border appearance/disappearance animation.
- */
- @NonNull
- public Animator buildAnimator(boolean isAppearing) {
- mRunningBorderAnimation = mBorderAnimationProgress.animateToValue(isAppearing ? 1f : 0f);
- mRunningBorderAnimation.setDuration(
- isAppearing ? mAppearanceDurationMs : mDisappearanceDurationMs);
-
- mRunningBorderAnimation.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- mBorderAnimationParams.onShowBorder();
- }
- });
- mRunningBorderAnimation.addListener(
- AnimatorListeners.forEndCallback(() -> {
- mRunningBorderAnimation = null;
- if (isAppearing) {
- return;
- }
- mBorderAnimationParams.onHideBorder();
- }));
-
- return mRunningBorderAnimation;
- }
-
- /**
- * Immediately shows/hides the border without an animation.
- * <p>
- * To animate the appearance/disappearance, see {@link BorderAnimator#buildAnimator(boolean)}
- */
- public void setBorderVisible(boolean visible) {
- if (mRunningBorderAnimation != null) {
- mRunningBorderAnimation.end();
- }
- if (visible) {
- mBorderAnimationParams.onShowBorder();
- }
- mBorderAnimationProgress.updateValue(visible ? 1f : 0f);
- if (!visible) {
- mBorderAnimationParams.onHideBorder();
- }
- }
-
- /**
- * Callback to update the border bounds when building this animation.
- */
- public interface BorderBoundsBuilder {
-
- /**
- * Sets the given rect to the most up-to-date bounds.
- */
- void updateBorderBounds(Rect rect);
- }
-
- /**
- * Params for handling different target view layout situation.
- */
- private abstract static class BorderAnimationParams {
-
- @NonNull private final Rect mBorderBounds = new Rect();
- @NonNull private final BorderBoundsBuilder mBoundsBuilder;
-
- @NonNull final View mTargetView;
- @Px final int mBorderWidthPx;
-
- private float mAnimationProgress = 0f;
- @Nullable private View.OnLayoutChangeListener mLayoutChangeListener;
-
- /**
- * @param borderWidthPx the width of the border, in pixels
- * @param boundsBuilder callback to update the border bounds
- * @param targetView the view that will be drawing the border
- */
- private BorderAnimationParams(
- @Px int borderWidthPx,
- @NonNull BorderBoundsBuilder boundsBuilder,
- @NonNull View targetView) {
- mBorderWidthPx = borderWidthPx;
- mBoundsBuilder = boundsBuilder;
- mTargetView = targetView;
- }
-
- private void setProgress(float progress) {
- mAnimationProgress = progress;
- }
-
- private float getBorderWidth() {
- return mBorderWidthPx * mAnimationProgress;
- }
-
- float getAlignmentAdjustment() {
- // Outset the border by half the width to create an outwards-growth animation
- return (-getBorderWidth() / 2f) + getAlignmentAdjustmentInset();
- }
-
-
- void onShowBorder() {
- if (mLayoutChangeListener == null) {
- mLayoutChangeListener =
- (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
- onShowBorder();
- mTargetView.invalidate();
- };
- mTargetView.addOnLayoutChangeListener(mLayoutChangeListener);
- }
- mBoundsBuilder.updateBorderBounds(mBorderBounds);
- }
-
- void onHideBorder() {
- if (mLayoutChangeListener != null) {
- mTargetView.removeOnLayoutChangeListener(mLayoutChangeListener);
- mLayoutChangeListener = null;
- }
- }
-
- abstract int getAlignmentAdjustmentInset();
-
- abstract float getRadiusAdjustment();
- }
-
- /**
- * Use an instance of this {@link BorderAnimationParams} if the border can be drawn outside the
- * target view's bounds without any additional logic.
- */
- public static final class SimpleParams extends BorderAnimationParams {
-
- public SimpleParams(
- @Px int borderWidthPx,
- @NonNull BorderBoundsBuilder boundsBuilder,
- @NonNull View targetView) {
- super(borderWidthPx, boundsBuilder, targetView);
- }
-
- @Override
- int getAlignmentAdjustmentInset() {
- return 0;
- }
-
- @Override
- float getRadiusAdjustment() {
- return -getAlignmentAdjustment();
- }
- }
-
- /**
- * Use an instance of this {@link BorderAnimationParams} if the border would other be clipped by
- * the target view's bound.
- * <p>
- * Note: using these params will set the scales and pivots of the
- * container and content views, however will only reset the scales back to 1.
- */
- public static final class ScalingParams extends BorderAnimationParams {
-
- @NonNull private final View mContentView;
-
- /**
- * @param targetView the view that will be drawing the border. this view will be scaled up
- * to make room for the border
- * @param contentView the view around which the border will be drawn. this view will be
- * scaled down reciprocally to keep its original size and location.
- */
- public ScalingParams(
- @Px int borderWidthPx,
- @NonNull BorderBoundsBuilder boundsBuilder,
- @NonNull View targetView,
- @NonNull View contentView) {
- super(borderWidthPx, boundsBuilder, targetView);
- mContentView = contentView;
- }
-
- @Override
- void onShowBorder() {
- super.onShowBorder();
- float width = mTargetView.getWidth();
- float height = mTargetView.getHeight();
- // Scale up just enough to make room for the border. Fail fast and fix the scaling
- // onLayout.
- float scaleX = width == 0 ? 1f : 1f + ((2 * mBorderWidthPx) / width);
- float scaleY = height == 0 ? 1f : 1f + ((2 * mBorderWidthPx) / height);
-
- mTargetView.setPivotX(width / 2);
- mTargetView.setPivotY(height / 2);
- mTargetView.setScaleX(scaleX);
- mTargetView.setScaleY(scaleY);
-
- mContentView.setPivotX(mContentView.getWidth() / 2f);
- mContentView.setPivotY(mContentView.getHeight() / 2f);
- mContentView.setScaleX(1f / scaleX);
- mContentView.setScaleY(1f / scaleY);
- }
-
- @Override
- void onHideBorder() {
- super.onHideBorder();
- mTargetView.setPivotX(mTargetView.getWidth());
- mTargetView.setPivotY(mTargetView.getHeight());
- mTargetView.setScaleX(1f);
- mTargetView.setScaleY(1f);
-
- mContentView.setPivotX(mContentView.getWidth() / 2f);
- mContentView.setPivotY(mContentView.getHeight() / 2f);
- mContentView.setScaleX(1f);
- mContentView.setScaleY(1f);
- }
-
- @Override
- int getAlignmentAdjustmentInset() {
- // Inset the border since we are scaling the container up
- return mBorderWidthPx;
- }
-
- @Override
- float getRadiusAdjustment() {
- // Increase the radius since we are scaling the container up
- return getAlignmentAdjustment();
- }
- }
-}
diff --git a/quickstep/src/com/android/quickstep/util/BorderAnimator.kt b/quickstep/src/com/android/quickstep/util/BorderAnimator.kt
new file mode 100644
index 0000000..44eb070
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/BorderAnimator.kt
@@ -0,0 +1,315 @@
+/*
+ * 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.
+ */
+package com.android.quickstep.util
+
+import android.animation.Animator
+import android.annotation.ColorInt
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Rect
+import android.view.View
+import android.view.View.OnLayoutChangeListener
+import android.view.animation.Interpolator
+import androidx.annotation.Px
+import androidx.core.animation.doOnEnd
+import androidx.core.animation.doOnStart
+import com.android.app.animation.Interpolators
+import com.android.launcher3.anim.AnimatedFloat
+import kotlin.math.roundToInt
+
+/**
+ * Utility class for drawing a rounded-rect border around a view.
+ *
+ * To use this class:
+ * 1. Create an instance in the target view. NOTE: The border will animate outwards from the
+ * provided border bounds.
+ * 2. Override the target view's [View.draw] method and call [drawBorder] after
+ * `super.draw(canvas)`.
+ * 3. Call [buildAnimator] and start the animation or call [setBorderVisibility] where appropriate.
+ */
+class BorderAnimator
+private constructor(
+ @field:Px @param:Px private val borderRadiusPx: Int,
+ @ColorInt borderColor: Int,
+ private val borderAnimationParams: BorderAnimationParams,
+ private val appearanceDurationMs: Long,
+ private val disappearanceDurationMs: Long,
+ private val interpolator: Interpolator,
+) {
+ private val borderAnimationProgress = AnimatedFloat { updateOutline() }
+ private val borderPaint =
+ Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ color = borderColor
+ style = Paint.Style.STROKE
+ alpha = 0
+ }
+ private var runningBorderAnimation: Animator? = null
+
+ companion object {
+ const val DEFAULT_BORDER_COLOR = Color.WHITE
+ private const val DEFAULT_APPEARANCE_ANIMATION_DURATION_MS = 300L
+ private const val DEFAULT_DISAPPEARANCE_ANIMATION_DURATION_MS = 133L
+ private val DEFAULT_INTERPOLATOR = Interpolators.EMPHASIZED_DECELERATE
+
+ /**
+ * Creates a BorderAnimator that simply draws the border outside the bound of the target
+ * view.
+ *
+ * Use this method if the border can be drawn outside the target view's bounds without any
+ * additional logic.
+ *
+ * @param borderRadiusPx the radius of the border's corners, in pixels
+ * @param borderWidthPx the width of the border, in pixels
+ * @param boundsBuilder callback to update the border bounds
+ * @param targetView the view that will be drawing the border
+ * @param borderColor the border's color
+ * @param appearanceDurationMs appearance animation duration, in milliseconds
+ * @param disappearanceDurationMs disappearance animation duration, in milliseconds
+ * @param interpolator animation interpolator
+ */
+ @JvmOverloads
+ @JvmStatic
+ fun createSimpleBorderAnimator(
+ @Px borderRadiusPx: Int,
+ @Px borderWidthPx: Int,
+ boundsBuilder: (rect: Rect?) -> Unit,
+ targetView: View,
+ @ColorInt borderColor: Int = DEFAULT_BORDER_COLOR,
+ appearanceDurationMs: Long = DEFAULT_APPEARANCE_ANIMATION_DURATION_MS,
+ disappearanceDurationMs: Long = DEFAULT_DISAPPEARANCE_ANIMATION_DURATION_MS,
+ interpolator: Interpolator = DEFAULT_INTERPOLATOR,
+ ): BorderAnimator {
+ return BorderAnimator(
+ borderRadiusPx,
+ borderColor,
+ SimpleParams(borderWidthPx, boundsBuilder, targetView),
+ appearanceDurationMs,
+ disappearanceDurationMs,
+ interpolator,
+ )
+ }
+
+ /**
+ * Creates a BorderAnimator that scales the target and content views to draw the border
+ * within the target's bounds without obscuring the content.
+ *
+ * Use this method if the border would otherwise be clipped by the target view's bound.
+ *
+ * Note: using this method will set the scales and pivots of the container and content
+ * views, however will only reset the scales back to 1.
+ *
+ * @param borderRadiusPx the radius of the border's corners, in pixels
+ * @param borderWidthPx the width of the border, in pixels
+ * @param boundsBuilder callback to update the border bounds
+ * @param targetView the view that will be drawing the border
+ * @param contentView the view around which the border will be drawn. this view will be
+ * scaled down reciprocally to keep its original size and location.
+ * @param borderColor the border's color
+ * @param appearanceDurationMs appearance animation duration, in milliseconds
+ * @param disappearanceDurationMs disappearance animation duration, in milliseconds
+ * @param interpolator animation interpolator
+ */
+ @JvmOverloads
+ @JvmStatic
+ fun createScalingBorderAnimator(
+ @Px borderRadiusPx: Int,
+ @Px borderWidthPx: Int,
+ boundsBuilder: (rect: Rect?) -> Unit,
+ targetView: View,
+ contentView: View,
+ @ColorInt borderColor: Int = DEFAULT_BORDER_COLOR,
+ appearanceDurationMs: Long = DEFAULT_APPEARANCE_ANIMATION_DURATION_MS,
+ disappearanceDurationMs: Long = DEFAULT_DISAPPEARANCE_ANIMATION_DURATION_MS,
+ interpolator: Interpolator = DEFAULT_INTERPOLATOR,
+ ): BorderAnimator {
+ return BorderAnimator(
+ borderRadiusPx,
+ borderColor,
+ ScalingParams(borderWidthPx, boundsBuilder, targetView, contentView),
+ appearanceDurationMs,
+ disappearanceDurationMs,
+ interpolator,
+ )
+ }
+ }
+
+ private fun updateOutline() {
+ val interpolatedProgress = interpolator.getInterpolation(borderAnimationProgress.value)
+ borderAnimationParams.animationProgress = interpolatedProgress
+ borderPaint.alpha = (255 * interpolatedProgress).roundToInt()
+ borderPaint.strokeWidth = borderAnimationParams.borderWidth
+ borderAnimationParams.targetView.invalidate()
+ }
+
+ /**
+ * Draws the border on the given canvas.
+ *
+ * Call this method in the target view's [View.draw] method after calling super.
+ */
+ fun drawBorder(canvas: Canvas) {
+ with(borderAnimationParams) {
+ val radius = borderRadiusPx + radiusAdjustment
+ canvas.drawRoundRect(
+ /* left= */ borderBounds.left + alignmentAdjustment,
+ /* top= */ borderBounds.top + alignmentAdjustment,
+ /* right= */ borderBounds.right - alignmentAdjustment,
+ /* bottom= */ borderBounds.bottom - alignmentAdjustment,
+ /* rx= */ radius,
+ /* ry= */ radius,
+ /* paint= */ borderPaint
+ )
+ }
+ }
+
+ /** Builds the border appearance/disappearance animation. */
+ fun buildAnimator(isAppearing: Boolean): Animator {
+ return borderAnimationProgress.animateToValue(if (isAppearing) 1f else 0f).apply {
+ duration = if (isAppearing) appearanceDurationMs else disappearanceDurationMs
+ doOnStart {
+ runningBorderAnimation?.cancel()
+ runningBorderAnimation = this
+ borderAnimationParams.onShowBorder()
+ }
+ doOnEnd {
+ runningBorderAnimation = null
+ if (!isAppearing) {
+ borderAnimationParams.onHideBorder()
+ }
+ }
+ }
+ }
+
+ /** Shows/hides the border, optionally with an animation. */
+ fun setBorderVisibility(visible: Boolean, animated: Boolean) {
+ if (animated) {
+ buildAnimator(visible).start()
+ return
+ }
+ runningBorderAnimation?.end()
+ if (visible) {
+ borderAnimationParams.onShowBorder()
+ }
+ borderAnimationProgress.updateValue(if (visible) 1f else 0f)
+ if (!visible) {
+ borderAnimationParams.onHideBorder()
+ }
+ }
+
+ /** Params for handling different target view layout situations. */
+ private abstract class BorderAnimationParams(
+ @field:Px @param:Px val borderWidthPx: Int,
+ private val boundsBuilder: (rect: Rect) -> Unit,
+ val targetView: View,
+ ) {
+ val borderBounds = Rect()
+ var animationProgress = 0f
+ private var layoutChangeListener: OnLayoutChangeListener? = null
+
+ abstract val alignmentAdjustmentInset: Int
+ abstract val radiusAdjustment: Float
+
+ val borderWidth: Float
+ get() = borderWidthPx * animationProgress
+ val alignmentAdjustment: Float
+ // Outset the border by half the width to create an outwards-growth animation
+ get() = -borderWidth / 2f + alignmentAdjustmentInset
+
+ open fun onShowBorder() {
+ if (layoutChangeListener == null) {
+ layoutChangeListener = OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
+ onShowBorder()
+ targetView.invalidate()
+ }
+ targetView.addOnLayoutChangeListener(layoutChangeListener)
+ }
+ boundsBuilder(borderBounds)
+ }
+
+ open fun onHideBorder() {
+ if (layoutChangeListener != null) {
+ targetView.removeOnLayoutChangeListener(layoutChangeListener)
+ layoutChangeListener = null
+ }
+ }
+ }
+
+ /** BorderAnimationParams that simply draws the border outside the bounds of the target view. */
+ private class SimpleParams(
+ @Px borderWidthPx: Int,
+ boundsBuilder: (rect: Rect?) -> Unit,
+ targetView: View,
+ ) : BorderAnimationParams(borderWidthPx, boundsBuilder, targetView) {
+ override val alignmentAdjustmentInset = 0
+ override val radiusAdjustment: Float
+ get() = -alignmentAdjustment
+ }
+
+ /**
+ * BorderAnimationParams that scales the target and content views to draw the border within the
+ * target's bounds without obscuring the content.
+ */
+ private class ScalingParams(
+ @Px borderWidthPx: Int,
+ boundsBuilder: (rect: Rect?) -> Unit,
+ targetView: View,
+ private val contentView: View,
+ ) : BorderAnimationParams(borderWidthPx, boundsBuilder, targetView) {
+ // Inset the border since we are scaling the container up
+ override val alignmentAdjustmentInset = borderWidthPx
+ override val radiusAdjustment: Float
+ // Increase the radius since we are scaling the container up
+ get() = alignmentAdjustment
+
+ override fun onShowBorder() {
+ super.onShowBorder()
+ val tvWidth = targetView.width.toFloat()
+ val tvHeight = targetView.height.toFloat()
+ // Scale up just enough to make room for the border. Fail fast and fix the scaling
+ // onLayout.
+ val newScaleX = if (tvWidth == 0f) 1f else 1f + 2 * borderWidthPx / tvWidth
+ val newScaleY = if (tvHeight == 0f) 1f else 1f + 2 * borderWidthPx / tvHeight
+ with(targetView) {
+ pivotX = width / 2f
+ pivotY = height / 2f
+ scaleX = newScaleX
+ scaleY = newScaleY
+ }
+ with(contentView) {
+ pivotX = width / 2f
+ pivotY = height / 2f
+ scaleX = 1f / newScaleX
+ scaleY = 1f / newScaleY
+ }
+ }
+
+ override fun onHideBorder() {
+ super.onHideBorder()
+ with(targetView) {
+ pivotX = width.toFloat()
+ pivotY = height.toFloat()
+ scaleX = 1f
+ scaleY = 1f
+ }
+ with(contentView) {
+ pivotX = width / 2f
+ pivotY = height / 2f
+ scaleX = 1f
+ scaleY = 1f
+ }
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index 32d6582..8a6c197 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -62,6 +62,8 @@
import java.util.List;
import java.util.function.Consumer;
+import kotlin.Unit;
+
/**
* TaskView that contains all tasks that are part of the desktop.
*/
@@ -142,9 +144,10 @@
}
@Override
- protected void updateBorderBounds(Rect bounds) {
+ protected Unit updateBorderBounds(@NonNull Rect bounds) {
bounds.set(mBackgroundView.getLeft(), mBackgroundView.getTop(), mBackgroundView.getRight(),
mBackgroundView.getBottom());
+ return Unit.INSTANCE;
}
@Override
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 7e58763..2e347ba 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -37,6 +37,8 @@
import java.util.HashMap;
import java.util.function.Consumer;
+import kotlin.Unit;
+
/**
* TaskView that contains and shows thumbnails for not one, BUT TWO(!!) tasks
*
@@ -76,10 +78,10 @@
}
@Override
- protected void updateBorderBounds(Rect bounds) {
+ protected Unit updateBorderBounds(@NonNull Rect bounds) {
if (mSplitBoundsConfig == null) {
super.updateBorderBounds(bounds);
- return;
+ return Unit.INSTANCE;
}
bounds.set(
Math.min(mSnapshotView.getLeft() + Math.round(mSnapshotView.getTranslationX()),
@@ -90,6 +92,7 @@
mSnapshotView2.getRight() + Math.round(mSnapshotView2.getTranslationX())),
Math.max(mSnapshotView.getBottom() + Math.round(mSnapshotView.getTranslationY()),
mSnapshotView2.getBottom() + Math.round(mSnapshotView2.getTranslationY())));
+ return Unit.INSTANCE;
}
@Override
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index d5b43a8..50b38ea 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -120,6 +120,8 @@
import java.util.function.Consumer;
import java.util.stream.Stream;
+import kotlin.Unit;
+
/**
* A task in the Recents view.
*/
@@ -440,48 +442,44 @@
boolean keyboardFocusHighlightEnabled = FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get()
|| DesktopTaskView.DESKTOP_MODE_SUPPORTED;
+ boolean cursorHoverStatesEnabled = FeatureFlags.enableCursorHoverStates();
- boolean willDrawBorder =
- keyboardFocusHighlightEnabled || FeatureFlags.enableCursorHoverStates();
- setWillNotDraw(!willDrawBorder);
+ setWillNotDraw(!keyboardFocusHighlightEnabled && !cursorHoverStatesEnabled);
- if (willDrawBorder) {
- TypedArray styledAttrs = context.obtainStyledAttributes(
- attrs, R.styleable.TaskView, defStyleAttr, defStyleRes);
+ TypedArray styledAttrs = context.obtainStyledAttributes(
+ attrs, R.styleable.TaskView, defStyleAttr, defStyleRes);
- mFocusBorderAnimator = keyboardFocusHighlightEnabled ? new BorderAnimator(
- /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
- /* borderColor= */ styledAttrs.getColor(
- R.styleable.TaskView_focusBorderColor, DEFAULT_BORDER_COLOR),
- /* borderAnimationParams= */ new BorderAnimator.SimpleParams(
- /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
- R.dimen.keyboard_quick_switch_border_width),
- /* boundsBuilder= */ this::updateBorderBounds,
- /* targetView= */ this)) : null;
+ mFocusBorderAnimator = keyboardFocusHighlightEnabled
+ ? BorderAnimator.createSimpleBorderAnimator(
+ /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
+ /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
+ R.dimen.keyboard_quick_switch_border_width),
+ /* boundsBuilder= */ this::updateBorderBounds,
+ /* targetView= */ this,
+ /* borderColor= */ styledAttrs.getColor(
+ R.styleable.TaskView_focusBorderColor, DEFAULT_BORDER_COLOR))
+ : null;
- mHoverBorderAnimator =
- FeatureFlags.enableCursorHoverStates() ? new BorderAnimator(
- /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
- /* borderColor= */ styledAttrs.getColor(
- R.styleable.TaskView_hoverBorderColor, DEFAULT_BORDER_COLOR),
- /* borderAnimationParams= */ new BorderAnimator.SimpleParams(
- /* borderWidthPx= */ context.getResources()
- .getDimensionPixelSize(R.dimen.task_hover_border_width),
- /* boundsBuilder= */ this::updateBorderBounds,
- /* targetView= */ this)) : null;
+ mHoverBorderAnimator = cursorHoverStatesEnabled
+ ? BorderAnimator.createSimpleBorderAnimator(
+ /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
+ /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
+ R.dimen.task_hover_border_width),
+ /* boundsBuilder= */ this::updateBorderBounds,
+ /* targetView= */ this,
+ /* borderColor= */ styledAttrs.getColor(
+ R.styleable.TaskView_hoverBorderColor, DEFAULT_BORDER_COLOR))
+ : null;
- styledAttrs.recycle();
- } else {
- mFocusBorderAnimator = null;
- mHoverBorderAnimator = null;
- }
+ styledAttrs.recycle();
}
- protected void updateBorderBounds(Rect bounds) {
+ protected Unit updateBorderBounds(@NonNull Rect bounds) {
bounds.set(mSnapshotView.getLeft() + Math.round(mSnapshotView.getTranslationX()),
mSnapshotView.getTop() + Math.round(mSnapshotView.getTranslationY()),
mSnapshotView.getRight() + Math.round(mSnapshotView.getTranslationX()),
mSnapshotView.getBottom() + Math.round(mSnapshotView.getTranslationY()));
+ return Unit.INSTANCE;
}
public void setTaskViewId(int id) {
@@ -529,19 +527,21 @@
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
if (mFocusBorderAnimator != null) {
- mFocusBorderAnimator.buildAnimator(gainFocus).start();
+ mFocusBorderAnimator.setBorderVisibility(gainFocus, /* animated= */ true);
}
}
@Override
public boolean onHoverEvent(MotionEvent event) {
- if (FeatureFlags.enableCursorHoverStates()) {
+ if (mHoverBorderAnimator != null) {
switch (event.getAction()) {
case MotionEvent.ACTION_HOVER_ENTER:
- mHoverBorderAnimator.buildAnimator(/* isAppearing= */ true).start();
+ mHoverBorderAnimator.setBorderVisibility(
+ /* visible= */ true, /* animated= */ true);
break;
case MotionEvent.ACTION_HOVER_EXIT:
- mHoverBorderAnimator.buildAnimator(/* isAppearing= */ false).start();
+ mHoverBorderAnimator.setBorderVisibility(
+ /* visible= */ false, /* animated= */ true);
break;
default:
break;
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java b/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java
new file mode 100644
index 0000000..74f37a4
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java
@@ -0,0 +1,187 @@
+/*
+ * 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.
+ */
+package com.android.quickstep;
+
+import android.content.Intent;
+
+import androidx.annotation.NonNull;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.tapl.KeyboardQuickSwitch;
+import com.android.launcher3.ui.TaplTestsLauncher3;
+
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class TaplTestsKeyboardQuickSwitch extends AbstractQuickStepTest {
+
+ private enum TestSurface {
+ HOME, LAUNCHED_APP, HOME_ALL_APPS, WIDGETS,
+ }
+
+ private enum TestCase {
+ DISMISS(0),
+ LAUNCH_LAST_APP(0),
+ LAUNCH_SELECTED_APP(1),
+ LAUNCH_OVERVIEW(5);
+
+ private final int mNumAdditionalRunningTasks;
+
+ TestCase(int numAdditionalRunningTasks) {
+ mNumAdditionalRunningTasks = numAdditionalRunningTasks;
+ }
+ }
+
+ private static final String CALCULATOR_APP_PACKAGE =
+ resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR);
+
+ @Override
+ public void setUp() throws Exception {
+ Assume.assumeTrue(mLauncher.isTablet());
+ super.setUp();
+ TaplTestsLauncher3.initialize(this);
+ startAppFast(CALCULATOR_APP_PACKAGE);
+ startTestActivity(2);
+ }
+
+ @Test
+ public void testDismiss_fromHome() {
+ runTest(TestSurface.HOME, TestCase.DISMISS);
+ }
+
+ @Test
+ public void testDismiss_fromApp() {
+ runTest(TestSurface.LAUNCHED_APP, TestCase.DISMISS);
+ }
+
+ @Test
+ public void testDismiss_fromHomeAllApps() {
+ runTest(TestSurface.HOME_ALL_APPS, TestCase.DISMISS);
+ }
+
+ @Test
+ public void testDismiss_fromWidgets() {
+ runTest(TestSurface.WIDGETS, TestCase.DISMISS);
+ }
+
+ @Test
+ public void testLaunchLastTask_fromHome() {
+ runTest(TestSurface.HOME, TestCase.LAUNCH_LAST_APP);
+ }
+
+ @Test
+ public void testLaunchLastTask_fromApp() {
+ runTest(TestSurface.LAUNCHED_APP, TestCase.LAUNCH_LAST_APP);
+ }
+
+ @Test
+ public void testLaunchLastTask_fromHomeAllApps() {
+ runTest(TestSurface.HOME_ALL_APPS, TestCase.LAUNCH_LAST_APP);
+ }
+
+ @Test
+ public void testLaunchLastTask_fromWidgets() {
+ runTest(TestSurface.WIDGETS, TestCase.LAUNCH_LAST_APP);
+ }
+
+ @Test
+ public void testLaunchSelectedTask_fromHome() {
+ runTest(TestSurface.HOME, TestCase.LAUNCH_SELECTED_APP);
+ }
+
+ @Test
+ public void testLaunchSelectedTask_fromApp() {
+ runTest(TestSurface.LAUNCHED_APP, TestCase.LAUNCH_SELECTED_APP);
+ }
+
+ @Test
+ public void testLaunchSelectedTask_fromHomeAllApps() {
+ runTest(TestSurface.HOME_ALL_APPS, TestCase.LAUNCH_SELECTED_APP);
+ }
+
+ @Test
+ public void testLaunchSelectedTask_fromWidgets() {
+ runTest(TestSurface.WIDGETS, TestCase.LAUNCH_SELECTED_APP);
+ }
+
+ @Test
+ public void testLaunchOverviewTask_fromHome() {
+ runTest(TestSurface.HOME, TestCase.LAUNCH_OVERVIEW);
+ }
+
+ @Test
+ public void testLaunchOverviewTask_fromApp() {
+ runTest(TestSurface.LAUNCHED_APP, TestCase.LAUNCH_OVERVIEW);
+ }
+
+ @Test
+ public void testLaunchOverviewTask_fromHomeAllApps() {
+ runTest(TestSurface.HOME_ALL_APPS, TestCase.LAUNCH_OVERVIEW);
+ }
+
+ @Test
+ public void testLaunchOverviewTask_fromWidgets() {
+ runTest(TestSurface.WIDGETS, TestCase.LAUNCH_OVERVIEW);
+ }
+
+ private void runTest(@NonNull TestSurface testSurface, @NonNull TestCase testCase) {
+ for (int i = 0; i < testCase.mNumAdditionalRunningTasks; i++) {
+ startTestActivity(3 + i);
+ }
+
+ KeyboardQuickSwitch kqs;
+ switch (testSurface) {
+ case HOME:
+ kqs = mLauncher.goHome().showQuickSwitchView();
+ break;
+ case LAUNCHED_APP:
+ mLauncher.setIgnoreTaskbarVisibility(true);
+ kqs = mLauncher.getLaunchedAppState().showQuickSwitchView();
+ break;
+ case HOME_ALL_APPS:
+ kqs = mLauncher.goHome().switchToAllApps().showQuickSwitchView();
+ break;
+ case WIDGETS:
+ kqs = mLauncher.goHome().openAllWidgets().showQuickSwitchView();
+ break;
+ default:
+ throw new IllegalStateException(
+ "KeyboardQuickSwitch could not be initialized for test surface: "
+ + testSurface);
+ }
+
+ switch (testCase) {
+ case DISMISS:
+ kqs.dismiss();
+ break;
+ case LAUNCH_LAST_APP:
+ kqs.launchFocusedAppTask(CALCULATOR_APP_PACKAGE);
+ break;
+ case LAUNCH_SELECTED_APP:
+ kqs.moveFocusForward().launchFocusedAppTask(CALCULATOR_APP_PACKAGE);
+ break;
+ case LAUNCH_OVERVIEW:
+ kqs.moveFocusBackward().moveFocusBackward().launchFocusedOverviewTask();
+ break;
+ default:
+ throw new IllegalStateException("Cannot run test case: " + testCase);
+ }
+ }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 5531c6e..211a13c 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -17,10 +17,6 @@
package com.android.quickstep;
import static com.android.launcher3.config.FeatureFlags.ENABLE_CURSOR_HOVER_STATES;
-import static com.android.launcher3.testing.shared.TestProtocol.FLAKY_QUICK_SWITCH_TO_PREVIOUS_APP;
-import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
import static com.android.quickstep.TaskbarModeSwitchRule.Mode.PERSISTENT;
import static com.android.quickstep.TaskbarModeSwitchRule.Mode.TRANSIENT;
@@ -32,7 +28,6 @@
import android.content.Intent;
import android.platform.test.annotations.PlatinumTest;
-import android.util.Log;
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -51,11 +46,9 @@
import com.android.launcher3.tapl.OverviewTaskMenu;
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.ui.TaplTestsLauncher3;
-import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.TestUtil;
import com.android.launcher3.util.Wait;
import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
-import com.android.launcher3.util.rule.TestStabilityRule;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
import com.android.quickstep.views.RecentsView;
@@ -320,7 +313,7 @@
@Test
@ScreenRecord // b/242163205
@PlatinumTest(focusArea = "launcher")
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/286084688
+ @TaskbarModeSwitch(mode = PERSISTENT)
public void testQuickSwitchToPreviousAppForTablet() throws Exception {
assumeTrue(mLauncher.isTablet());
startTestActivity(2);
@@ -340,16 +333,7 @@
"The first app we should have quick switched to is not running");
// Expect task bar visible when the launched app was the test activity.
launchedAppState = getAndAssertLaunchedApp();
-
- Log.e(FLAKY_QUICK_SWITCH_TO_PREVIOUS_APP,
- "is Taskbar Transient : " + DisplayController.isTransientTaskbar(mTargetContext));
- // TODO(b/286084688): Remove this branching check after test corruption is resolved.
- // Branching this check because of test corruption.
- if (DisplayController.isTransientTaskbar(mTargetContext)) {
- launchedAppState.assertTaskbarHidden();
- } else {
- launchedAppState.assertTaskbarVisible();
- }
+ launchedAppState.assertTaskbarVisible();
}
@Test
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 0cc1887..4215e31 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -43,7 +43,6 @@
import static com.android.launcher3.LauncherState.NO_SCALE;
import static com.android.launcher3.LauncherState.SPRING_LOADED;
import static com.android.launcher3.Utilities.postAsyncCallback;
-import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.getSupportedActions;
import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
import static com.android.launcher3.config.FeatureFlags.MULTI_SELECT_EDIT_MODE;
import static com.android.launcher3.config.FeatureFlags.SHOW_DOT_PAGINATION;
@@ -114,7 +113,6 @@
import android.util.SparseArray;
import android.view.KeyEvent;
import android.view.KeyboardShortcutGroup;
-import android.view.KeyboardShortcutInfo;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MotionEvent;
@@ -137,7 +135,6 @@
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.accessibility.BaseAccessibilityDelegate.LauncherAction;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.allapps.ActivityAllAppsContainerView;
import com.android.launcher3.allapps.AllAppsRecyclerView;
@@ -205,6 +202,7 @@
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
+import com.android.launcher3.util.KeyboardShortcutsDelegate;
import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.util.PackageUserKey;
@@ -335,6 +333,7 @@
private static final boolean DESKTOP_MODE_SUPPORTED =
"1".equals(Utilities.getSystemProperty("persist.wm.debug.desktop_mode_2", "0"));
+ KeyboardShortcutsDelegate mKeyboardShortcutsDelegate = new KeyboardShortcutsDelegate(this);
@Thunk
Workspace<?> mWorkspace;
@Thunk
@@ -3004,84 +3003,51 @@
mOverlayManager.dump(prefix, writer);
}
+ /**
+ * Populates the list of shortcuts. Logic delegated to {@Link KeyboardShortcutsDelegate}.
+ *
+ * @param data The data list to populate with shortcuts.
+ * @param menu The current menu, which may be null.
+ * @param deviceId The id for the connected device the shortcuts should be provided for.
+ */
@Override
public void onProvideKeyboardShortcuts(
List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
-
- ArrayList<KeyboardShortcutInfo> shortcutInfos = new ArrayList<>();
- if (isInState(NORMAL)) {
- shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.all_apps_button_label),
- KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON));
- shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.widget_button_text),
- KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON));
- }
- getSupportedActions(this, getCurrentFocus()).forEach(la ->
- shortcutInfos.add(new KeyboardShortcutInfo(
- la.accessibilityAction.getLabel(), la.keyCode, KeyEvent.META_CTRL_ON)));
- if (!shortcutInfos.isEmpty()) {
- data.add(new KeyboardShortcutGroup(getString(R.string.home_screen), shortcutInfos));
- }
-
+ mKeyboardShortcutsDelegate.onProvideKeyboardShortcuts(data, menu, deviceId);
super.onProvideKeyboardShortcuts(data, menu, deviceId);
}
+ /**
+ * Logic delegated to {@Link KeyboardShortcutsDelegate}.
+ * @param keyCode The value in event.getKeyCode().
+ * @param event Description of the key event.
+ */
@Override
public boolean onKeyShortcut(int keyCode, KeyEvent event) {
- if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_A:
- if (isInState(NORMAL)) {
- getStateManager().goToState(ALL_APPS);
- return true;
- }
- break;
- case KeyEvent.KEYCODE_W:
- if (isInState(NORMAL)) {
- OptionsPopupView.openWidgets(this);
- return true;
- }
- break;
- default:
- for (LauncherAction la : getSupportedActions(this, getCurrentFocus())) {
- if (la.keyCode == keyCode) {
- return la.invokeFromKeyboard(getCurrentFocus());
- }
- }
- }
- }
- return super.onKeyShortcut(keyCode, event);
+ Boolean result = mKeyboardShortcutsDelegate.onKeyShortcut(keyCode, event);
+ return result != null ? result : super.onKeyShortcut(keyCode, event);
}
+ /**
+ * Logic delegated to {@Link KeyboardShortcutsDelegate}.
+ * @param keyCode The value in event.getKeyCode().
+ * @param event Description of the key event.
+ */
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_ESCAPE) {
- // Close any open floating views.
- closeOpenViews();
- return true;
- }
- return super.onKeyDown(keyCode, event);
+ Boolean result = mKeyboardShortcutsDelegate.onKeyDown(keyCode, event);
+ return result != null ? result : super.onKeyDown(keyCode, event);
}
+ /**
+ * Logic delegated to {@Link KeyboardShortcutsDelegate}.
+ * @param keyCode The value in event.getKeyCode().
+ * @param event Description of the key event.
+ */
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_MENU) {
- // KEYCODE_MENU is sent by some tests, for example
- // LauncherJankTests#testWidgetsContainerFling. Don't just remove its handling.
- if (!mDragController.isDragging() && !mWorkspace.isSwitchingState() &&
- isInState(NORMAL)) {
- // Close any open floating views.
- closeOpenViews();
-
- // Setting the touch point to (-1, -1) will show the options popup in the center of
- // the screen.
- if (Utilities.isRunningInTestHarness()) {
- Log.d(TestProtocol.PERMANENT_DIAG_TAG, "Opening options popup on key up");
- }
- showDefaultOptions(-1, -1);
- }
- return true;
- }
- return super.onKeyUp(keyCode, event);
+ Boolean result = mKeyboardShortcutsDelegate.onKeyUp(keyCode, event);
+ return result != null ? result : super.onKeyUp(keyCode, event);
}
/**
@@ -3147,7 +3113,7 @@
getStateManager().goToState(LauncherState.NORMAL);
}
- private void closeOpenViews() {
+ public void closeOpenViews() {
closeOpenViews(true);
}
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index e724858..378dbf3 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -82,17 +82,29 @@
}
/**
+ * Calling {@link #setApps(AppInfo[], int, Map, boolean)} with shouldPreinflate set to
+ * {@code true}. This method should be called in launcher (not for taskbar).
+ */
+ public void setApps(@Nullable AppInfo[] apps, int flags, Map<PackageUserKey, Integer> map) {
+ setApps(apps, flags, map, /* shouldPreinflate= */ true);
+ }
+
+ /**
* Sets the current set of apps and sets mapping for {@link PackageUserKey} to Uid for
* the current set of apps.
+ *
+ * <p> Note that shouldPreinflate param should be set to {@code false} for taskbar, because this
+ * method is too late to preinflate all apps, as user will open all apps in the same frame.
*/
- public void setApps(@Nullable AppInfo[] apps, int flags, Map<PackageUserKey, Integer> map) {
+ public void setApps(@Nullable AppInfo[] apps, int flags, Map<PackageUserKey, Integer> map,
+ boolean shouldPreinflate) {
mApps = apps == null ? EMPTY_ARRAY : apps;
mModelFlags = flags;
notifyUpdate();
mPackageUserKeytoUidMap = map;
// Preinflate all apps RV when apps has changed, which can happen after unlocking screen,
// rotating screen, or downloading/upgrading apps.
- if (ENABLE_ALL_APPS_RV_PREINFLATION.get()) {
+ if (shouldPreinflate && ENABLE_ALL_APPS_RV_PREINFLATION.get()) {
mAllAppsRecyclerViewPool.preInflateAllAppsViewHolders(mContext);
}
}
diff --git a/src/com/android/launcher3/util/KeyboardShortcutsDelegate.java b/src/com/android/launcher3/util/KeyboardShortcutsDelegate.java
new file mode 100644
index 0000000..3ec339d
--- /dev/null
+++ b/src/com/android/launcher3/util/KeyboardShortcutsDelegate.java
@@ -0,0 +1,145 @@
+/*
+ * 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.
+ */
+package com.android.launcher3.util;
+
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.getSupportedActions;
+
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
+import android.view.KeyboardShortcutInfo;
+import android.view.Menu;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.accessibility.BaseAccessibilityDelegate;
+import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.views.OptionsPopupView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Delegate to define the keyboard shortcuts.
+ */
+public class KeyboardShortcutsDelegate {
+
+ Launcher mLauncher;
+
+ public KeyboardShortcutsDelegate(Launcher launcher) {
+ mLauncher = launcher;
+ }
+
+ /**
+ * Populates the list of shortcuts.
+ */
+ public void onProvideKeyboardShortcuts(
+ List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
+ ArrayList<KeyboardShortcutInfo> shortcutInfos = new ArrayList<>();
+ if (mLauncher.isInState(NORMAL)) {
+ shortcutInfos.add(
+ new KeyboardShortcutInfo(mLauncher.getString(R.string.all_apps_button_label),
+ KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON));
+ shortcutInfos.add(
+ new KeyboardShortcutInfo(mLauncher.getString(R.string.widget_button_text),
+ KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON));
+ }
+ getSupportedActions(mLauncher, mLauncher.getCurrentFocus()).forEach(la ->
+ shortcutInfos.add(new KeyboardShortcutInfo(
+ la.accessibilityAction.getLabel(), la.keyCode, KeyEvent.META_CTRL_ON)));
+ if (!shortcutInfos.isEmpty()) {
+ data.add(new KeyboardShortcutGroup(mLauncher.getString(R.string.home_screen),
+ shortcutInfos));
+ }
+ }
+
+ /**
+ * Handles combinations of keys like ctrl+s or ctrl+c and runs before onKeyDown.
+ * @param keyCode code of the key being pressed.
+ * @see android.view.KeyEvent
+ * @return weather the event is already handled and if it should be passed to other components.
+ */
+ public Boolean onKeyShortcut(int keyCode, KeyEvent event) {
+ if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_A:
+ if (mLauncher.isInState(NORMAL)) {
+ mLauncher.getStateManager().goToState(ALL_APPS);
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_W:
+ if (mLauncher.isInState(NORMAL)) {
+ OptionsPopupView.openWidgets(mLauncher);
+ return true;
+ }
+ break;
+ default:
+ for (BaseAccessibilityDelegate.LauncherAction la : getSupportedActions(
+ mLauncher, mLauncher.getCurrentFocus())) {
+ if (la.keyCode == keyCode) {
+ return la.invokeFromKeyboard(mLauncher.getCurrentFocus());
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Handle key down event.
+ * @param keyCode code of the key being pressed.
+ * @see android.view.KeyEvent
+ */
+ public Boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_ESCAPE) {
+ // Close any open floating views.
+ mLauncher.closeOpenViews();
+ return true;
+ }
+ return null;
+ }
+
+ /**
+ * Handle key up event.
+ * @param keyCode code of the key being pressed.
+ * @see android.view.KeyEvent
+ */
+ public Boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_MENU) {
+ // KEYCODE_MENU is sent by some tests, for example
+ // LauncherJankTests#testWidgetsContainerFling. Don't just remove its handling.
+ if (!mLauncher.getDragController().isDragging()
+ && !mLauncher.getWorkspace().isSwitchingState()
+ && mLauncher.isInState(NORMAL)) {
+ // Close any open floating views.
+ mLauncher.closeOpenViews();
+
+ // Setting the touch point to (-1, -1) will show the options popup in the center of
+ // the screen.
+ if (Utilities.isRunningInTestHarness()) {
+ Log.d(TestProtocol.PERMANENT_DIAG_TAG, "Opening options popup on key up");
+ }
+ mLauncher.showDefaultOptions(-1, -1);
+ }
+ return true;
+ }
+ return null;
+ }
+}
diff --git a/tests/Android.bp b/tests/Android.bp
index e1b97de..654edad 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -43,6 +43,7 @@
name: "launcher-oop-tests-src",
srcs: [
"src/com/android/launcher3/allapps/OopTaplOpenCloseAllApps.java",
+ "src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java",
"src/com/android/launcher3/dragging/TaplDragTest.java",
"src/com/android/launcher3/dragging/TaplUninstallRemove.java",
"src/com/android/launcher3/ui/AbstractLauncherUiTest.java",
diff --git a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index 0798e97..f5aa820 100644
--- a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -155,7 +155,6 @@
public static final String PERMANENT_DIAG_TAG = "TaplTarget";
public static final String TWO_TASKBAR_LONG_CLICKS = "b/262282528";
- public static final String FLAKY_QUICK_SWITCH_TO_PREVIOUS_APP = "b/286084688";
public static final String ICON_MISSING = "b/282963545";
public static final String INCORRECT_HOME_STATE = "b/293191790";
diff --git a/tests/src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java b/tests/src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java
new file mode 100644
index 0000000..66c67f5
--- /dev/null
+++ b/tests/src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ */
+package com.android.launcher3.appiconmenu;
+
+import static com.android.launcher3.ui.TaplTestsLauncher3.APP_NAME;
+import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.PlatinumTest;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.popup.ArrowPopup;
+import com.android.launcher3.tapl.AllApps;
+import com.android.launcher3.tapl.AppIconMenu;
+import com.android.launcher3.tapl.AppIconMenuItem;
+import com.android.launcher3.tapl.HomeAllApps;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * This test run in both Out of process (Oop) and in-process (Ipc).
+ * Tests the AppIconMenu (the menu that appears when you long press an app icon) and also make sure
+ * we can launch a shortcut from it.
+ */
+public class TaplAppIconMenuTest extends AbstractLauncherUiTest {
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ initialize(this);
+ }
+
+ private boolean isOptionsPopupVisible(Launcher launcher) {
+ final ArrowPopup<?> popup = launcher.getOptionsPopup();
+ return popup != null && popup.isShown();
+ }
+
+ /**
+ * Open All apps then open the AppIconMenu then launch a shortcut from the menu and make sure it
+ * launches.
+ */
+ @Test
+ @PortraitLandscape
+ @PlatinumTest(focusArea = "launcher")
+ public void testLaunchMenuItem() {
+ final AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
+ allApps.freeze();
+ try {
+ final AppIconMenu menu = allApps.getAppIcon(APP_NAME).openDeepShortcutMenu();
+
+ executeOnLauncher(
+ launcher -> assertTrue("Launcher internal state didn't switch to Showing Menu",
+ isOptionsPopupVisible(launcher)));
+
+ final AppIconMenuItem menuItem = menu.getMenuItem(1);
+ assertEquals("Wrong menu item", "Shortcut 2", menuItem.getText());
+ menuItem.launch(getAppPackageName());
+ } finally {
+ allApps.unfreeze();
+ }
+ }
+
+ /**
+ * Drag icon from AllApps to the workspace and then open the AppIconMenu and launch a shortcut
+ * from it.
+ */
+ @Test
+ public void testLaunchHomeScreenMenuItem() {
+ // Drag the test app icon to home screen and open short cut menu from the icon
+ final HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
+ allApps.freeze();
+ try {
+ allApps.getAppIcon(APP_NAME).dragToWorkspace(false, false);
+ final AppIconMenu menu = mLauncher.getWorkspace().getWorkspaceAppIcon(
+ APP_NAME).openDeepShortcutMenu();
+
+ executeOnLauncher(
+ launcher -> assertTrue("Launcher internal state didn't switch to Showing Menu",
+ isOptionsPopupVisible(launcher)));
+
+ final AppIconMenuItem menuItem = menu.getMenuItem(1);
+ assertEquals("Wrong menu item", "Shortcut 2", menuItem.getText());
+ menuItem.launch(getAppPackageName());
+ } finally {
+ allApps.unfreeze();
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java b/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
index e6fdbaa..f0c4fdb 100644
--- a/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
+++ b/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
@@ -323,7 +323,7 @@
mBubbleTextView.setDisplay(DISPLAY_PREDICTION_ROW);
mBubbleTextView.applyLabel(mItemInfoWithIcon);
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
- mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
+ mBubbleTextView.measure(mLimitedWidth, MAX_HEIGHT);
mBubbleTextView.onPreDraw();
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 5417e3f..bc53d6d 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -36,11 +36,8 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.popup.ArrowPopup;
import com.android.launcher3.tapl.AllApps;
import com.android.launcher3.tapl.AppIcon;
-import com.android.launcher3.tapl.AppIconMenu;
-import com.android.launcher3.tapl.AppIconMenuItem;
import com.android.launcher3.tapl.HomeAllApps;
import com.android.launcher3.tapl.HomeAppIcon;
import com.android.launcher3.tapl.Widgets;
@@ -89,7 +86,6 @@
public static void initialize(
AbstractLauncherUiTest test, boolean clearWorkspace) throws Exception {
test.reinitializeLauncherData(clearWorkspace);
- test.mLauncher.resetFreezeRecentTaskList();
test.mDevice.pressHome();
test.waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null);
test.waitForState("Launcher internal state didn't switch to Home",
@@ -248,55 +244,6 @@
return getWidgetsView(launcher).computeVerticalScrollOffset();
}
- private boolean isOptionsPopupVisible(Launcher launcher) {
- final ArrowPopup<?> popup = launcher.getOptionsPopup();
- return popup != null && popup.isShown();
- }
-
- @Test
- @PortraitLandscape
- @PlatinumTest(focusArea = "launcher")
- public void testLaunchMenuItem() throws Exception {
- final AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
- allApps.freeze();
- try {
- final AppIconMenu menu = allApps.
- getAppIcon(APP_NAME).
- openDeepShortcutMenu();
-
- executeOnLauncher(
- launcher -> assertTrue("Launcher internal state didn't switch to Showing Menu",
- isOptionsPopupVisible(launcher)));
-
- final AppIconMenuItem menuItem = menu.getMenuItem(1);
- assertEquals("Wrong menu item", "Shortcut 2", menuItem.getText());
- menuItem.launch(getAppPackageName());
- } finally {
- allApps.unfreeze();
- }
- }
-
- @Test
- public void testLaunchHomeScreenMenuItem() {
- // Drag the test app icon to home screen and open short cut menu from the icon
- final HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
- allApps.freeze();
- try {
- allApps.getAppIcon(APP_NAME).dragToWorkspace(false, false);
- final AppIconMenu menu = mLauncher.getWorkspace().getWorkspaceAppIcon(
- APP_NAME).openDeepShortcutMenu();
-
- executeOnLauncher(
- launcher -> assertTrue("Launcher internal state didn't switch to Showing Menu",
- isOptionsPopupVisible(launcher)));
-
- final AppIconMenuItem menuItem = menu.getMenuItem(1);
- assertEquals("Wrong menu item", "Shortcut 2", menuItem.getText());
- menuItem.launch(getAppPackageName());
- } finally {
- allApps.unfreeze();
- }
- }
@FlakyTest(bugId = 256615483)
@Test
@PortraitLandscape
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 44875d5..b82fa35 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -42,7 +42,8 @@
/**
* Operations on AllApps opened from Home. Also a parent for All Apps opened from Overview.
*/
-public abstract class AllApps extends LauncherInstrumentation.VisibleContainer {
+public abstract class AllApps extends LauncherInstrumentation.VisibleContainer
+ implements KeyboardQuickSwitchSource {
// Defer updates flag used to defer all apps updates by a test's request.
private static final int DEFER_UPDATES_TEST = 1 << 1;
@@ -65,6 +66,16 @@
.getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
+ @Override
+ public LauncherInstrumentation getLauncher() {
+ return mLauncher;
+ }
+
+ @Override
+ public LauncherInstrumentation.ContainerType getStartingContainerType() {
+ return getContainerType();
+ }
+
private boolean hasClickableIcon(UiObject2 allAppsContainer, UiObject2 appListRecycler,
BySelector appIconSelector, int displayBottom) {
final UiObject2 icon;
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 677f204..8713b68 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -39,7 +39,8 @@
* Indicates the base state with a UI other than Overview running as foreground. It can also
* indicate Launcher as long as Launcher is not in Overview state.
*/
-public abstract class Background extends LauncherInstrumentation.VisibleContainer {
+public abstract class Background extends LauncherInstrumentation.VisibleContainer
+ implements KeyboardQuickSwitchSource {
private static final int ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION = 500;
private static final Pattern SQUARE_BUTTON_EVENT = Pattern.compile("onOverviewToggle");
@@ -47,6 +48,16 @@
super(launcher);
}
+ @Override
+ public LauncherInstrumentation getLauncher() {
+ return mLauncher;
+ }
+
+ @Override
+ public LauncherInstrumentation.ContainerType getStartingContainerType() {
+ return getContainerType();
+ }
+
/**
* Swipes up or presses the square button to switch to Overview.
* Returns the base overview, which can be either in Launcher or the fallback recents.
diff --git a/tests/tapl/com/android/launcher3/tapl/Home.java b/tests/tapl/com/android/launcher3/tapl/Home.java
index 252435b..85e28e8 100644
--- a/tests/tapl/com/android/launcher3/tapl/Home.java
+++ b/tests/tapl/com/android/launcher3/tapl/Home.java
@@ -62,4 +62,9 @@
protected boolean zeroButtonToOverviewGestureStateTransitionWhileHolding() {
return true;
}
+
+ @Override
+ public boolean isHomeState() {
+ return true;
+ }
}
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java b/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
index 8542f91..d9b179c 100644
--- a/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
@@ -113,4 +113,9 @@
protected void verifyVisibleContainerOnDismiss() {
mLauncher.getWorkspace();
}
+
+ @Override
+ public boolean isHomeState() {
+ return true;
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java b/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java
new file mode 100644
index 0000000..2a98a24
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java
@@ -0,0 +1,212 @@
+/*
+ * 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.
+ */
+package com.android.launcher3.tapl;
+
+import static com.android.launcher3.tapl.LauncherInstrumentation.KEYBOARD_QUICK_SWITCH_RES_ID;
+
+import android.view.KeyEvent;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.testing.shared.TestProtocol;
+
+import java.util.regex.Pattern;
+
+/**
+ * Operations on the Keyboard Quick Switch View
+ */
+public final class KeyboardQuickSwitch {
+
+ private static final Pattern EVENT_ALT_TAB_DOWN = Pattern.compile(
+ "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_DOWN.*?keyCode=KEYCODE_TAB"
+ + ".*?metaState=META_ALT_ON");
+ private static final Pattern EVENT_ALT_TAB_UP = Pattern.compile(
+ "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_TAB"
+ + ".*?metaState=META_ALT_ON");
+
+ private static final Pattern EVENT_ALT_SHIFT_TAB_DOWN = Pattern.compile(
+ "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_DOWN.*?keyCode=KEYCODE_TAB"
+ + ".*?metaState=META_ALT_ON|META_SHIFT_ON");
+ private static final Pattern EVENT_ALT_SHIFT_TAB_UP = Pattern.compile(
+ "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_TAB"
+ + ".*?metaState=META_ALT_ON|META_SHIFT_ON");
+ private static final Pattern EVENT_ALT_ESC_DOWN = Pattern.compile(
+ "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_DOWN"
+ + ".*?keyCode=KEYCODE_ESCAPE.*?metaState=META_ALT_ON");
+ private static final Pattern EVENT_ALT_ESC_UP = Pattern.compile(
+ "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_UP"
+ + ".*?keyCode=KEYCODE_ESCAPE.*?metaState=META_ALT_ON");
+ private static final Pattern EVENT_ALT_LEFT_UP = Pattern.compile(
+ "Key event: KeyEvent.*?action=ACTION_UP"
+ + ".*?keyCode=KEYCODE_ALT_LEFT");
+
+ private final LauncherInstrumentation mLauncher;
+ private final LauncherInstrumentation.ContainerType mStartingContainerType;
+ private final boolean mExpectHomeKeyEventsOnDismiss;
+
+ KeyboardQuickSwitch(
+ LauncherInstrumentation launcher,
+ LauncherInstrumentation.ContainerType startingContainerType,
+ boolean expectHomeKeyEventsOnDismiss) {
+ mLauncher = launcher;
+ mStartingContainerType = startingContainerType;
+ mExpectHomeKeyEventsOnDismiss = expectHomeKeyEventsOnDismiss;
+ }
+
+ /**
+ * Focuses the next task in the Keyboard quick switch view.
+ * <p>
+ * Tasks are ordered left-to-right in LTR, and vice versa in RLT, in a carousel.
+ * <ul>
+ * <li>If no task has been focused yet, and there is only one task, then that task will be
+ * focused</li>
+ * <li>If no task has been focused yet, and there are two or more tasks, then the second
+ * task will be focused</li>
+ * <li>If the currently-focused task is at the end of the list, the first task will be
+ * focused</li>
+ * </ul>
+ */
+ public KeyboardQuickSwitch moveFocusForward() {
+ try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+ "want to move keyboard quick switch focus forward")) {
+ mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID);
+
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_TAB_DOWN);
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_TAB_UP);
+ mLauncher.assertTrue("Failed to press alt+tab",
+ mLauncher.getDevice().pressKeyCode(
+ KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_ON));
+
+ try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
+ "pressed alt+tab")) {
+ mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID);
+
+ return this;
+ }
+ }
+ }
+ }
+
+ /**
+ * Focuses the next task in the Keyboard quick switch view.
+ * <p>
+ * Tasks are ordered left-to-right in LTR, and vice versa in RLT, in a carousel.
+ * <ul>
+ * <li>If no task has been focused yet, and there is only one task, then that task will be
+ * focused</li>
+ * <li>If no task has been focused yet, and there are two or more tasks, then the second
+ * task will be focused</li>
+ * <li>If the currently-focused task is at the start of the list, the last task will be
+ * focused</li>
+ * </ul>
+ */
+ public KeyboardQuickSwitch moveFocusBackward() {
+ try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+ "want to move keyboard quick switch focus backward")) {
+ mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID);
+
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_SHIFT_TAB_DOWN);
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_SHIFT_TAB_UP);
+ mLauncher.assertTrue("Failed to press alt+shift+tab",
+ mLauncher.getDevice().pressKeyCode(
+ KeyEvent.KEYCODE_TAB,
+ KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON));
+
+ try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
+ "pressed alt+shift+tab")) {
+ mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID);
+
+ return this;
+ }
+ }
+ }
+ }
+
+ /**
+ * Dismisses the Keyboard Quick Switch view without launching the focused task.
+ * <p>
+ * The device will return to the same state it started in before displaying the Keyboard Quick
+ * Switch view.
+ */
+ public void dismiss() {
+ try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+ "want to dismiss keyboard quick switch view")) {
+ mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID);
+
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_DOWN);
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_UP);
+ mLauncher.assertTrue("Failed to press alt+tab",
+ mLauncher.getDevice().pressKeyCode(
+ KeyEvent.KEYCODE_ESCAPE, KeyEvent.META_ALT_ON));
+
+ try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
+ "pressed alt+esc")) {
+ mLauncher.waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
+ if (mExpectHomeKeyEventsOnDismiss) {
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_LEFT_UP);
+ }
+ mLauncher.unpressKeyCode(KeyEvent.KEYCODE_ALT_LEFT, 0);
+
+ // Verify the final state is the same as the initial state
+ mLauncher.verifyContainerType(mStartingContainerType);
+ }
+ }
+ }
+ }
+
+ /**
+ * Launches the currently-focused app task.
+ * <p>
+ * This method should only be used if the focused task is for a recent running app, otherwise
+ * use {@link #launchFocusedOverviewTask()}.
+ *
+ * @param expectedPackageName the package name of the expected launched app
+ */
+ public LaunchedAppState launchFocusedAppTask(@NonNull String expectedPackageName) {
+ return (LaunchedAppState) launchFocusedTask(expectedPackageName);
+ }
+
+ /**
+ * Launches the currently-focused overview task.
+ * <p>
+ * This method only should be used if the focused task is for overview, otherwise use
+ * {@link #launchFocusedAppTask(String)}.
+ */
+ public Overview launchFocusedOverviewTask() {
+ return (Overview) launchFocusedTask(null);
+ }
+
+ private LauncherInstrumentation.VisibleContainer launchFocusedTask(
+ @Nullable String expectedPackageName) {
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "want to launch focused task: "
+ + (expectedPackageName == null ? "Overview" : expectedPackageName))) {
+ mLauncher.unpressKeyCode(KeyEvent.KEYCODE_ALT_LEFT, 0);
+ mLauncher.waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
+
+ if (expectedPackageName != null) {
+ mLauncher.assertAppLaunched(expectedPackageName);
+ return mLauncher.getLaunchedAppState();
+ } else {
+ return mLauncher.getOverview();
+ }
+ }
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitchSource.java b/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitchSource.java
new file mode 100644
index 0000000..b7e3d38
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitchSource.java
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+package com.android.launcher3.tapl;
+
+import static com.android.launcher3.tapl.LauncherInstrumentation.KEYBOARD_QUICK_SWITCH_RES_ID;
+
+import android.view.KeyEvent;
+
+/**
+ * {@link com.android.launcher3.tapl.LauncherInstrumentation.VisibleContainer} that can be used to
+ * show the keyboard quick switch view.
+ */
+interface KeyboardQuickSwitchSource {
+
+ /**
+ * Shows the Keyboard Quick Switch view.
+ */
+ default KeyboardQuickSwitch showQuickSwitchView() {
+ LauncherInstrumentation launcher = getLauncher();
+
+ try (LauncherInstrumentation.Closable c1 = launcher.addContextLayer(
+ "want to show keyboard quick switch object")) {
+ launcher.pressAndHoldKeyCode(KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_LEFT_ON);
+
+ try (LauncherInstrumentation.Closable c2 = launcher.addContextLayer(
+ "press and held alt+tab")) {
+ launcher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID);
+ launcher.unpressKeyCode(KeyEvent.KEYCODE_TAB, 0);
+
+ return new KeyboardQuickSwitch(
+ launcher, getStartingContainerType(), isHomeState());
+ }
+ }
+ }
+
+ /** This method requires public access, however should not be called in tests. */
+ LauncherInstrumentation getLauncher();
+
+ /** This method requires public access, however should not be called in tests. */
+ LauncherInstrumentation.ContainerType getStartingContainerType();
+
+ /** This method requires public access, however should not be called in tests. */
+ boolean isHomeState();
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index 3450ea7..f6fcfa64 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -20,15 +20,10 @@
import static com.android.launcher3.testing.shared.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
-import android.app.UiAutomation;
import android.graphics.Point;
import android.view.MotionEvent;
-import android.view.accessibility.AccessibilityEvent;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.BySelector;
import androidx.test.uiautomator.UiObject2;
-import androidx.test.uiautomator.Until;
import com.android.launcher3.testing.shared.TestProtocol;
@@ -57,7 +52,19 @@
*/
public LaunchedAppState launch(String expectedPackageName) {
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
- return launch(By.pkg(expectedPackageName));
+ try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+ "want to launch an app from " + launchableType())) {
+ LauncherInstrumentation.log("Launchable.launch before click "
+ + mObject.getVisibleCenter() + " in "
+ + mLauncher.getVisibleBounds(mObject));
+
+ mLauncher.clickLauncherObject(mObject);
+
+ try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("clicked")) {
+ expectActivityStartEvents();
+ return mLauncher.assertAppLaunched(expectedPackageName);
+ }
+ }
}
}
@@ -65,21 +72,6 @@
protected abstract String launchableType();
- private LaunchedAppState launch(BySelector selector) {
- try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
- "want to launch an app from " + launchableType())) {
- LauncherInstrumentation.log("Launchable.launch before click "
- + mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(mObject));
-
- mLauncher.clickLauncherObject(mObject);
-
- try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("clicked")) {
- expectActivityStartEvents();
- return assertAppLaunched(selector);
- }
- }
- }
-
/**
* Clicks a launcher object to initiate splitscreen, where the selected app will be one of two
* apps running on the screen. Should be called when Launcher is in a "split staging" state
@@ -107,14 +99,6 @@
}
}
- protected LaunchedAppState assertAppLaunched(BySelector selector) {
- mLauncher.assertTrue(
- "App didn't start: (" + selector + ")",
- mLauncher.getDevice().wait(Until.hasObject(selector),
- LauncherInstrumentation.WAIT_TIME_MS));
- return new LaunchedAppState(mLauncher);
- }
-
Point startDrag(long downTime, Runnable expectLongClickEvents, boolean runToSpringLoadedState) {
final Point iconCenter = getObject().getVisibleCenter();
final Point dragStartCenter = new Point(iconCenter.x,
diff --git a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
index 30417c0..9f8fb92 100644
--- a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
+++ b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
@@ -34,7 +34,6 @@
import android.view.MotionEvent;
import android.view.ViewConfiguration;
-import androidx.test.uiautomator.By;
import androidx.test.uiautomator.Condition;
import androidx.test.uiautomator.UiDevice;
@@ -72,6 +71,11 @@
return LauncherInstrumentation.ContainerType.LAUNCHED_APP;
}
+ @Override
+ public boolean isHomeState() {
+ return false;
+ }
+
/**
* Returns the taskbar.
*
@@ -200,8 +204,8 @@
try (LauncherInstrumentation.Closable c4 = launcher.addContextLayer(
"dropped item")) {
- launchable.assertAppLaunched(By.pkg(expectedNewPackageName));
- launchable.assertAppLaunched(By.pkg(expectedExistingPackageName));
+ launcher.assertAppLaunched(expectedNewPackageName);
+ launcher.assertAppLaunched(expectedExistingPackageName);
}
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index b7d2530..130bd3a 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -20,7 +20,10 @@
import static android.content.pm.PackageManager.DONT_KILL_APP;
import static android.content.pm.PackageManager.MATCH_ALL;
import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
+import static android.view.KeyEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_UP;
import static android.view.MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT;
+
import static com.android.launcher3.tapl.Folder.FOLDER_CONTENT_RES_ID;
import static com.android.launcher3.tapl.TestHelpers.getOverviewPackageName;
import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
@@ -49,6 +52,9 @@
import android.text.TextUtils;
import android.util.Log;
import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.WindowManager;
@@ -170,6 +176,7 @@
private static final String OPEN_FOLDER_RES_ID = "folder_content";
static final String TASKBAR_RES_ID = "taskbar_view";
private static final String SPLIT_PLACEHOLDER_RES_ID = "split_placeholder";
+ static final String KEYBOARD_QUICK_SWITCH_RES_ID = "keyboard_quick_switch_view";
public static final int WAIT_TIME_MS = 30000;
static final long DEFAULT_POLL_INTERVAL = 1000;
private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
@@ -755,18 +762,7 @@
return isTablet() ? getLauncherPackageName() : SYSTEMUI_PACKAGE;
}
- /**
- * Resets the frozen recent tasks list if necessary from a previous quickswitch.
- */
- public void resetFreezeRecentTaskList() {
- try {
- mDevice.executeShellCommand("wm reset-freeze-recent-tasks");
- } catch (IOException e) {
- Log.e(TAG, "Failed to reset fozen recent tasks list", e);
- }
- }
-
- private UiObject2 verifyContainerType(ContainerType containerType) {
+ UiObject2 verifyContainerType(ContainerType containerType) {
waitForLauncherInitialized();
if (mExpectedRotationCheckEnabled && mExpectedRotation != null) {
@@ -795,6 +791,7 @@
waitUntilLauncherObjectGone(WIDGETS_RES_ID);
waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID);
waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
+ waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
if (is3PLauncher() && isTablet()) {
waitForSystemLauncherObject(TASKBAR_RES_ID);
@@ -809,6 +806,7 @@
waitUntilLauncherObjectGone(APPS_RES_ID);
waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID);
waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
+ waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
if (is3PLauncher() && isTablet()) {
waitForSystemLauncherObject(TASKBAR_RES_ID);
@@ -824,6 +822,7 @@
waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID);
waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
+ waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
return waitForLauncherObject(APPS_RES_ID);
}
@@ -832,6 +831,7 @@
waitUntilLauncherObjectGone(WIDGETS_RES_ID);
waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID);
waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
+ waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
if (is3PLauncher() && isTablet()) {
waitForSystemLauncherObject(TASKBAR_RES_ID);
@@ -852,6 +852,7 @@
waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
}
waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
+ waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
return waitForSystemLauncherObject(OVERVIEW_RES_ID);
}
@@ -866,6 +867,7 @@
}
waitForSystemLauncherObject(SPLIT_PLACEHOLDER_RES_ID);
+ waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
return waitForSystemLauncherObject(OVERVIEW_RES_ID);
}
case LAUNCHED_APP: {
@@ -874,6 +876,7 @@
waitUntilLauncherObjectGone(WIDGETS_RES_ID);
waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID);
waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
+ waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
if (mIgnoreTaskbarVisibility) {
return null;
@@ -992,6 +995,25 @@
}
/**
+ * Goes to home from immersive fullscreen app by first swiping up to bring navbar, and then
+ * performing {@code goHome()} action.
+ * Currently only supports gesture navigation mode.
+ *
+ * @return the Workspace object.
+ */
+ public Workspace goHomeFromImmersiveFullscreenApp() {
+ assertTrue("expected gesture navigation mode",
+ getNavigationModel() == NavigationModel.ZERO_BUTTON);
+ final Point displaySize = getRealDisplaySize();
+ linearGesture(
+ displaySize.x / 2, displaySize.y - 1,
+ displaySize.x / 2, 0,
+ ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME,
+ false, GestureScope.EXPECT_PILFER);
+ return goHome();
+ }
+
+ /**
* Goes to home by swiping up in zero-button mode or pressing Home button.
* Calling it after another TAPL call is safe because all TAPL methods wait for the animations
* to finish.
@@ -1181,6 +1203,14 @@
}
}
+ LaunchedAppState assertAppLaunched(@NonNull String expectedPackageName) {
+ BySelector packageSelector = By.pkg(expectedPackageName);
+ assertTrue("App didn't start: (" + packageSelector + ")",
+ mDevice.wait(Until.hasObject(packageSelector),
+ LauncherInstrumentation.WAIT_TIME_MS));
+ return new LaunchedAppState(this);
+ }
+
void waitUntilLauncherObjectGone(String resId) {
waitUntilGoneBySelector(getLauncherObjectSelector(resId));
}
@@ -1746,6 +1776,12 @@
InputDevice.SOURCE_TOUCHSCREEN);
}
+ private void injectEvent(InputEvent event) {
+ assertTrue("injectInputEvent failed: event=" + event,
+ mInstrumentation.getUiAutomation().injectInputEvent(event, true, false));
+ event.recycle();
+ }
+
public void sendPointer(long downTime, long currentTime, int action, Point point,
GestureScope gestureScope, int source) {
final boolean hasTIS = hasTIS();
@@ -1783,9 +1819,39 @@
|| action == MotionEvent.ACTION_BUTTON_RELEASE) {
event.setActionButton(MotionEvent.BUTTON_PRIMARY);
}
- assertTrue("injectInputEvent failed",
- mInstrumentation.getUiAutomation().injectInputEvent(event, true, false));
- event.recycle();
+ injectEvent(event);
+ }
+
+ private KeyEvent createKeyEvent(int keyCode, int metaState, boolean actionDown) {
+ long eventTime = SystemClock.uptimeMillis();
+ return KeyEvent.obtain(
+ eventTime,
+ eventTime,
+ actionDown ? ACTION_DOWN : ACTION_UP,
+ keyCode,
+ /* repeat= */ 0,
+ metaState,
+ KeyCharacterMap.VIRTUAL_KEYBOARD,
+ /* scancode= */ 0,
+ /* flags= */ 0,
+ InputDevice.SOURCE_KEYBOARD,
+ /* characters =*/ null);
+ }
+
+ /**
+ * Sends a {@link KeyEvent} with {@link ACTION_DOWN} for the given key codes without sending
+ * a {@link KeyEvent} with {@link ACTION_UP}.
+ */
+ public void pressAndHoldKeyCode(int keyCode, int metaState) {
+ injectEvent(createKeyEvent(keyCode, metaState, true));
+ }
+
+
+ /**
+ * Sends a {@link KeyEvent} with {@link ACTION_UP} for the given key codes.
+ */
+ public void unpressKeyCode(int keyCode, int metaState) {
+ injectEvent(createKeyEvent(keyCode, metaState, false));
}
public long movePointer(long downTime, long startTime, long duration, Point from, Point to,
diff --git a/tests/tapl/com/android/launcher3/tapl/SplitscreenDragSource.java b/tests/tapl/com/android/launcher3/tapl/SplitscreenDragSource.java
index ce1c3c0..2870877 100644
--- a/tests/tapl/com/android/launcher3/tapl/SplitscreenDragSource.java
+++ b/tests/tapl/com/android/launcher3/tapl/SplitscreenDragSource.java
@@ -15,7 +15,7 @@
*/
package com.android.launcher3.tapl;
-/** Launchable that can serve as a source for dragging and dropping to splitscreen. */
+/** {@link Launchable} that can serve as a source for dragging and dropping to splitscreen. */
interface SplitscreenDragSource {
/**
@@ -35,5 +35,6 @@
}
}
+ /** This method requires public access, however should not be called in tests. */
Launchable getLaunchable();
}
diff --git a/tests/tapl/com/android/launcher3/tapl/TaskbarAllApps.java b/tests/tapl/com/android/launcher3/tapl/TaskbarAllApps.java
index c1234fe..3d39041 100644
--- a/tests/tapl/com/android/launcher3/tapl/TaskbarAllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/TaskbarAllApps.java
@@ -73,4 +73,9 @@
protected void verifyVisibleContainerOnDismiss() {
mLauncher.getLaunchedAppState().assertTaskbarVisible();
}
+
+ @Override
+ public boolean isHomeState() {
+ return false;
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 79b54ba..105bc3b 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -34,7 +34,8 @@
/**
* All widgets container.
*/
-public final class Widgets extends LauncherInstrumentation.VisibleContainer {
+public final class Widgets extends LauncherInstrumentation.VisibleContainer
+ implements KeyboardQuickSwitchSource {
private static final int FLING_STEPS = 10;
private static final int SCROLL_ATTEMPTS = 60;
@@ -43,6 +44,21 @@
verifyActiveContainer();
}
+ @Override
+ public LauncherInstrumentation getLauncher() {
+ return mLauncher;
+ }
+
+ @Override
+ public LauncherInstrumentation.ContainerType getStartingContainerType() {
+ return getContainerType();
+ }
+
+ @Override
+ public boolean isHomeState() {
+ return true;
+ }
+
/**
* Flings forward (down) and waits the fling's end.
*/
diff --git a/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java b/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java
index 141476c..5a4d562 100644
--- a/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java
+++ b/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java
@@ -19,7 +19,7 @@
import java.util.function.Supplier;
-/** Launchable that can serve as a source for dragging and dropping to the workspace. */
+/** {@link Launchable} that can serve as a source for dragging and dropping to the workspace. */
interface WorkspaceDragSource {
/**