diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java index 5fc79f078f..35151f1a68 100644 --- a/quickstep/src/com/android/launcher3/LauncherInitListener.java +++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java @@ -17,11 +17,8 @@ package com.android.launcher3; import android.animation.AnimatorSet; import android.annotation.TargetApi; -import android.content.Context; -import android.content.Intent; import android.os.Build; import android.os.CancellationSignal; -import android.os.Handler; import com.android.quickstep.util.ActivityInitListener; import com.android.quickstep.util.RemoteAnimationProvider; @@ -78,11 +75,4 @@ public class LauncherInitListener extends ActivityInitListener { mRemoteAnimationProvider = null; super.unregister(); } - - @Override - public void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider, - Context context, Handler handler, long duration) { - mRemoteAnimationProvider = animProvider; - super.registerAndStartActivity(intent, animProvider, context, handler, duration); - } } diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java index 7e5bda538e..2ccec44fe2 100644 --- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java +++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java @@ -285,7 +285,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener long statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION - STATUS_BAR_TRANSITION_PRE_DELAY; RemoteAnimationAdapterCompat adapterCompat = - new RemoteAnimationAdapterCompat(runner, duration, statusBarTransitionDelay); + new RemoteAnimationAdapterCompat(runner, duration, statusBarTransitionDelay, + mLauncher.getIApplicationThread()); return new ActivityOptionsWrapper( ActivityOptionsCompat.makeRemoteAnimation(adapterCompat), onEndCallback); } @@ -1081,7 +1082,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener new RemoteAnimationAdapterCompat( new LauncherAnimationRunner(mHandler, mWallpaperOpenRunner, false /* startAtFrontOfQueue */), - CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */)); + CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */, + mLauncher.getIApplicationThread())); if (KEYGUARD_ANIMATION.get()) { mKeyguardGoingAwayRunner = createWallpaperOpenRunner(true /* fromUnlock */); @@ -1091,7 +1093,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener new LauncherAnimationRunner( mHandler, mKeyguardGoingAwayRunner, true /* startAtFrontOfQueue */), - CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */)); + CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */, + mLauncher.getIApplicationThread())); } new ActivityCompat(mLauncher).registerRemoteAnimations(definition); @@ -1109,7 +1112,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener mWallpaperOpenTransitionRunner = createWallpaperOpenRunner(false /* fromUnlock */); mLauncherOpenTransition = RemoteAnimationAdapterCompat.buildRemoteTransition( new LauncherAnimationRunner(mHandler, mWallpaperOpenTransitionRunner, - false /* startAtFrontOfQueue */)); + false /* startAtFrontOfQueue */), mLauncher.getIApplicationThread()); mLauncherOpenTransition.addHomeOpenCheck(mLauncher.getComponentName()); SystemUiProxy.INSTANCE.getNoCreate().registerRemoteTransition(mLauncherOpenTransition); } diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java index d35b0f6c1a..4b6dacd95e 100644 --- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java @@ -234,14 +234,6 @@ public class NavbarButtonsViewController { mPropertyHolders.forEach(StatePropertyHolder::endAnimation); } - public void onDestroy() { - mPropertyHolders.clear(); - mControllers.rotationButtonController.unregisterListeners(); - if (mFloatingRotationButton != null) { - mFloatingRotationButton.hide(); - } - } - private void initButtons(ViewGroup navContainer, ViewGroup endContainer, TaskbarNavButtonController navButtonController) { @@ -438,6 +430,14 @@ public class NavbarButtonsViewController { return mFloatingRotationButtonBounds.contains((int) ev.getX(), (int) ev.getY()); } + public void onDestroy() { + mPropertyHolders.clear(); + mControllers.rotationButtonController.unregisterListeners(); + if (mFloatingRotationButton != null) { + mFloatingRotationButton.hide(); + } + } + private class RotationButtonListener implements RotationButton.RotationButtonUpdatesCallback { @Override public void onVisibilityChanged(boolean isVisible) { diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java index ad7e4dfd3d..09a0b7d387 100644 --- a/quickstep/src/com/android/quickstep/RecentsActivity.java +++ b/quickstep/src/com/android/quickstep/RecentsActivity.java @@ -245,7 +245,7 @@ public final class RecentsActivity extends StatefulActivity { RemoteAnimationAdapterCompat adapterCompat = new RemoteAnimationAdapterCompat( wrapper, RECENTS_LAUNCH_DURATION, RECENTS_LAUNCH_DURATION - STATUS_BAR_TRANSITION_DURATION - - STATUS_BAR_TRANSITION_PRE_DELAY); + - STATUS_BAR_TRANSITION_PRE_DELAY, getIApplicationThread()); final ActivityOptionsWrapper activityOptions = new ActivityOptionsWrapper( ActivityOptionsCompat.makeRemoteAnimation(adapterCompat), onEndCallback); @@ -394,7 +394,8 @@ public final class RecentsActivity extends StatefulActivity { LauncherAnimationRunner runner = new LauncherAnimationRunner( getMainThreadHandler(), mAnimationToHomeFactory, true); RemoteAnimationAdapterCompat adapterCompat = - new RemoteAnimationAdapterCompat(runner, HOME_APPEAR_DURATION, 0); + new RemoteAnimationAdapterCompat(runner, HOME_APPEAR_DURATION, 0, + getIApplicationThread()); startActivity(createHomeIntent(), ActivityOptionsCompat.makeRemoteAnimation(adapterCompat).toBundle()); } diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java index d9319a9b00..b6f9d5846d 100644 --- a/quickstep/src/com/android/quickstep/SystemUiProxy.java +++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java @@ -37,6 +37,7 @@ import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; +import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.SplitConfigurationOptions; import com.android.systemui.shared.recents.ISystemUiProxy; @@ -444,6 +445,8 @@ public class SystemUiProxy implements ISystemUiProxy, } catch (RemoteException e) { Log.w(TAG, "Failed call handleImageBundleAsScreenshot"); } + } else if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.NO_SCREENSHOT, "sysuiproxy, no proxy available"); } } diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java index 4b8998115d..258a0c2c03 100644 --- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java +++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java @@ -178,7 +178,8 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn if (ENABLE_SHELL_TRANSITIONS) { RemoteTransitionCompat transition = new RemoteTransitionCompat(mCallbacks, - mController != null ? mController.getController() : null); + mController != null ? mController.getController() : null, + mCtx.getIApplicationThread()); Bundle options = ActivityOptionsCompat.makeRemoteTransition(transition) .setTransientLaunch().toBundle(); mCtx.startActivity(intent, options); diff --git a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java index b9879ab82b..aeec36f63c 100644 --- a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java +++ b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java @@ -15,11 +15,6 @@ */ package com.android.quickstep.util; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; - import com.android.launcher3.BaseActivity; import com.android.launcher3.util.ActivityTracker; import com.android.launcher3.util.ActivityTracker.SchedulerCallback; @@ -75,17 +70,4 @@ public class ActivityInitListener implements mIsRegistered = false; mOnInitListener = null; } - - /** - * Starts the given intent with the provided animation. Unlike {@link #register(Intent)}, this - * method will not call {@link #init} if the activity already exists, it will only call it when - * we get handleIntent() for the provided intent that we're starting. - */ - public void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider, - Context context, Handler handler, long duration) { - register(); - - Bundle options = animProvider.toActivityOptions(handler, duration, context).toBundle(); - context.startActivity(new Intent(intent), options); - } } diff --git a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java index 51a9915523..2858cbdf50 100644 --- a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java +++ b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java @@ -49,6 +49,7 @@ import androidx.core.content.FileProvider; import com.android.internal.app.ChooserActivity; import com.android.launcher3.BuildConfig; +import com.android.launcher3.testing.TestProtocol; import com.android.quickstep.SystemUiProxy; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.utilities.BitmapUtil; @@ -77,6 +78,9 @@ public class ImageActionUtils { public static void saveScreenshot(SystemUiProxy systemUiProxy, Bitmap screenshot, Rect screenshotBounds, Insets visibleInsets, Task.TaskKey task) { + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.NO_SCREENSHOT, "image action utils calling into sysuiproxy"); + } systemUiProxy.handleImageBundleAsScreenshot(BitmapUtil.hardwareBitmapToBundle(screenshot), screenshotBounds, visibleInsets, task); } diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java index 98dbd47ba4..ee82ae67df 100644 --- a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java +++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java @@ -16,32 +16,14 @@ package com.android.quickstep.util; import android.animation.AnimatorSet; -import android.app.ActivityOptions; -import android.content.Context; -import android.os.Handler; -import com.android.launcher3.LauncherAnimationRunner; -import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory; -import com.android.systemui.shared.system.ActivityOptionsCompat; -import com.android.systemui.shared.system.RemoteAnimationAdapterCompat; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; public abstract class RemoteAnimationProvider { - RemoteAnimationFactory mAnimationRunner; - public abstract AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets); - ActivityOptions toActivityOptions(Handler handler, long duration, Context context) { - mAnimationRunner = (transit, appTargets, wallpaperTargets, nonApps, result) -> - result.setAnimation(createWindowAnimation(appTargets, wallpaperTargets), context); - final LauncherAnimationRunner wrapper = new LauncherAnimationRunner( - handler, mAnimationRunner, false /* startAtFrontOfQueue */); - return ActivityOptionsCompat.makeRemoteAnimation( - new RemoteAnimationAdapterCompat(wrapper, duration, 0)); - } - /** * @return the target with the lowest opaque layer for a certain app animation, or null. */ diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java index 1dae2c8712..4c300ec17a 100644 --- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java +++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java @@ -94,7 +94,8 @@ public class SplitSelectStateController { new RemoteSplitLaunchTransitionRunner(task1, task2); mSystemUiProxy.startTasks(taskIds[0], null /* mainOptions */, taskIds[1], null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT, - new RemoteTransitionCompat(animationRunner, MAIN_EXECUTOR)); + new RemoteTransitionCompat(animationRunner, MAIN_EXECUTOR, + ActivityThread.currentActivityThread().getApplicationThread())); } else { RemoteSplitLaunchAnimationRunner animationRunner = new RemoteSplitLaunchAnimationRunner(task1, task2, callback); diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java index 734c844a82..977c696dc5 100644 --- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java +++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java @@ -15,6 +15,7 @@ */ package com.android.quickstep.util; +import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.states.RotationHelper.deltaRotation; import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; @@ -99,6 +100,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { private boolean mLayoutValid = false; private int mOrientationStateId; private StagedSplitBounds mStagedSplitBounds; + private boolean mDrawsBelowRecents; public TaskViewSimulator(Context context, BaseActivityInterface sizeStrategy) { mContext = context; @@ -198,6 +200,10 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { recentsViewScroll.value = scroll; } + public void setDrawsBelowRecents(boolean drawsBelowRecents) { + mDrawsBelowRecents = drawsBelowRecents; + } + /** * Adds animation for all the components corresponding to transition from an app to overview. */ @@ -351,6 +357,12 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { builder.withMatrix(mMatrix) .withWindowCrop(mTmpCropRect) .withCornerRadius(getCurrentCornerRadius()); + + if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.getRecentsSurface() != null) { + // When relativeLayer = 0, it reverts the surfaces back to the original order. + builder.withRelativeLayerTo(params.getRecentsSurface(), + mDrawsBelowRecents ? Integer.MIN_VALUE : 0); + } } /** diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java index 16c3c7f9e9..76d3591325 100644 --- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java +++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java @@ -16,8 +16,6 @@ package com.android.quickstep.views; -import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS; - import android.content.Context; import android.content.res.Configuration; import android.graphics.Rect; @@ -32,7 +30,6 @@ import androidx.annotation.Nullable; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Insettable; import com.android.launcher3.R; -import com.android.launcher3.uioverrides.ApiWrapper; import com.android.launcher3.util.MultiValueAlpha; import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; import com.android.quickstep.SysUINavigationMode; @@ -149,7 +146,7 @@ public class OverviewActionsView extends FrameLayo public void setInsets(Rect insets) { mInsets.set(insets); updateVerticalMargin(SysUINavigationMode.getMode(getContext())); - updatePaddingAndTranslations(); + updateHorizontalPadding(); } public void updateHiddenFlags(@ActionsHiddenFlags int visibilityFlags, boolean enable) { @@ -192,37 +189,8 @@ public class OverviewActionsView extends FrameLayo return mMultiValueAlpha.getProperty(INDEX_FULLSCREEN_ALPHA); } - /** - * Aligns OverviewActionsView vertically with and offsets horizontal position based on - * 3 button nav container in taskbar. - */ - private void updatePaddingAndTranslations() { - boolean alignFor3ButtonTaskbar = mDp.isTaskbarPresent && - SysUINavigationMode.getMode(getContext()) == THREE_BUTTONS; - if (alignFor3ButtonTaskbar) { - // Add extra horizontal spacing - int additionalPadding = ApiWrapper.getHotseatEndOffset(getContext()); - if (isLayoutRtl()) { - setPadding(mInsets.left + additionalPadding, 0, mInsets.right, 0); - } else { - setPadding(mInsets.left, 0, mInsets.right + additionalPadding, 0); - } - - // Align vertically, using taskbar height + mDp.taskbarOffsetY() to guestimate - // where the button nav top is - View startActionView = findViewById(R.id.action_buttons); - int marginBottom = getOverviewActionsBottomMarginPx( - SysUINavigationMode.getMode(getContext()), mDp); - int actionsTop = (mDp.heightPx - marginBottom - mInsets.bottom); - int navTop = mDp.heightPx - (mDp.taskbarSize + mDp.getTaskbarOffsetY()); - int transY = navTop - actionsTop - + ((mDp.taskbarSize - startActionView.getHeight()) / 2); - setTranslationY(transY); - } else { - setPadding(mInsets.left, 0, mInsets.right, 0); - setTranslationX(0); - setTranslationY(0); - } + private void updateHorizontalPadding() { + setPadding(mInsets.left, 0, mInsets.right, 0); } /** Updates vertical margins for different navigation mode or configuration changes. */ diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index d825fada57..96c5561306 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -1094,7 +1094,7 @@ public abstract class RecentsView remoteTargetHandle.getTransformParams() - .setTargetSet(null)); + runActionOnRemoteHandles(remoteTargetHandle -> { + remoteTargetHandle.getTransformParams().setTargetSet(null); + remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true); + }); // These are relatively expensive and don't need to be done this frame (RecentsView isn't // visible anyway), so defer by a frame to get off the critical path, e.g. app to home. @@ -4368,12 +4370,13 @@ public abstract class RecentsView consumer) { + public void runActionOnRemoteHandles(Consumer consumer) { if (mRemoteTargetHandles == null) { return; } diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java index 5c73fbbd22..77ac373827 100644 --- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java +++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java @@ -16,6 +16,7 @@ package com.android.quickstep.views; +import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED; import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA; @@ -242,7 +243,17 @@ public class TaskMenuView extends AbstractFloatingView implements OnScrollChange LayoutParams lp = (LayoutParams) menuOptionView.getLayoutParams(); mTaskView.getPagedOrientationHandler().setLayoutParamsForTaskMenuOptionItem(lp, menuOptionView, mActivity.getDeviceProfile()); - menuOptionView.setOnClickListener(menuOption::onClick); + menuOptionView.setOnClickListener(view -> { + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + RecentsView recentsView = mTaskView.getRecentsView(); + recentsView.switchToScreenshot(null, + () -> recentsView.finishRecentsAnimation(true /* toRecents */, + false /* shouldPip */, + () -> menuOption.onClick(view))); + } else { + menuOption.onClick(view); + } + }); mOptionLayout.addView(menuOptionView); } diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java index a9db400df2..2d58ba5ecf 100644 --- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java +++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java @@ -31,6 +31,8 @@ import android.graphics.ColorFilter; import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; @@ -78,6 +80,7 @@ public class TaskThumbnailView extends View { private TaskOverlay mOverlay; private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint mClearPaint = new Paint(); private final Paint mDimmingPaintAfterClearing = new Paint(); private final int mDimColor; @@ -107,6 +110,7 @@ public class TaskThumbnailView extends View { super(context, attrs, defStyleAttr); mPaint.setFilterBitmap(true); mBackgroundPaint.setColor(Color.WHITE); + mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); mActivity = BaseActivity.fromContext(context); // Initialize with placeholder value. It is overridden later by TaskView mFullscreenParams = TEMP_PARAMS.get(context); @@ -271,6 +275,7 @@ public class TaskThumbnailView extends View { float cornerRadius) { if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { if (mTask != null && getTaskView().isRunningTask() && !getTaskView().showScreenshot()) { + canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mClearPaint); canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mDimmingPaintAfterClearing); return; diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index eef5fb3433..f88b243cab 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -94,6 +94,7 @@ import com.android.quickstep.util.CancellableTask; import com.android.quickstep.util.LauncherSplitScreenListener; import com.android.quickstep.util.RecentsOrientedState; import com.android.quickstep.util.TaskCornerRadius; +import com.android.quickstep.util.TaskViewSimulator; import com.android.quickstep.util.TransformParams; import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper; import com.android.systemui.shared.recents.model.Task; @@ -624,8 +625,22 @@ public class TaskView extends FrameLayout implements Reusable { mActivity.getStateManager(), recentsView, recentsView.getDepthController()); anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animator) { + recentsView.runActionOnRemoteHandles( + (Consumer) remoteTargetHandle -> + remoteTargetHandle + .getTaskViewSimulator() + .setDrawsBelowRecents(false)); + } + @Override public void onAnimationEnd(Animator animator) { + recentsView.runActionOnRemoteHandles( + (Consumer) remoteTargetHandle -> + remoteTargetHandle + .getTaskViewSimulator() + .setDrawsBelowRecents(true)); mIsClickableAsLiveTile = true; } }); @@ -821,15 +836,7 @@ public class TaskView extends FrameLayout implements Reusable { if (confirmSecondSplitSelectApp()) { return; } - if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask()) { - RecentsView recentsView = getRecentsView(); - recentsView.switchToScreenshot( - () -> recentsView.finishRecentsAnimation(true /* toRecents */, - false /* shouldPip */, - () -> showTaskMenu(iconView))); - } else { - showTaskMenu(iconView); - } + showTaskMenu(iconView); }); iconView.setOnLongClickListener(v -> { requestDisallowInterceptTouchEvent(true); diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java index 4895b107e1..437a19b1f1 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java @@ -31,6 +31,7 @@ import androidx.test.uiautomator.Until; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; +import com.android.launcher3.tapl.AllApps; import com.android.launcher3.tapl.Background; import com.android.launcher3.tapl.LauncherInstrumentation.NavigationModel; import com.android.launcher3.tapl.Overview; @@ -49,6 +50,9 @@ import org.junit.runner.RunWith; @LargeTest @RunWith(AndroidJUnit4.class) public class TaplTestsQuickstep extends AbstractQuickStepTest { + + private static final String APP_NAME = "LauncherTestApp"; + @Before public void setUp() throws Exception { super.setUp(); @@ -286,6 +290,30 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { getAndAssertBackground(); } + // TODO(b/204830798): test with all navigation modes(add @NavigationModeSwitch annotation) + // after the bug resolved. + @Test + @PortraitLandscape + @ScreenRecord + public void testPressBack() throws Exception { + mLauncher.getWorkspace().switchToAllApps(); + mLauncher.pressBack(); + mLauncher.getWorkspace(); + waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL); + + AllApps allApps = mLauncher.getWorkspace().switchToAllApps(); + allApps.freeze(); + try { + allApps.getAppIcon(APP_NAME).dragToWorkspace(false, false); + } finally { + allApps.unfreeze(); + } + mLauncher.getWorkspace().getWorkspaceAppIcon(APP_NAME).launch(getAppPackageName()); + mLauncher.pressBack(); + mLauncher.getWorkspace(); + waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL); + } + @Test @PortraitLandscape public void testOverviewForTablet() throws Exception { diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java index 97052b29c5..dd5812302d 100644 --- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java +++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java @@ -41,11 +41,10 @@ public class AccessibilityManagerCompat { } /** - * * @param target The view the accessibility event is initialized on. * If null, this method has no effect. - * @param type See TYPE_ constants defined in {@link AccessibilityEvent}. - * @param text Optional text to add to the event, which will be announced to the user. + * @param type See TYPE_ constants defined in {@link AccessibilityEvent}. + * @param text Optional text to add to the event, which will be announced to the user. */ public static void sendCustomAccessibilityEvent(@Nullable View target, int type, @Nullable String text) { @@ -97,6 +96,16 @@ public class AccessibilityManagerCompat { null); } + /** + * Notify running tests of a folder opened. + */ + public static void sendFolderOpenedEventToTest(Context context) { + final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context); + if (accessibilityManager == null) return; + + sendEventToTest(accessibilityManager, context, TestProtocol.FOLDER_OPENED_MESSAGE, null); + } + private static void sendEventToTest( AccessibilityManager accessibilityManager, Context context, String eventTag, Bundle data) { diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 879739f171..daef68226d 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -78,6 +78,7 @@ import com.android.launcher3.Utilities; import com.android.launcher3.accessibility.AccessibleDragListenerAdapter; import com.android.launcher3.accessibility.FolderAccessibilityHelper; import com.android.launcher3.anim.KeyboardInsetAnimationCallback; +import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragController.DragListener; @@ -687,6 +688,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo public void onAnimationEnd(Animator animation) { mState = STATE_OPEN; announceAccessibilityChanges(); + AccessibilityManagerCompat.sendFolderOpenedEventToTest(getContext()); mContent.setFocusOnFirstChild(); } @@ -1265,7 +1267,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo PendingAddShortcutInfo pasi = d.dragInfo instanceof PendingAddShortcutInfo ? (PendingAddShortcutInfo) d.dragInfo : null; - WorkspaceItemInfo pasiSi = pasi != null ? pasi.activityInfo.createWorkspaceItemInfo() : null; + WorkspaceItemInfo pasiSi = + pasi != null ? pasi.activityInfo.createWorkspaceItemInfo() : null; if (pasi != null && pasiSi == null) { // There is no WorkspaceItemInfo, so we have to go through a configuration activity. pasi.container = mInfo.id; diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java index 5a9c074f96..17d925c1f9 100644 --- a/src/com/android/launcher3/testing/TestInformationHandler.java +++ b/src/com/android/launcher3/testing/TestInformationHandler.java @@ -21,6 +21,7 @@ import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; +import android.content.res.Resources; import android.graphics.Insets; import android.os.Build; import android.os.Bundle; @@ -148,6 +149,14 @@ public class TestInformationHandler implements ResourceBasedOverride { TestProtocol.TEST_INFO_RESPONSE_FIELD, TestLogging.sHadEventsNotFromTest); return response; + case TestProtocol.REQUEST_START_DRAG_THRESHOLD: { + final Resources resources = mContext.getResources(); + response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, + resources.getDimensionPixelSize(R.dimen.deep_shortcuts_start_drag_threshold) + + resources.getDimensionPixelSize(R.dimen.pre_drag_view_scale)); + return response; + } + default: return null; } @@ -193,6 +202,7 @@ public class TestInformationHandler implements ResourceBasedOverride { /** * Generic interface for setting a fiend in bundle + * * @param the type of value being set */ public interface BundleSetter { diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java index 5bf0342fcc..db289021a5 100644 --- a/src/com/android/launcher3/testing/TestProtocol.java +++ b/src/com/android/launcher3/testing/TestProtocol.java @@ -25,6 +25,7 @@ public final class TestProtocol { public static final String SCROLL_FINISHED_MESSAGE = "TAPL_SCROLL_FINISHED"; public static final String PAUSE_DETECTED_MESSAGE = "TAPL_PAUSE_DETECTED"; public static final String DISMISS_ANIMATION_ENDS_MESSAGE = "TAPL_DISMISS_ANIMATION_ENDS"; + public static final String FOLDER_OPENED_MESSAGE = "TAPL_FOLDER_OPENED"; public static final int NORMAL_STATE_ORDINAL = 0; public static final int SPRING_LOADED_STATE_ORDINAL = 1; public static final int OVERVIEW_STATE_ORDINAL = 2; @@ -99,6 +100,7 @@ public final class TestProtocol { public static final String REQUEST_CLEAR_DATA = "clear-data"; public static final String REQUEST_IS_TABLET = "is-tablet"; public static final String REQUEST_IS_TWO_PANELS = "is-two-panel"; + public static final String REQUEST_START_DRAG_THRESHOLD = "start-drag-threshold"; public static final String REQUEST_GET_ACTIVITIES_CREATED_COUNT = "get-activities-created-count"; public static final String REQUEST_GET_ACTIVITIES = "get-activities"; @@ -122,4 +124,5 @@ public final class TestProtocol { public static final String TASK_VIEW_ID_CRASH = "b/195430732"; public static final String NO_DROP_TARGET = "b/195031154"; public static final String NULL_INT_SET = "b/200572078"; + public static final String NO_SCREENSHOT = "b/202414125"; } diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index 0ffbeeb95e..82163cbfdb 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -211,7 +211,8 @@ public abstract class AbstractLauncherUiTest { } protected TestRule getRulesInsideActivityMonitor() { - final RuleChain inner = RuleChain.outerRule(new PortraitLandscapeRunner(this)) + final RuleChain inner = RuleChain + .outerRule(new PortraitLandscapeRunner(this)) .around(new FailureWatcher(mDevice, mLauncher)); return TestHelpers.isInLauncherProcess() diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java index 881f50cafb..4007c26f54 100644 --- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java +++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java @@ -34,6 +34,8 @@ 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.Folder; +import com.android.launcher3.tapl.FolderIcon; import com.android.launcher3.tapl.Widgets; import com.android.launcher3.tapl.Workspace; import com.android.launcher3.views.OptionsPopupView; @@ -369,6 +371,69 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { } } + private AppIcon createShortcutIfNotExist(String name) { + AppIcon appIcon = mLauncher.getWorkspace().tryGetWorkspaceAppIcon(name); + if (appIcon == null) { + AllApps allApps = mLauncher.getWorkspace().switchToAllApps(); + allApps.freeze(); + try { + appIcon = allApps.getAppIcon(name); + appIcon.dragToWorkspace(false, false); + } finally { + allApps.unfreeze(); + } + appIcon = mLauncher.getWorkspace().getWorkspaceAppIcon(name); + } + return appIcon; + } + + @Test + @PortraitLandscape + public void testDragToFolder() throws Exception { + final AppIcon playStoreIcon = createShortcutIfNotExist("Play Store"); + final AppIcon gmailIcon = createShortcutIfNotExist("Gmail"); + + FolderIcon folderIcon = gmailIcon.dragToIcon(playStoreIcon); + + Folder folder = folderIcon.open(); + folder.getAppIcon("Play Store"); + folder.getAppIcon("Gmail"); + Workspace workspace = folder.close(); + + assertNull("Gmail should be moved to a folder.", + workspace.tryGetWorkspaceAppIcon("Gmail")); + assertNull("Play Store should be moved to a folder.", + workspace.tryGetWorkspaceAppIcon("Play Store")); + + final AppIcon youTubeIcon = createShortcutIfNotExist("YouTube"); + + folderIcon = youTubeIcon.dragToIcon(folderIcon); + folder = folderIcon.open(); + folder.getAppIcon("YouTube"); + folder.close(); + } + + @Test + @PortraitLandscape + public void testPressBack() throws Exception { + mLauncher.getWorkspace().switchToAllApps(); + mLauncher.pressBack(); + mLauncher.getWorkspace(); + waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL); + + AllApps allApps = mLauncher.getWorkspace().switchToAllApps(); + allApps.freeze(); + try { + allApps.getAppIcon(APP_NAME).dragToWorkspace(false, false); + } finally { + allApps.unfreeze(); + } + mLauncher.getWorkspace().getWorkspaceAppIcon(APP_NAME).launch(getAppPackageName()); + mLauncher.pressBack(); + mLauncher.getWorkspace(); + waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL); + } + public static String getAppPackageName() { return getInstrumentation().getContext().getPackageName(); } diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java index 21099b4934..6da59da66c 100644 --- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java +++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java @@ -16,8 +16,11 @@ package com.android.launcher3.tapl; +import android.graphics.Point; +import android.graphics.Rect; import android.widget.TextView; +import androidx.annotation.NonNull; import androidx.test.uiautomator.By; import androidx.test.uiautomator.BySelector; import androidx.test.uiautomator.UiObject2; @@ -29,7 +32,7 @@ import java.util.regex.Pattern; /** * App icon, whether in all apps or in workspace/ */ -public final class AppIcon extends Launchable { +public final class AppIcon extends Launchable implements FolderDragTarget { private static final Pattern LONG_CLICK_EVENT = Pattern.compile("onAllAppsItemLongClick"); @@ -61,6 +64,29 @@ public final class AppIcon extends Launchable { } } + /** + * Drag the AppIcon to the given position of other icon. The drag must result in a folder. + * + * @param target the destination icon. + */ + @NonNull + public FolderIcon dragToIcon(FolderDragTarget target) { + try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); + LauncherInstrumentation.Closable c = mLauncher.addContextLayer("want to drag icon")) { + final Rect dropBounds = target.getDropLocationBounds(); + Workspace.dragIconToWorkspace( + mLauncher, this, + () -> { + final Rect bounds = target.getDropLocationBounds(); + return new Point(bounds.centerX(), bounds.centerY()); + }, + getLongPressIndicator()); + FolderIcon result = target.getTargetFolder(dropBounds); + mLauncher.assertTrue("Can't find the target folder.", result != null); + return result; + } + } + @Override protected void addExpectedEventsForLongClick() { mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT); @@ -80,4 +106,20 @@ public final class AppIcon extends Launchable { protected String launchableType() { return "app icon"; } + + @Override + public Rect getDropLocationBounds() { + return mLauncher.getVisibleBounds(mObject); + } + + @Override + public FolderIcon getTargetFolder(Rect bounds) { + for (FolderIcon folderIcon : mLauncher.getWorkspace().getFolderIcons()) { + final Rect folderIconBounds = folderIcon.getDropLocationBounds(); + if (bounds.contains(folderIconBounds.centerX(), folderIconBounds.centerY())) { + return folderIcon; + } + } + return null; + } } diff --git a/tests/tapl/com/android/launcher3/tapl/Folder.java b/tests/tapl/com/android/launcher3/tapl/Folder.java new file mode 100644 index 0000000000..dba308dbf7 --- /dev/null +++ b/tests/tapl/com/android/launcher3/tapl/Folder.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2021 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 android.graphics.Point; +import android.graphics.Rect; +import android.os.SystemClock; +import android.view.MotionEvent; + +import androidx.annotation.NonNull; +import androidx.test.uiautomator.UiObject2; + +public class Folder { + + protected static final String FOLDER_CONTENT_RES_ID = "folder_content"; + + private final UiObject2 mContainer; + private final LauncherInstrumentation mLauncher; + + Folder(LauncherInstrumentation launcher) { + this.mLauncher = launcher; + this.mContainer = launcher.waitForLauncherObject(FOLDER_CONTENT_RES_ID); + } + + /** + * Find an app icon with given name or raise assertion error. + */ + @NonNull + public AppIcon getAppIcon(String appName) { + try (LauncherInstrumentation.Closable ignored = mLauncher.addContextLayer( + "Want to get app icon in folder")) { + return new AppIcon(mLauncher, + mLauncher.waitForObjectInContainer( + mContainer, + AppIcon.getAppIconSelector(appName, mLauncher))); + } + } + + private void touchOutsideFolder() { + Rect containerBounds = mLauncher.getVisibleBounds(this.mContainer); + final long downTime = SystemClock.uptimeMillis(); + Point containerLeftTopCorner = new Point(containerBounds.left - 1, containerBounds.top - 1); + mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, + containerLeftTopCorner, LauncherInstrumentation.GestureScope.INSIDE); + mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_UP, + containerLeftTopCorner, LauncherInstrumentation.GestureScope.INSIDE); + } + + /** + * CLose opened folder if possible. It throws assertion error if the folder is already closed. + */ + public Workspace close() { + try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); + LauncherInstrumentation.Closable c = mLauncher.addContextLayer( + "Want to close opened folder")) { + mLauncher.waitForLauncherObject(FOLDER_CONTENT_RES_ID); + touchOutsideFolder(); + mLauncher.waitUntilLauncherObjectGone(FOLDER_CONTENT_RES_ID); + return mLauncher.getWorkspace(); + } + } +} diff --git a/tests/tapl/com/android/launcher3/tapl/FolderDragTarget.java b/tests/tapl/com/android/launcher3/tapl/FolderDragTarget.java new file mode 100644 index 0000000000..d797418ab4 --- /dev/null +++ b/tests/tapl/com/android/launcher3/tapl/FolderDragTarget.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2021 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 android.graphics.Rect; + +public interface FolderDragTarget { + Rect getDropLocationBounds(); + + FolderIcon getTargetFolder(Rect bounds); +} diff --git a/tests/tapl/com/android/launcher3/tapl/FolderIcon.java b/tests/tapl/com/android/launcher3/tapl/FolderIcon.java new file mode 100644 index 0000000000..2e79d70ee6 --- /dev/null +++ b/tests/tapl/com/android/launcher3/tapl/FolderIcon.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2021 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 android.graphics.Rect; + +import androidx.annotation.NonNull; +import androidx.test.uiautomator.UiObject2; + +import com.android.launcher3.testing.TestProtocol; + +/** + * Folder Icon, an app folder in workspace. + */ +public class FolderIcon implements FolderDragTarget { + + protected final UiObject2 mObject; + protected final LauncherInstrumentation mLauncher; + + FolderIcon(LauncherInstrumentation launcher, UiObject2 icon) { + mObject = icon; + mLauncher = launcher; + } + + /** + * Open and return a folder or raise assertion error. + */ + @NonNull + public Folder open() { + try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); + LauncherInstrumentation.Closable c = mLauncher.addContextLayer("open folder")) { + mLauncher.executeAndWaitForLauncherEvent(() -> mLauncher.clickLauncherObject(mObject), + event -> TestProtocol.FOLDER_OPENED_MESSAGE.equals( + event.getClassName().toString()), + () -> "Fail to open folder.", + "open folder"); + } + return new Folder(mLauncher); + } + + @Override + public Rect getDropLocationBounds() { + return mLauncher.getVisibleBounds(mObject.getParent()); + } + + @Override + public FolderIcon getTargetFolder(Rect bounds) { + return this; + } +} diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index 7ffdf4cef8..e2d023825c 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -21,6 +21,7 @@ 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 com.android.launcher3.tapl.Folder.FOLDER_CONTENT_RES_ID; import static com.android.launcher3.tapl.TestHelpers.getOverviewPackageName; import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL; @@ -75,11 +76,13 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Deque; import java.util.LinkedList; import java.util.List; +import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Consumer; @@ -108,6 +111,9 @@ public final class LauncherInstrumentation { static final Pattern EVENT_TOUCH_DOWN_TIS = getTouchEventPatternTIS("ACTION_DOWN"); static final Pattern EVENT_TOUCH_UP_TIS = getTouchEventPatternTIS("ACTION_UP"); + static final Pattern EVENT_KEY_BACK_DOWN = getKeyEventPattern("ACTION_DOWN", "KEYCODE_BACK"); + static final Pattern EVENT_KEY_BACK_UP = getKeyEventPattern("ACTION_UP", "KEYCODE_BACK"); + private final String mLauncherPackage; private Boolean mIsLauncher3; private long mTestStartTime = -1; @@ -123,7 +129,8 @@ public final class LauncherInstrumentation { // Where the gesture happens: outside of Launcher, inside or from inside to outside and // whether the gesture recognition triggers pilfer. public enum GestureScope { - OUTSIDE_WITHOUT_PILFER, OUTSIDE_WITH_PILFER, INSIDE, INSIDE_TO_OUTSIDE + OUTSIDE_WITHOUT_PILFER, OUTSIDE_WITH_PILFER, INSIDE, INSIDE_TO_OUTSIDE, + INSIDE_TO_OUTSIDE_WITHOUT_PILFER, } // Base class for launcher containers. @@ -193,6 +200,10 @@ public final class LauncherInstrumentation { return getTouchEventPattern("TouchInteractionService.onInputEvent", action); } + private static Pattern getKeyEventPattern(String action, String keyCode) { + return Pattern.compile("Key event: KeyEvent.*action=" + action + ".*keyCode=" + keyCode); + } + /** * Constructs the root of TAPL hierarchy. You get all other objects from it. */ @@ -767,6 +778,47 @@ public final class LauncherInstrumentation { } } + /** + * Get the resource ID of visible floating view. + */ + private Optional getFloatingResId() { + if (hasLauncherObject(CONTEXT_MENU_RES_ID)) { + return Optional.of(CONTEXT_MENU_RES_ID); + } + if (hasLauncherObject(FOLDER_CONTENT_RES_ID)) { + return Optional.of(FOLDER_CONTENT_RES_ID); + } + return Optional.empty(); + } + + /** + * Using swiping up gesture to dismiss closable floating views, such as Menu or Folder Content. + */ + private void swipeUpToCloseFloatingView(boolean gestureStartFromLauncher) { + final Point displaySize = getRealDisplaySize(); + + final Optional floatingRes = getFloatingResId(); + + if (!floatingRes.isPresent()) { + return; + } + + GestureScope gestureScope = gestureStartFromLauncher + ? (isTablet() ? GestureScope.INSIDE : GestureScope.INSIDE_TO_OUTSIDE) + : GestureScope.OUTSIDE_WITH_PILFER; + linearGesture( + displaySize.x / 2, displaySize.y - 1, + displaySize.x / 2, 0, + ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, + false, gestureScope); + + try (LauncherInstrumentation.Closable c1 = addContextLayer( + String.format("Swiped up from floating view %s to home", floatingRes.get()))) { + waitUntilLauncherObjectGone(floatingRes.get()); + waitForLauncherObject(getAnyObjectSelector()); + } + } + /** * Presses nav bar home button. * @@ -791,21 +843,9 @@ public final class LauncherInstrumentation { ? !isLauncher3() || hasLauncherObject(WORKSPACE_RES_ID) : isLauncherVisible(); - if (hasLauncherObject(CONTEXT_MENU_RES_ID)) { - GestureScope gestureScope = gestureStartFromLauncher - ? (isTablet() ? GestureScope.INSIDE : GestureScope.INSIDE_TO_OUTSIDE) - : GestureScope.OUTSIDE_WITH_PILFER; - linearGesture( - displaySize.x / 2, displaySize.y - 1, - displaySize.x / 2, 0, - ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, - false, gestureScope); - try (LauncherInstrumentation.Closable c1 = addContextLayer( - "Swiped up from context menu to home")) { - waitUntilLauncherObjectGone(CONTEXT_MENU_RES_ID); - waitForLauncherObject(getAnyObjectSelector()); - } - } + // CLose floating views before going back to home. + swipeUpToCloseFloatingView(gestureStartFromLauncher); + if (hasLauncherObject(WORKSPACE_RES_ID)) { log(action = "already at home"); } else { @@ -848,6 +888,38 @@ public final class LauncherInstrumentation { } } + /** + * Press navbar back button or swipe back if in gesture navigation mode. + */ + public void pressBack() { + try (Closable e = eventsCheck(); Closable c = addContextLayer("want to press back")) { + waitForLauncherInitialized(); + final boolean launcherVisible = + isTablet() ? isLauncherContainerVisible() : isLauncherVisible(); + if (getNavigationModel() == NavigationModel.ZERO_BUTTON) { + final Point displaySize = getRealDisplaySize(); + final GestureScope gestureScope = + launcherVisible ? GestureScope.INSIDE_TO_OUTSIDE_WITHOUT_PILFER + : GestureScope.OUTSIDE_WITHOUT_PILFER; + linearGesture(0, displaySize.y / 2, displaySize.x / 2, displaySize.y / 2, + 10, false, gestureScope); + } else { + waitForNavigationUiObject("back").click(); + if (isTablet()) { + expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_DOWN); + expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_UP); + } else if (!isLauncher3() && getNavigationModel() == NavigationModel.TWO_BUTTON) { + expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS); + expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_UP_TIS); + } + } + if (launcherVisible) { + expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KEY_BACK_DOWN); + expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KEY_BACK_UP); + } + } + } + private static BySelector getAnyObjectSelector() { return By.textStartsWith(""); } @@ -857,6 +929,11 @@ public final class LauncherInstrumentation { return hasLauncherObject(getAnyObjectSelector()); } + boolean isLauncherContainerVisible() { + final String[] containerResources = {WORKSPACE_RES_ID, OVERVIEW_RES_ID, APPS_RES_ID}; + return Arrays.stream(containerResources).anyMatch(r -> hasLauncherObject(r)); + } + /** * Gets the Workspace object if the current state is "active home", i.e. workspace. Fails if the * launcher is not in that state. @@ -1383,6 +1460,7 @@ public final class LauncherInstrumentation { break; case MotionEvent.ACTION_UP: if (notLauncher3 && gestureScope != GestureScope.INSIDE + && gestureScope != GestureScope.INSIDE_TO_OUTSIDE_WITHOUT_PILFER && (gestureScope == GestureScope.OUTSIDE_WITH_PILFER || gestureScope == GestureScope.INSIDE_TO_OUTSIDE)) { expectEvent(TestProtocol.SEQUENCE_PILFER, EVENT_PILFER_POINTERS); diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java index 288c85319b..0145690909 100644 --- a/tests/tapl/com/android/launcher3/tapl/Workspace.java +++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java @@ -36,7 +36,10 @@ import androidx.test.uiautomator.UiObject2; import com.android.launcher3.testing.TestProtocol; +import java.util.List; +import java.util.function.Supplier; import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * Operations on the workspace screen. @@ -170,40 +173,100 @@ public final class Workspace extends Home { mHotseat, AppIcon.getAppIconSelector(appName, mLauncher))); } - static void dragIconToWorkspace( - LauncherInstrumentation launcher, Launchable launchable, Point dest, - String longPressIndicator, boolean startsActivity, boolean isWidgetShortcut, - Runnable expectLongClickEvents) { - LauncherInstrumentation.log("dragIconToWorkspace: begin"); - final Point launchableCenter = launchable.getObject().getVisibleCenter(); - final long downTime = SystemClock.uptimeMillis(); - launcher.runToState( - () -> { - launcher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, - launchableCenter, LauncherInstrumentation.GestureScope.INSIDE); - LauncherInstrumentation.log("dragIconToWorkspace: sent down"); - expectLongClickEvents.run(); - launcher.waitForLauncherObject(longPressIndicator); - LauncherInstrumentation.log("dragIconToWorkspace: indicator"); - launcher.movePointer(launchableCenter, dest, 10, downTime, true, - LauncherInstrumentation.GestureScope.INSIDE); - }, - SPRING_LOADED_STATE_ORDINAL, - "long-pressing and moving"); - LauncherInstrumentation.log("dragIconToWorkspace: moved pointer"); + private static int getStartDragThreshold(LauncherInstrumentation launcher) { + return launcher.getTestInfo(TestProtocol.REQUEST_START_DRAG_THRESHOLD).getInt( + TestProtocol.TEST_INFO_RESPONSE_FIELD); + } + + /** + * Finds folder icons in the current workspace. + * + * @return a list of folder icons. + */ + List getFolderIcons() { + final UiObject2 workspace = verifyActiveContainer(); + return mLauncher.getObjectsInContainer(workspace, "folder_icon_name").stream().map( + o -> new FolderIcon(mLauncher, o)).collect(Collectors.toList()); + } + + /** + * Drag an icon up with a short distance that makes workspace go to spring loaded state. + * + * @return the position after dragging. + */ + private static Point dragIconToSpringLoaded(LauncherInstrumentation launcher, long downTime, + UiObject2 icon, + String longPressIndicator, Runnable expectLongClickEvents) { + final Point iconCenter = icon.getVisibleCenter(); + final Point dragStartCenter = new Point(iconCenter.x, + iconCenter.y - getStartDragThreshold(launcher)); + + launcher.runToState(() -> { + launcher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, + iconCenter, LauncherInstrumentation.GestureScope.INSIDE); + LauncherInstrumentation.log("dragIconToSpringLoaded: sent down"); + expectLongClickEvents.run(); + launcher.waitForLauncherObject(longPressIndicator); + LauncherInstrumentation.log("dragIconToSpringLoaded: indicator"); + launcher.movePointer(iconCenter, dragStartCenter, 10, downTime, true, + LauncherInstrumentation.GestureScope.INSIDE); + }, SPRING_LOADED_STATE_ORDINAL, "long-pressing and triggering drag start"); + return dragStartCenter; + } + + private static void dropDraggedIcon(LauncherInstrumentation launcher, Point dest, long downTime, + @Nullable Runnable expectedEvents) { launcher.runToState( () -> launcher.sendPointer( downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, dest, LauncherInstrumentation.GestureScope.INSIDE), NORMAL_STATE_ORDINAL, "sending UP event"); - if (startsActivity || isWidgetShortcut) { - launcher.expectEvent(TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_START); + if (expectedEvents != null) { + expectedEvents.run(); } - LauncherInstrumentation.log("dragIconToWorkspace: end"); + LauncherInstrumentation.log("dropIcon: end"); launcher.waitUntilLauncherObjectGone("drop_target_bar"); } + static void dragIconToWorkspace(LauncherInstrumentation launcher, Launchable launchable, + Point dest, String longPressIndicator, boolean startsActivity, boolean isWidgetShortcut, + Runnable expectLongClickEvents) { + Runnable expectDropEvents = null; + if (startsActivity || isWidgetShortcut) { + expectDropEvents = () -> launcher.expectEvent(TestProtocol.SEQUENCE_MAIN, + LauncherInstrumentation.EVENT_START); + } + dragIconToWorkspace(launcher, launchable, () -> dest, longPressIndicator, + expectLongClickEvents, expectDropEvents); + } + + /** + * Drag icon in workspace to else where. + * This function expects the launchable is inside the workspace and there is no drop event. + */ + static void dragIconToWorkspace(LauncherInstrumentation launcher, Launchable launchable, + Supplier destSupplier, String longPressIndicator) { + dragIconToWorkspace(launcher, launchable, destSupplier, longPressIndicator, + () -> launcher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT), null); + } + + static void dragIconToWorkspace( + LauncherInstrumentation launcher, Launchable launchable, Supplier dest, + String longPressIndicator, Runnable expectLongClickEvents, + @Nullable Runnable expectDropEvents) { + try (LauncherInstrumentation.Closable ignored = launcher.addContextLayer( + "want to drag icon to workspace")) { + final long downTime = SystemClock.uptimeMillis(); + final Point dragStartCenter = dragIconToSpringLoaded(launcher, downTime, + launchable.getObject(), longPressIndicator, expectLongClickEvents); + final Point targetDest = dest.get(); + launcher.movePointer(dragStartCenter, targetDest, 10, downTime, true, + LauncherInstrumentation.GestureScope.INSIDE); + dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents); + } + } + /** * Flings to get to screens on the right. Waits for scrolling and a possible overscroll * recoil to complete.