diff options
74 files changed, 1535 insertions, 917 deletions
diff --git a/Android.mk b/Android.mk index 7956d2810..9d113d954 100644 --- a/Android.mk +++ b/Android.mk @@ -298,7 +298,7 @@ LOCAL_PROGUARD_ENABLED := full LOCAL_PACKAGE_NAME := Launcher3GoIconRecents LOCAL_PRIVILEGED_MODULE := true LOCAL_PRODUCT_MODULE := true -LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3Go Launcher3QuickStep Launcher3QuickStepGo +LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3Go Launcher3QuickStep LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3 LOCAL_FULL_LIBS_MANIFEST_FILES := \ diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java index b5fefb46d..cbc77d272 100644 --- a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java +++ b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java @@ -16,11 +16,6 @@ package com.android.launcher3.uioverrides; -import static android.view.View.VISIBLE; -import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; - -import android.view.View; - import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherStateManager.StateHandler; @@ -43,8 +38,6 @@ import java.util.ArrayList; public abstract class RecentsUiFactory { public static final boolean GO_LOW_RAM_RECENTS_ENABLED = true; - // Scale recents takes before animating in - private static final float RECENTS_PREPARE_SCALE = 1.33f; public static TouchController[] createTouchControllers(Launcher launcher) { ArrayList<TouchController> list = new ArrayList<>(); @@ -77,18 +70,6 @@ public abstract class RecentsUiFactory { } /** - * Prepare the recents view to animate in. - * - * @param launcher the launcher activity - */ - public static void prepareToShowOverview(Launcher launcher) { - View overview = launcher.getOverviewPanel(); - if (overview.getVisibility() != VISIBLE) { - SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE); - } - } - - /** * Clean-up logic that occurs when recents is no longer in use/visible. * * @param launcher the launcher activity @@ -108,4 +89,6 @@ public abstract class RecentsUiFactory { public static RotationMode getRotationMode(DeviceProfile dp) { return RotationMode.NORMAL; } + + public static void clearSwipeSharedState(boolean finishAnimation) {} } diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java index 1b24fc856..d0cfcf97a 100644 --- a/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java +++ b/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java @@ -16,15 +16,31 @@ package com.android.launcher3.uioverrides.states; +import static android.view.View.VISIBLE; + import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS; +import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE; +import static com.android.launcher3.anim.Interpolators.ACCEL; import static com.android.launcher3.anim.Interpolators.DEACCEL_2; +import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; +import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7; import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE; +import android.view.View; + import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.R; +import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.userevent.nano.LauncherLogProto; +import com.android.quickstep.SysUINavigationMode; import com.android.quickstep.views.IconRecentsView; /** @@ -32,6 +48,9 @@ import com.android.quickstep.views.IconRecentsView; */ public class OverviewState extends LauncherState { + // Scale recents takes before animating in + private static final float RECENTS_PREPARE_SCALE = 1.33f; + private static final int STATE_FLAGS = FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED | FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI | FLAG_DISABLE_ACCESSIBILITY; @@ -103,6 +122,27 @@ public class OverviewState extends LauncherState { return dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx; } + @Override + public void prepareForAtomicAnimation(Launcher launcher, LauncherState fromState, + AnimatorSetBuilder builder) { + if (fromState == NORMAL && this == OVERVIEW) { + if (SysUINavigationMode.getMode(launcher) == SysUINavigationMode.Mode.NO_BUTTON) { + builder.setInterpolator(ANIM_WORKSPACE_SCALE, ACCEL); + builder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL); + } else { + builder.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2); + } + builder.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2); + builder.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2); + builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7); + builder.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2); + + View overview = launcher.getOverviewPanel(); + if (overview.getVisibility() != VISIBLE) { + SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE); + } + } + } public static OverviewState newBackgroundState(int id) { return new OverviewState(id); diff --git a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java index 39f8448d1..900b94e18 100644 --- a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -19,15 +19,21 @@ import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYS import android.annotation.TargetApi; import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.graphics.Region; import android.os.Build; import android.os.Bundle; import android.os.IBinder; +import android.os.Process; import android.os.RemoteException; import android.util.Log; import android.view.MotionEvent; +import com.android.launcher3.Utilities; +import com.android.launcher3.compat.UserManagerCompat; import com.android.systemui.shared.recents.IOverviewProxy; import com.android.systemui.shared.recents.ISystemUiProxy; @@ -37,7 +43,16 @@ import com.android.systemui.shared.recents.ISystemUiProxy; @TargetApi(Build.VERSION_CODES.O) public class TouchInteractionService extends Service { - private static final String TAG = "TouchInteractionService"; + private static final String TAG = "GoTouchInteractionService"; + private boolean mIsUserUnlocked; + private BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { + initWhenUserUnlocked(); + } + } + }; private final IBinder mMyBinder = new IOverviewProxy.Stub() { @@ -53,17 +68,21 @@ public class TouchInteractionService extends Service { @Override public void onOverviewToggle() { - mOverviewCommandHelper.onOverviewToggle(); + if (mIsUserUnlocked) { + mOverviewCommandHelper.onOverviewToggle(); + } } @Override public void onOverviewShown(boolean triggeredFromAltTab) { - mOverviewCommandHelper.onOverviewShown(triggeredFromAltTab); + if (mIsUserUnlocked) { + mOverviewCommandHelper.onOverviewShown(triggeredFromAltTab); + } } @Override public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { - if (triggeredFromAltTab && !triggeredFromHomeKey) { + if (mIsUserUnlocked && triggeredFromAltTab && !triggeredFromHomeKey) { // onOverviewShownFromAltTab hides the overview and ends at the target app mOverviewCommandHelper.onOverviewHidden(); } @@ -71,7 +90,9 @@ public class TouchInteractionService extends Service { @Override public void onTip(int actionType, int viewType) { - mOverviewCommandHelper.onTip(actionType, viewType); + if (mIsUserUnlocked) { + mOverviewCommandHelper.onTip(actionType, viewType); + } } @Override @@ -123,17 +144,31 @@ public class TouchInteractionService extends Service { @Override public void onCreate() { super.onCreate(); + if (UserManagerCompat.getInstance(this).isUserUnlocked(Process.myUserHandle())) { + initWhenUserUnlocked(); + } else { + mIsUserUnlocked = false; + registerReceiver(mUserUnlockedReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED)); + } + + sConnected = true; + } + + private void initWhenUserUnlocked() { mRecentsModel = RecentsModel.INSTANCE.get(this); mOverviewComponentObserver = new OverviewComponentObserver(this); mOverviewCommandHelper = new OverviewCommandHelper(this, mOverviewComponentObserver); - - sConnected = true; + mIsUserUnlocked = true; + Utilities.unregisterReceiverSafely(this, mUserUnlockedReceiver); } @Override public void onDestroy() { - mOverviewComponentObserver.onDestroy(); + if (mIsUserUnlocked) { + mOverviewComponentObserver.onDestroy(); + } + Utilities.unregisterReceiverSafely(this, mUserUnlockedReceiver); sConnected = false; super.onDestroy(); } diff --git a/iconloaderlib/build.gradle b/iconloaderlib/build.gradle index 49d427ede..8a4d2b76e 100644 --- a/iconloaderlib/build.gradle +++ b/iconloaderlib/build.gradle @@ -1,13 +1,3 @@ -buildscript { - repositories { - mavenCentral() - google() - } - dependencies { - classpath GRADLE_CLASS_PATH - } -} - apply plugin: 'com.android.library' android { @@ -44,12 +34,6 @@ android { } } - -repositories { - mavenCentral() - google() -} - dependencies { implementation "androidx.core:core:${ANDROID_X_VERSION}" } diff --git a/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java b/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java index af07aa3a9..97a0fd3ff 100644 --- a/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java +++ b/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java @@ -85,6 +85,14 @@ public class DotRenderer { return pos; } + public float[] getLeftDotPosition() { + return mLeftDotPosition; + } + + public float[] getRightDotPosition() { + return mRightDotPosition; + } + /** * Draw a circle on top of the canvas according to the given params. */ diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java index eac4dce18..371161ebd 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java @@ -16,25 +16,32 @@ package com.android.launcher3; +import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; import static com.android.launcher3.LauncherState.NORMAL; +import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS; import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch; import static com.android.quickstep.TaskViewUtils.getRecentsWindowAnimator; +import static androidx.dynamicanimation.animation.DynamicAnimation.MIN_VISIBLE_CHANGE_PIXELS; +import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY; +import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; -import android.app.ActivityOptions; import android.content.Context; import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.Interpolators; +import com.android.launcher3.anim.SpringObjectAnimator; import com.android.quickstep.util.ClipAnimationHelper; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; @@ -46,11 +53,12 @@ import com.android.systemui.shared.system.RemoteAnimationTargetCompat; */ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransitionManagerImpl { - private RecentsView mRecentsView; + public static final int INDEX_SHELF_ANIM = 0; + public static final int INDEX_RECENTS_FADE_ANIM = 1; + public static final int INDEX_RECENTS_TRANSLATE_X_ANIM = 2; public LauncherAppTransitionManagerImpl(Context context) { super(context); - mRecentsView = mLauncher.getOverviewPanel(); } @Override @@ -133,4 +141,25 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti mLauncher.getStateManager().reapplyState(); }; } + + @Override + public int getStateElementAnimationsCount() { + return 3; + } + + @Override + public Animator createStateElementAnimation(int index, float... values) { + switch (index) { + case INDEX_SHELF_ANIM: + return mLauncher.getAllAppsController().createSpringAnimation(values); + case INDEX_RECENTS_FADE_ANIM: + return ObjectAnimator.ofFloat(mLauncher.getOverviewPanel(), + RecentsView.CONTENT_ALPHA, values); + case INDEX_RECENTS_TRANSLATE_X_ANIM: + return new SpringObjectAnimator<>(mLauncher.getOverviewPanel(), + VIEW_TRANSLATE_X, MIN_VISIBLE_CHANGE_PIXELS, 0.8f, 250, values); + default: + return super.createStateElementAnimation(index, values); + } + } } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java index d84362c9f..6ecf1c11b 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java @@ -16,8 +16,6 @@ package com.android.launcher3.uioverrides; -import static android.view.View.VISIBLE; -import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; @@ -48,6 +46,7 @@ import com.android.launcher3.util.UiThreadHelper; import com.android.launcher3.util.UiThreadHelper.AsyncCommand; import com.android.quickstep.SysUINavigationMode; import com.android.quickstep.SysUINavigationMode.Mode; +import com.android.quickstep.TouchInteractionService; import com.android.quickstep.views.RecentsView; import com.android.systemui.shared.system.WindowManagerWrapper; @@ -62,9 +61,6 @@ public abstract class RecentsUiFactory { private static final AsyncCommand SET_SHELF_HEIGHT_CMD = (visible, height) -> WindowManagerWrapper.getInstance().setShelfHeight(visible != 0, height); - // Scale recents takes before animating in - private static final float RECENTS_PREPARE_SCALE = 1.33f; - public static RotationMode ROTATION_LANDSCAPE = new RotationMode(-90) { @Override public void mapRect(int left, int top, int right, int bottom, Rect out) { @@ -189,19 +185,10 @@ public abstract class RecentsUiFactory { } /** - * Prepare the recents view to animate in. - * - * @param launcher the launcher activity + * Clears the swipe shared state for the current swipe gesture. */ - public static void prepareToShowOverview(Launcher launcher) { - if (SysUINavigationMode.getMode(launcher) == NO_BUTTON) { - // Overview lives on the side, so doesn't scale in from above. - return; - } - RecentsView overview = launcher.getOverviewPanel(); - if (overview.getVisibility() != VISIBLE || overview.getContentAlpha() == 0) { - SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE); - } + public static void clearSwipeSharedState(boolean finishAnimation) { + TouchInteractionService.getSwipeSharedState().clearAllState(finishAnimation); } /** diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java index 1c6696858..d14de70c5 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java @@ -83,4 +83,16 @@ public class BackgroundAppState extends OverviewState { public int getVisibleElements(Launcher launcher) { return super.getVisibleElements(launcher) & ~RECENTS_CLEAR_ALL_BUTTON; } + + @Override + public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) { + if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) { + // Translate hotseat offscreen if we show it in overview. + ScaleAndTranslation scaleAndTranslation = super.getHotseatScaleAndTranslation(launcher); + scaleAndTranslation.translationY = LayoutUtils.getShelfTrackingDistance(launcher, + launcher.getDeviceProfile()); + return scaleAndTranslation; + } + return super.getHotseatScaleAndTranslation(launcher); + } } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java index bc1d4ba0c..c95476283 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java @@ -14,8 +14,15 @@ * limitations under the License. */ package com.android.launcher3.uioverrides.states; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X; +import static com.android.launcher3.anim.Interpolators.INSTANT; +import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7; + import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherState; import com.android.launcher3.R; +import com.android.launcher3.anim.AnimatorSetBuilder; public class OverviewPeekState extends OverviewState { public OverviewPeekState(int id) { @@ -29,4 +36,13 @@ public class OverviewPeekState extends OverviewState { - launcher.getResources().getDimension(R.dimen.overview_peek_distance); return result; } + + @Override + public void prepareForAtomicAnimation(Launcher launcher, LauncherState fromState, + AnimatorSetBuilder builder) { + if (this == OVERVIEW_PEEK && fromState == NORMAL) { + builder.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT); + builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7); + } + } } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java index 9a99c1552..5543860ee 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java @@ -15,8 +15,20 @@ */ package com.android.launcher3.uioverrides.states; +import static android.view.View.VISIBLE; + import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS; +import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE; +import static com.android.launcher3.anim.Interpolators.ACCEL; import static com.android.launcher3.anim.Interpolators.DEACCEL_2; +import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; +import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7; import static com.android.launcher3.logging.LoggerUtils.newContainerTarget; import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE; @@ -30,8 +42,11 @@ import com.android.launcher3.LauncherState; import com.android.launcher3.R; import com.android.launcher3.Workspace; import com.android.launcher3.allapps.DiscoveryBounce; +import com.android.launcher3.anim.AnimatorSetBuilder; +import com.android.launcher3.uioverrides.UiFactory; import com.android.launcher3.userevent.nano.LauncherLogProto.Action; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; +import com.android.quickstep.SysUINavigationMode; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; @@ -40,6 +55,9 @@ import com.android.quickstep.views.TaskView; */ public class OverviewState extends LauncherState { + // Scale recents takes before animating in + private static final float RECENTS_PREPARE_SCALE = 1.33f; + protected static final Rect sTempRect = new Rect(); private static final int STATE_FLAGS = FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED @@ -127,6 +145,10 @@ public class OverviewState extends LauncherState { // We have no all apps content, so we're still at the fully down progress. return super.getVerticalProgress(launcher); } + return getDefaultVerticalProgress(launcher); + } + + public static float getDefaultVerticalProgress(Launcher launcher) { return 1 - (getDefaultSwipeHeight(launcher) / launcher.getAllAppsController().getShiftRange()); } @@ -156,6 +178,29 @@ public class OverviewState extends LauncherState { } } + @Override + public void prepareForAtomicAnimation(Launcher launcher, LauncherState fromState, + AnimatorSetBuilder builder) { + if (fromState == NORMAL && this == OVERVIEW) { + if (SysUINavigationMode.getMode(launcher) == SysUINavigationMode.Mode.NO_BUTTON) { + builder.setInterpolator(ANIM_WORKSPACE_SCALE, ACCEL); + builder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL); + } else { + builder.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2); + + // Scale up the recents, if it is not coming from the side + RecentsView overview = launcher.getOverviewPanel(); + if (overview.getVisibility() != VISIBLE || overview.getContentAlpha() == 0) { + SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE); + } + } + builder.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2); + builder.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2); + builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7); + builder.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2); + } + } + public static OverviewState newBackgroundState(int id) { return new BackgroundAppState(id); } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java index e3e339add..3fe4bcfd9 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java @@ -16,11 +16,20 @@ package com.android.launcher3.uioverrides.touchcontrollers; +import static com.android.launcher3.LauncherState.ALL_APPS; +import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.OVERVIEW_PEEK; import static com.android.launcher3.LauncherStateManager.ANIM_ALL; import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_PEEK_COMPONENT; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_SCALE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_TRANSLATE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE; +import static com.android.launcher3.anim.Interpolators.DEACCEL_3; import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; @@ -38,14 +47,14 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.quickstep.OverviewInteractionState; import com.android.quickstep.util.MotionPauseDetector; import com.android.quickstep.views.RecentsView; -import com.android.systemui.shared.system.QuickStepContract; /** * Touch controller which handles swipe and hold to go to Overview */ public class FlingAndHoldTouchController extends PortraitStatesTouchController { - private static final long PEEK_ANIM_DURATION = 100; + private static final long PEEK_IN_ANIM_DURATION = 240; + private static final long PEEK_OUT_ANIM_DURATION = 100; private static final float MAX_DISPLACEMENT_PERCENT = 0.75f; private final MotionPauseDetector mMotionPauseDetector; @@ -81,9 +90,9 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { } LauncherState fromState = isPaused ? NORMAL : OVERVIEW_PEEK; LauncherState toState = isPaused ? OVERVIEW_PEEK : NORMAL; + long peekDuration = isPaused ? PEEK_IN_ANIM_DURATION : PEEK_OUT_ANIM_DURATION; mPeekAnim = mLauncher.getStateManager().createAtomicAnimation(fromState, toState, - new AnimatorSetBuilder(), ATOMIC_OVERVIEW_PEEK_COMPONENT, - PEEK_ANIM_DURATION); + new AnimatorSetBuilder(), ATOMIC_OVERVIEW_PEEK_COMPONENT, peekDuration); mPeekAnim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -107,6 +116,21 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { } @Override + protected AnimatorSetBuilder getAnimatorSetBuilderForStates(LauncherState fromState, + LauncherState toState) { + if (fromState == NORMAL && toState == ALL_APPS) { + AnimatorSetBuilder builder = new AnimatorSetBuilder(); + + // Get workspace out of the way quickly, to prepare for potential pause. + builder.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL_3); + builder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, DEACCEL_3); + builder.setInterpolator(ANIM_WORKSPACE_FADE, DEACCEL_3); + return builder; + } + return super.getAnimatorSetBuilderForStates(fromState, toState); + } + + @Override public boolean onDrag(float displacement, MotionEvent event) { float upDisplacement = -displacement; mMotionPauseDetector.setDisallowPause(upDisplacement < mMotionPauseMinDisplacement @@ -123,8 +147,11 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { } AnimatorSetBuilder builder = new AnimatorSetBuilder(); - builder.setInterpolator(AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2); - builder.setInterpolator(AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE, OVERSHOOT_1_2); + builder.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2); + if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) { + builder.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2); + builder.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2); + } AnimatorSet overviewAnim = mLauncher.getStateManager().createAtomicAnimation( NORMAL, OVERVIEW, builder, ANIM_ALL, ATOMIC_DURATION); overviewAnim.addListener(new AnimatorListenerAdapter() { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java index 0d06c19ab..07c049642 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java @@ -18,12 +18,13 @@ package com.android.quickstep; import static android.view.View.TRANSLATION_Y; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; +import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_RECENTS_FADE_ANIM; +import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_RECENTS_TRANSLATE_X_ANIM; +import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_SHELF_ANIM; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherStateManager.ANIM_ALL; -import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_DAMPING_RATIO; -import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_STIFFNESS; import static com.android.launcher3.anim.Interpolators.ACCEL_2; import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; import static com.android.launcher3.anim.Interpolators.INSTANT; @@ -31,7 +32,6 @@ import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.quickstep.WindowTransformSwipeHandler.RECENTS_ATTACH_DURATION; import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; @@ -48,8 +48,6 @@ import android.view.animation.Interpolator; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; -import androidx.dynamicanimation.animation.SpringAnimation; -import androidx.dynamicanimation.animation.SpringForce; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; @@ -60,13 +58,14 @@ import com.android.launcher3.LauncherStateManager; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.AnimatorSetBuilder; -import com.android.launcher3.anim.SpringObjectAnimator; import com.android.launcher3.testing.TestProtocol; +import com.android.launcher3.uioverrides.states.OverviewState; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.views.FloatingIconView; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.util.LayoutUtils; import com.android.quickstep.util.StaggeredWorkspaceAnim; +import com.android.quickstep.views.LauncherRecentsView; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; @@ -216,10 +215,7 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe activity.getAppsView().getContentView().setVisibility(View.GONE); return new AnimationFactory() { - private Animator mShelfAnim; private ShelfAnimState mShelfState; - private Animator mAttachToWindowFadeAnim; - private SpringAnimation mAttachToWindowTranslationXAnim; private boolean mIsAttachedToWindow; @Override @@ -252,31 +248,26 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe return; } mShelfState = shelfState; - if (mShelfAnim != null) { - mShelfAnim.cancel(); - } + activity.getStateManager().cancelStateElementAnimation(INDEX_SHELF_ANIM); if (mShelfState == ShelfAnimState.CANCEL) { return; } float shelfHiddenProgress = BACKGROUND_APP.getVerticalProgress(activity); float shelfOverviewProgress = OVERVIEW.getVerticalProgress(activity); + // Peek based on default overview progress so we can see hotseat if we're showing + // that instead of predictions in overview. + float defaultOverviewProgress = OverviewState.getDefaultVerticalProgress(activity); float shelfPeekingProgress = shelfHiddenProgress - - (shelfHiddenProgress - shelfOverviewProgress) * 0.25f; + - (shelfHiddenProgress - defaultOverviewProgress) * 0.25f; float toProgress = mShelfState == ShelfAnimState.HIDE ? shelfHiddenProgress : mShelfState == ShelfAnimState.PEEK ? shelfPeekingProgress : shelfOverviewProgress; - mShelfAnim = createShelfAnim(activity, toProgress); - mShelfAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mShelfAnim = null; - } - }); - mShelfAnim.setInterpolator(interpolator); - mShelfAnim.setDuration(duration); - mShelfAnim.start(); + Animator shelfAnim = activity.getStateManager() + .createStateElementAnimation(INDEX_SHELF_ANIM, toProgress); + shelfAnim.setInterpolator(interpolator); + shelfAnim.setDuration(duration).start(); } @Override @@ -285,12 +276,10 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe return; } mIsAttachedToWindow = attached; - if (mAttachToWindowFadeAnim != null) { - mAttachToWindowFadeAnim.cancel(); - } - RecentsView recentsView = activity.getOverviewPanel(); - mAttachToWindowFadeAnim = ObjectAnimator.ofFloat(recentsView, - RecentsView.CONTENT_ALPHA, attached ? 1 : 0); + LauncherRecentsView recentsView = activity.getOverviewPanel(); + Animator fadeAnim = activity.getStateManager() + .createStateElementAnimation( + INDEX_RECENTS_FADE_ANIM, attached ? 1 : 0); int runningTaskIndex = recentsView.getRunningTaskIndex(); if (runningTaskIndex == 0) { @@ -312,33 +301,28 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe float fromTranslationX = attached ? offscreenX - scrollOffsetX : 0; float toTranslationX = attached ? 0 : offscreenX - scrollOffsetX; - if (mAttachToWindowTranslationXAnim == null) { - mAttachToWindowTranslationXAnim = new SpringAnimation(recentsView, - SpringAnimation.TRANSLATION_X).setSpring(new SpringForce() - .setDampingRatio(0.8f) - .setStiffness(250)); - } + activity.getStateManager() + .cancelStateElementAnimation(INDEX_RECENTS_TRANSLATE_X_ANIM); + if (!recentsView.isShown() && animate) { recentsView.setTranslationX(fromTranslationX); - mAttachToWindowTranslationXAnim.setStartValue(fromTranslationX); + } else { + fromTranslationX = recentsView.getTranslationX(); } - mAttachToWindowTranslationXAnim.animateToFinalPosition(toTranslationX); - if (!animate && mAttachToWindowTranslationXAnim.canSkipToEnd()) { - mAttachToWindowTranslationXAnim.skipToEnd(); + + if (!animate) { + recentsView.setTranslationX(toTranslationX); + } else { + activity.getStateManager().createStateElementAnimation( + INDEX_RECENTS_TRANSLATE_X_ANIM, + fromTranslationX, toTranslationX).start(); } - mAttachToWindowFadeAnim.setInterpolator(attached ? INSTANT : ACCEL_2); + fadeAnim.setInterpolator(attached ? INSTANT : ACCEL_2); } else { - mAttachToWindowFadeAnim.setInterpolator(ACCEL_DEACCEL); + fadeAnim.setInterpolator(ACCEL_DEACCEL); } - mAttachToWindowFadeAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mAttachToWindowFadeAnim = null; - } - }); - mAttachToWindowFadeAnim.setDuration(animate ? RECENTS_ATTACH_DURATION : 0); - mAttachToWindowFadeAnim.start(); + fadeAnim.setDuration(animate ? RECENTS_ATTACH_DURATION : 0).start(); } }; } @@ -354,10 +338,10 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe if (!activity.getDeviceProfile().isVerticalBarLayout() && SysUINavigationMode.getMode(activity) != Mode.NO_BUTTON) { // Don't animate the shelf when the mode is NO_BUTTON, because we update it atomically. - Animator shiftAnim = createShelfAnim(activity, + anim.play(activity.getStateManager().createStateElementAnimation( + INDEX_SHELF_ANIM, fromState.getVerticalProgress(activity), - endState.getVerticalProgress(activity)); - anim.play(shiftAnim); + endState.getVerticalProgress(activity))); } playScaleDownAnim(anim, activity, fromState, endState); @@ -375,13 +359,6 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe callback.accept(controller); } - private Animator createShelfAnim(Launcher activity, float ... progressValues) { - Animator shiftAnim = new SpringObjectAnimator<>(activity.getAllAppsController(), - "allAppsSpringFromACH", activity.getAllAppsController().getShiftRange(), - SPRING_DAMPING_RATIO, SPRING_STIFFNESS, progressValues); - return shiftAnim; - } - /** * Scale down recents from the center task being full screen to being in overview. */ diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java index 5eecf1713..ddd28a350 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java @@ -19,6 +19,8 @@ import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; +import static com.android.launcher3.Utilities.FLAG_NO_GESTURES; + import android.view.InputEvent; import android.view.KeyEvent; import android.view.MotionEvent; @@ -182,7 +184,10 @@ public class RecentsAnimationWrapper { } } if (mInputConsumer != null) { + int flags = ev.getEdgeFlags(); + ev.setEdgeFlags(flags | FLAG_NO_GESTURES); mInputConsumer.onMotionEvent(ev); + ev.setEdgeFlags(flags); } return true; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java index 6689ce3d7..c55f656df 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java @@ -72,13 +72,15 @@ public class SwipeSharedState implements SwipeAnimationListener { mLastAnimationRunning = false; } - private void clearListenerState() { + private void clearListenerState(boolean finishAnimation) { if (mRecentsAnimationListener != null) { mRecentsAnimationListener.removeListener(this); mRecentsAnimationListener.cancelListener(); if (mLastAnimationRunning && mLastAnimationTarget != null) { Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(), - mLastAnimationTarget::cancelAnimation); + finishAnimation + ? mLastAnimationTarget::finishAnimation + : mLastAnimationTarget::cancelAnimation); mLastAnimationTarget = null; } } @@ -106,7 +108,7 @@ public class SwipeSharedState implements SwipeAnimationListener { } } - clearListenerState(); + clearListenerState(false /* finishAnimation */); boolean shouldMinimiseSplitScreen = mOverviewComponentObserver == null ? false : mOverviewComponentObserver.getActivityControlHelper().shouldMinimizeSplitScreen(); mRecentsAnimationListener = new RecentsAnimationListenerSet( @@ -138,8 +140,8 @@ public class SwipeSharedState implements SwipeAnimationListener { mLastAnimationTarget = mLastAnimationTarget.cloneWithoutTargets(); } - public void clearAllState() { - clearListenerState(); + public void clearAllState(boolean finishAnimation) { + clearListenerState(finishAnimation); canGestureBeContinued = false; recentsAnimationFinishInterrupted = false; nextRunningTaskId = -1; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java index d0ea73a6f..6897c1e7d 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java @@ -29,8 +29,6 @@ import android.view.View; import com.android.launcher3.BaseActivity; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.ItemInfo; -import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherAppState; import com.android.launcher3.Utilities; import com.android.quickstep.util.ClipAnimationHelper; import com.android.quickstep.util.MultiValueUpdateListener; @@ -123,6 +121,7 @@ public final class TaskViewUtils { new RemoteAnimationTargetSet(targets, MODE_OPENING); targetSet.addDependentTransactionApplier(applier); + final RecentsView recentsView = v.getRecentsView(); final ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1); appAnimator.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR); appAnimator.addUpdateListener(new MultiValueUpdateListener() { @@ -153,7 +152,10 @@ public final class TaskViewUtils { // TODO: Take into account the current fullscreen progress for animating the insets params.setProgress(1 - percent); RectF taskBounds = inOutHelper.applyTransform(targetSet, params); - if (!skipViewChanges) { + int taskIndex = recentsView.indexOfChild(v); + int centerTaskIndex = recentsView.getCurrentPage(); + boolean parallaxCenterAndAdjacentTask = taskIndex != centerTaskIndex; + if (!skipViewChanges && parallaxCenterAndAdjacentTask) { float scale = taskBounds.width() / mThumbnailRect.width(); v.setScaleX(scale); v.setScaleY(scale); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java index 769d207ab..53da0f92d 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -81,6 +81,7 @@ import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; import com.android.quickstep.inputconsumers.AccessibilityInputConsumer; import com.android.quickstep.inputconsumers.AssistantTouchConsumer; import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer; +import com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer; import com.android.quickstep.inputconsumers.InputConsumer; import com.android.quickstep.inputconsumers.OtherActivityInputConsumer; import com.android.quickstep.inputconsumers.OverviewInputConsumer; @@ -224,14 +225,18 @@ public class TouchInteractionService extends Service implements }; private static boolean sConnected = false; + private static final SwipeSharedState sSwipeSharedState = new SwipeSharedState(); public static boolean isConnected() { return sConnected; } - private final SwipeSharedState mSwipeSharedState = new SwipeSharedState(); + public static SwipeSharedState getSwipeSharedState() { + return sSwipeSharedState; + } + private final InputConsumer mResetGestureInputConsumer = - new ResetGestureInputConsumer(mSwipeSharedState); + new ResetGestureInputConsumer(sSwipeSharedState); private ActivityManagerWrapper mAM; private RecentsModel mRecentsModel; @@ -267,6 +272,9 @@ public class TouchInteractionService extends Service implements private Mode mMode = Mode.THREE_BUTTONS; private int mDefaultDisplayId; private final RectF mSwipeTouchRegion = new RectF(); + private final RectF mAssistantLeftRegion = new RectF(); + private final RectF mAssistantRightRegion = new RectF(); + private ComponentName mGestureBlockingActivity; private Region mExclusionRegion; @@ -349,9 +357,25 @@ public class TouchInteractionService extends Service implements defaultDisplay.getRealSize(realSize); mSwipeTouchRegion.set(0, 0, realSize.x, realSize.y); if (mMode == Mode.NO_BUTTON) { - mSwipeTouchRegion.top = mSwipeTouchRegion.bottom - - getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE); + int touchHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE); + mSwipeTouchRegion.top = mSwipeTouchRegion.bottom - touchHeight; + + final int assistantWidth = getResources() + .getDimensionPixelSize(R.dimen.gestures_assistant_width); + final float assistantHeight = Math.max(touchHeight, + QuickStepContract.getWindowCornerRadius(getResources())); + mAssistantLeftRegion.bottom = mAssistantRightRegion.bottom = mSwipeTouchRegion.bottom; + mAssistantLeftRegion.top = mAssistantRightRegion.top = + mSwipeTouchRegion.bottom - assistantHeight; + + mAssistantLeftRegion.left = 0; + mAssistantLeftRegion.right = assistantWidth; + + mAssistantRightRegion.right = mSwipeTouchRegion.right; + mAssistantRightRegion.left = mSwipeTouchRegion.right - assistantWidth; } else { + mAssistantLeftRegion.setEmpty(); + mAssistantRightRegion.setEmpty(); switch (defaultDisplay.getRotation()) { case Surface.ROTATION_90: mSwipeTouchRegion.left = mSwipeTouchRegion.right @@ -416,7 +440,7 @@ public class TouchInteractionService extends Service implements mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer(); mIsUserUnlocked = true; - mSwipeSharedState.setOverviewComponentObserver(mOverviewComponentObserver); + sSwipeSharedState.setOverviewComponentObserver(mOverviewComponentObserver); mInputConsumer.registerInputConsumer(); onSystemUiProxySet(); onSystemUiFlagsChanged(); @@ -491,6 +515,15 @@ public class TouchInteractionService extends Service implements mConsumer = newConsumer(useSharedState, event); TOUCH_INTERACTION_LOG.addLog("setInputConsumer", mConsumer.getType()); mUncheckedConsumer = mConsumer; + } else if (mIsUserUnlocked && mMode == Mode.NO_BUTTON + && canTriggerAssistantAction(event)) { + // Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we should + // not interrupt it. QuickSwitch assumes that interruption can only happen if the + // next gesture is also quick switch. + mUncheckedConsumer = + new AssistantTouchConsumer(this, mISystemUiProxy, + mOverviewComponentObserver.getActivityControlHelper(), + InputConsumer.NO_OP, mInputMonitorCompat); } else { mUncheckedConsumer = InputConsumer.NO_OP; } @@ -505,6 +538,14 @@ public class TouchInteractionService extends Service implements || (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0); } + private boolean canTriggerAssistantAction(MotionEvent ev) { + return mAssistantAvailable + && !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags) + && (mAssistantLeftRegion.contains(ev.getX(), ev.getY()) || + mAssistantRightRegion.contains(ev.getX(), ev.getY())) + && !ActivityManagerWrapper.getInstance().isLockToAppActive(); + } + private InputConsumer newConsumer(boolean useSharedState, MotionEvent event) { boolean isInValidSystemUiState = validSystemUiFlags(); @@ -525,10 +566,7 @@ public class TouchInteractionService extends Service implements if (mMode == Mode.NO_BUTTON) { final ActivityControlHelper activityControl = mOverviewComponentObserver.getActivityControlHelper(); - if (mAssistantAvailable - && !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags) - && AssistantTouchConsumer.withinTouchRegion(this, event) - && !ActivityManagerWrapper.getInstance().isLockToAppActive()) { + if (canTriggerAssistantAction(event)) { base = new AssistantTouchConsumer(this, mISystemUiProxy, activityControl, base, mInputMonitorCompat); } @@ -555,7 +593,7 @@ public class TouchInteractionService extends Service implements private InputConsumer newBaseConsumer(boolean useSharedState, MotionEvent event) { final RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0); if (!useSharedState) { - mSwipeSharedState.clearAllState(); + sSwipeSharedState.clearAllState(false /* finishAnimation */); } if ((mSystemUiStateFlags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0) { // This handles apps showing over the lockscreen (e.g. camera) @@ -565,22 +603,26 @@ public class TouchInteractionService extends Service implements final ActivityControlHelper activityControl = mOverviewComponentObserver.getActivityControlHelper(); - if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher - && !mSwipeSharedState.recentsAnimationFinishInterrupted) { + if (runningTaskInfo == null && !sSwipeSharedState.goingToLauncher + && !sSwipeSharedState.recentsAnimationFinishInterrupted) { return mResetGestureInputConsumer; - } else if (mSwipeSharedState.recentsAnimationFinishInterrupted) { + } else if (sSwipeSharedState.recentsAnimationFinishInterrupted) { // If the finish animation was interrupted, then continue using the other activity input // consumer but with the next task as the running task RunningTaskInfo info = new ActivityManager.RunningTaskInfo(); - info.id = mSwipeSharedState.nextRunningTaskId; + info.id = sSwipeSharedState.nextRunningTaskId; return createOtherActivityInputConsumer(event, info); - } else if (mSwipeSharedState.goingToLauncher || activityControl.isResumed()) { + } else if (sSwipeSharedState.goingToLauncher || activityControl.isResumed()) { return createOverviewInputConsumer(event); } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() && activityControl.isInLiveTileMode()) { return createOverviewInputConsumer(event); } else if (mGestureBlockingActivity != null && runningTaskInfo != null && mGestureBlockingActivity.equals(runningTaskInfo.topActivity)) { return mResetGestureInputConsumer; + } else if (mMode == Mode.NO_BUTTON && !mOverviewComponentObserver.isHomeAndOverviewSame()) { + return new FallbackNoButtonInputConsumer(this, activityControl, + mInputMonitorCompat, sSwipeSharedState, mSwipeTouchRegion, + mOverviewComponentObserver, disableHorizontalSwipe(event), runningTaskInfo); } else { return createOtherActivityInputConsumer(event, runningTaskInfo); } @@ -602,13 +644,13 @@ public class TouchInteractionService extends Service implements return new OtherActivityInputConsumer(this, runningTaskInfo, mRecentsModel, mOverviewComponentObserver.getOverviewIntent(), activityControl, shouldDefer, mOverviewCallbacks, mInputConsumer, this::onConsumerInactive, - mSwipeSharedState, mInputMonitorCompat, mSwipeTouchRegion, + sSwipeSharedState, mInputMonitorCompat, mSwipeTouchRegion, disableHorizontalSwipe(event)); } private InputConsumer createDeviceLockedInputConsumer(RunningTaskInfo taskInfo) { if (mMode == Mode.NO_BUTTON && taskInfo != null) { - return new DeviceLockedInputConsumer(this, mSwipeSharedState, mInputMonitorCompat, + return new DeviceLockedInputConsumer(this, sSwipeSharedState, mInputMonitorCompat, mSwipeTouchRegion, taskInfo.taskId); } else { return mResetGestureInputConsumer; @@ -623,7 +665,7 @@ public class TouchInteractionService extends Service implements return mResetGestureInputConsumer; } - if (activity.getRootView().hasWindowFocus() || mSwipeSharedState.goingToLauncher) { + if (activity.getRootView().hasWindowFocus() || sSwipeSharedState.goingToLauncher) { return new OverviewInputConsumer(activity, mInputMonitorCompat, false /* startingInActivityBounds */); } else { @@ -670,7 +712,7 @@ public class TouchInteractionService extends Service implements + mOverviewComponentObserver.getActivityControlHelper().isResumed()); pw.println(" useSharedState=" + mConsumer.useSharedSwipeState()); if (mConsumer.useSharedSwipeState()) { - mSwipeSharedState.dump(" ", pw); + sSwipeSharedState.dump(" ", pw); } pw.println(" mConsumer=" + mConsumer.getName()); pw.println("FeatureFlags:"); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java index 837423ace..38b5a137c 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java @@ -39,6 +39,8 @@ import android.os.Bundle; import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; +import android.view.GestureDetector; +import android.view.GestureDetector.SimpleOnGestureListener; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.ViewConfiguration; @@ -47,17 +49,14 @@ import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.R; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.logging.UserEventDispatcher; -import com.android.launcher3.touch.SwipeDetector; import com.android.quickstep.ActivityControlHelper; import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.system.InputMonitorCompat; -import com.android.systemui.shared.system.QuickStepContract; /** * Touch consumer for handling events to launch assistant from launcher */ -public class AssistantTouchConsumer extends DelegateInputConsumer - implements SwipeDetector.Listener { +public class AssistantTouchConsumer extends DelegateInputConsumer { private static final String TAG = "AssistantTouchConsumer"; private static final long RETRACT_ANIMATION_DURATION_MS = 300; @@ -68,7 +67,6 @@ public class AssistantTouchConsumer extends DelegateInputConsumer private static final int OPA_BUNDLE_TRIGGER_DIAG_SWIPE_GESTURE = 83; private static final String INVOCATION_TYPE_KEY = "invocation_type"; private static final int INVOCATION_TYPE_GESTURE = 1; - private static final int INVOCATION_TYPE_FLING = 6; private final PointF mDownPos = new PointF(); private final PointF mLastPos = new PointF(); @@ -90,7 +88,7 @@ public class AssistantTouchConsumer extends DelegateInputConsumer private final float mSquaredSlop; private final ISystemUiProxy mSysUiProxy; private final Context mContext; - private final SwipeDetector mSwipeDetector; + private final GestureDetector mGestureDetector; public AssistantTouchConsumer(Context context, ISystemUiProxy systemUiProxy, ActivityControlHelper activityControlHelper, InputConsumer delegate, @@ -107,8 +105,8 @@ public class AssistantTouchConsumer extends DelegateInputConsumer mSquaredSlop = slop * slop; mActivityControlHelper = activityControlHelper; - mSwipeDetector = new SwipeDetector(mContext, this, SwipeDetector.VERTICAL); - mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_POSITIVE, false); + + mGestureDetector = new GestureDetector(context, new AssistantGestureListener()); } @Override @@ -119,7 +117,7 @@ public class AssistantTouchConsumer extends DelegateInputConsumer @Override public void onMotionEvent(MotionEvent ev) { // TODO add logging - mSwipeDetector.onTouchEvent(ev); + mGestureDetector.onTouchEvent(ev); switch (ev.getActionMasked()) { case ACTION_DOWN: { @@ -171,13 +169,8 @@ public class AssistantTouchConsumer extends DelegateInputConsumer mStartDragPos.set(mLastPos.x, mLastPos.y); mDragTime = SystemClock.uptimeMillis(); - // Determine if angle is larger than threshold for assistant detection - float angle = (float) Math.toDegrees( - Math.atan2(mDownPos.y - mLastPos.y, mDownPos.x - mLastPos.x)); - mDirection = angle > 90 ? UPLEFT : UPRIGHT; - angle = angle > 90 ? 180 - angle : angle; - - if (angle > mAngleThreshold && angle < 90) { + if (isValidAssistantGestureAngle( + mDownPos.x - mLastPos.x, mDownPos.y - mLastPos.y)) { setActive(ev); } else { mState = STATE_DELEGATE_ACTIVE; @@ -261,40 +254,41 @@ public class AssistantTouchConsumer extends DelegateInputConsumer } } - public static boolean withinTouchRegion(Context context, MotionEvent ev) { - final Resources res = context.getResources(); - final int width = res.getDisplayMetrics().widthPixels; - final int height = res.getDisplayMetrics().heightPixels; - final int size = res.getDimensionPixelSize(R.dimen.gestures_assistant_size); - return (ev.getX() > width - size || ev.getX() < size) && ev.getY() > height - size; - } - - @Override - public void onDragStart(boolean start) { - // do nothing + /** + * Determine if angle is larger than threshold for assistant detection + */ + private boolean isValidAssistantGestureAngle(float deltaX, float deltaY) { + float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX)); + mDirection = angle > 90 ? UPLEFT : UPRIGHT; + + // normalize so that angle is measured clockwise from horizontal in the bottom right corner + // and counterclockwise from horizontal in the bottom left corner + angle = angle > 90 ? 180 - angle : angle; + return (angle > mAngleThreshold && angle < 90); } - @Override - public boolean onDrag(float displacement) { - return false; - } + private class AssistantGestureListener extends SimpleOnGestureListener { + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + if (isValidAssistantGestureAngle(velocityX, -velocityY) + && !mLaunchedAssistant && mState != STATE_DELEGATE_ACTIVE) { + mLastProgress = 1; + try { + mSysUiProxy.onAssistantGestureCompletion( + (float) Math.sqrt(velocityX * velocityX + velocityY * velocityY)); + startAssistantInternal(FLING); - @Override - public void onDragEnd(float velocity, boolean fling) { - if (fling && !mLaunchedAssistant && mState != STATE_DELEGATE_ACTIVE) { - mLastProgress = 1; - try { - mSysUiProxy.onAssistantGestureCompletion(velocity); - startAssistantInternal(FLING); - - Bundle args = new Bundle(); - args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_GESTURE); - mSysUiProxy.startAssistant(args); - mLaunchedAssistant = true; - } catch (RemoteException e) { - Log.w(TAG, "Failed to send SysUI start/send assistant progress: " + mLastProgress, - e); + Bundle args = new Bundle(); + args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_GESTURE); + mSysUiProxy.startAssistant(args); + mLaunchedAssistant = true; + } catch (RemoteException e) { + Log.w(TAG, + "Failed to send SysUI start/send assistant progress: " + mLastProgress, + e); + } } + return true; } } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java new file mode 100644 index 000000000..d05ca2a16 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2019 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.inputconsumers; + +import static android.view.MotionEvent.ACTION_CANCEL; +import static android.view.MotionEvent.ACTION_DOWN; +import static android.view.MotionEvent.ACTION_MOVE; +import static android.view.MotionEvent.ACTION_POINTER_DOWN; +import static android.view.MotionEvent.ACTION_POINTER_UP; +import static android.view.MotionEvent.ACTION_UP; +import static android.view.MotionEvent.INVALID_POINTER_ID; + +import static com.android.quickstep.WindowTransformSwipeHandler.MAX_SWIPE_DURATION; +import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW; +import static com.android.quickstep.WindowTransformSwipeHandler.MIN_SWIPE_DURATION; +import static com.android.quickstep.inputconsumers.OtherActivityInputConsumer.QUICKSTEP_TOUCH_SLOP_RATIO; +import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.app.ActivityManager.RunningTaskInfo; +import android.content.Context; +import android.content.Intent; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.RectF; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.ViewConfiguration; +import android.view.WindowManager; + +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.R; +import com.android.quickstep.ActivityControlHelper; +import com.android.quickstep.OverviewComponentObserver; +import com.android.quickstep.SwipeSharedState; +import com.android.quickstep.util.ClipAnimationHelper; +import com.android.quickstep.util.ClipAnimationHelper.TransformParams; +import com.android.quickstep.util.NavBarPosition; +import com.android.quickstep.util.RecentsAnimationListenerSet; +import com.android.quickstep.util.SwipeAnimationTargetSet; +import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener; +import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.BackgroundExecutor; +import com.android.systemui.shared.system.InputMonitorCompat; +import com.android.systemui.shared.system.RemoteAnimationTargetCompat; + +public class FallbackNoButtonInputConsumer implements InputConsumer, SwipeAnimationListener { + + private static final int STATE_NOT_FINISHED = 0; + private static final int STATE_FINISHED_TO_HOME = 1; + private static final int STATE_FINISHED_TO_APP = 2; + + private static final float PROGRESS_TO_END_GESTURE = -2; + + private final ActivityControlHelper mActivityControlHelper; + private final InputMonitorCompat mInputMonitor; + private final Context mContext; + private final NavBarPosition mNavBarPosition; + private final SwipeSharedState mSwipeSharedState; + private final OverviewComponentObserver mOverviewComponentObserver; + private final int mRunningTaskId; + + private final ClipAnimationHelper mClipAnimationHelper; + private final TransformParams mTransformParams = new TransformParams(); + private final float mTransitionDragLength; + private final DeviceProfile mDP; + + private final RectF mSwipeTouchRegion; + private final boolean mDisableHorizontalSwipe; + + private final PointF mDownPos = new PointF(); + private final PointF mLastPos = new PointF(); + + private int mActivePointerId = -1; + // Slop used to determine when we say that the gesture has started. + private boolean mPassedPilferInputSlop; + + private VelocityTracker mVelocityTracker; + + // Distance after which we start dragging the window. + private final float mTouchSlop; + + // Might be displacement in X or Y, depending on the direction we are swiping from the nav bar. + private float mStartDisplacement; + private SwipeAnimationTargetSet mSwipeAnimationTargetSet; + private float mProgress; + + private int mState = STATE_NOT_FINISHED; + + public FallbackNoButtonInputConsumer(Context context, + ActivityControlHelper activityControlHelper, InputMonitorCompat inputMonitor, + SwipeSharedState swipeSharedState, RectF swipeTouchRegion, + OverviewComponentObserver overviewComponentObserver, + boolean disableHorizontalSwipe, RunningTaskInfo runningTaskInfo) { + mContext = context; + mActivityControlHelper = activityControlHelper; + mInputMonitor = inputMonitor; + mOverviewComponentObserver = overviewComponentObserver; + mRunningTaskId = runningTaskInfo.id; + + mSwipeSharedState = swipeSharedState; + mSwipeTouchRegion = swipeTouchRegion; + mDisableHorizontalSwipe = disableHorizontalSwipe; + + mNavBarPosition = new NavBarPosition(context); + mVelocityTracker = VelocityTracker.obtain(); + + mTouchSlop = QUICKSTEP_TOUCH_SLOP_RATIO + * ViewConfiguration.get(context).getScaledTouchSlop(); + + mClipAnimationHelper = new ClipAnimationHelper(context); + + mDP = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context).copy(context); + Rect tempRect = new Rect(); + mTransitionDragLength = mActivityControlHelper.getSwipeUpDestinationAndLength( + mDP, context, tempRect); + mClipAnimationHelper.updateTargetRect(tempRect); + } + + @Override + public int getType() { + return TYPE_FALLBACK_NO_BUTTON; + } + + @Override + public void onMotionEvent(MotionEvent ev) { + if (mVelocityTracker == null) { + return; + } + + mVelocityTracker.addMovement(ev); + if (ev.getActionMasked() == ACTION_POINTER_UP) { + mVelocityTracker.clear(); + } + + switch (ev.getActionMasked()) { + case ACTION_DOWN: { + mActivePointerId = ev.getPointerId(0); + mDownPos.set(ev.getX(), ev.getY()); + mLastPos.set(mDownPos); + break; + } + case ACTION_POINTER_DOWN: { + if (!mPassedPilferInputSlop) { + // Cancel interaction in case of multi-touch interaction + int ptrIdx = ev.getActionIndex(); + if (!mSwipeTouchRegion.contains(ev.getX(ptrIdx), ev.getY(ptrIdx))) { + forceCancelGesture(ev); + } + } + break; + } + case ACTION_POINTER_UP: { + int ptrIdx = ev.getActionIndex(); + int ptrId = ev.getPointerId(ptrIdx); + if (ptrId == mActivePointerId) { + final int newPointerIdx = ptrIdx == 0 ? 1 : 0; + mDownPos.set( + ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x), + ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y)); + mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx)); + mActivePointerId = ev.getPointerId(newPointerIdx); + } + break; + } + case ACTION_MOVE: { + int pointerIndex = ev.findPointerIndex(mActivePointerId); + if (pointerIndex == INVALID_POINTER_ID) { + break; + } + mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex)); + float displacement = getDisplacement(ev); + + if (!mPassedPilferInputSlop) { + if (mDisableHorizontalSwipe && Math.abs(mLastPos.x - mDownPos.x) + > Math.abs(mLastPos.y - mDownPos.y)) { + // Horizontal gesture is not allowed in this region + forceCancelGesture(ev); + break; + } + + if (Math.abs(displacement) >= mTouchSlop) { + mPassedPilferInputSlop = true; + + // Deferred gesture, start the animation and gesture tracking once + // we pass the actual touch slop + startTouchTrackingForWindowAnimation(displacement); + } + } else { + updateDisplacement(displacement - mStartDisplacement); + } + break; + } + case ACTION_CANCEL: + case ACTION_UP: { + finishTouchTracking(ev); + break; + } + } + } + + private void startTouchTrackingForWindowAnimation(float displacement) { + mStartDisplacement = Math.min(displacement, -mTouchSlop); + + RecentsAnimationListenerSet listenerSet = + mSwipeSharedState.newRecentsAnimationListenerSet(); + listenerSet.addListener(this); + Intent homeIntent = mOverviewComponentObserver.getHomeIntent(); + BackgroundExecutor.get().submit( + () -> ActivityManagerWrapper.getInstance().startRecentsActivity( + homeIntent, null, listenerSet, null, null)); + + ActivityManagerWrapper.getInstance().closeSystemWindows( + CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); + mInputMonitor.pilferPointers(); + } + + private void updateDisplacement(float displacement) { + mProgress = displacement / mTransitionDragLength; + mTransformParams.setProgress(mProgress); + + if (mSwipeAnimationTargetSet != null) { + mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams); + } + } + + private void forceCancelGesture(MotionEvent ev) { + int action = ev.getAction(); + ev.setAction(ACTION_CANCEL); + finishTouchTracking(ev); + ev.setAction(action); + } + + /** + * Called when the gesture has ended. Does not correlate to the completion of the interaction as + * the animation can still be running. + */ + private void finishTouchTracking(MotionEvent ev) { + if (ev.getAction() == ACTION_CANCEL) { + mState = STATE_FINISHED_TO_APP; + } else { + mVelocityTracker.computeCurrentVelocity(1000, + ViewConfiguration.get(mContext).getScaledMaximumFlingVelocity()); + float velocityX = mVelocityTracker.getXVelocity(mActivePointerId); + float velocityY = mVelocityTracker.getYVelocity(mActivePointerId); + float velocity = mNavBarPosition.isRightEdge() ? velocityX + : mNavBarPosition.isLeftEdge() ? -velocityX + : velocityY; + float flingThreshold = mContext.getResources() + .getDimension(R.dimen.quickstep_fling_threshold_velocity); + boolean isFling = Math.abs(velocity) > flingThreshold; + + boolean goingHome; + if (!isFling) { + goingHome = -mProgress >= MIN_PROGRESS_FOR_OVERVIEW; + } else { + goingHome = velocity < 0; + } + + if (goingHome) { + mState = STATE_FINISHED_TO_HOME; + } else { + mState = STATE_FINISHED_TO_APP; + } + } + + if (mSwipeAnimationTargetSet != null) { + finishAnimationTargetSet(); + } + } + + private void finishAnimationTargetSet() { + if (mState == STATE_FINISHED_TO_APP) { + mSwipeAnimationTargetSet.finishController(false, null, false); + } else { + if (mProgress < PROGRESS_TO_END_GESTURE) { + mSwipeAnimationTargetSet.finishController(true, null, true); + } else { + long duration = (long) (Math.min(mProgress - PROGRESS_TO_END_GESTURE, 1) + * MAX_SWIPE_DURATION / Math.abs(PROGRESS_TO_END_GESTURE)); + if (duration < 0) { + duration = MIN_SWIPE_DURATION; + } + + ValueAnimator anim = ValueAnimator.ofFloat(mProgress, PROGRESS_TO_END_GESTURE); + anim.addUpdateListener(a -> { + float p = (Float) anim.getAnimatedValue(); + mTransformParams.setProgress(p); + mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams); + }); + anim.setDuration(duration); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mSwipeAnimationTargetSet.finishController(true, null, true); + } + }); + anim.start(); + } + } + } + + @Override + public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) { + mSwipeAnimationTargetSet = targetSet; + Rect overviewStackBounds = new Rect(0, 0, mDP.widthPx, mDP.heightPx); + RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mRunningTaskId); + + mDP.updateIsSeascape(mContext.getSystemService(WindowManager.class)); + if (runningTaskTarget != null) { + mClipAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget); + } + mClipAnimationHelper.prepareAnimation(mDP, false /* isOpening */); + + overviewStackBounds + .inset(-overviewStackBounds.width() / 5, -overviewStackBounds.height() / 5); + mClipAnimationHelper.updateTargetRect(overviewStackBounds); + mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams); + + if (mState != STATE_NOT_FINISHED) { + finishAnimationTargetSet(); + } + } + + @Override + public void onRecentsAnimationCanceled() { } + + private float getDisplacement(MotionEvent ev) { + if (mNavBarPosition.isRightEdge()) { + return ev.getX() - mDownPos.x; + } else if (mNavBarPosition.isLeftEdge()) { + return mDownPos.x - ev.getX(); + } else { + return ev.getY() - mDownPos.y; + } + } + + @Override + public boolean allowInterceptByParent() { + return !mPassedPilferInputSlop; + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java index a1e5d47a5..f5cf654b1 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java @@ -33,6 +33,7 @@ public interface InputConsumer { int TYPE_SCREEN_PINNED = 1 << 6; int TYPE_OVERVIEW_WITHOUT_FOCUS = 1 << 7; int TYPE_RESET_GESTURE = 1 << 8; + int TYPE_FALLBACK_NO_BUTTON = 1 << 9; String[] NAMES = new String[] { "TYPE_NO_OP", // 0 @@ -44,6 +45,7 @@ public interface InputConsumer { "TYPE_SCREEN_PINNED", // 6 "TYPE_OVERVIEW_WITHOUT_FOCUS", // 7 "TYPE_RESET_GESTURE", // 8 + "TYPE_FALLBACK_NO_BUTTON", // 9 }; InputConsumer NO_OP = () -> TYPE_NO_OP; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java index 6bc543f07..4c137d3bf 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java @@ -79,7 +79,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC private static final String UP_EVT = "OtherActivityInputConsumer.UP"; // TODO: Move to quickstep contract - private static final float QUICKSTEP_TOUCH_SLOP_RATIO = 3; + public static final float QUICKSTEP_TOUCH_SLOP_RATIO = 3; private final CachedEventDispatcher mRecentsViewDispatcher = new CachedEventDispatcher(); private final RunningTaskInfo mRunningTask; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java index 56cba2192..8eede81b8 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java @@ -39,7 +39,7 @@ public class ResetGestureInputConsumer implements InputConsumer { public void onMotionEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN && mSwipeSharedState.getActiveListener() != null) { - mSwipeSharedState.clearAllState(); + mSwipeSharedState.clearAllState(false /* finishAnimation */); } } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java index 9eda2f9d4..07e96869e 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java @@ -17,19 +17,15 @@ package com.android.quickstep.util; import android.animation.Animator; import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; import android.view.View; import android.view.ViewGroup; import androidx.annotation.Nullable; -import androidx.dynamicanimation.animation.SpringForce; import com.android.launcher3.CellLayout; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherAnimUtils.ViewProgressProperty; import com.android.launcher3.LauncherState; -import com.android.launcher3.LauncherStateManager; import com.android.launcher3.LauncherStateManager.AnimationConfig; import com.android.launcher3.R; import com.android.launcher3.ShortcutAndWidgetContainer; @@ -40,6 +36,7 @@ import com.android.launcher3.anim.SpringObjectAnimator; import java.util.ArrayList; import java.util.List; +import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.anim.Interpolators.LINEAR; @@ -147,9 +144,8 @@ public class StaggeredWorkspaceAnim { long startDelay = (long) ((invertedRow + 1) * APP_CLOSE_ROW_START_DELAY_MS); v.setTranslationY(mSpringTransY); - SpringObjectAnimator springTransY = new SpringObjectAnimator<>( - new ViewProgressProperty(v, View.TRANSLATION_Y), "staggeredSpringTransY", 1f, - DAMPING_RATIO, STIFFNESS, mSpringTransY, 0); + SpringObjectAnimator springTransY = new SpringObjectAnimator<>(v, VIEW_TRANSLATE_Y, + 1f, DAMPING_RATIO, STIFFNESS, mSpringTransY, 0); springTransY.setStartDelay(startDelay); mAnimators.add(springTransY); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java index df9efa247..381c27a28 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java @@ -106,6 +106,10 @@ public class SwipeAnimationTargetSet extends RemoteAnimationTargetSet { finishController(false /* toRecents */, null, false /* sendUserLeaveHint */); } + public void finishAnimation() { + finishController(true /* toRecents */, null, false /* sendUserLeaveHint */); + } + public interface SwipeAnimationListener { void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java index 5b2e27e53..03441c87e 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java @@ -33,9 +33,11 @@ import android.graphics.Canvas; import android.graphics.Rect; import android.os.Build; import android.util.AttributeSet; +import android.view.MotionEvent; import android.view.View; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.Hotseat; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherStateManager.StateListener; @@ -250,4 +252,16 @@ public class LauncherRecentsView extends RecentsView<Launcher> implements StateL setDisallowScrollToClearAll(!hasClearAllButton); } } + + @Override + protected boolean shouldStealTouchFromSiblingsBelow(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + // Allow touches to go through to the hotseat. + Hotseat hotseat = mActivity.getHotseat(); + boolean touchingHotseat = hotseat.isShown() + && mActivity.getDragLayer().isEventOverView(hotseat, ev, this); + return !touchingHotseat; + } + return super.shouldStealTouchFromSiblingsBelow(ev); + } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java index 9058e7eda..a98df0fa1 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java @@ -16,9 +16,13 @@ package com.android.quickstep.views; +import static androidx.dynamicanimation.animation.DynamicAnimation.MIN_VISIBLE_CHANGE_PIXELS; + import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS; import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; +import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; +import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; import static com.android.launcher3.Utilities.EDGE_NAV_BAR; import static com.android.launcher3.Utilities.squaredHypot; import static com.android.launcher3.Utilities.squaredTouchSlop; @@ -79,7 +83,6 @@ import com.android.launcher3.BaseActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Insettable; import com.android.launcher3.InvariantDeviceProfile; -import com.android.launcher3.LauncherAnimUtils.ViewProgressProperty; import com.android.launcher3.LauncherState; import com.android.launcher3.PagedView; import com.android.launcher3.R; @@ -124,10 +127,6 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl private static final String TAG = RecentsView.class.getSimpleName(); - public static final float SPRING_MIN_VISIBLE_CHANGE = 0.001f; - public static final float SPRING_DAMPING_RATIO = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY; - public static final float SPRING_STIFFNESS = SpringForce.STIFFNESS_MEDIUM; - public static final FloatProperty<RecentsView> CONTENT_ALPHA = new FloatProperty<RecentsView>("contentAlpha") { @Override @@ -521,6 +520,10 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl // Do not let touch escape to siblings below this view. + return isHandlingTouch() || shouldStealTouchFromSiblingsBelow(ev); + } + + protected boolean shouldStealTouchFromSiblingsBelow(MotionEvent ev) { return true; } @@ -1027,9 +1030,10 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl private void addDismissedTaskAnimations(View taskView, AnimatorSet anim, long duration) { addAnim(ObjectAnimator.ofFloat(taskView, ALPHA, 0), duration, ACCEL_2, anim); if (QUICKSTEP_SPRINGS.get() && taskView instanceof TaskView) - addAnim(new SpringObjectAnimator<>(new ViewProgressProperty(taskView, - View.TRANSLATION_Y), "taskViewTransY", SPRING_MIN_VISIBLE_CHANGE, - SPRING_DAMPING_RATIO, SPRING_STIFFNESS, 0, -taskView.getHeight()), + addAnim(new SpringObjectAnimator<>(taskView, VIEW_TRANSLATE_Y, + MIN_VISIBLE_CHANGE_PIXELS, SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY, + SpringForce.STIFFNESS_MEDIUM, + 0, -taskView.getHeight()), duration, LINEAR, anim); else { addAnim(ObjectAnimator.ofFloat(taskView, TRANSLATION_Y, -taskView.getHeight()), @@ -1105,10 +1109,10 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl int scrollDiff = newScroll[i] - oldScroll[i] + offset; if (scrollDiff != 0) { if (QUICKSTEP_SPRINGS.get() && child instanceof TaskView) { - addAnim(new SpringObjectAnimator<>( - new ViewProgressProperty(child, View.TRANSLATION_X), - "taskViewTransX", SPRING_MIN_VISIBLE_CHANGE, SPRING_DAMPING_RATIO, - SPRING_STIFFNESS, 0, scrollDiff), duration, ACCEL, anim); + addAnim(new SpringObjectAnimator<>(child, VIEW_TRANSLATE_X, + MIN_VISIBLE_CHANGE_PIXELS, SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY, + SpringForce.STIFFNESS_MEDIUM, + 0, scrollDiff), duration, ACCEL, anim); } else { addAnim(ObjectAnimator.ofFloat(child, TRANSLATION_X, scrollDiff), duration, ACCEL, anim); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java index 6f10b42fb..d55a52044 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java @@ -367,8 +367,11 @@ public class TaskThumbnailView extends View { } mRotated = isRotated; - updateOverlay(); invalidate(); + + // Update can be called from {@link #onSizeChanged} during layout, post handling of overlay + // as overlay could modify the views in the overlay as a side effect of its update. + post(this::updateOverlay); } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java index e7e41893c..b26fdce92 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java @@ -368,6 +368,9 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { } else { mSnapshotView.setThumbnail(null, null); setIcon(null); + // Reset the task thumbnail reference as well (it will be fetched from the cache or + // reloaded next time we need it) + mTask.thumbnail = null; } } @@ -488,9 +491,6 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { mSnapshotView.setThumbnail(mTask, null); setOverlayEnabled(false); onTaskListVisibilityChanged(false); - if (mTask != null) { - mTask.thumbnail = null; - } } @Override diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index 82d1aa672..71259fd7a 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -24,7 +24,7 @@ <dimen name="recents_page_spacing">10dp</dimen> <dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen> - <dimen name="overview_peek_distance">32dp</dimen> + <dimen name="overview_peek_distance">96dp</dimen> <!-- The speed in dp/s at which the user needs to be scrolling in recents such that we start loading full resolution screenshots. --> @@ -66,7 +66,8 @@ <dimen name="shelf_surface_offset">24dp</dimen> <!-- Assistant Gestures --> - <dimen name="gestures_assistant_size">48dp</dimen> + <!-- Distance from the vertical edges of the screen in which assist gestures are recognized --> + <dimen name="gestures_assistant_width">48dp</dimen> <dimen name="gestures_assistant_drag_threshold">55dp</dimen> <!-- Distance to move elements when swiping up to go home from launcher --> diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java index 864316095..44324cb2e 100644 --- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java +++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java @@ -765,7 +765,7 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans LauncherAnimationRunner.AnimationResult result) { if (!mLauncher.hasBeenResumed()) { // If launcher is not resumed, wait until new async-frame after resume - mLauncher.setOnResumeCallback(() -> + mLauncher.addOnResumeCallback(() -> postAsyncCallback(mHandler, () -> onCreateAnimation(targetCompats, result))); return; diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java index ab24f5f5a..85a954507 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java @@ -23,6 +23,7 @@ import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; +import com.android.quickstep.SysUINavigationMode; /** * Definition for AllApps state @@ -63,7 +64,13 @@ public class AllAppsState extends LauncherState { public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) { ScaleAndTranslation scaleAndTranslation = LauncherState.OVERVIEW .getWorkspaceScaleAndTranslation(launcher); - scaleAndTranslation.scale = 1; + if (SysUINavigationMode.getMode(launcher) == SysUINavigationMode.Mode.NO_BUTTON) { + float normalScale = 1; + // Scale down halfway to where we'd be in overview, to prepare for a potential pause. + scaleAndTranslation.scale = (scaleAndTranslation.scale + normalScale) / 2; + } else { + scaleAndTranslation.scale = 1; + } return scaleAndTranslation; } diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java index fee18204e..f5ba3725d 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java @@ -17,17 +17,22 @@ package com.android.launcher3.uioverrides.touchcontrollers; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_MOVE; +import static android.view.MotionEvent.ACTION_UP; +import static android.view.MotionEvent.ACTION_CANCEL; +import android.graphics.PointF; import android.os.RemoteException; import android.util.Log; +import android.util.SparseArray; import android.view.MotionEvent; import android.view.ViewConfiguration; +import android.view.Window; +import android.view.WindowManager; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; -import com.android.launcher3.touch.TouchEventTranslator; import com.android.launcher3.util.TouchController; import com.android.quickstep.RecentsModel; import com.android.systemui.shared.recents.ISystemUiProxy; @@ -36,18 +41,29 @@ import java.io.PrintWriter; /** * TouchController for handling touch events that get sent to the StatusBar. Once the - * Once the event delta y passes the touch slop, the events start getting forwarded. + * Once the event delta mDownY passes the touch slop, the events start getting forwarded. * All events are offset by initial Y value of the pointer. */ public class StatusBarTouchController implements TouchController { private static final String TAG = "StatusBarController"; + /** + * Window flag: Enable touches to slide out of a window into neighboring + * windows in mid-gesture instead of being captured for the duration of + * the gesture. + * + * This flag changes the behavior of touch focus for this window only. + * Touches can slide out of the window but they cannot necessarily slide + * back in (unless the other window with touch focus permits it). + */ + private static final int FLAG_SLIPPERY = 0x20000000; + protected final Launcher mLauncher; - protected final TouchEventTranslator mTranslator; private final float mTouchSlop; private ISystemUiProxy mSysUiProxy; private int mLastAction; + private final SparseArray<PointF> mDownEvents; /* If {@code false}, this controller should not handle the input {@link MotionEvent}.*/ private boolean mCanIntercept; @@ -56,7 +72,7 @@ public class StatusBarTouchController implements TouchController { mLauncher = l; // Guard against TAPs by increasing the touch slop. mTouchSlop = 2 * ViewConfiguration.get(l).getScaledTouchSlop(); - mTranslator = new TouchEventTranslator((MotionEvent ev)-> dispatchTouchEvent(ev)); + mDownEvents = new SparseArray<>(); } @Override @@ -64,7 +80,6 @@ public class StatusBarTouchController implements TouchController { writer.println(prefix + "mCanIntercept:" + mCanIntercept); writer.println(prefix + "mLastAction:" + MotionEvent.actionToString(mLastAction)); writer.println(prefix + "mSysUiProxy available:" + (mSysUiProxy != null)); - } private void dispatchTouchEvent(MotionEvent ev) { @@ -81,26 +96,31 @@ public class StatusBarTouchController implements TouchController { @Override public final boolean onControllerInterceptTouchEvent(MotionEvent ev) { int action = ev.getActionMasked(); + int idx = ev.getActionIndex(); + int pid = ev.getPointerId(idx); if (action == ACTION_DOWN) { mCanIntercept = canInterceptTouch(ev); if (!mCanIntercept) { return false; } - mTranslator.reset(); - mTranslator.setDownParameters(0, ev); + mDownEvents.put(pid, new PointF(ev.getX(), ev.getY())); } else if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { - // Check!! should only set it only when threshold is not entered. - mTranslator.setDownParameters(ev.getActionIndex(), ev); + // Check!! should only set it only when threshold is not entered. + mDownEvents.put(pid, new PointF(ev.getX(idx), ev.getY(idx))); } if (!mCanIntercept) { return false; } if (action == ACTION_MOVE) { - float dy = ev.getY() - mTranslator.getDownY(); - float dx = ev.getX() - mTranslator.getDownX(); - if (dy > mTouchSlop && dy > Math.abs(dx)) { - mTranslator.dispatchDownEvents(ev); - mTranslator.processMotionEvent(ev); + float dy = ev.getY(idx) - mDownEvents.get(pid).y; + float dx = ev.getX(idx) - mDownEvents.get(pid).x; + // Currently input dispatcher will not do touch transfer if there are more than + // one touch pointer. Hence, even if slope passed, only set the slippery flag + // when there is single touch event. (context: InputDispatcher.cpp line 1445) + if (dy > mTouchSlop && dy > Math.abs(dx) && ev.getPointerCount() == 1) { + ev.setAction(ACTION_DOWN); + dispatchTouchEvent(ev); + setWindowSlippery(true); return true; } if (Math.abs(dx) > mTouchSlop) { @@ -110,13 +130,27 @@ public class StatusBarTouchController implements TouchController { return false; } - @Override public final boolean onControllerTouchEvent(MotionEvent ev) { - mTranslator.processMotionEvent(ev); + if (ev.getAction() == ACTION_UP || ev.getAction() == ACTION_CANCEL) { + dispatchTouchEvent(ev); + setWindowSlippery(false); + return true; + } return true; } + private void setWindowSlippery(boolean enable) { + Window w = mLauncher.getWindow(); + WindowManager.LayoutParams wlp = w.getAttributes(); + if (enable) { + wlp.flags |= FLAG_SLIPPERY; + } else { + wlp.flags &= ~FLAG_SLIPPERY; + } + w.setAttributes(wlp); + } + private boolean canInterceptTouch(MotionEvent ev) { if (!mLauncher.isInState(LauncherState.NORMAL) || AbstractFloatingView.getTopOpenViewWithType(mLauncher, @@ -132,4 +166,4 @@ public class StatusBarTouchController implements TouchController { mSysUiProxy = RecentsModel.INSTANCE.get(mLauncher).getSystemUiProxy(); return mSysUiProxy != null; } -} +}
\ No newline at end of file diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java index 0a73b8b19..0738affa9 100644 --- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java +++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java @@ -33,7 +33,6 @@ import android.content.pm.ResolveInfo; import com.android.systemui.shared.system.PackageManagerWrapper; -import com.android.systemui.shared.system.QuickStepContract; import java.util.ArrayList; /** @@ -58,7 +57,9 @@ public final class OverviewComponentObserver { private String mUpdateRegisteredPackage; private ActivityControlHelper mActivityControlHelper; private Intent mOverviewIntent; + private Intent mHomeIntent; private int mSystemUiStateFlags; + private boolean mIsHomeAndOverviewSame; public OverviewComponentObserver(Context context) { mContext = context; @@ -93,11 +94,14 @@ public final class OverviewComponentObserver { final String overviewIntentCategory; ComponentName overviewComponent; + mHomeIntent = null; + if ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0 && (defaultHome == null || mMyHomeComponent.equals(defaultHome))) { // User default home is same as out home app. Use Overview integrated in Launcher. overviewComponent = mMyHomeComponent; mActivityControlHelper = new LauncherActivityControllerHelper(); + mIsHomeAndOverviewSame = true; overviewIntentCategory = Intent.CATEGORY_HOME; if (mUpdateRegisteredPackage != null) { @@ -109,8 +113,12 @@ public final class OverviewComponentObserver { // The default home app is a different launcher. Use the fallback Overview instead. overviewComponent = new ComponentName(mContext, RecentsActivity.class); mActivityControlHelper = new FallbackActivityControllerHelper(); + mIsHomeAndOverviewSame = false; overviewIntentCategory = Intent.CATEGORY_DEFAULT; + mHomeIntent = new Intent(Intent.ACTION_MAIN) + .addCategory(Intent.CATEGORY_HOME) + .setComponent(defaultHome); // User's default home app can change as a result of package updates of this app (such // as uninstalling the app or removing the "Launcher" feature in an update). // Listen for package updates of this app (and remove any previously attached @@ -135,6 +143,9 @@ public final class OverviewComponentObserver { .addCategory(overviewIntentCategory) .setComponent(overviewComponent) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + if (mHomeIntent == null) { + mHomeIntent = mOverviewIntent; + } } /** @@ -159,6 +170,20 @@ public final class OverviewComponentObserver { } /** + * Get the current intent for going to the home activity. + */ + public Intent getHomeIntent() { + return mHomeIntent; + } + + /** + * Returns true if home and overview are same activity. + */ + public boolean isHomeAndOverviewSame() { + return mIsHomeAndOverviewSame; + } + + /** * Get the current activity control helper for managing interactions to the overview activity. * * @return the current activity control helper diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepProcessInitializer.java b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java index befeee0db..7bfa9a0f9 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepProcessInitializer.java +++ b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java @@ -15,6 +15,7 @@ */ package com.android.quickstep; +import android.app.ActivityManager; import android.content.Context; import android.content.pm.PackageManager; import android.os.UserManager; @@ -22,17 +23,29 @@ import android.util.Log; import com.android.launcher3.BuildConfig; import com.android.launcher3.MainProcessInitializer; +import com.android.launcher3.Utilities; import com.android.systemui.shared.system.ThreadedRendererCompat; @SuppressWarnings("unused") public class QuickstepProcessInitializer extends MainProcessInitializer { private static final String TAG = "QuickstepProcessInitializer"; + private static final int HEAP_LIMIT_MB = 250; public QuickstepProcessInitializer(Context context) { } @Override protected void init(Context context) { + if (Utilities.IS_DEBUG_DEVICE) { + try { + // Trigger a heap dump if the PSS reaches beyond the target heap limit + final ActivityManager am = context.getSystemService(ActivityManager.class); + am.setWatchHeapLimit(HEAP_LIMIT_MB * 1024 * 1024); + } catch (SecurityException e) { + // Do nothing + } + } + // Workaround for b/120550382, an external app can cause the launcher process to start for // a work profile user which we do not support. Disable the application immediately when we // detect this to be the case. diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java index 353837312..f27ba8538 100644 --- a/quickstep/src/com/android/quickstep/RecentTasksList.java +++ b/quickstep/src/com/android/quickstep/RecentTasksList.java @@ -84,7 +84,7 @@ public class RecentTasksList extends TaskStackChangeListener { final int requestLoadId = mChangeId; Runnable resultCallback = callback == null ? () -> { } - : () -> callback.accept(mTasks); + : () -> callback.accept(copyOf(mTasks)); if (mLastLoadedId == mChangeId && (!mLastLoadHadKeysOnly || loadKeysOnly)) { // The list is up to date, callback with the same list @@ -183,4 +183,14 @@ public class RecentTasksList extends TaskStackChangeListener { return allTasks; } + + private ArrayList<Task> copyOf(ArrayList<Task> tasks) { + ArrayList<Task> newTasks = new ArrayList<>(); + for (int i = 0; i < tasks.size(); i++) { + Task t = tasks.get(i); + newTasks.add(new Task(t.key, t.colorPrimary, t.colorBackground, t.isDockable, + t.isLocked, t.taskDescription, t.topActivity)); + } + return newTasks; + } }
\ No newline at end of file diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java index c8aed8191..050bdff09 100644 --- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java +++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java @@ -110,7 +110,7 @@ public class LayoutUtils { float y = insets.top + Math.max(topIconMargin, (launcherVisibleHeight - extraVerticalSpace - outHeight) / 2); outRect.set(Math.round(x), Math.round(y), - Math.round(x + outWidth), Math.round(y + outHeight)); + Math.round(x) + Math.round(outWidth), Math.round(y) + Math.round(outHeight)); } public static int getShelfTrackingDistance(Context context, DeviceProfile dp) { diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java index 36521e5cc..b6ddb5fd1 100644 --- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java +++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java @@ -18,6 +18,7 @@ package com.android.quickstep.views; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.anim.Interpolators.ACCEL; +import static com.android.launcher3.anim.Interpolators.ACCEL_2; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; @@ -29,6 +30,7 @@ import android.graphics.Path; import android.graphics.Path.Direction; import android.graphics.Path.Op; import android.util.AttributeSet; +import android.view.animation.Interpolator; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; @@ -69,6 +71,9 @@ public class ShelfScrimView extends ScrimView implements NavigationModeChangeLis private int mMidAlpha; private float mMidProgress; + private Interpolator mBeforeMidProgressColorInterpolator = ACCEL; + private Interpolator mAfterMidProgressColorInterpolator = ACCEL; + private float mShiftRange; private final float mShelfOffset; @@ -120,6 +125,15 @@ public class ShelfScrimView extends ScrimView implements NavigationModeChangeLis @Override public void onNavigationModeChanged(Mode newMode) { mSysUINavigationMode = newMode; + // Note that these interpolators are inverted because progress goes 1 to 0. + if (mSysUINavigationMode == Mode.NO_BUTTON) { + // Show the shelf more quickly before reaching overview progress. + mBeforeMidProgressColorInterpolator = ACCEL_2; + mAfterMidProgressColorInterpolator = ACCEL; + } else { + mBeforeMidProgressColorInterpolator = ACCEL; + mAfterMidProgressColorInterpolator = Interpolators.clampToProgress(ACCEL, 0.5f, 1f); + } } @Override @@ -171,7 +185,7 @@ public class ShelfScrimView extends ScrimView implements NavigationModeChangeLis mRemainingScreenColor = 0; int alpha = Math.round(Utilities.mapToRange( - mProgress, mMidProgress, 1, mMidAlpha, 0, ACCEL)); + mProgress, mMidProgress, 1, mMidAlpha, 0, mBeforeMidProgressColorInterpolator)); mShelfColor = setColorAlphaBound(mEndScrim, alpha); } else { mDragHandleOffset += mShiftRange * (mMidProgress - mProgress); @@ -179,7 +193,7 @@ public class ShelfScrimView extends ScrimView implements NavigationModeChangeLis // Note that these ranges and interpolators are inverted because progress goes 1 to 0. int alpha = Math.round( Utilities.mapToRange(mProgress, (float) 0, mMidProgress, (float) mEndAlpha, - (float) mMidAlpha, Interpolators.clampToProgress(ACCEL, 0.5f, 1f))); + (float) mMidAlpha, mAfterMidProgressColorInterpolator)); mShelfColor = setColorAlphaBound(mEndScrim, alpha); int remainingScrimAlpha = Math.round( diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java index 013591171..e5f949b88 100644 --- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java +++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java @@ -43,10 +43,12 @@ import androidx.test.uiautomator.Until; import com.android.launcher3.tapl.LauncherInstrumentation; import com.android.launcher3.testcomponent.TestCommandReceiver; +import com.android.launcher3.util.rule.FailureWatcher; import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.RuleChain; import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.model.Statement; @@ -62,10 +64,14 @@ public class FallbackRecentsTest { private final LauncherInstrumentation mLauncher; private final ActivityInfo mOtherLauncherActivity; - @Rule public final TestRule mDisableHeadsUpNotification = disableHeadsUpNotification(); - @Rule public final TestRule mQuickstepOnOffExecutor; + @Rule + public final TestRule mDisableHeadsUpNotification = disableHeadsUpNotification(); - @Rule public final TestRule mSetLauncherCommand; + @Rule + public final TestRule mSetLauncherCommand; + + @Rule + public final TestRule mOrderSensitiveRules; public FallbackRecentsTest() throws RemoteException { Instrumentation instrumentation = getInstrumentation(); @@ -74,7 +80,10 @@ public class FallbackRecentsTest { mDevice.setOrientationNatural(); mLauncher = new LauncherInstrumentation(instrumentation); - mQuickstepOnOffExecutor = new NavigationModeSwitchRule(mLauncher); + mOrderSensitiveRules = RuleChain. + outerRule(new NavigationModeSwitchRule(mLauncher)). + around(new FailureWatcher(mDevice)); + mOtherLauncherActivity = context.getPackageManager().queryIntentActivities( getHomeIntentInPackage(context), MATCH_DISABLED_COMPONENTS).get(0).activityInfo; diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java index 90763b871..3b35c86af 100644 --- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java +++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java @@ -159,13 +159,20 @@ public class NavigationModeSwitchRule implements TestRule { } for (int i = 0; i != 100; ++i) { - if (mLauncher.getNavigationModel() == expectedMode) { - Thread.sleep(5000); - return; - } + if (mLauncher.getNavigationModel() == expectedMode) break; + Thread.sleep(100); + } + Assert.assertTrue("Couldn't switch to " + overlayPackage, + mLauncher.getNavigationModel() == expectedMode); + + for (int i = 0; i != 100; ++i) { + if (mLauncher.getNavigationModeMismatchError() == null) break; Thread.sleep(100); } - Assert.fail("Couldn't switch to " + overlayPackage); + final String error = mLauncher.getNavigationModeMismatchError(); + Assert.assertTrue("Switching nav mode: " + error, error == null); + + Thread.sleep(5000); } private void setOverlayPackageEnabled(String overlayPackage, boolean enable) diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java index f02859f46..9e3bf2f56 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java @@ -208,7 +208,7 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { @Test @NavigationModeSwitch - @PortraitLandscape +// @PortraitLandscape public void testBackground() throws Exception { startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)); final Background background = mLauncher.getBackground(); diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 7085c6052..1619e3645 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -43,6 +43,7 @@ import android.view.ViewDebug; import android.widget.TextView; import com.android.launcher3.Launcher.OnResumeCallback; +import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; import com.android.launcher3.dot.DotInfo; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.graphics.DrawableFactory; @@ -227,6 +228,18 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, applyFromWorkspaceItem(info, false); } + @Override + public void setAccessibilityDelegate(AccessibilityDelegate delegate) { + if (delegate instanceof LauncherAccessibilityDelegate) { + super.setAccessibilityDelegate(delegate); + } else { + // NO-OP + // Workaround for b/129745295 where RecyclerView is setting our Accessibility + // delegate incorrectly. There are no cases when we shouldn't be using the + // LauncherAccessibilityDelegate for BubbleTextView. + } + } + public void applyFromWorkspaceItem(WorkspaceItemInfo info, boolean promiseStateChanged) { applyIconAndLabel(info); setTag(info); diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 80ea78f19..d9af4da8e 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -244,7 +244,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, @Thunk boolean mWorkspaceLoading = true; - private OnResumeCallback mOnResumeCallback; + private ArrayList<OnResumeCallback> mOnResumeCallbacks = new ArrayList<>(); private ViewOnDrawExecutor mPendingExecutor; @@ -430,6 +430,10 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, super.onConfigurationChanged(newConfig); } + public void reload() { + onIdpChanged(mDeviceProfile.inv); + } + private boolean supportsFakeLandscapeUI() { return FeatureFlags.FAKE_LANDSCAPE_UI.get() && !mRotationHelper.homeScreenCanRotate(); } @@ -865,6 +869,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, @Override protected void onStop() { super.onStop(); + if (mLauncherCallbacks != null) { mLauncherCallbacks.onStop(); } @@ -947,7 +952,10 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, mHandler.removeCallbacks(mHandleDeferredResume); Utilities.postAsyncCallback(mHandler, mHandleDeferredResume); - setOnResumeCallback(null); + for (OnResumeCallback cb : mOnResumeCallbacks) { + cb.onLauncherResume(); + } + mOnResumeCallbacks.clear(); if (mLauncherCallbacks != null) { mLauncherCallbacks.onResume(); @@ -1801,6 +1809,16 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, android.util.Log.d(TestProtocol.NO_START_TAG, "startActivitySafely outer"); } + + if (!hasBeenResumed()) { + // Workaround an issue where the WM launch animation is clobbered when finishing the + // recents animation into launcher. Defer launching the activity until Launcher is + // next resumed. + addOnResumeCallback(() -> startActivitySafely(v, intent, item, sourceContainer)); + UiFactory.clearSwipeSharedState(true /* finishAnimation */); + return true; + } + boolean success = super.startActivitySafely(v, intent, item, sourceContainer); if (success && v instanceof BubbleTextView) { // This is set to the view that launched the activity that navigated the user away @@ -1809,7 +1827,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, // state when we return to launcher. BubbleTextView btv = (BubbleTextView) v; btv.setStayPressed(true); - setOnResumeCallback(btv); + addOnResumeCallback(btv); } return success; } @@ -1857,11 +1875,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, return result; } - public void setOnResumeCallback(OnResumeCallback callback) { - if (mOnResumeCallback != null) { - mOnResumeCallback.onLauncherResume(); - } - mOnResumeCallback = callback; + public void addOnResumeCallback(OnResumeCallback callback) { + mOnResumeCallbacks.add(callback); } /** diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java index 04f2b5276..74362ed5a 100644 --- a/src/com/android/launcher3/LauncherAnimUtils.java +++ b/src/com/android/launcher3/LauncherAnimUtils.java @@ -17,6 +17,7 @@ package com.android.launcher3; import android.graphics.drawable.Drawable; +import android.util.FloatProperty; import android.util.Property; import android.view.View; import android.view.ViewGroup.LayoutParams; @@ -47,15 +48,15 @@ public class LauncherAnimUtils { } }; - public static final Property<View, Float> SCALE_PROPERTY = - new Property<View, Float>(Float.class, "scale") { + public static final FloatProperty<View> SCALE_PROPERTY = + new FloatProperty<View>("scale") { @Override public Float get(View view) { return view.getScaleX(); } @Override - public void set(View view, Float scale) { + public void setValue(View view, float scale) { view.setScaleX(scale); view.setScaleY(scale); } @@ -92,23 +93,31 @@ public class LauncherAnimUtils { } }; - public static class ViewProgressProperty implements ProgressInterface { - View mView; - Property<View, Float> mProperty; - - public ViewProgressProperty(View view, Property<View, Float> property) { - mView = view; - mProperty = property; - } - - @Override - public void setProgress(float progress) { - mProperty.set(mView, progress); - } - - @Override - public float getProgress() { - return mProperty.get(mView); - } - } + public static final FloatProperty<View> VIEW_TRANSLATE_X = + View.TRANSLATION_X instanceof FloatProperty ? (FloatProperty) View.TRANSLATION_X + : new FloatProperty<View>("translateX") { + @Override + public void setValue(View view, float v) { + view.setTranslationX(v); + } + + @Override + public Float get(View view) { + return view.getTranslationX(); + } + }; + + public static final FloatProperty<View> VIEW_TRANSLATE_Y = + View.TRANSLATION_Y instanceof FloatProperty ? (FloatProperty) View.TRANSLATION_Y + : new FloatProperty<View>("translateY") { + @Override + public void setValue(View view, float v) { + view.setTranslationY(v); + } + + @Override + public Float get(View view) { + return view.getTranslationY(); + } + }; } diff --git a/src/com/android/launcher3/LauncherAppTransitionManager.java b/src/com/android/launcher3/LauncherAppTransitionManager.java index 4bddc6a4e..c55c120a6 100644 --- a/src/com/android/launcher3/LauncherAppTransitionManager.java +++ b/src/com/android/launcher3/LauncherAppTransitionManager.java @@ -17,6 +17,7 @@ package com.android.launcher3; +import android.animation.Animator; import android.app.ActivityOptions; import android.content.Context; import android.graphics.Rect; @@ -55,4 +56,15 @@ public class LauncherAppTransitionManager implements ResourceBasedOverride { public boolean supportsAdaptiveIconAnimation() { return false; } + + /** + * Number of animations which run on state properties. + */ + public int getStateElementAnimationsCount() { + return 0; + } + + public Animator createStateElementAnimation(int index, float... values) { + throw new RuntimeException("Unknown gesture animation " + index); + } } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index ac392a6e6..d79f5d5a9 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -301,7 +301,8 @@ public class LauncherModel extends BroadcastReceiver } } } else if (IS_DOGFOOD_BUILD && ACTION_FORCE_ROLOAD.equals(action)) { - forceReload(); + Launcher l = (Launcher) getCallback(); + l.reload(); } } diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java index 148064889..dcfd272b4 100644 --- a/src/com/android/launcher3/LauncherState.java +++ b/src/com/android/launcher3/LauncherState.java @@ -17,7 +17,18 @@ package com.android.launcher3; import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO; import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS; +import static android.view.View.VISIBLE; import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED; + +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE; +import static com.android.launcher3.anim.Interpolators.ACCEL; +import static com.android.launcher3.anim.Interpolators.DEACCEL; +import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7; +import static com.android.launcher3.anim.Interpolators.clampToProgress; import static com.android.launcher3.testing.TestProtocol.ALL_APPS_STATE_ORDINAL; import static com.android.launcher3.testing.TestProtocol.BACKGROUND_APP_STATE_ORDINAL; import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL; @@ -30,11 +41,11 @@ import static com.android.launcher3.states.RotationHelper.REQUEST_NONE; import android.view.animation.Interpolator; +import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.states.SpringLoadedState; import com.android.launcher3.uioverrides.UiFactory; import com.android.launcher3.uioverrides.states.AllAppsState; import com.android.launcher3.uioverrides.states.OverviewState; -import com.android.launcher3.userevent.nano.LauncherLogProto.Action; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import java.util.Arrays; @@ -272,6 +283,46 @@ public class LauncherState { } } + /** + * Prepares for a non-user controlled animation from fromState to this state. Preparations + * include: + * - Setting interpolators for various animations included in the state transition. + * - Setting some start values (e.g. scale) for views that are hidden but about to be shown. + */ + public void prepareForAtomicAnimation(Launcher launcher, LauncherState fromState, + AnimatorSetBuilder builder) { + if (this == NORMAL && fromState == OVERVIEW) { + builder.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL); + builder.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL); + builder.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f)); + builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL); + builder.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7); + Workspace workspace = launcher.getWorkspace(); + + // Start from a higher workspace scale, but only if we're invisible so we don't jump. + boolean isWorkspaceVisible = workspace.getVisibility() == VISIBLE; + if (isWorkspaceVisible) { + CellLayout currentChild = (CellLayout) workspace.getChildAt( + workspace.getCurrentPage()); + isWorkspaceVisible = currentChild.getVisibility() == VISIBLE + && currentChild.getShortcutsAndWidgets().getAlpha() > 0; + } + if (!isWorkspaceVisible) { + workspace.setScaleX(0.92f); + workspace.setScaleY(0.92f); + } + Hotseat hotseat = launcher.getHotseat(); + boolean isHotseatVisible = hotseat.getVisibility() == VISIBLE && hotseat.getAlpha() > 0; + if (!isHotseatVisible) { + hotseat.setScaleX(0.92f); + hotseat.setScaleY(0.92f); + } + } else if (this == NORMAL && fromState == OVERVIEW_PEEK) { + // Keep fully visible until the very end (when overview is offscreen) to make invisible. + builder.setInterpolator(ANIM_OVERVIEW_FADE, t -> t < 1 ? 0 : 1); + } + } + protected static void dispatchWindowStateChanged(Launcher launcher) { launcher.getWindow().getDecorView().sendAccessibilityEvent(TYPE_WINDOW_STATE_CHANGED); } diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java index 8b03691c8..2c8c20845 100644 --- a/src/com/android/launcher3/LauncherStateManager.java +++ b/src/com/android/launcher3/LauncherStateManager.java @@ -16,23 +16,7 @@ package com.android.launcher3; -import static android.view.View.VISIBLE; - import static com.android.launcher3.LauncherState.NORMAL; -import static com.android.launcher3.LauncherState.OVERVIEW; -import static com.android.launcher3.LauncherState.OVERVIEW_PEEK; -import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE; -import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE; -import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X; -import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE; -import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE; -import static com.android.launcher3.anim.Interpolators.ACCEL; -import static com.android.launcher3.anim.Interpolators.DEACCEL; -import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7; -import static com.android.launcher3.anim.Interpolators.INSTANT; -import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; -import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7; -import static com.android.launcher3.anim.Interpolators.clampToProgress; import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER; import android.animation.Animator; @@ -42,8 +26,6 @@ import android.os.Handler; import android.os.Looper; import android.util.Log; -import androidx.annotation.IntDef; - import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.AnimatorSetBuilder; @@ -58,6 +40,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import androidx.annotation.IntDef; + /** * TODO: figure out what kind of tests we can write for this * @@ -127,6 +111,9 @@ public class LauncherStateManager { private final Launcher mLauncher; private final ArrayList<StateListener> mListeners = new ArrayList<>(); + // Animators which are run on properties also controlled by state animations. + private Animator[] mStateElementAnimators; + private StateHandler[] mStateHandlers; private LauncherState mState = NORMAL; @@ -223,13 +210,18 @@ public class LauncherStateManager { } public void reapplyState(boolean cancelCurrentAnimation) { + boolean wasInAnimation = mConfig.mCurrentAnimation != null; if (cancelCurrentAnimation) { + cancelAllStateElementAnimation(); cancelAnimation(); } if (mConfig.mCurrentAnimation == null) { for (StateHandler handler : getStateHandlers()) { handler.setState(mState); } + if (wasInAnimation) { + onStateTransitionEnd(mState); + } } } @@ -262,6 +254,7 @@ public class LauncherStateManager { mConfig.reset(); if (!animated) { + cancelAllStateElementAnimation(); onStateTransitionStart(state); for (StateHandler handler : getStateHandlers()) { handler.setState(state); @@ -310,47 +303,7 @@ public class LauncherStateManager { */ public void prepareForAtomicAnimation(LauncherState fromState, LauncherState toState, AnimatorSetBuilder builder) { - if (fromState == NORMAL && toState == OVERVIEW) { - builder.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2); - builder.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2); - builder.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2); - builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7); - builder.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2); - - // Start from a higher overview scale, but only if we're invisible so we don't jump. - UiFactory.prepareToShowOverview(mLauncher); - } else if (fromState == OVERVIEW && toState == NORMAL) { - builder.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL); - builder.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL); - builder.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f)); - builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL); - builder.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7); - Workspace workspace = mLauncher.getWorkspace(); - - // Start from a higher workspace scale, but only if we're invisible so we don't jump. - boolean isWorkspaceVisible = workspace.getVisibility() == VISIBLE; - if (isWorkspaceVisible) { - CellLayout currentChild = (CellLayout) workspace.getChildAt( - workspace.getCurrentPage()); - isWorkspaceVisible = currentChild.getVisibility() == VISIBLE - && currentChild.getShortcutsAndWidgets().getAlpha() > 0; - } - if (!isWorkspaceVisible) { - workspace.setScaleX(0.92f); - workspace.setScaleY(0.92f); - } - Hotseat hotseat = workspace.getHotseat(); - boolean isHotseatVisible = hotseat.getVisibility() == VISIBLE && hotseat.getAlpha() > 0; - if (!isHotseatVisible) { - hotseat.setScaleX(0.92f); - hotseat.setScaleY(0.92f); - } - } else if (fromState == NORMAL && toState == OVERVIEW_PEEK) { - builder.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT); - } else if (fromState == OVERVIEW_PEEK && toState == NORMAL) { - // Keep fully visible until the very end (when overview is offscreen) to make invisible. - builder.setInterpolator(ANIM_OVERVIEW_FADE, t -> t < 1 ? 0 : 1); - } + toState.prepareForAtomicAnimation(mLauncher, fromState, builder); } public AnimatorSet createAtomicAnimation(LauncherState fromState, LauncherState toState, @@ -573,6 +526,47 @@ public class LauncherStateManager { mConfig.setAnimation(anim, null); } + private void cancelAllStateElementAnimation() { + if (mStateElementAnimators == null) { + return; + } + + for (Animator animator : mStateElementAnimators) { + if (animator != null) { + animator.cancel(); + } + } + } + + /** + * Cancels a currently running gesture animation + */ + public void cancelStateElementAnimation(int index) { + if (mStateElementAnimators == null) { + return; + } + if (mStateElementAnimators[index] != null) { + mStateElementAnimators[index].cancel(); + } + } + + public Animator createStateElementAnimation(int index, float... values) { + cancelStateElementAnimation(index); + LauncherAppTransitionManager latm = mLauncher.getAppTransitionManager(); + if (mStateElementAnimators == null) { + mStateElementAnimators = new Animator[latm.getStateElementAnimationsCount()]; + } + Animator anim = latm.createStateElementAnimation(index, values); + mStateElementAnimators[index] = anim; + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mStateElementAnimators[index] = null; + } + }); + return anim; + } + private void clearCurrentAnimation() { if (mConfig.mCurrentAnimation != null) { mConfig.mCurrentAnimation.removeListener(mConfig); diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index 80e17c9ff..2eeb132bb 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -16,6 +16,7 @@ package com.android.launcher3; +import static com.android.launcher3.Utilities.shouldDisableGestures; import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled; import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType; import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS; @@ -844,10 +845,11 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou * If we return true, onTouchEvent will be called and we do the actual * scrolling there. */ - acquireVelocityTrackerAndAddMovement(ev); // Skip touch handling if there are no pages to swipe - if (getChildCount() <= 0) return super.onInterceptTouchEvent(ev); + if (getChildCount() <= 0 || shouldDisableGestures(ev)) return false; + + acquireVelocityTrackerAndAddMovement(ev); /* * Shortcut the most recurring case: the user is in the dragging @@ -1093,7 +1095,7 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou @Override public boolean onTouchEvent(MotionEvent ev) { // Skip touch handling if there are no pages to swipe - if (getChildCount() <= 0) return false; + if (getChildCount() <= 0 || shouldDisableGestures(ev)) return false; acquireVelocityTrackerAndAddMovement(ev); @@ -1204,6 +1206,7 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou if (((initialScrollX >= mMaxScrollX) && (isVelocityXLeft || !isFling)) || ((initialScrollX <= mMinScrollX) && (!isVelocityXLeft || !isFling))) { mScroller.springBack(getScrollX(), mMinScrollX, mMaxScrollX); + mNextPage = getPageNearestToCenterOfScreen(); } else { mScroller.setInterpolator(mDefaultInterpolator); mScroller.fling(initialScrollX, -velocityX, diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java index 0cf6e44c6..55cb6f214 100644 --- a/src/com/android/launcher3/SecondaryDropTarget.java +++ b/src/com/android/launcher3/SecondaryDropTarget.java @@ -179,7 +179,7 @@ public class SecondaryDropTarget extends ButtonDropTarget implements OnAlarmList DeferredOnComplete deferred = (DeferredOnComplete) d.dragSource; if (target != null) { deferred.mPackageName = target.getPackageName(); - mLauncher.setOnResumeCallback(deferred); + mLauncher.addOnResumeCallback(deferred); } else { deferred.sendFailure(); } diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 7bdbb95f7..65aa3a775 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -128,6 +128,16 @@ public final class Utilities { public static final int EDGE_NAV_BAR = 1 << 8; /** + * Set on a motion event do disallow any gestures and only handle touch. + * See {@link MotionEvent#setEdgeFlags(int)}. + */ + public static final int FLAG_NO_GESTURES = 1 << 9; + + public static boolean shouldDisableGestures(MotionEvent ev) { + return (ev.getEdgeFlags() & FLAG_NO_GESTURES) == FLAG_NO_GESTURES; + } + + /** * Indicates if the device has a debug build. Should only be used to store additional info or * add extra logging and not for changing the app behavior. */ diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java index 065d06568..40c6b5f1b 100644 --- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java +++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java @@ -19,6 +19,8 @@ package com.android.launcher3; import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_SCALE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_TRANSLATE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE; @@ -104,7 +106,10 @@ public class WorkspaceStateTransitionAnimation { hotseat.setPivotY(workspacePivot[1]); } float hotseatScale = hotseatScaleAndTranslation.scale; - propertySetter.setFloat(hotseat, SCALE_PROPERTY, hotseatScale, scaleInterpolator); + Interpolator hotseatScaleInterpolator = builder.getInterpolator(ANIM_HOTSEAT_SCALE, + scaleInterpolator); + propertySetter.setFloat(hotseat, SCALE_PROPERTY, hotseatScale, + hotseatScaleInterpolator); float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0; propertySetter.setViewAlpha(hotseat, hotseatIconsAlpha, fadeInterpolator); @@ -125,10 +130,12 @@ public class WorkspaceStateTransitionAnimation { propertySetter.setFloat(mWorkspace, View.TRANSLATION_Y, scaleAndTranslation.translationY, translationInterpolator); + Interpolator hotseatTranslationInterpolator = builder.getInterpolator( + ANIM_HOTSEAT_TRANSLATE, translationInterpolator); propertySetter.setFloat(hotseat, View.TRANSLATION_Y, - hotseatScaleAndTranslation.translationY, translationInterpolator); + hotseatScaleAndTranslation.translationY, hotseatTranslationInterpolator); propertySetter.setFloat(mWorkspace.getPageIndicator(), View.TRANSLATION_Y, - hotseatScaleAndTranslation.translationY, translationInterpolator); + hotseatScaleAndTranslation.translationY, hotseatTranslationInterpolator); setScrim(propertySetter, state); } diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java index c62fc3d8f..4683893ea 100644 --- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java @@ -2,6 +2,8 @@ package com.android.launcher3.allapps; import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT; import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA; +import static com.android.launcher3.LauncherState.BACKGROUND_APP; +import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE; @@ -14,8 +16,8 @@ import static com.android.launcher3.util.SystemUiController.UI_STATE_ALL_APPS; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.util.FloatProperty; import android.util.Log; -import android.util.Property; import android.view.animation.Interpolator; import com.android.launcher3.DeviceProfile; @@ -24,19 +26,15 @@ import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherStateManager.AnimationConfig; import com.android.launcher3.LauncherStateManager.StateHandler; -import com.android.launcher3.ProgressInterface; import com.android.launcher3.R; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorSetBuilder; -import com.android.launcher3.anim.SpringObjectAnimator; import com.android.launcher3.anim.PropertySetter; +import com.android.launcher3.anim.SpringObjectAnimator; import com.android.launcher3.testing.TestProtocol; -import com.android.launcher3.util.MultiValueAlpha; import com.android.launcher3.util.Themes; import com.android.launcher3.views.ScrimView; -import androidx.dynamicanimation.animation.FloatPropertyCompat; - /** * Handles AllApps view transition. * 1) Slides all apps view using direct manipulation @@ -47,14 +45,13 @@ import androidx.dynamicanimation.animation.FloatPropertyCompat; * If release velocity < THRES1, snap according to either top or bottom depending on whether it's * closer to top or closer to the page indicator. */ -public class AllAppsTransitionController implements StateHandler, OnDeviceProfileChangeListener, - ProgressInterface { +public class AllAppsTransitionController implements StateHandler, OnDeviceProfileChangeListener { - public static final float SPRING_DAMPING_RATIO = 0.9f; - public static final float SPRING_STIFFNESS = 600f; + private static final float SPRING_DAMPING_RATIO = 0.9f; + private static final float SPRING_STIFFNESS = 600f; - public static final Property<AllAppsTransitionController, Float> ALL_APPS_PROGRESS = - new Property<AllAppsTransitionController, Float>(Float.class, "allAppsProgress") { + public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PROGRESS = + new FloatProperty<AllAppsTransitionController>("allAppsProgress") { @Override public Float get(AllAppsTransitionController controller) { @@ -62,7 +59,7 @@ public class AllAppsTransitionController implements StateHandler, OnDeviceProfil } @Override - public void set(AllAppsTransitionController controller, Float progress) { + public void setValue(AllAppsTransitionController controller, float progress) { controller.setProgress(progress); } }; @@ -122,7 +119,6 @@ public class AllAppsTransitionController implements StateHandler, OnDeviceProfil * @see #setState(LauncherState) * @see #setStateWithAnimation(LauncherState, AnimatorSetBuilder, AnimationConfig) */ - @Override public void setProgress(float progress) { mProgress = progress; mScrimView.setProgress(progress); @@ -139,9 +135,17 @@ public class AllAppsTransitionController implements StateHandler, OnDeviceProfil } else { mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, 0); } + + if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) { + // Translate hotseat with the shelf until reaching overview. + float overviewProgress = OVERVIEW.getVerticalProgress(mLauncher); + if (progress >= overviewProgress || mLauncher.isInState(BACKGROUND_APP)) { + float hotseatShift = (progress - overviewProgress) * mShiftRange; + mLauncher.getHotseat().setTranslationY(hotseatShift); + } + } } - @Override public float getProgress() { return mProgress; } @@ -184,8 +188,7 @@ public class AllAppsTransitionController implements StateHandler, OnDeviceProfil Interpolator interpolator = config.userControlled ? LINEAR : toState == OVERVIEW ? builder.getInterpolator(ANIM_OVERVIEW_SCALE, FAST_OUT_SLOW_IN) : FAST_OUT_SLOW_IN; - Animator anim = new SpringObjectAnimator<>(this, "allAppsSpringFromAATC", 1f / mShiftRange, - SPRING_DAMPING_RATIO, SPRING_STIFFNESS, mProgress, targetProgress); + Animator anim = createSpringAnimation(mProgress, targetProgress); anim.setDuration(config.duration); anim.setInterpolator(builder.getInterpolator(ANIM_VERTICAL_PROGRESS, interpolator)); anim.addListener(getProgressAnimatorListener()); @@ -195,6 +198,11 @@ public class AllAppsTransitionController implements StateHandler, OnDeviceProfil setAlphas(toState, config, builder); } + public Animator createSpringAnimation(float... progressValues) { + return new SpringObjectAnimator<>(this, ALL_APPS_PROGRESS, 1f / mShiftRange, + SPRING_DAMPING_RATIO, SPRING_STIFFNESS, progressValues); + } + private void setAlphas(LauncherState toState, AnimationConfig config, AnimatorSetBuilder builder) { setAlphas(toState.getVisibleElements(mLauncher), config, builder); diff --git a/src/com/android/launcher3/anim/AnimatorSetBuilder.java b/src/com/android/launcher3/anim/AnimatorSetBuilder.java index 5c498f8bb..52a896eda 100644 --- a/src/com/android/launcher3/anim/AnimatorSetBuilder.java +++ b/src/com/android/launcher3/anim/AnimatorSetBuilder.java @@ -32,11 +32,13 @@ public class AnimatorSetBuilder { public static final int ANIM_WORKSPACE_SCALE = 1; public static final int ANIM_WORKSPACE_TRANSLATE = 2; public static final int ANIM_WORKSPACE_FADE = 3; - public static final int ANIM_OVERVIEW_SCALE = 4; - public static final int ANIM_OVERVIEW_TRANSLATE_X = 5; - public static final int ANIM_OVERVIEW_TRANSLATE_Y = 6; - public static final int ANIM_OVERVIEW_FADE = 7; - public static final int ANIM_ALL_APPS_FADE = 8; + public static final int ANIM_HOTSEAT_SCALE = 4; + public static final int ANIM_HOTSEAT_TRANSLATE = 5; + public static final int ANIM_OVERVIEW_SCALE = 6; + public static final int ANIM_OVERVIEW_TRANSLATE_X = 7; + public static final int ANIM_OVERVIEW_TRANSLATE_Y = 8; + public static final int ANIM_OVERVIEW_FADE = 9; + public static final int ANIM_ALL_APPS_FADE = 10; public static final int FLAG_DONT_ANIMATE_OVERVIEW = 1 << 0; diff --git a/src/com/android/launcher3/anim/SpringObjectAnimator.java b/src/com/android/launcher3/anim/SpringObjectAnimator.java index b1395af89..395fed259 100644 --- a/src/com/android/launcher3/anim/SpringObjectAnimator.java +++ b/src/com/android/launcher3/anim/SpringObjectAnimator.java @@ -15,6 +15,8 @@ */ package com.android.launcher3.anim; +import static androidx.dynamicanimation.animation.FloatPropertyCompat.createFloatPropertyCompat; + import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS; import android.animation.Animator; @@ -24,15 +26,12 @@ import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.os.Handler; import android.os.Looper; +import android.util.FloatProperty; import android.util.Log; -import android.util.Property; - -import com.android.launcher3.ProgressInterface; import java.util.ArrayList; import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener; -import androidx.dynamicanimation.animation.FloatPropertyCompat; import androidx.dynamicanimation.animation.SpringAnimation; import androidx.dynamicanimation.animation.SpringForce; @@ -40,12 +39,11 @@ import androidx.dynamicanimation.animation.SpringForce; * This animator allows for an object's property to be be controlled by an {@link ObjectAnimator} or * a {@link SpringAnimation}. It extends ValueAnimator so it can be used in an AnimatorSet. */ -public class SpringObjectAnimator<T extends ProgressInterface> extends ValueAnimator { +public class SpringObjectAnimator<T> extends ValueAnimator { private static final String TAG = "SpringObjectAnimator"; private static boolean DEBUG = false; - private T mObject; private ObjectAnimator mObjectAnimator; private float[] mValues; @@ -57,29 +55,15 @@ public class SpringObjectAnimator<T extends ProgressInterface> extends ValueAnim private boolean mAnimatorEnded = true; private boolean mEnded = true; - private static final FloatPropertyCompat<ProgressInterface> sFloatProperty = - new FloatPropertyCompat<ProgressInterface>("springObjectAnimator") { - @Override - public float getValue(ProgressInterface object) { - return object.getProgress(); - } - - @Override - public void setValue(ProgressInterface object, float progress) { - object.setProgress(progress); - } - }; - - public SpringObjectAnimator(T object, String name, float minimumVisibleChange, float damping, - float stiffness, float... values) { - mObject = object; - mSpring = new SpringAnimation(object, sFloatProperty); + public SpringObjectAnimator(T object, FloatProperty<T> property, float minimumVisibleChange, + float damping, float stiffness, float... values) { + mSpring = new SpringAnimation(object, createFloatPropertyCompat(property)); mSpring.setMinimumVisibleChange(minimumVisibleChange); mSpring.setSpring(new SpringForce(0) .setDampingRatio(damping) .setStiffness(stiffness)); mSpring.setStartVelocity(0.01f); - mProperty = new SpringProperty<T>(name, mSpring); + mProperty = new SpringProperty<>(property, mSpring); mObjectAnimator = ObjectAnimator.ofFloat(object, mProperty, values); mValues = values; mListeners = new ArrayList<>(); @@ -285,13 +269,15 @@ public class SpringObjectAnimator<T extends ProgressInterface> extends ValueAnim mObjectAnimator.setCurrentPlayTime(playTime); } - public static class SpringProperty<T extends ProgressInterface> extends Property<T, Float> { + public static class SpringProperty<T> extends FloatProperty<T> { boolean useSpring = false; + final FloatProperty<T> mProperty; final SpringAnimation mSpring; - public SpringProperty(String name, SpringAnimation spring) { - super(Float.class, name); + public SpringProperty(FloatProperty<T> property, SpringAnimation spring) { + super(property.getName()); + mProperty = property; mSpring = spring; } @@ -301,15 +287,15 @@ public class SpringObjectAnimator<T extends ProgressInterface> extends ValueAnim @Override public Float get(T object) { - return object.getProgress(); + return mProperty.get(object); } @Override - public void set(T object, Float progress) { + public void setValue(T object, float progress) { if (useSpring) { mSpring.animateToFinalPosition(progress); } else { - object.setProgress(progress); + mProperty.setValue(object, progress); } } } diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java index 54d0db100..54efcb786 100644 --- a/src/com/android/launcher3/config/BaseFlags.java +++ b/src/com/android/launcher3/config/BaseFlags.java @@ -109,7 +109,7 @@ abstract class BaseFlags { "Show chip hints and gleams on the overview screen"); public static final TogglableFlag FAKE_LANDSCAPE_UI = new TogglableFlag( - "FAKE_LANDSCAPE_UI", true, + "FAKE_LANDSCAPE_UI", false, "Rotate launcher UI instead of using transposed layout"); public static void initialize(Context context) { diff --git a/src/com/android/launcher3/touch/TouchEventTranslator.java b/src/com/android/launcher3/touch/TouchEventTranslator.java deleted file mode 100644 index 3fcda9084..000000000 --- a/src/com/android/launcher3/touch/TouchEventTranslator.java +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright (C) 2018 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.touch; - -import android.graphics.PointF; -import android.util.Log; -import android.util.Pair; -import android.util.SparseArray; -import android.view.MotionEvent; -import android.view.MotionEvent.PointerCoords; -import android.view.MotionEvent.PointerProperties; - -import java.util.function.Consumer; - -/** - * To minimize the size of the MotionEvent, historic events are not copied and passed via the - * listener. - */ -public class TouchEventTranslator { - - private static final String TAG = "TouchEventTranslator"; - private static final boolean DEBUG = false; - - private class DownState { - long timeStamp; - float downX; - float downY; - public DownState(long timeStamp, float downX, float downY) { - this.timeStamp = timeStamp; - this.downX = downX; - this.downY = downY; - } - }; - private final DownState ZERO = new DownState(0, 0f, 0f); - - private final Consumer<MotionEvent> mListener; - - private final SparseArray<DownState> mDownEvents; - private final SparseArray<PointF> mFingers; - - private final SparseArray<Pair<PointerProperties[], PointerCoords[]>> mCache; - - public TouchEventTranslator(Consumer<MotionEvent> listener) { - mDownEvents = new SparseArray<>(); - mFingers = new SparseArray<>(); - mCache = new SparseArray<>(); - - mListener = listener; - } - - public void reset() { - mDownEvents.clear(); - mFingers.clear(); - } - - public float getDownX() { - return mDownEvents.get(0).downX; - } - - public float getDownY() { - return mDownEvents.get(0).downY; - } - - public void setDownParameters(int idx, MotionEvent e) { - DownState ev = new DownState(e.getEventTime(), e.getX(idx), e.getY(idx)); - mDownEvents.append(idx, ev); - } - - public void dispatchDownEvents(MotionEvent ev) { - for(int i = 0; i < ev.getPointerCount() && i < mDownEvents.size(); i++) { - int pid = ev.getPointerId(i); - put(pid, i, ev.getX(i), 0, mDownEvents.get(i).timeStamp, ev); - } - } - - public void processMotionEvent(MotionEvent ev) { - if (DEBUG) { - printSamples(TAG + " processMotionEvent", ev); - } - int index = ev.getActionIndex(); - float x = ev.getX(index); - float y = ev.getY(index) - mDownEvents.get(index, ZERO).downY; - switch (ev.getActionMasked()) { - case MotionEvent.ACTION_POINTER_DOWN: - int pid = ev.getPointerId(index); - if(mFingers.get(pid, null) != null) { - for(int i=0; i < ev.getPointerCount(); i++) { - pid = ev.getPointerId(i); - position(pid, x, y); - } - generateEvent(ev.getAction(), ev); - } else { - put(pid, index, x, y, ev); - } - break; - case MotionEvent.ACTION_MOVE: - for(int i=0; i < ev.getPointerCount(); i++) { - pid = ev.getPointerId(i); - position(pid, x, y); - } - generateEvent(ev.getAction(), ev); - break; - case MotionEvent.ACTION_POINTER_UP: - case MotionEvent.ACTION_UP: - pid = ev.getPointerId(index); - lift(pid, index, x, y, ev); - break; - case MotionEvent.ACTION_CANCEL: - cancel(ev); - break; - default: - Log.v(TAG, "Didn't process "); - printSamples(TAG, ev); - - } - } - - private TouchEventTranslator put(int id, int index, float x, float y, MotionEvent ev) { - return put(id, index, x, y, ev.getEventTime(), ev); - } - - private TouchEventTranslator put(int id, int index, float x, float y, long ms, MotionEvent ev) { - checkFingerExistence(id, false); - boolean isInitialDown = (mFingers.size() == 0); - - mFingers.put(id, new PointF(x, y)); - int n = mFingers.size(); - - if (mCache.get(n) == null) { - PointerProperties[] properties = new PointerProperties[n]; - PointerCoords[] coords = new PointerCoords[n]; - for (int i = 0; i < n; i++) { - properties[i] = new PointerProperties(); - coords[i] = new PointerCoords(); - } - mCache.put(n, new Pair(properties, coords)); - } - - int action; - if (isInitialDown) { - action = MotionEvent.ACTION_DOWN; - } else { - action = MotionEvent.ACTION_POINTER_DOWN; - // Set the id of the changed pointer. - action |= index << MotionEvent.ACTION_POINTER_INDEX_SHIFT; - } - generateEvent(action, ms, ev); - return this; - } - - public TouchEventTranslator position(int id, float x, float y) { - checkFingerExistence(id, true); - mFingers.get(id).set(x, y); - return this; - } - - private TouchEventTranslator lift(int id, int index, MotionEvent ev) { - checkFingerExistence(id, true); - boolean isFinalUp = (mFingers.size() == 1); - int action; - if (isFinalUp) { - action = MotionEvent.ACTION_UP; - } else { - action = MotionEvent.ACTION_POINTER_UP; - // Set the id of the changed pointer. - action |= index << MotionEvent.ACTION_POINTER_INDEX_SHIFT; - } - generateEvent(action, ev); - mFingers.remove(id); - return this; - } - - private TouchEventTranslator lift(int id, int index, float x, float y, MotionEvent ev) { - checkFingerExistence(id, true); - mFingers.get(id).set(x, y); - return lift(id, index, ev); - } - - public TouchEventTranslator cancel(MotionEvent ev) { - generateEvent(MotionEvent.ACTION_CANCEL, ev); - mFingers.clear(); - return this; - } - - private void checkFingerExistence(int id, boolean shouldExist) { - if (shouldExist != (mFingers.get(id, null) != null)) { - throw new IllegalArgumentException( - shouldExist ? "Finger does not exist" : "Finger already exists"); - } - } - - - /** - * Used to debug MotionEvents being sent/received. - */ - public void printSamples(String msg, MotionEvent ev) { - System.out.printf("%s %s", msg, MotionEvent.actionToString(ev.getActionMasked())); - final int pointerCount = ev.getPointerCount(); - System.out.printf("#%d/%d", ev.getActionIndex(), pointerCount); - System.out.printf(" t=%d:", ev.getEventTime()); - for (int p = 0; p < pointerCount; p++) { - System.out.printf(" id=%d: (%f,%f)", - ev.getPointerId(p), ev.getX(p), ev.getY(p)); - } - System.out.println(); - } - - private void generateEvent(int action, MotionEvent ev) { - generateEvent(action, ev.getEventTime(), ev); - } - - private void generateEvent(int action, long ms, MotionEvent ev) { - Pair<PointerProperties[], PointerCoords[]> state = getFingerState(); - MotionEvent event = MotionEvent.obtain( - mDownEvents.get(0).timeStamp, - ms, - action, - state.first.length, - state.first, - state.second, - ev.getMetaState(), - ev.getButtonState() /* buttonState */, - ev.getXPrecision() /* xPrecision */, - ev.getYPrecision() /* yPrecision */, - ev.getDeviceId(), - ev.getEdgeFlags(), - ev.getSource(), - ev.getFlags() /* flags */); - if (DEBUG) { - printSamples(TAG + " generateEvent", event); - } - if (event.getPointerId(event.getActionIndex()) < 0) { - printSamples(TAG + "generateEvent", event); - throw new IllegalStateException(event.getActionIndex() + " not found in MotionEvent"); - } - mListener.accept(event); - event.recycle(); - } - - /** - * Returns the description of the fingers' state expected by MotionEvent. - */ - private Pair<PointerProperties[], PointerCoords[]> getFingerState() { - int nFingers = mFingers.size(); - - Pair<PointerProperties[], PointerCoords[]> result = mCache.get(nFingers); - PointerProperties[] properties = result.first; - PointerCoords[] coordinates = result.second; - - int index = 0; - for (int i = 0; i < mFingers.size(); i++) { - int id = mFingers.keyAt(i); - PointF location = mFingers.get(id); - - PointerProperties property = properties[i]; - property.id = id; - property.toolType = MotionEvent.TOOL_TYPE_FINGER; - properties[index] = property; - - PointerCoords coordinate = coordinates[i]; - coordinate.x = location.x; - coordinate.y = location.y; - coordinate.pressure = 1.0f; - coordinates[index] = coordinate; - - index++; - } - return mCache.get(nFingers); - } -} diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java index ac152dbff..c1ba78050 100644 --- a/src/com/android/launcher3/views/BaseDragLayer.java +++ b/src/com/android/launcher3/views/BaseDragLayer.java @@ -21,6 +21,7 @@ import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; import static com.android.launcher3.Utilities.SINGLE_FRAME_MS; +import static com.android.launcher3.Utilities.shouldDisableGestures; import android.annotation.TargetApi; import android.content.Context; @@ -116,11 +117,25 @@ public abstract class BaseDragLayer<T extends Context & ActivityContext> mMultiValueAlpha = new MultiValueAlpha(this, alphaChannelCount); } + /** + * Same as {@link #isEventOverView(View, MotionEvent, View)} where evView == this drag layer. + */ public boolean isEventOverView(View view, MotionEvent ev) { getDescendantRectRelativeToSelf(view, mHitRect); return mHitRect.contains((int) ev.getX(), (int) ev.getY()); } + /** + * Given a motion event in evView's coordinates, return whether the event is within another + * view's bounds. + */ + public boolean isEventOverView(View view, MotionEvent ev, View evView) { + int[] xy = new int[] {(int) ev.getX(), (int) ev.getY()}; + getDescendantCoordRelativeToSelf(evView, xy); + getDescendantRectRelativeToSelf(view, mHitRect); + return mHitRect.contains(xy[0], xy[1]); + } + @Override public boolean onInterceptTouchEvent(MotionEvent ev) { int action = ev.getAction(); @@ -137,6 +152,8 @@ public abstract class BaseDragLayer<T extends Context & ActivityContext> } private TouchController findControllerToHandleTouch(MotionEvent ev) { + if (shouldDisableGestures(ev)) return null; + AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity); if (topView != null && topView.onControllerInterceptTouchEvent(ev)) { return topView; diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java index e41916cf4..5cc64dc9a 100644 --- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java +++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java @@ -69,8 +69,6 @@ public class UiFactory { return false; } - public static void prepareToShowOverview(Launcher launcher) { } - public static void setBackButtonAlpha(Launcher launcher, float alpha, boolean animate) { } @@ -95,4 +93,6 @@ public class UiFactory { public static void resetPendingActivityResults(Launcher launcher, int requestCode) { } + public static void clearSwipeSharedState(boolean finishAnimation) {} + } diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index abc93cd4b..4a0ca5c24 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -35,7 +35,6 @@ import android.content.pm.PackageManager; import android.os.Process; import android.os.RemoteException; import android.util.Log; -import android.view.Surface; import androidx.test.InstrumentationRegistry; import androidx.test.uiautomator.By; @@ -67,7 +66,6 @@ import org.junit.Before; import org.junit.Rule; import org.junit.rules.RuleChain; import org.junit.rules.TestRule; -import org.junit.runners.model.Statement; import java.io.IOException; import java.lang.annotation.ElementType; @@ -124,46 +122,10 @@ public abstract class AbstractLauncherUiTest { protected @interface PortraitLandscape { } - @Rule - public TestRule mPortraitLandscapeExecutor = - (base, description) -> false && description.getAnnotation(PortraitLandscape.class) - != null ? new Statement() { - @Override - public void evaluate() throws Throwable { - try { - // Create launcher activity if necessary and bring it to the front. - mLauncher.pressHome(); - waitForLauncherCondition("Launcher activity wasn't created", - launcher -> launcher != null); - - executeOnLauncher(launcher -> - launcher.getRotationHelper().forceAllowRotationForTesting(true)); - - evaluateInPortrait(); - evaluateInLandscape(); - } finally { - mDevice.setOrientationNatural(); - executeOnLauncher(launcher -> - launcher.getRotationHelper().forceAllowRotationForTesting(false)); - mLauncher.setExpectedRotation(Surface.ROTATION_0); - } - } - - private void evaluateInPortrait() throws Throwable { - mDevice.setOrientationNatural(); - mLauncher.setExpectedRotation(Surface.ROTATION_0); - base.evaluate(); - } - - private void evaluateInLandscape() throws Throwable { - mDevice.setOrientationLeft(); - mLauncher.setExpectedRotation(Surface.ROTATION_90); - base.evaluate(); - } - } : base; - protected TestRule getRulesInsideActivityMonitor() { - return new FailureWatcher(this); + return RuleChain. + outerRule(new PortraitLandscapeRunner(this)). + around(new FailureWatcher(mDevice)); } @Rule diff --git a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java index 48335a602..58c74cef1 100644 --- a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java +++ b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java @@ -25,26 +25,23 @@ import android.net.Uri; import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor.AutoCloseOutputStream; +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.MediumTest; +import androidx.test.runner.AndroidJUnit4; + import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.testcomponent.TestCommandReceiver; import com.android.launcher3.util.LauncherLayoutBuilder; import com.android.launcher3.util.rule.ShellCommandRule; -import com.android.launcher3.widget.LauncherAppWidgetHostView; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import java.io.OutputStreamWriter; -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.MediumTest; -import androidx.test.runner.AndroidJUnit4; -import androidx.test.uiautomator.UiSelector; - @MediumTest @RunWith(AndroidJUnit4.class) public class DefaultLayoutProviderTest extends AbstractLauncherUiTest { @@ -71,7 +68,6 @@ public class DefaultLayoutProviderTest extends AbstractLauncherUiTest { } @Test - // Convert test to TAPL; b/131116002 public void testCustomProfileLoaded_with_icon_on_hotseat() throws Exception { writeLayout(new LauncherLayoutBuilder().atHotseat(0).putApp(SETTINGS_APP, SETTINGS_APP)); @@ -79,14 +75,10 @@ public class DefaultLayoutProviderTest extends AbstractLauncherUiTest { mActivityMonitor.startLauncher(); waitForModelLoaded(); - // Verify widget present - UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName()) - .description(getSettingsApp().getLabel().toString()); - assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_UI_TIMEOUT)); + mLauncher.getWorkspace().getHotseatAppIcon(getSettingsApp().getLabel().toString()); } @Test - // Convert test to TAPL; b/131116002 public void testCustomProfileLoaded_with_widget() throws Exception { // A non-restored widget with no config screen gets restored automatically. LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false); @@ -100,13 +92,11 @@ public class DefaultLayoutProviderTest extends AbstractLauncherUiTest { waitForModelLoaded(); // Verify widget present - UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName()) - .className(LauncherAppWidgetHostView.class).description(info.label); - assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_UI_TIMEOUT)); + assertTrue("Widget is not present", + mLauncher.getWorkspace().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT) != null); } @Test - // Convert test to TAPL; b/131116002 public void testCustomProfileLoaded_with_folder() throws Exception { writeLayout(new LauncherLayoutBuilder().atHotseat(0).putFolder(android.R.string.copy) .addApp(SETTINGS_APP, SETTINGS_APP) @@ -118,10 +108,7 @@ public class DefaultLayoutProviderTest extends AbstractLauncherUiTest { mActivityMonitor.startLauncher(); waitForModelLoaded(); - // Verify widget present - UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName()) - .descriptionContains(mTargetContext.getString(android.R.string.copy)); - assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_UI_TIMEOUT)); + mLauncher.getWorkspace().getHotseatFolder("Folder: Copy"); } @After diff --git a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java new file mode 100644 index 000000000..0f36292f9 --- /dev/null +++ b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java @@ -0,0 +1,63 @@ +package com.android.launcher3.ui; + +import android.view.Surface; + +import com.android.launcher3.tapl.TestHelpers; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +class PortraitLandscapeRunner implements TestRule { + private AbstractLauncherUiTest mTest; + + public PortraitLandscapeRunner(AbstractLauncherUiTest test) { + mTest = test; + } + + @Override + public Statement apply(Statement base, Description description) { + if (!TestHelpers.isInLauncherProcess() || + description.getAnnotation(AbstractLauncherUiTest.PortraitLandscape.class) == null) { + return base; + } + + return new Statement() { + @Override + public void evaluate() throws Throwable { + try { + mTest.mDevice.pressHome(); + mTest.waitForLauncherCondition("Launcher activity wasn't created", + launcher -> launcher != null); + + mTest.executeOnLauncher(launcher -> + launcher.getRotationHelper().forceAllowRotationForTesting( + true)); + + evaluateInPortrait(); + evaluateInLandscape(); + } finally { + mTest.mDevice.setOrientationNatural(); + mTest.executeOnLauncher(launcher -> + launcher.getRotationHelper().forceAllowRotationForTesting( + false)); + mTest.mLauncher.setExpectedRotation(Surface.ROTATION_0); + } + } + + private void evaluateInPortrait() throws Throwable { + mTest.mDevice.setOrientationNatural(); + mTest.mLauncher.setExpectedRotation(Surface.ROTATION_0); + base.evaluate(); + mTest.getDevice().pressHome(); + } + + private void evaluateInLandscape() throws Throwable { + mTest.mDevice.setOrientationLeft(); + mTest.mLauncher.setExpectedRotation(Surface.ROTATION_90); + base.evaluate(); + mTest.getDevice().pressHome(); + } + }; + } +} diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java index d171004fc..c3168f812 100644 --- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java +++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java @@ -59,11 +59,7 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { public static void initialize(AbstractLauncherUiTest test) throws Exception { test.clearLauncherData(); - if (TestHelpers.isInLauncherProcess()) { - test.mActivityMonitor.returnToHome(); - } else { - test.mDevice.pressHome(); - } + test.mDevice.pressHome(); test.waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null); test.waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL); test.waitForResumed("Launcher internal state is still Background"); @@ -279,8 +275,6 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { @Test @PortraitLandscape public void testLaunchMenuItem() throws Exception { - if (!TestHelpers.isInLauncherProcess()) return; - final AllApps allApps = mLauncher. getWorkspace(). switchToAllApps(); @@ -327,8 +321,6 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { @Test @PortraitLandscape public void testDragShortcut() throws Throwable { - if (!TestHelpers.isInLauncherProcess()) return; - // 1. Open all apps and wait for load complete. // 2. Find the app and long press it to show shortcuts. // 3. Press icon center until shortcuts appear diff --git a/tests/src/com/android/launcher3/ui/TestViewHelpers.java b/tests/src/com/android/launcher3/ui/TestViewHelpers.java index a73bde011..d13d31952 100644 --- a/tests/src/com/android/launcher3/ui/TestViewHelpers.java +++ b/tests/src/com/android/launcher3/ui/TestViewHelpers.java @@ -54,21 +54,6 @@ public class TestViewHelpers { return UiDevice.getInstance(getInstrumentation()); } - /** - * Opens all apps and returns the recycler view - */ - public static UiObject2 openAllApps() { - final UiDevice device = getDevice(); - device.waitForIdle(); - UiObject2 hotseat = device.wait( - Until.findObject(getSelectorForId(R.id.hotseat)), 2500); - Point start = hotseat.getVisibleCenter(); - int endY = (int) (device.getDisplayHeight() * 0.1f); - // 100 px/step - device.swipe(start.x, start.y, start.x, endY, (start.y - endY) / 100); - return findViewById(R.id.apps_list_view); - } - public static UiObject2 findViewById(int id) { return getDevice().wait(Until.findObject(getSelectorForId(id)), AbstractLauncherUiTest.DEFAULT_UI_TIMEOUT); diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java index 874ff1995..d36126bb1 100644 --- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java +++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java @@ -15,8 +15,9 @@ */ package com.android.launcher3.ui.widget; +import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID; + import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -24,43 +25,37 @@ import android.appwidget.AppWidgetHost; import android.appwidget.AppWidgetManager; import android.content.ComponentName; import android.content.ContentResolver; -import android.content.ContentValues; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionParams; import android.content.pm.PackageManager; import android.database.Cursor; import android.os.Bundle; +import androidx.test.filters.LargeTest; +import androidx.test.runner.AndroidJUnit4; + import com.android.launcher3.LauncherAppWidgetHost; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.LauncherSettings; -import com.android.launcher3.Workspace; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.PackageInstallerCompat; +import com.android.launcher3.tapl.Workspace; import com.android.launcher3.ui.AbstractLauncherUiTest; import com.android.launcher3.ui.TestViewHelpers; import com.android.launcher3.util.ContentWriter; import com.android.launcher3.util.rule.ShellCommandRule; -import com.android.launcher3.widget.LauncherAppWidgetHostView; import com.android.launcher3.widget.PendingAddWidgetInfo; -import com.android.launcher3.widget.PendingAppWidgetHostView; import com.android.launcher3.widget.WidgetHostViewLoader; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import java.util.Set; -import androidx.test.filters.LargeTest; -import androidx.test.runner.AndroidJUnit4; -import androidx.test.uiautomator.UiSelector; -import java.util.concurrent.Callable; - /** * Tests for bind widget flow. * @@ -131,16 +126,14 @@ public class BindWidgetTest extends AbstractLauncherUiTest { setupContents(item); - // Since there is no widget to verify, just wait until the workspace is ready. - // TODO: fix LauncherInstrumentation#LAUNCHER_PKG - mLauncher.getWorkspace(); + final Workspace workspace = mLauncher.getWorkspace(); // Item deleted from db mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id), null, null, null, null, null); assertEquals(0, mCursor.getCount()); // The view does not exist - assertFalse(mDevice.findObject(new UiSelector().description(info.label)).exists()); + assertTrue("Widget exists", workspace.tryGetWidget(info.label, 0) == null); } @Test @@ -189,12 +182,8 @@ public class BindWidgetTest extends AbstractLauncherUiTest { setupContents(item); - // Since there is no widget to verify, just wait until the workspace is ready. - // TODO: fix LauncherInstrumentation#LAUNCHER_PKG - mLauncher.getWorkspace(); - // The view does not exist - assertFalse(mDevice.findObject( - new UiSelector().className(PendingAppWidgetHostView.class)).exists()); + assertTrue("Pending widget exists", + mLauncher.getWorkspace().tryGetPendingWidget(0) == null); // Item deleted from db mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id), null, null, null, null, null); @@ -258,12 +247,12 @@ public class BindWidgetTest extends AbstractLauncherUiTest { * widget class is displayed on the homescreen. */ private void setupContents(LauncherAppWidgetInfo item) { - int screenId = Workspace.FIRST_SCREEN_ID; + int screenId = FIRST_SCREEN_ID; // Update the screen id counter for the provider. LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_NEW_SCREEN_ID); - if (screenId > Workspace.FIRST_SCREEN_ID) { - screenId = Workspace.FIRST_SCREEN_ID; + if (screenId > FIRST_SCREEN_ID) { + screenId = FIRST_SCREEN_ID; } // Insert the item @@ -283,15 +272,13 @@ public class BindWidgetTest extends AbstractLauncherUiTest { } private void verifyWidgetPresent(LauncherAppWidgetProviderInfo info) { - UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName()) - .className(LauncherAppWidgetHostView.class).description(info.label); - assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_UI_TIMEOUT)); + assertTrue("Widget is not present", + mLauncher.getWorkspace().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT) != null); } private void verifyPendingWidgetPresent() { - UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName()) - .className(PendingAppWidgetHostView.class); - assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_UI_TIMEOUT)); + assertTrue("Pending widget is not present", + mLauncher.getWorkspace().tryGetPendingWidget(DEFAULT_UI_TIMEOUT) != null); } /** diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java index a57d7bab8..6122daec2 100644 --- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java +++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java @@ -15,6 +15,8 @@ */ package com.android.launcher3.ui.widget; +import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName; + import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; @@ -26,9 +28,6 @@ import android.view.View; import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; -import androidx.test.uiautomator.By; -import androidx.test.uiautomator.UiObject2; -import androidx.test.uiautomator.Until; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppWidgetInfo; @@ -37,15 +36,14 @@ import com.android.launcher3.Utilities; import com.android.launcher3.Workspace.ItemOperator; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.shortcuts.ShortcutKey; +import com.android.launcher3.tapl.AddToHomeScreenPrompt; import com.android.launcher3.testcomponent.AppWidgetNoConfig; import com.android.launcher3.testcomponent.AppWidgetWithConfig; import com.android.launcher3.testcomponent.RequestPinItemActivity; import com.android.launcher3.ui.AbstractLauncherUiTest; -import com.android.launcher3.ui.TestViewHelpers; import com.android.launcher3.util.Condition; import com.android.launcher3.util.Wait; import com.android.launcher3.util.rule.ShellCommandRule; -import com.android.launcher3.widget.WidgetCell; import org.junit.Before; import org.junit.Rule; @@ -80,15 +78,10 @@ public class RequestPinItemTest extends AbstractLauncherUiTest { @Test public void testPinWidgetNoConfig() throws Throwable { - runTest("pinWidgetNoConfig", true, new ItemOperator() { - @Override - public boolean evaluate(ItemInfo info, View view) { - return info instanceof LauncherAppWidgetInfo && - ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId && - ((LauncherAppWidgetInfo) info).providerName.getClassName() - .equals(AppWidgetNoConfig.class.getName()); - } - }); + runTest("pinWidgetNoConfig", true, (info, view) -> info instanceof LauncherAppWidgetInfo && + ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId && + ((LauncherAppWidgetInfo) info).providerName.getClassName() + .equals(AppWidgetNoConfig.class.getName())); } @Test @@ -98,28 +91,19 @@ public class RequestPinItemTest extends AbstractLauncherUiTest { RequestPinItemActivity.class, "setRemoteViewColor").putExtra( RequestPinItemActivity.EXTRA_PARAM + "0", Color.RED); - runTest("pinWidgetNoConfig", true, new ItemOperator() { - @Override - public boolean evaluate(ItemInfo info, View view) { - return info instanceof LauncherAppWidgetInfo && - ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId && - ((LauncherAppWidgetInfo) info).providerName.getClassName() - .equals(AppWidgetNoConfig.class.getName()); - } - }, command); + runTest("pinWidgetNoConfig", true, (info, view) -> info instanceof LauncherAppWidgetInfo && + ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId && + ((LauncherAppWidgetInfo) info).providerName.getClassName() + .equals(AppWidgetNoConfig.class.getName()), command); } @Test public void testPinWidgetWithConfig() throws Throwable { - runTest("pinWidgetWithConfig", true, new ItemOperator() { - @Override - public boolean evaluate(ItemInfo info, View view) { - return info instanceof LauncherAppWidgetInfo && + runTest("pinWidgetWithConfig", true, + (info, view) -> info instanceof LauncherAppWidgetInfo && ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId && ((LauncherAppWidgetInfo) info).providerName.getClassName() - .equals(AppWidgetWithConfig.class.getName()); - } - }); + .equals(AppWidgetWithConfig.class.getName())); } @Test @@ -149,14 +133,14 @@ public class RequestPinItemTest extends AbstractLauncherUiTest { clearHomescreen(); mActivityMonitor.startLauncher(); - // Open all apps and wait for load complete - final UiObject2 appsContainer = TestViewHelpers.openAllApps(); - Wait.atMost(null, Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT); - // Open Pin item activity BlockingBroadcastReceiver openMonitor = new BlockingBroadcastReceiver( RequestPinItemActivity.class.getName()); - scrollAndFind(appsContainer, By.text("Test Pin Item")).click(); + mLauncher. + getWorkspace(). + switchToAllApps(). + getAppIcon("Test Pin Item"). + launch(getAppPackageName()); assertNotNull(openMonitor.blockingGetExtraIntent()); // Set callback @@ -173,15 +157,11 @@ public class RequestPinItemTest extends AbstractLauncherUiTest { // call the requested method to start the flow mTargetContext.sendBroadcast(RequestPinItemActivity.getCommandIntent( RequestPinItemActivity.class, activityMethod)); - UiObject2 widgetCell = mDevice.wait( - Until.findObject(By.clazz(WidgetCell.class)), DEFAULT_ACTIVITY_TIMEOUT); - assertNotNull(widgetCell); + final AddToHomeScreenPrompt addToHomeScreenPrompt = mLauncher.getAddToHomeScreenPrompt(); // Accept confirmation: BlockingBroadcastReceiver resultReceiver = new BlockingBroadcastReceiver(mCallbackAction); - mDevice.wait(Until.findObject( - By.text(mLauncher.isAvd() ? "ADD AUTOMATICALLY" : "Add automatically")), - DEFAULT_UI_TIMEOUT).click(); + addToHomeScreenPrompt.addAutomatically(); Intent result = resultReceiver.blockingGetIntent(); assertNotNull(result); mAppWidgetId = result.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); @@ -190,7 +170,7 @@ public class RequestPinItemTest extends AbstractLauncherUiTest { } // Go back to home - mActivityMonitor.returnToHome(); + mLauncher.pressHome(); Wait.atMost(null, new ItemSearchCondition(itemMatcher), DEFAULT_ACTIVITY_TIMEOUT); } diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java index 09cc98de0..eef2f24ba 100644 --- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java +++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java @@ -4,7 +4,7 @@ import static androidx.test.InstrumentationRegistry.getInstrumentation; import android.util.Log; -import com.android.launcher3.ui.AbstractLauncherUiTest; +import androidx.test.uiautomator.UiDevice; import org.junit.rules.TestWatcher; import org.junit.runner.Description; @@ -16,16 +16,16 @@ import java.io.IOException; public class FailureWatcher extends TestWatcher { private static final String TAG = "FailureWatcher"; private static int sScreenshotCount = 0; - private AbstractLauncherUiTest mAbstractLauncherUiTest; + final private UiDevice mDevice; - public FailureWatcher(AbstractLauncherUiTest abstractLauncherUiTest) { - mAbstractLauncherUiTest = abstractLauncherUiTest; + public FailureWatcher(UiDevice device) { + mDevice = device; } private void dumpViewHierarchy() { final ByteArrayOutputStream stream = new ByteArrayOutputStream(); try { - mAbstractLauncherUiTest.getDevice().dumpWindowHierarchy(stream); + mDevice.dumpWindowHierarchy(stream); stream.flush(); stream.close(); for (String line : stream.toString().split("\\r?\\n")) { @@ -38,7 +38,7 @@ public class FailureWatcher extends TestWatcher { @Override protected void failed(Throwable e, Description description) { - if (mAbstractLauncherUiTest.getDevice() == null) return; + if (mDevice == null) return; final String pathname = getInstrumentation().getTargetContext(). getFilesDir().getPath() + "/TaplTestScreenshot" + sScreenshotCount++ + ".png"; Log.e(TAG, "Failed test " + description.getMethodName() + @@ -48,12 +48,12 @@ public class FailureWatcher extends TestWatcher { dumpViewHierarchy(); try { - final String dumpsysResult = mAbstractLauncherUiTest.getDevice().executeShellCommand( + final String dumpsysResult = mDevice.executeShellCommand( "dumpsys activity service TouchInteractionService"); Log.d(TAG, "TouchInteractionService: " + dumpsysResult); } catch (IOException ex) { } - mAbstractLauncherUiTest.getDevice().takeScreenshot(new File(pathname)); + mDevice.takeScreenshot(new File(pathname)); } } diff --git a/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java b/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java index 145c3c89e..2aba7a56d 100644 --- a/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java +++ b/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java @@ -72,11 +72,6 @@ public class LauncherActivityRule implements TestRule { getInstrumentation().startActivitySync(getHomeIntentInPackage(getTargetContext())); } - public void returnToHome() { - getTargetContext().startActivity(getHomeIntentInPackage(getTargetContext())); - getInstrumentation().waitForIdleSync(); - } - private class MyStatement extends Statement implements ActivityLifecycleCallbacks { private final Statement mBase; diff --git a/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java b/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java new file mode 100644 index 000000000..7f561a2af --- /dev/null +++ b/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2019 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 androidx.test.uiautomator.By; +import androidx.test.uiautomator.UiObject2; + +public class AddToHomeScreenPrompt { + private final LauncherInstrumentation mLauncher; + private final UiObject2 mWidgetCell; + + AddToHomeScreenPrompt(LauncherInstrumentation launcher) { + mLauncher = launcher; + mWidgetCell = launcher.waitForLauncherObject(By.clazz( + "com.android.launcher3.widget.WidgetCell")); + mLauncher.assertNotNull("Can't find widget cell object", mWidgetCell); + } + + public void addAutomatically() { + mLauncher.waitForObjectInContainer( + mWidgetCell.getParent().getParent().getParent().getParent(), + By.text(LauncherInstrumentation.isAvd() + ? "ADD AUTOMATICALLY" + : "Add automatically")). + click(); + } +} diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java index 6c4619235..c9eaf276d 100644 --- a/tests/tapl/com/android/launcher3/tapl/Background.java +++ b/tests/tapl/com/android/launcher3/tapl/Background.java @@ -87,16 +87,24 @@ public class Background extends LauncherInstrumentation.VisibleContainer { } case TWO_BUTTON: { - final int centerX = mLauncher.getDevice().getDisplayWidth() / 2; - final int startY = getSwipeStartY(); - final int swipeHeight = mLauncher.getTestInfo(getSwipeHeightRequestName()). - getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD); - - mLauncher.swipeToState( - centerX, startY, centerX, - startY - swipeHeight - mLauncher.getTouchSlop(), - 10, - expectedState); + final int startX; + final int startY; + final int endX; + final int endY; + final int swipeLength = mLauncher.getTestInfo(getSwipeHeightRequestName()). + getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD) + mLauncher.getTouchSlop(); + + if (mLauncher.getDevice().isNaturalOrientation()) { + startX = endX = mLauncher.getDevice().getDisplayWidth() / 2; + startY = getSwipeStartY(); + endY = startY - swipeLength; + } else { + startX = getSwipeStartX(); + endX = startX - swipeLength; + startY = endY = mLauncher.getDevice().getDisplayHeight() / 2; + } + + mLauncher.swipeToState(startX, startY, endX, endY, 10, expectedState); break; } @@ -111,6 +119,10 @@ public class Background extends LauncherInstrumentation.VisibleContainer { return TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT; } + protected int getSwipeStartX() { + return mLauncher.getRealDisplaySize().x - 1; + } + protected int getSwipeStartY() { return mLauncher.getRealDisplaySize().y - 1; } 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 000000000..6e6734d81 --- /dev/null +++ b/tests/tapl/com/android/launcher3/tapl/Folder.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2019 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.widget.FrameLayout; + +import androidx.test.uiautomator.By; +import androidx.test.uiautomator.BySelector; +import androidx.test.uiautomator.UiObject2; + +/** + * App folder in workspace/ + */ +public final class Folder { + Folder(LauncherInstrumentation launcher, UiObject2 icon) { + } + + static BySelector getSelector(String folderName, LauncherInstrumentation launcher) { + return By.clazz(FrameLayout.class).desc(folderName).pkg(launcher.getLauncherPackageName()); + } +} diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index 2db9d0826..a7e633619 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -122,7 +122,7 @@ public final class LauncherInstrumentation { private static final String APPS_RES_ID = "apps_view"; private static final String OVERVIEW_RES_ID = "overview_panel"; private static final String WIDGETS_RES_ID = "widgets_list_view"; - public static final int WAIT_TIME_MS = 10000; + public static final int WAIT_TIME_MS = 60000; private static final String SYSTEMUI_PACKAGE = "com.android.systemui"; private static WeakReference<VisibleContainer> sActiveContainer = new WeakReference<>(null); @@ -315,25 +315,37 @@ public final class LauncherInstrumentation { mExpectedRotation = expectedRotation; } - private UiObject2 verifyContainerType(ContainerType containerType) { - assertEquals("Unexpected display rotation", - mExpectedRotation, mDevice.getDisplayRotation()); + public String getNavigationModeMismatchError() { final NavigationModel navigationModel = getNavigationModel(); final boolean hasRecentsButton = hasSystemUiObject("recent_apps"); final boolean hasHomeButton = hasSystemUiObject("home"); - assertTrue("Presence of recents button doesn't match the interaction mode, mode=" - + navigationModel.name() + ", hasRecents=" + hasRecentsButton, - (navigationModel == NavigationModel.THREE_BUTTON) == hasRecentsButton); - assertTrue("Presence of home button doesn't match the interaction mode, mode=" - + navigationModel.name() + ", hasHome=" + hasHomeButton, - (navigationModel != NavigationModel.ZERO_BUTTON) == hasHomeButton); + if ((navigationModel == NavigationModel.THREE_BUTTON) != hasRecentsButton) { + return "Presence of recents button doesn't match the interaction mode, mode=" + + navigationModel.name() + ", hasRecents=" + hasRecentsButton; + } + if ((navigationModel != NavigationModel.ZERO_BUTTON) != hasHomeButton) { + return "Presence of home button doesn't match the interaction mode, mode=" + + navigationModel.name() + ", hasHome=" + hasHomeButton; + } + return null; + } + + private UiObject2 verifyContainerType(ContainerType containerType) { + assertEquals("Unexpected display rotation", + mExpectedRotation, mDevice.getDisplayRotation()); + final String error = getNavigationModeMismatchError(); + assertTrue(error, error == null); log("verifyContainerType: " + containerType); try (Closable c = addContextLayer( "but the current state is not " + containerType.name())) { switch (containerType) { case WORKSPACE: { - waitForLauncherObject(APPS_RES_ID); + if (mDevice.isNaturalOrientation()) { + waitForLauncherObject(APPS_RES_ID); + } else { + waitUntilGone(APPS_RES_ID); + } waitUntilGone(OVERVIEW_RES_ID); waitUntilGone(WIDGETS_RES_ID); return waitForLauncherObject(WORKSPACE_RES_ID); @@ -490,6 +502,13 @@ public final class LauncherInstrumentation { } } + @NonNull + public AddToHomeScreenPrompt getAddToHomeScreenPrompt() { + try (LauncherInstrumentation.Closable c = addContextLayer("want to get widget cell")) { + return new AddToHomeScreenPrompt(this); + } + } + /** * Gets the Overview object if the current state is showing the overview panel. Fails if the * launcher is not in that state. @@ -504,17 +523,6 @@ public final class LauncherInstrumentation { } /** - * Gets the Base overview object if either Launcher is in overview state or the fallback - * overview activity is showing. Fails otherwise. - * - * @return BaseOverview object. - */ - @NonNull - public BaseOverview getBaseOverview() { - return new BaseOverview(this); - } - - /** * Gets the All Apps object if the current state is showing the all apps panel opened by swiping * from workspace. Fails if the launcher is not in that state. Please don't call this method if * App Apps was opened by swiping up from Overview, as it won't fail and will return an @@ -605,6 +613,16 @@ public final class LauncherInstrumentation { } @NonNull + UiObject2 waitForLauncherObject(BySelector selector) { + return waitForObjectBySelector(selector.pkg(getLauncherPackageName())); + } + + @NonNull + UiObject2 tryWaitForLauncherObject(BySelector selector, long timeout) { + return tryWaitForObjectBySelector(selector.pkg(getLauncherPackageName()), timeout); + } + + @NonNull UiObject2 waitForFallbackLauncherObject(String resName) { return waitForObjectBySelector(getFallbackLauncherObjectSelector(resName)); } @@ -615,6 +633,10 @@ public final class LauncherInstrumentation { return object; } + private UiObject2 tryWaitForObjectBySelector(BySelector selector, long timeout) { + return mDevice.wait(Until.findObject(selector), timeout); + } + BySelector getLauncherObjectSelector(String resName) { return By.res(getLauncherPackageName(), resName); } @@ -688,7 +710,7 @@ public final class LauncherInstrumentation { // Inject a swipe gesture. Inject exactly 'steps' motion points, incrementing event time by a // fixed interval each time. - private void linearGesture(int startX, int startY, int endX, int endY, int steps) { + void linearGesture(int startX, int startY, int endX, int endY, int steps) { final long downTime = SystemClock.uptimeMillis(); final Point start = new Point(startX, startY); final Point end = new Point(endX, endY); diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java index 8b124641f..641c41353 100644 --- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java +++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java @@ -16,7 +16,8 @@ package com.android.launcher3.tapl; -import androidx.test.uiautomator.Direction; +import android.graphics.Rect; + import androidx.test.uiautomator.UiObject2; import androidx.test.uiautomator.Until; @@ -26,7 +27,6 @@ import com.android.launcher3.testing.TestProtocol; * A recent task in the overview panel carousel. */ public final class OverviewTask { - static final int FLING_SPEED = 3000; private static final long WAIT_TIME_MS = 60000; private final LauncherInstrumentation mLauncher; private final UiObject2 mTask; @@ -51,7 +51,10 @@ public final class OverviewTask { "want to dismiss a task")) { verifyActiveContainer(); // Dismiss the task via flinging it up. - mTask.fling(Direction.DOWN, (int) (FLING_SPEED * mLauncher.getDisplayDensity())); + final Rect taskBounds = mTask.getVisibleBounds(); + final int centerX = taskBounds.centerX(); + final int centerY = taskBounds.centerY(); + mLauncher.linearGesture(centerX, centerY, centerX, 0, 10); mLauncher.waitForIdle(); } } diff --git a/src/com/android/launcher3/ProgressInterface.java b/tests/tapl/com/android/launcher3/tapl/Widget.java index 663d8ba5e..128789dbc 100644 --- a/src/com/android/launcher3/ProgressInterface.java +++ b/tests/tapl/com/android/launcher3/tapl/Widget.java @@ -14,13 +14,11 @@ * limitations under the License. */ -package com.android.launcher3; +package com.android.launcher3.tapl; -/** - * Progress is defined as a value with range [0, 1], and is specific to each implementor. - * It is used when there is a transition from one state of the UI to another. - */ -public interface ProgressInterface { - void setProgress(float progress); - float getProgress(); -}
\ No newline at end of file +import androidx.test.uiautomator.UiObject2; + +public class Widget { + Widget(LauncherInstrumentation launcher, UiObject2 widget) { + } +} diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java index 33754c125..b01b6f363 100644 --- a/tests/tapl/com/android/launcher3/tapl/Workspace.java +++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java @@ -27,6 +27,7 @@ import android.view.MotionEvent; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.test.uiautomator.By; import androidx.test.uiautomator.Direction; import androidx.test.uiautomator.UiObject2; @@ -142,11 +143,17 @@ public final class Workspace extends Home { } @NonNull - private AppIcon getHotseatAppIcon(String appName) { + public AppIcon getHotseatAppIcon(String appName) { return new AppIcon(mLauncher, mLauncher.getObjectInContainer( mHotseat, AppIcon.getAppIconSelector(appName, mLauncher))); } + @NonNull + public Folder getHotseatFolder(String appName) { + return new Folder(mLauncher, mLauncher.getObjectInContainer( + mHotseat, Folder.getSelector(appName, mLauncher))); + } + static void dragIconToWorkspace( LauncherInstrumentation launcher, Launchable launchable, Point dest, String longPressIndicator) { @@ -213,6 +220,21 @@ public final class Workspace extends Home { @Override protected int getSwipeStartY() { - return mLauncher.waitForLauncherObject("hotseat").getVisibleBounds().top; + return mLauncher.getRealDisplaySize().y - 1; + } + + @Nullable + public Widget tryGetWidget(String label, long timeout) { + final UiObject2 widget = mLauncher.tryWaitForLauncherObject( + By.clazz("com.android.launcher3.widget.LauncherAppWidgetHostView").desc(label), + timeout); + return widget != null ? new Widget(mLauncher, widget) : null; + } + + @Nullable + public Widget tryGetPendingWidget(long timeout) { + final UiObject2 widget = mLauncher.tryWaitForLauncherObject( + By.clazz("com.android.launcher3.widget.PendingAppWidgetHostView"), timeout); + return widget != null ? new Widget(mLauncher, widget) : null; } }
\ No newline at end of file |