diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java index d0059f7207..71148493e8 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java @@ -194,7 +194,8 @@ public class TaskbarDragLayer extends BaseDragLayer { public boolean dispatchKeyEvent(KeyEvent event) { if (event.getAction() == ACTION_UP && event.getKeyCode() == KEYCODE_BACK) { AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity); - if (topView != null && topView.onBackPressed()) { + if (topView != null && topView.canHandleBack()) { + topView.onBackInvoked(); // Handled by the floating view. return true; } diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java index 044afd6725..d91b650311 100644 --- a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java +++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java @@ -71,7 +71,8 @@ public class TaskbarOverlayDragLayer extends public boolean dispatchKeyEvent(KeyEvent event) { if (event.getAction() == ACTION_UP && event.getKeyCode() == KEYCODE_BACK) { AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity); - if (topView != null && topView.onBackPressed()) { + if (topView != null && topView.canHandleBack()) { + topView.onBackInvoked(); return true; } } diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 0673dc6e9f..30850b9186 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -84,6 +84,7 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.LauncherState; +import com.android.launcher3.OnBackPressedHandler; import com.android.launcher3.QuickstepAccessibilityDelegate; import com.android.launcher3.QuickstepTransitionManager; import com.android.launcher3.R; @@ -638,20 +639,49 @@ public class QuickstepLauncher extends Launcher { getOnBackInvokedDispatcher().registerOnBackInvokedCallback( OnBackInvokedDispatcher.PRIORITY_DEFAULT, new OnBackAnimationCallback() { + + @Nullable OnBackPressedHandler mActiveOnBackPressedHandler; + + @Override + public void onBackStarted(@NonNull BackEvent backEvent) { + if (mActiveOnBackPressedHandler != null) { + mActiveOnBackPressedHandler.onBackCancelled(); + } + mActiveOnBackPressedHandler = getOnBackPressedHandler(); + mActiveOnBackPressedHandler.onBackStarted(); + } + @Override public void onBackInvoked() { - onBackPressed(); + // Recreate mActiveOnBackPressedHandler if necessary to avoid NPE because: + // 1. b/260636433: In 3-button-navigation mode, onBackStarted() is not + // called on ACTION_DOWN before onBackInvoked() is called in ACTION_UP. + // 2. Launcher#onBackPressed() will call onBackInvoked() without calling + // onBackInvoked() beforehand. + if (mActiveOnBackPressedHandler == null) { + mActiveOnBackPressedHandler = getOnBackPressedHandler(); + } + mActiveOnBackPressedHandler.onBackInvoked(); + mActiveOnBackPressedHandler = null; TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onBackInvoked"); } @Override public void onBackProgressed(@NonNull BackEvent backEvent) { - QuickstepLauncher.this.onBackProgressed(backEvent.getProgress()); + if (!FeatureFlags.IS_STUDIO_BUILD && mActiveOnBackPressedHandler == null) { + return; + } + mActiveOnBackPressedHandler + .onBackProgressed(backEvent.getProgress()); } @Override public void onBackCancelled() { - QuickstepLauncher.this.onBackCancelled(); + if (!FeatureFlags.IS_STUDIO_BUILD && mActiveOnBackPressedHandler == null) { + return; + } + mActiveOnBackPressedHandler.onBackCancelled(); + mActiveOnBackPressedHandler = null; } }); } diff --git a/quickstep/src/com/android/quickstep/views/AllAppsEduView.java b/quickstep/src/com/android/quickstep/views/AllAppsEduView.java index d79b318023..716d389a2c 100644 --- a/quickstep/src/com/android/quickstep/views/AllAppsEduView.java +++ b/quickstep/src/com/android/quickstep/views/AllAppsEduView.java @@ -111,11 +111,6 @@ public class AllAppsEduView extends AbstractFloatingView { return (type & TYPE_ALL_APPS_EDU) != 0; } - @Override - public boolean onBackPressed() { - return true; - } - @Override public boolean canInterceptEventsInSystemGestureRegion() { return true; diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java index 49afc1f170..796fa80b71 100644 --- a/src/com/android/launcher3/AbstractFloatingView.java +++ b/src/com/android/launcher3/AbstractFloatingView.java @@ -46,7 +46,8 @@ import java.lang.annotation.RetentionPolicy; /** * Base class for a View which shows a floating UI on top of the launcher UI. */ -public abstract class AbstractFloatingView extends LinearLayout implements TouchController { +public abstract class AbstractFloatingView extends LinearLayout implements TouchController, + OnBackPressedHandler { @IntDef(flag = true, value = { TYPE_FOLDER, @@ -165,12 +166,16 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch protected abstract boolean isOfType(@FloatingViewType int type); - /** @return Whether the back is consumed. If false, Launcher will handle the back as well. */ - public boolean onBackPressed() { - close(true); + /** Return true if this view can consume back press. */ + public boolean canHandleBack() { return true; } + @Override + public void onBackInvoked() { + close(true); + } + @Override public boolean onControllerTouchEvent(MotionEvent ev) { return false; diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java index ca1fe40ed9..6f3e94829c 100644 --- a/src/com/android/launcher3/BaseDraggingActivity.java +++ b/src/com/android/launcher3/BaseDraggingActivity.java @@ -125,9 +125,13 @@ public abstract class BaseDraggingActivity extends BaseActivity mCurrentActionMode = null; } + protected boolean isInAutoCancelActionMode() { + return mCurrentActionMode != null && AUTO_CANCEL_ACTION_MODE == mCurrentActionMode.getTag(); + } + @Override public boolean finishAutoCancelActionMode() { - if (mCurrentActionMode != null && AUTO_CANCEL_ACTION_MODE == mCurrentActionMode.getTag()) { + if (isInAutoCancelActionMode()) { mCurrentActionMode.finish(); return true; } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index de5419eb7b..96dd5692c7 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -560,6 +560,61 @@ public class Launcher extends StatefulActivity setTitle(R.string.home_screen); } + /** + * Provide {@link OnBackPressedHandler} in below order: + *
    + *
  1. auto cancel action mode handler + *
  2. drag handler + *
  3. view handler + *
  4. state handler + *
+ * + * A back gesture (a single click on back button, or a swipe back gesture that contains a series + * of swipe events) should be handled by the same handler from above list. For a new back + * gesture, a new handler should be regenerated. + * + * Note that state handler will always be handling the back press event if the previous 3 don't. + */ + @NonNull + protected OnBackPressedHandler getOnBackPressedHandler() { + // #1 auto cancel action mode handler + if (isInAutoCancelActionMode()) { + return this::finishAutoCancelActionMode; + } + + // #2 drag handler + if (mDragController.isDragging()) { + return mDragController::cancelDrag; + } + + // #3 view handler + AbstractFloatingView topView = + AbstractFloatingView.getTopOpenView(Launcher.this); + if (topView != null && topView.canHandleBack()) { + return topView; + } + + // #4 state handler + return new OnBackPressedHandler() { + @Override + public void onBackInvoked() { + onStateBack(); + } + + @Override + public void onBackProgressed( + @FloatRange(from = 0.0, to = 1.0) float backProgress) { + mStateManager.getState().onBackProgressed( + Launcher.this, backProgress); + } + + @Override + public void onBackCancelled() { + mStateManager.getState().onBackCancelled(Launcher.this); + } + }; + } + protected LauncherOverlayManager getDefaultOverlay() { return new LauncherOverlayManager() { }; } @@ -2047,36 +2102,13 @@ public class Launcher extends StatefulActivity @Override public void onBackPressed() { - if (finishAutoCancelActionMode()) { - return; - } - - if (mDragController.isDragging()) { - mDragController.cancelDrag(); - return; - } - - // Note: There should be at most one log per method call. This is enforced implicitly - // by using if-else statements. - AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this); - if (topView == null || !topView.onBackPressed()) { - // Not handled by the floating view. - onStateBack(); - } + getOnBackPressedHandler().onBackInvoked(); } protected void onStateBack() { mStateManager.getState().onBackPressed(this); } - protected void onBackProgressed(@FloatRange(from = 0.0, to = 1.0) float backProgress) { - mStateManager.getState().onBackProgressed(this, backProgress); - } - - protected void onBackCancelled() { - mStateManager.getState().onBackCancelled(this); - } - protected void onScreenOnChanged(boolean isOn) { // Reset AllApps to its initial state only if we are not in the middle of // processing a multi-step drop diff --git a/src/com/android/launcher3/OnBackPressedHandler.java b/src/com/android/launcher3/OnBackPressedHandler.java new file mode 100644 index 0000000000..485aa0ae38 --- /dev/null +++ b/src/com/android/launcher3/OnBackPressedHandler.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3; + +import androidx.annotation.FloatRange; + +/** + * Interface that mimics {@link android.window.OnBackInvokedCallback} without dependencies on U's + * API such as {@link android.window.BackEvent}. + * + *

Impl can assume below order during a back gesture: + *

    + *
  1. [optional] one {@link #onBackStarted()} will be called to start the gesture + *
  2. zero or multiple {@link #onBackProgressed(float)} will be called during swipe gesture + *
  3. either one of {@link #onBackInvoked()} or {@link #onBackCancelled()} will be called to end + * the gesture + */ +public interface OnBackPressedHandler { + + /** Called when back has started. */ + default void onBackStarted() {} + + /** Called when back is committed. */ + void onBackInvoked(); + + /** Called with back gesture's progress. */ + default void onBackProgressed(@FloatRange(from = 0.0, to = 1.0) float backProgress) {} + + /** Called when user drops the back gesture. */ + default void onBackCancelled() {} +} diff --git a/src/com/android/launcher3/allapps/DiscoveryBounce.java b/src/com/android/launcher3/allapps/DiscoveryBounce.java index be261f76b5..0188a476d6 100644 --- a/src/com/android/launcher3/allapps/DiscoveryBounce.java +++ b/src/com/android/launcher3/allapps/DiscoveryBounce.java @@ -81,10 +81,10 @@ public class DiscoveryBounce extends AbstractFloatingView { } @Override - public boolean onBackPressed() { - super.onBackPressed(); - // Go back to the previous state (from a user's perspective this floating view isn't - // something to go back from). + public boolean canHandleBack() { + // Since DiscoveryBounce doesn't handle back, onBackInvoked() won't be called and we should + // close it without animation. + close(false); return false; } diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 94eea3598e..9a5d77e3a2 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -1579,17 +1579,14 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo return getOpenView(activityContext, TYPE_FOLDER); } - /** - * Navigation bar back key or hardware input back key has been issued. - */ + /** Navigation bar back key or hardware input back key has been issued. */ @Override - public boolean onBackPressed() { + public void onBackInvoked() { if (isEditingName()) { mFolderName.dispatchBackKey(); } else { - super.onBackPressed(); + super.onBackInvoked(); } - return true; } @Override diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java index a2353d8259..56dffa9b64 100644 --- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java +++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java @@ -175,8 +175,9 @@ public class SecondaryDisplayLauncher extends BaseDraggingActivity // Note: There should be at most one log per method call. This is enforced implicitly // by using if-else statements. AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this); - if (topView != null && topView.onBackPressed()) { + if (topView != null && topView.canHandleBack()) { // Handled by the floating view. + topView.onBackInvoked(); } else { showAppDrawer(false); } diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java index 38cdb4b500..c78ecf5f51 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java +++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java @@ -17,7 +17,9 @@ package com.android.launcher3.widget.picker; import static android.view.View.MeasureSpec.makeMeasureSpec; +import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; +import static com.android.launcher3.allapps.AllAppsTransitionController.SWIPE_ALL_APPS_TO_HOME_MIN_SCALE; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_SEARCHED; import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL; @@ -45,6 +47,7 @@ import android.view.animation.Interpolator; import android.widget.Button; import android.widget.TextView; +import androidx.annotation.FloatRange; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.recyclerview.widget.DefaultItemAnimator; @@ -54,6 +57,7 @@ import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; import com.android.launcher3.Utilities; +import com.android.launcher3.anim.Interpolators; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.model.UserManagerState; @@ -264,6 +268,15 @@ public class WidgetsFullSheet extends BaseWidgetSheet attachScrollbarToRecyclerView(currentRecyclerView); } + @Override + public void onBackProgressed(@FloatRange(from = 0.0, to = 1.0) float progress) { + float deceleratedProgress = + Interpolators.PREDICTIVE_BACK_DECELERATED_EASE.getInterpolation(progress); + float scaleProgress = SWIPE_ALL_APPS_TO_HOME_MIN_SCALE + + (1 - SWIPE_ALL_APPS_TO_HOME_MIN_SCALE) * (1 - deceleratedProgress); + SCALE_PROPERTY.set(this, scaleProgress); + } + private void attachScrollbarToRecyclerView(WidgetsRecyclerView recyclerView) { recyclerView.bindFastScrollbar(); if (mCurrentWidgetsRecyclerView != recyclerView) { @@ -736,12 +749,12 @@ public class WidgetsFullSheet extends BaseWidgetSheet } @Override - public boolean onBackPressed() { + public void onBackInvoked() { if (mIsInSearchMode) { mSearchBar.reset(); - return true; + } else { + super.onBackInvoked(); } - return super.onBackPressed(); } @Override