diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml index d67b23be0a..31c0f5f2ab 100644 --- a/quickstep/res/values/config.xml +++ b/quickstep/res/values/config.xml @@ -30,6 +30,7 @@ determines how many thumbnails will be fetched in the background. --> 3 12 + 20 200 diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java index 43f015cd2d..978bd470ff 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java @@ -77,6 +77,7 @@ public class TaskbarUnfoldAnimationController { @Override public void onTransitionFinished() { mMoveFromCenterAnimator.onTransitionFinished(); + mMoveFromCenterAnimator.clearRegisteredViews(); } @Override diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java index 283743dc64..ef6f53e8f5 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java @@ -24,7 +24,7 @@ import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.Utilities.EDGE_NAV_BAR; import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback; import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; -import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC; +import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; import android.animation.ObjectAnimator; @@ -38,11 +38,11 @@ import com.android.launcher3.LauncherState; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.states.StateAnimationConfig; -import com.android.launcher3.util.VibratorWrapper; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.util.AnimatorControllerWithResistance; import com.android.quickstep.util.MotionPauseDetector; import com.android.quickstep.util.OverviewToHomeAnim; +import com.android.quickstep.util.VibratorWrapper; import com.android.quickstep.views.RecentsView; /** diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java index 40c3e02238..ff3c517949 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java @@ -41,7 +41,7 @@ import static com.android.launcher3.states.StateAnimationConfig.SKIP_SCRIM; import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT; import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP; import static com.android.launcher3.util.DisplayController.getSingleFrameMs; -import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC; +import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC; import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET; import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA; import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS; @@ -67,14 +67,15 @@ import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.touch.BaseSwipeDetector; import com.android.launcher3.touch.BothAxesSwipeDetector; import com.android.launcher3.util.TouchController; -import com.android.launcher3.util.VibratorWrapper; import com.android.quickstep.AnimatedFloat; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.util.AnimatorControllerWithResistance; import com.android.quickstep.util.LayoutUtils; import com.android.quickstep.util.MotionPauseDetector; +import com.android.quickstep.util.VibratorWrapper; import com.android.quickstep.util.WorkspaceRevealAnim; import com.android.quickstep.views.LauncherRecentsView; +import com.android.quickstep.views.RecentsView; /** * Handles quick switching to a recent task from the home screen. To give as much flexibility to @@ -398,6 +399,14 @@ public class NoButtonQuickSwitchTouchController implements TouchController, nonOverviewAnim.setFloatValues(startProgress, endProgress); mNonOverviewAnim.dispatchOnStart(); } + if (targetState == QUICK_SWITCH) { + // Navigating to quick switch, add scroll feedback since the first time is not + // considered a scroll by the RecentsView. + VibratorWrapper.INSTANCE.get(mLauncher).vibrate( + RecentsView.SCROLL_VIBRATION_PRIMITIVE, + RecentsView.SCROLL_VIBRATION_PRIMITIVE_SCALE, + RecentsView.SCROLL_VIBRATION_FALLBACK); + } nonOverviewAnim.setDuration(Math.max(xDuration, yDuration)); mNonOverviewAnim.setEndAction(() -> onAnimationToStateCompleted(targetState)); diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java index 0603ba502a..010f463438 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java @@ -22,6 +22,7 @@ import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_BOTH import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.os.SystemClock; +import android.os.VibrationEffect; import android.view.MotionEvent; import android.view.View; import android.view.animation.Interpolator; @@ -41,6 +42,7 @@ import com.android.launcher3.util.FlingBlockCheck; import com.android.launcher3.util.TouchController; import com.android.launcher3.views.BaseDragLayer; import com.android.quickstep.SysUINavigationMode; +import com.android.quickstep.util.VibratorWrapper; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; @@ -55,6 +57,12 @@ public abstract class TaskViewTouchController private static final long MIN_TASK_DISMISS_ANIMATION_DURATION = 300; private static final long MAX_TASK_DISMISS_ANIMATION_DURATION = 600; + public static final int TASK_DISMISS_VIBRATION_PRIMITIVE = + Utilities.ATLEAST_R ? VibrationEffect.Composition.PRIMITIVE_TICK : -1; + public static final float TASK_DISMISS_VIBRATION_PRIMITIVE_SCALE = 1f; + public static final VibrationEffect TASK_DISMISS_VIBRATION_FALLBACK = + VibratorWrapper.EFFECT_TEXTURE_TICK; + protected final T mActivity; private final SingleAxisSwipeDetector mDetector; private final RecentsView mRecentsView; @@ -334,10 +342,10 @@ public abstract class TaskViewTouchController fling = false; } PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler(); + boolean goingUp = orientationHandler.isGoingUp(velocity, mIsRtl); float progress = mCurrentAnimation.getProgressFraction(); float interpolatedProgress = mCurrentAnimation.getInterpolatedProgress(); if (fling) { - boolean goingUp = orientationHandler.isGoingUp(velocity, mIsRtl); goingToEnd = goingUp == mCurrentAnimationIsGoingUp; } else { goingToEnd = interpolatedProgress > SUCCESS_TRANSITION_PROGRESS; @@ -357,6 +365,10 @@ public abstract class TaskViewTouchController mCurrentAnimation.startWithVelocity(mActivity, goingToEnd, velocity * orientationHandler.getSecondaryTranslationDirectionFactor(), mEndDisplacement, animationDuration); + if (goingUp && goingToEnd) { + VibratorWrapper.INSTANCE.get(mActivity).vibrate(TASK_DISMISS_VIBRATION_PRIMITIVE, + TASK_DISMISS_VIBRATION_PRIMITIVE_SCALE, TASK_DISMISS_VIBRATION_FALLBACK); + } } private void clearState() { diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java index 830737d8c0..1e841da40b 100644 --- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java +++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java @@ -36,7 +36,6 @@ import static com.android.launcher3.util.DisplayController.getSingleFrameMs; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK; -import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC; import static com.android.quickstep.GestureState.GestureEndTarget.HOME; import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK; import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK; @@ -46,6 +45,7 @@ import static com.android.quickstep.GestureState.STATE_END_TARGET_SET; import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_CANCELED; import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED; import static com.android.quickstep.MultiStateCallback.DEBUG_STATES; +import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC; import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD; import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME; @@ -92,7 +92,6 @@ import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.tracing.InputConsumerProto; import com.android.launcher3.tracing.SwipeHandlerProto; import com.android.launcher3.util.TraceHelper; -import com.android.launcher3.util.VibratorWrapper; import com.android.launcher3.util.WindowBounds; import com.android.quickstep.BaseActivityInterface.AnimationFactory; import com.android.quickstep.GestureState.GestureEndTarget; @@ -112,6 +111,7 @@ import com.android.quickstep.util.SurfaceTransactionApplier; import com.android.quickstep.util.SwipePipToHomeAnimator; import com.android.quickstep.util.TaskViewSimulator; import com.android.quickstep.util.TransformParams; +import com.android.quickstep.util.VibratorWrapper; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -945,6 +945,7 @@ public abstract class AbsSwipeUpHandler, mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT); // Notify swipe-to-home (recents animation) is finished SystemUiProxy.INSTANCE.get(mContext).notifySwipeToHomeFinished(); + LauncherSplitScreenListener.INSTANCE.getNoCreate().notifySwipingToHome(); break; case RECENTS: mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT @@ -1805,8 +1806,13 @@ public abstract class AbsSwipeUpHandler, mGestureState.updateLastStartedTaskId(taskId); boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds() .contains(taskId); + boolean isOldTaskSplit = LauncherSplitScreenListener.INSTANCE.getNoCreate() + .getRunningSplitTaskIds().length > 0; nextTask.launchTask(success -> { resultCallback.accept(success); + if (isOldTaskSplit) { + SystemUiProxy.INSTANCE.getNoCreate().exitSplitScreen(taskId); + } if (success) { if (hasTaskPreviouslyAppeared) { onRestartPreviouslyAppearedTask(); diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java index 8fb851c399..b2324640e5 100644 --- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java +++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java @@ -32,6 +32,7 @@ import androidx.annotation.UiThread; import com.android.launcher3.statemanager.StatefulActivity; import com.android.launcher3.util.RunnableList; import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener; +import com.android.quickstep.util.LauncherSplitScreenListener; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -157,6 +158,7 @@ public class OverviewCommandHelper { } if (cmd.type == TYPE_HOME) { mService.startActivity(mOverviewComponentObserver.getHomeIntent()); + LauncherSplitScreenListener.INSTANCE.getNoCreate().notifySwipingToHome(); return true; } } else { @@ -175,6 +177,7 @@ public class OverviewCommandHelper { return launchTask(recents, getNextTask(recents), cmd); case TYPE_HOME: recents.startHome(); + LauncherSplitScreenListener.INSTANCE.getNoCreate().notifySwipingToHome(); return true; } } diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java index 7d2d41326c..aea2d4c645 100644 --- a/quickstep/src/com/android/quickstep/SystemUiProxy.java +++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java @@ -533,10 +533,17 @@ public class SystemUiProxy implements ISystemUiProxy, } } - public void exitSplitScreen() { + /** + * To be called whenever the user exits out of split screen apps (either by launching another + * app or by swiping home) + * @param topTaskId The taskId of the new app that was launched. System will then move this task + * to the front of what the user sees while removing all other split stages. + * If swiping to home (or there is no task to put at the top), can pass in -1. + */ + public void exitSplitScreen(int topTaskId) { if (mSplitScreen != null) { try { - mSplitScreen.exitSplitScreen(); + mSplitScreen.exitSplitScreen(topTaskId); } catch (RemoteException e) { Log.w(TAG, "Failed call exitSplitScreen"); } diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java index 7465db3025..b2b2f59773 100644 --- a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java +++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java @@ -43,7 +43,7 @@ import androidx.dynamicanimation.animation.SpringForce; import com.android.launcher3.R; import com.android.launcher3.ResourceUtils; import com.android.launcher3.anim.Interpolators; -import com.android.launcher3.util.VibratorWrapper; +import com.android.quickstep.util.VibratorWrapper; /** Forked from platform/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java. */ public class EdgeBackGesturePanel extends View { diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java index fc1dd6f893..851cccf09a 100644 --- a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java +++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java @@ -16,7 +16,6 @@ package com.android.quickstep.interaction; import static com.android.launcher3.Utilities.squaredHypot; -import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC; import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_COMPLETED; import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_NOT_STARTED_BAD_ANGLE; import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_NOT_STARTED_SWIPE_TOO_SHORT; @@ -26,6 +25,7 @@ import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestu import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION; import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.OVERVIEW_GESTURE_COMPLETED; import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE; +import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC; import android.animation.ValueAnimator; import android.content.Context; @@ -47,11 +47,11 @@ import androidx.annotation.Nullable; import com.android.launcher3.R; import com.android.launcher3.ResourceUtils; import com.android.launcher3.anim.Interpolators; -import com.android.launcher3.util.VibratorWrapper; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.util.MotionPauseDetector; import com.android.quickstep.util.NavBarPosition; import com.android.quickstep.util.TriggerSwipeUpTouchTracker; +import com.android.quickstep.util.VibratorWrapper; import com.android.systemui.shared.system.QuickStepContract; /** Utility class to handle Home and Assistant gestures. */ diff --git a/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java b/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java index 0f4ed014f0..fa4cddc426 100644 --- a/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java +++ b/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java @@ -44,8 +44,6 @@ public class LauncherSplitScreenListener extends ISplitScreenListener.Stub { if (frozen) { mPersistentGroupedIds = getRunningSplitTaskIds(); } else { - // TODO(b/198310766) Need to also explicitly exit split screen if - // we're not currently viewing split screened apps mPersistentGroupedIds = EMPTY_ARRAY; } } @@ -53,8 +51,11 @@ public class LauncherSplitScreenListener extends ISplitScreenListener.Stub { /** * Gets set to current split taskIDs whenever the task list is frozen, and set to empty array - * whenever task list unfreezes. - * When not null, this indicates that we need to load a GroupedTaskView as the most recent + * whenever task list unfreezes. This also gets set to empty array whenever the user swipes to + * home - in that case the task list does not unfreeze immediately after the gesture, so it's + * done via {@link #notifySwipingToHome()}. + * + * When not empty, this indicates that we need to load a GroupedTaskView as the most recent * page, so user can quickswitch back to a grouped task. */ private int[] mPersistentGroupedIds; @@ -140,6 +141,18 @@ public class LauncherSplitScreenListener extends ISplitScreenListener.Stub { } } + /** Notifies SystemUi to remove any split screen state */ + public void notifySwipingToHome() { + boolean hasSplitTasks = LauncherSplitScreenListener.INSTANCE.getNoCreate() + .getPersistentSplitIds().length > 0; + if (!hasSplitTasks) { + return; + } + + SystemUiProxy.INSTANCE.getNoCreate().exitSplitScreen(-1); + mPersistentGroupedIds = EMPTY_ARRAY; + } + private void resetTaskId(StagedSplitTaskPosition taskPosition) { taskPosition.taskId = -1; } diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/quickstep/src/com/android/quickstep/util/VibratorWrapper.java similarity index 66% rename from src/com/android/launcher3/util/VibratorWrapper.java rename to quickstep/src/com/android/quickstep/util/VibratorWrapper.java index b0defd445a..211bd08b33 100644 --- a/src/com/android/launcher3/util/VibratorWrapper.java +++ b/quickstep/src/com/android/quickstep/util/VibratorWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.launcher3.util; +package com.android.quickstep.util; import static android.os.VibrationEffect.createPredefined; import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED; @@ -21,15 +21,20 @@ import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; +import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; +import android.media.AudioAttributes; import android.os.Build; import android.os.VibrationEffect; import android.os.Vibrator; import android.provider.Settings; +import com.android.launcher3.Utilities; +import com.android.launcher3.util.MainThreadInitializedObject; + /** * Wrapper around {@link Vibrator} to easily perform haptic feedback where necessary. */ @@ -39,8 +44,15 @@ public class VibratorWrapper { public static final MainThreadInitializedObject INSTANCE = new MainThreadInitializedObject<>(VibratorWrapper::new); + public static final AudioAttributes VIBRATION_ATTRS = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .build(); + public static final VibrationEffect EFFECT_CLICK = createPredefined(VibrationEffect.EFFECT_CLICK); + public static final VibrationEffect EFFECT_TEXTURE_TICK = + VibrationEffect.createPredefined(VibrationEffect.EFFECT_TEXTURE_TICK); /** * Haptic when entering overview. @@ -78,7 +90,27 @@ public class VibratorWrapper { /** Vibrates with the given effect if haptic feedback is available and enabled. */ public void vibrate(VibrationEffect vibrationEffect) { if (mHasVibrator && mIsHapticFeedbackEnabled) { - UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(vibrationEffect)); + UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(vibrationEffect, VIBRATION_ATTRS)); + } + } + + /** + * Vibrates with a single primitive, if supported, or use a fallback effect instead. This only + * vibrates if haptic feedback is available and enabled. + */ + @SuppressLint("NewApi") + public void vibrate(int primitiveId, float primitiveScale, VibrationEffect fallbackEffect) { + if (mHasVibrator && mIsHapticFeedbackEnabled) { + UI_HELPER_EXECUTOR.execute(() -> { + if (Utilities.ATLEAST_R && primitiveId >= 0 + && mVibrator.areAllPrimitivesSupported(primitiveId)) { + mVibrator.vibrate(VibrationEffect.startComposition() + .addPrimitive(primitiveId, primitiveScale) + .compose(), VIBRATION_ATTRS); + } else { + mVibrator.vibrate(fallbackEffect, VIBRATION_ATTRS); + } + }); } } } diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index e85adcc2b9..dd470e87d0 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -79,7 +79,9 @@ import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; +import android.os.SystemClock; import android.os.UserHandle; +import android.os.VibrationEffect; import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; @@ -160,6 +162,7 @@ import com.android.quickstep.util.SplitSelectStateController; import com.android.quickstep.util.SurfaceTransactionApplier; import com.android.quickstep.util.TaskViewSimulator; import com.android.quickstep.util.TransformParams; +import com.android.quickstep.util.VibratorWrapper; import com.android.systemui.plugins.ResourceProvider; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.Task.TaskKey; @@ -244,6 +247,12 @@ public abstract class RecentsView mScrollHapticMinGapMillis) { + mScrollLastHapticTimestamp = now; + VibratorWrapper.INSTANCE.get(mContext).vibrate(SCROLL_VIBRATION_PRIMITIVE, + SCROLL_VIBRATION_PRIMITIVE_SCALE, SCROLL_VIBRATION_FALLBACK); + } + } + @Override protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) { // Enables swiping to the left or right only if the task overlay is not modal. @@ -2002,9 +2034,6 @@ public abstract class RecentsView 0; if (ActivityManagerWrapper.getInstance() .startActivityFromRecents(mTask.key, opts.options)) { + if (isOldTaskSplit) { + SystemUiProxy.INSTANCE.getNoCreate().exitSplitScreen(mTask.key.id); + } RecentsView recentsView = getRecentsView(); if (ENABLE_QUICKSTEP_LIVE_TILE.get() && recentsView.getRunningTaskViewId() != -1) { recentsView.onTaskLaunchedInLiveTileMode(); diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index 2f9b5af050..108091c1ee 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -108,6 +108,8 @@ public abstract class PagedView extends ViewGrou // relative scroll position unchanged in updateCurrentPageScroll. Cleared when snapping to a // page. protected int mCurrentPageScrollDiff; + // The current page the PagedView is scrolling over on it's way to the destination page. + protected int mCurrentScrollOverPage; @ViewDebug.ExportedProperty(category = "launcher") protected int mNextPage = INVALID_PAGE; @@ -180,6 +182,7 @@ public abstract class PagedView extends ViewGrou mScroller = new OverScroller(context, SCROLL); mCurrentPage = 0; + mCurrentScrollOverPage = 0; final ViewConfiguration configuration = ViewConfiguration.get(context); mTouchSlop = configuration.getScaledTouchSlop(); @@ -437,6 +440,7 @@ public abstract class PagedView extends ViewGrou } int prevPage = overridePrevPage != INVALID_PAGE ? overridePrevPage : mCurrentPage; mCurrentPage = validateNewPage(currentPage); + mCurrentScrollOverPage = mCurrentPage; updateCurrentPageScroll(); notifyPageSwitchListener(prevPage); invalidate(); @@ -557,9 +561,11 @@ public abstract class PagedView extends ViewGrou if (newPos < mMinScroll && oldPos >= mMinScroll) { mEdgeGlowLeft.onAbsorb((int) mScroller.getCurrVelocity()); mScroller.abortAnimation(); + onEdgeAbsorbingScroll(); } else if (newPos > mMaxScroll && oldPos <= mMaxScroll) { mEdgeGlowRight.onAbsorb((int) mScroller.getCurrVelocity()); mScroller.abortAnimation(); + onEdgeAbsorbingScroll(); } } @@ -577,6 +583,7 @@ public abstract class PagedView extends ViewGrou sendScrollAccessibilityEvent(); int prevPage = mCurrentPage; mCurrentPage = validateNewPage(mNextPage); + mCurrentScrollOverPage = mCurrentPage; mNextPage = INVALID_PAGE; notifyPageSwitchListener(prevPage); @@ -766,7 +773,8 @@ public abstract class PagedView extends ViewGrou childStart += primaryDimension + getChildGap(); // This makes sure that the space is added after the page, not after each panel - if (i % panelCount == panelCount - 1) { + int lastPanel = mIsRtl ? 0 : panelCount - 1; + if (i % panelCount == lastPanel) { childStart += mPageSpacing; } } @@ -837,6 +845,7 @@ public abstract class PagedView extends ViewGrou public void onViewRemoved(View child) { super.onViewRemoved(child); mCurrentPage = validateNewPage(mCurrentPage); + mCurrentScrollOverPage = mCurrentPage; dispatchPageCountChanged(); } @@ -1408,6 +1417,20 @@ public abstract class PagedView extends ViewGrou protected void onNotSnappingToPageInFreeScroll() { } + /** + * Called when the view edges absorb part of the scroll. Subclasses can override this + * to provide custom behavior during animation. + */ + protected void onEdgeAbsorbingScroll() { + } + + /** + * Called when the current page closest to the center of the screen changes as part of the + * scroll. Subclasses can override this to provide custom behavior during scroll. + */ + protected void onScrollOverPageChanged() { + } + protected boolean shouldFlingForVelocity(int velocity) { float threshold = mAllowEasyFling ? mEasyFlingThresholdVelocity : mFlingThresholdVelocity; return Math.abs(velocity) > threshold; @@ -1553,7 +1576,7 @@ public abstract class PagedView extends ViewGrou return getDisplacementFromScreenCenter(childIndex, screenCenter); } - private int getScreenCenter(int primaryScroll) { + protected int getScreenCenter(int primaryScroll) { float primaryScale = mOrientationHandler.getPrimaryScale(this); float primaryPivot = mOrientationHandler.getPrimaryValue(getPivotX(), getPivotY()); int pageOrientationSize = mOrientationHandler.getMeasuredSize(this); @@ -1691,6 +1714,15 @@ public abstract class PagedView extends ViewGrou return mAllowOverScroll; } + @Override + protected void onScrollChanged(int l, int t, int oldl, int oldt) { + int newDestinationPage = getDestinationPage(); + if (newDestinationPage >= 0 && newDestinationPage != mCurrentScrollOverPage) { + mCurrentScrollOverPage = newDestinationPage; + onScrollOverPageChanged(); + } + } + @Override public CharSequence getAccessibilityClassName() { // Some accessibility services have special logic for ScrollView. Since we provide same diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index a21c8e30f1..9a3da53c85 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -338,15 +338,17 @@ public class Workspace extends PagedView int paddingBottom = grid.cellLayoutBottomPaddingPx; int panelCount = getPanelCount(); + int rightPanelModulus = mIsRtl ? 0 : panelCount - 1; + int leftPanelModulus = mIsRtl ? panelCount - 1 : 0; int numberOfScreens = mScreenOrder.size(); for (int i = 0; i < numberOfScreens; i++) { int paddingLeft = paddingLeftRight; int paddingRight = paddingLeftRight; if (panelCount > 1) { - if (i % panelCount == 0) { // left side panel + if (i % panelCount == leftPanelModulus) { paddingLeft = paddingLeftRight; paddingRight = 0; - } else if (i % panelCount == panelCount - 1) { // right side panel + } else if (i % panelCount == rightPanelModulus) { paddingLeft = 0; paddingRight = paddingLeftRight; } else { // middle panel @@ -2453,21 +2455,32 @@ public class Workspace extends PagedView } int nextPage = getNextPage(); - if (layout == null && !isPageInTransition()) { - layout = verifyInsidePage(nextPage + (mIsRtl ? 1 : -1), Math.min(centerX, d.x), d.y); + IntSet pageIndexesToVerify = IntSet.wrap(nextPage - 1, nextPage + 1); + if (isTwoPanelEnabled()) { + // If two panel is enabled, users can also drag items to nextPage + 2 + pageIndexesToVerify.add(nextPage + 2); } - if (layout == null && !isPageInTransition()) { - layout = verifyInsidePage(nextPage + (mIsRtl ? -1 : 1), Math.max(centerX, d.x), d.y); + int touchX = (int) Math.min(centerX, d.x); + int touchY = d.y; + + // Go through the pages and check if the dragged item is inside one of them + for (int pageIndex : pageIndexesToVerify) { + if (layout != null || isPageInTransition()) { + break; + } + layout = verifyInsidePage(pageIndex, touchX, touchY); } - // If two panel is enabled, users can also drag items to currentPage + 2 - if (isTwoPanelEnabled() && layout == null && !isPageInTransition()) { - layout = verifyInsidePage(nextPage + (mIsRtl ? -2 : 2), Math.max(centerX, d.x), d.y); - } - - // Always pick the current page. + // If the dragged item isn't located in one of the pages above, the icon will stay on the + // current screen. For two panel pick the closest panel on the current screen, + // on one panel just choose the current page. if (layout == null && nextPage >= 0 && nextPage < getPageCount()) { + if (isTwoPanelEnabled()) { + nextPage = getScreenCenter(getScrollX()) > touchX + ? (mIsRtl ? nextPage + 1 : nextPage) // left side + : (mIsRtl ? nextPage : nextPage + 1); // right side + } layout = (CellLayout) getChildAt(nextPage); } if (layout != mDragTargetLayout) { diff --git a/src/com/android/launcher3/model/DeviceGridState.java b/src/com/android/launcher3/model/DeviceGridState.java index 761053d2d0..1076e8834b 100644 --- a/src/com/android/launcher3/model/DeviceGridState.java +++ b/src/com/android/launcher3/model/DeviceGridState.java @@ -28,6 +28,7 @@ import android.text.TextUtils; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.Utilities; import com.android.launcher3.logging.StatsLogManager.LauncherEvent; +import com.android.launcher3.util.IntSet; import java.util.Locale; import java.util.Objects; @@ -44,6 +45,7 @@ public class DeviceGridState { public static final int TYPE_PHONE = 0; public static final int TYPE_MULTI_DISPLAY = 1; public static final int TYPE_TABLET = 2; + public static final IntSet COMPATIBLE_TYPES = IntSet.wrap(TYPE_PHONE, TYPE_MULTI_DISPLAY); private final String mGridSizeString; private final int mNumHotseat; @@ -109,7 +111,9 @@ public class DeviceGridState { if (o == null || getClass() != o.getClass()) return false; DeviceGridState that = (DeviceGridState) o; return mNumHotseat == that.mNumHotseat - && mDeviceType == that.mDeviceType + && (mDeviceType == that.mDeviceType + || (COMPATIBLE_TYPES.contains(mDeviceType) + && COMPATIBLE_TYPES.contains(that.mDeviceType))) && Objects.equals(mGridSizeString, that.mGridSizeString); } } diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java index 4d63218785..b06b8a10bf 100644 --- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java +++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java @@ -20,6 +20,7 @@ import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS; import static android.view.View.GONE; import static android.view.View.VISIBLE; +import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT_ARG_KEY; import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.PLUGIN_CHANGED; import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.pluginEnabledKey; @@ -29,6 +30,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; @@ -44,6 +46,7 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.EditText; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -57,12 +60,15 @@ import androidx.preference.PreferenceViewHolder; import androidx.preference.SwitchPreference; import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.config.FlagTogglerPrefUi; import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; +import com.android.launcher3.util.OnboardingPrefs; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -104,6 +110,7 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat { initFlags(); loadPluginPrefs(); maybeAddSandboxCategory(); + addOnboardingPrefsCatergory(); if (getActivity() != null) { getActivity().setTitle("Developer Options"); @@ -153,6 +160,15 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat { } }); + if (getArguments() != null) { + String filter = getArguments().getString(EXTRA_FRAGMENT_ARG_KEY); + // Normally EXTRA_FRAGMENT_ARG_KEY is used to highlight the preference with the given + // key. This is a slight variation where we instead filter by the human-readable titles. + if (filter != null) { + filterBox.setText(filter); + } + } + View listView = getListView(); final int bottomPadding = listView.getPaddingBottom(); listView.setOnApplyWindowInsetsListener((v, insets) -> { @@ -355,6 +371,28 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat { sandboxCategory.addPreference(launchSandboxModeTutorialPreference); } + private void addOnboardingPrefsCatergory() { + PreferenceCategory onboardingCategory = newCategory("Onboarding Flows"); + onboardingCategory.setSummary("Reset these if you want to see the education again."); + for (Map.Entry titleAndKeys : OnboardingPrefs.ALL_PREF_KEYS.entrySet()) { + String title = titleAndKeys.getKey(); + String[] keys = titleAndKeys.getValue(); + Preference onboardingPref = new Preference(getContext()); + onboardingPref.setTitle(title); + onboardingPref.setSummary("Tap to reset"); + onboardingPref.setOnPreferenceClickListener(preference -> { + SharedPreferences.Editor sharedPrefsEdit = Utilities.getPrefs(getContext()).edit(); + for (String key : keys) { + sharedPrefsEdit.remove(key); + } + sharedPrefsEdit.apply(); + Toast.makeText(getContext(), "Reset " + title, Toast.LENGTH_SHORT).show(); + return true; + }); + onboardingCategory.addPreference(onboardingPref); + } + } + private String toName(String action) { String str = action.replace("com.android.systemui.action.PLUGIN_", "") .replace("com.android.launcher3.action.PLUGIN_", ""); diff --git a/src/com/android/launcher3/util/OnboardingPrefs.java b/src/com/android/launcher3/util/OnboardingPrefs.java index cf1467ade4..5ba0d30256 100644 --- a/src/com/android/launcher3/util/OnboardingPrefs.java +++ b/src/com/android/launcher3/util/OnboardingPrefs.java @@ -39,6 +39,14 @@ public class OnboardingPrefs { public static final String SEARCH_EDU_SEEN = "launcher.search_edu_seen"; public static final String SEARCH_SNACKBAR_COUNT = "launcher.keyboard_snackbar_count"; public static final String TASKBAR_EDU_SEEN = "launcher.taskbar_edu_seen"; + // When adding a new key, add it here as well, to be able to reset it from Developer Options. + public static final Map ALL_PREF_KEYS = Map.of( + "All Apps Bounce", new String[] { HOME_BOUNCE_SEEN, HOME_BOUNCE_COUNT }, + "Hybrid Hotseat Education", new String[] { HOTSEAT_DISCOVERY_TIP_COUNT, + HOTSEAT_LONGPRESS_TIP_SEEN }, + "Search Education", new String[] { SEARCH_EDU_SEEN, SEARCH_SNACKBAR_COUNT }, + "Taskbar Education", new String[] { TASKBAR_EDU_SEEN } + ); /** * Events that either have happened or have not (booleans).