From db22e3008e1c46b3c4c907ee58a6048c41b529fa Mon Sep 17 00:00:00 2001 From: Johannes Gallmann Date: Tue, 20 Feb 2024 14:23:26 +0100 Subject: [PATCH 1/9] Use STANDARD_DECELERATE interpolator for predictive back to home Bug: 325647546 Flag: ACONFIG com.android.systemui.predictive_back_system_animations TEAMFOOD Test: Manual, i.e. verifying increased responsiveness during first phase of back gesture Change-Id: Ie31d7fba7ca307c96830e0656887cda89d9bbb1a --- .../android/quickstep/LauncherBackAnimationController.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java index 5772450b62..364d9dcccf 100644 --- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java +++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java @@ -52,6 +52,7 @@ import android.window.BackMotionEvent; import android.window.BackProgressAnimator; import android.window.IOnBackInvokedCallback; +import com.android.app.animation.Interpolators; import com.android.internal.view.AppearanceRegion; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BubbleTextView; @@ -103,7 +104,8 @@ public class LauncherBackAnimationController { private float mWindowScaleEndCornerRadius; private float mWindowScaleStartCornerRadius; private final Interpolator mCancelInterpolator; - private final Interpolator mProgressInterpolator = new DecelerateInterpolator(); + private final Interpolator mProgressInterpolator = Interpolators.STANDARD_DECELERATE; + private final Interpolator mVerticalMoveInterpolator = new DecelerateInterpolator(); private final PointF mInitialTouchPos = new PointF(); private RemoteAnimationTarget mBackTarget; @@ -376,7 +378,7 @@ public class LauncherBackAnimationController { float yDirection = rawYDelta < 0 ? -1 : 1; // limit yDelta interpretation to 1/2 of screen height in either direction float deltaYRatio = Math.min(screenHeight / 2f, Math.abs(rawYDelta)) / (screenHeight / 2f); - float interpolatedYRatio = mProgressInterpolator.getInterpolation(deltaYRatio); + float interpolatedYRatio = mVerticalMoveInterpolator.getInterpolation(deltaYRatio); // limit y-shift so surface never passes 8dp screen margin float deltaY = yDirection * interpolatedYRatio * Math.max(0f, (screenHeight - height) / 2f - mWindowScaleMarginX); From 56aa9ed61bdc2597463430b88ee5b49cc0f28183 Mon Sep 17 00:00:00 2001 From: Schneider Victor-tulias Date: Tue, 13 Feb 2024 10:47:55 -0500 Subject: [PATCH 2/9] Update KQS launch task callbacks Updated KQS to allow launching tasks through OverviewCommandHelper to allow launching the overview task without it having focus. Flag: LEGACY ENABLE_KEYBOARD_QUICK_SWITCH ENABLED Fixes: 324888738 Fixes: 324904244 Test: used enter and tap to launch tasks in KQS Change-Id: I6de38ceea22c362a52a420c8ba0165ba23e5c862 --- .../taskbar/DesktopTaskbarUIController.java | 9 +++++++ .../taskbar/FallbackTaskbarUIController.java | 9 +++++++ .../taskbar/KeyboardQuickSwitchView.java | 4 +-- .../KeyboardQuickSwitchViewController.java | 16 +++++++----- .../taskbar/LauncherTaskbarUIController.java | 7 ++++++ .../taskbar/TaskbarActivityContext.java | 4 +++ .../taskbar/TaskbarUIController.java | 25 +++++++++++++++++++ .../uioverrides/QuickstepLauncher.java | 5 ++++ .../android/quickstep/RecentsActivity.java | 5 ++++ 9 files changed, 76 insertions(+), 8 deletions(-) diff --git a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarUIController.java index 633325bf4b..2dd610c467 100644 --- a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarUIController.java @@ -16,7 +16,10 @@ package com.android.launcher3.taskbar; +import androidx.annotation.Nullable; + import com.android.launcher3.uioverrides.QuickstepLauncher; +import com.android.quickstep.util.TISBindHelper; /** * A data source which integrates with a Launcher instance, used specifically for a @@ -50,4 +53,10 @@ public class DesktopTaskbarUIController extends TaskbarUIController { public boolean supportsVisualStashing() { return false; } + + @Nullable + @Override + protected TISBindHelper getTISBindHelper() { + return mLauncher.getTISBindHelper(); + } } diff --git a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java index f9816102d4..c0ecc6151a 100644 --- a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java @@ -21,11 +21,14 @@ import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASH import android.animation.Animator; +import androidx.annotation.Nullable; + import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.statemanager.StateManager; import com.android.quickstep.RecentsActivity; import com.android.quickstep.TopTaskTracker; import com.android.quickstep.fallback.RecentsState; +import com.android.quickstep.util.TISBindHelper; import com.android.quickstep.views.RecentsView; import java.util.stream.Stream; @@ -124,4 +127,10 @@ public class FallbackTaskbarUIController extends TaskbarUIController { .get(mControllers.taskbarActivityContext).getCachedTopTask(true); return topTask.isHomeTask() || topTask.isRecentsTask(); } + + @Nullable + @Override + protected TISBindHelper getTISBindHelper() { + return mRecentsActivity.getTISBindHelper(); + } } diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java index 42c423ce83..2d9e23644d 100644 --- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java +++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java @@ -147,7 +147,7 @@ public class KeyboardQuickSwitchView extends ConstraintLayout { KeyboardQuickSwitchTaskView taskView = (KeyboardQuickSwitchTaskView) layoutInflater.inflate( R.layout.keyboard_quick_switch_taskview, mContent, false); taskView.setId(View.generateViewId()); - taskView.setOnClickListener(v -> mViewCallbacks.launchTappedTask(index)); + taskView.setOnClickListener(v -> mViewCallbacks.launchTaskAt(index)); LayoutParams lp = new LayoutParams(width, mTaskViewHeight); // Create a left-to-right ordering of views (or right-to-left in RTL locales) @@ -186,7 +186,7 @@ public class KeyboardQuickSwitchView extends ConstraintLayout { KeyboardQuickSwitchTaskView overviewButton = (KeyboardQuickSwitchTaskView) layoutInflater.inflate( R.layout.keyboard_quick_switch_overview, this, false); - overviewButton.setOnClickListener(v -> mViewCallbacks.launchTappedTask(MAX_TASKS)); + overviewButton.setOnClickListener(v -> mViewCallbacks.launchTaskAt(MAX_TASKS)); overviewButton.findViewById(R.id.text).setText(overflowString); diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java index 3e262e5f35..c830aa8640 100644 --- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java @@ -22,7 +22,6 @@ import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import android.animation.Animator; import android.app.ActivityOptions; import android.view.KeyEvent; -import android.view.View; import android.view.animation.AnimationUtils; import android.window.RemoteTransition; @@ -137,7 +136,6 @@ public class KeyboardQuickSwitchViewController { } // Even with a valid index, this can be null if the user tries to quick switch before the // views have been added in the KeyboardQuickSwitchView. - View taskView = mKeyboardQuickSwitchView.getTaskAt(index); GroupTask task = mControllerCallbacks.getTaskAt(index); if (task == null) { return Math.max(0, index); @@ -198,13 +196,18 @@ public class KeyboardQuickSwitchViewController { && keyCode != KeyEvent.KEYCODE_DPAD_RIGHT && keyCode != KeyEvent.KEYCODE_DPAD_LEFT && keyCode != KeyEvent.KEYCODE_GRAVE - && keyCode != KeyEvent.KEYCODE_ESCAPE) { + && keyCode != KeyEvent.KEYCODE_ESCAPE + && keyCode != KeyEvent.KEYCODE_ENTER) { return false; } if (keyCode == KeyEvent.KEYCODE_GRAVE || keyCode == KeyEvent.KEYCODE_ESCAPE) { closeQuickSwitchView(true); return true; } + if (keyCode == KeyEvent.KEYCODE_ENTER) { + launchTaskAt(mCurrentFocusIndex); + return true; + } if (!allowTraversal) { return false; } @@ -234,9 +237,10 @@ public class KeyboardQuickSwitchViewController { mCurrentFocusIndex = index; } - void launchTappedTask(int index) { - KeyboardQuickSwitchViewController.this.launchTaskAt(index); - closeQuickSwitchView(true); + void launchTaskAt(int index) { + mCurrentFocusIndex = Utilities.boundToRange( + index, 0, mKeyboardQuickSwitchView.getChildCount() - 1); + mControllers.taskbarActivityContext.launchKeyboardFocusedTask(); } void updateThumbnailInBackground(Task task, Consumer callback) { diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java index 50e8d0edc7..1e861d2fb5 100644 --- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java @@ -52,6 +52,7 @@ import com.android.launcher3.util.OnboardingPrefs; import com.android.quickstep.LauncherActivityInterface; import com.android.quickstep.RecentsAnimationCallbacks; import com.android.quickstep.util.GroupTask; +import com.android.quickstep.util.TISBindHelper; import com.android.quickstep.views.RecentsView; import java.io.PrintWriter; @@ -428,6 +429,12 @@ public class LauncherTaskbarUIController extends TaskbarUIController { mTaskbarLauncherStateController.resetIconAlignment(); } + @Nullable + @Override + protected TISBindHelper getTISBindHelper() { + return mLauncher.getTISBindHelper(); + } + @Override public void dumpLogs(String prefix, PrintWriter pw) { super.dumpLogs(prefix, pw); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index b69f657183..a0eb13399d 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -1485,6 +1485,10 @@ public class TaskbarActivityContext extends BaseTaskbarContext { btv.post(() -> mControllers.taskbarPopupController.showForIcon(btv)); } + public void launchKeyboardFocusedTask() { + mControllers.uiController.launchKeyboardFocusedTask(); + } + public boolean isInApp() { return mControllers.taskbarStashController.isInApp(); } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java index 8a26054a66..8f7cebb658 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java @@ -20,6 +20,7 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP; +import static com.android.quickstep.OverviewCommandHelper.TYPE_HIDE; import android.content.Intent; import android.graphics.drawable.BitmapDrawable; @@ -38,7 +39,9 @@ import com.android.launcher3.model.data.ItemInfoWithIcon; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.SplitConfigurationOptions; +import com.android.quickstep.OverviewCommandHelper; import com.android.quickstep.util.GroupTask; +import com.android.quickstep.util.TISBindHelper; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; import com.android.quickstep.views.TaskView.TaskIdAttributeContainer; @@ -359,6 +362,28 @@ public class TaskbarUIController { /** Adjusts the hotseat for the bubble bar. */ public void adjustHotseatForBubbleBar(boolean isBubbleBarVisible) {} + @Nullable + protected TISBindHelper getTISBindHelper() { + return null; + } + + /** + * Launches the focused task in the Keyboard Quick Switch view through the OverviewCommandHelper + *

+ * Use this helper method when the focused task may be the overview task. + */ + public void launchKeyboardFocusedTask() { + TISBindHelper tisBindHelper = getTISBindHelper(); + if (tisBindHelper == null) { + return; + } + OverviewCommandHelper overviewCommandHelper = tisBindHelper.getOverviewCommandHelper(); + if (overviewCommandHelper == null) { + return; + } + overviewCommandHelper.addCommand(TYPE_HIDE); + } + /** * Adjusts the taskbar based on the visibility of the launcher. * @param isVisible True if launcher is visible, false otherwise. diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index f3f36c5abb..7066063af2 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -1344,6 +1344,11 @@ public class QuickstepLauncher extends Launcher { return (mTaskbarUIController != null && mTaskbarUIController.hasBubbles()); } + @NonNull + public TISBindHelper getTISBindHelper() { + return mTISBindHelper; + } + @Override public boolean handleIncorrectSplitTargetSelection() { if (!enableSplitContextually() || !mSplitSelectStateController.isSplitSelectActive()) { diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java index 22163b9614..02f9a6962e 100644 --- a/quickstep/src/com/android/quickstep/RecentsActivity.java +++ b/quickstep/src/com/android/quickstep/RecentsActivity.java @@ -498,4 +498,9 @@ public final class RecentsActivity extends StatefulActivity { OverviewCommandHelper overviewCommandHelper = mTISBindHelper.getOverviewCommandHelper(); return overviewCommandHelper == null || overviewCommandHelper.canStartHomeSafely(); } + + @NonNull + public TISBindHelper getTISBindHelper() { + return mTISBindHelper; + } } From 0467404c25d70cfeb118dfb03fcdb3299e118748 Mon Sep 17 00:00:00 2001 From: Will Leshner Date: Fri, 2 Feb 2024 17:08:03 -0800 Subject: [PATCH 3/9] Tweak WidgetPickerActivity size filtering. Better conform to documentation about how to use widget sizes when filtering for sizes in WidgetPickerActivity. Bug: 322191186 Test: atest Launcher3Tests Flag: NA Change-Id: I4239e64dd620e00a3c55046bfb66fe3099336aac --- .../launcher3/WidgetPickerActivity.java | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java index 68e7824793..8c4db4a569 100644 --- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java +++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java @@ -219,7 +219,9 @@ public class WidgetPickerActivity extends BaseActivity { final boolean isHorizontallyResizable = (info.resizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0; if (mDesiredWidgetWidth > 0 && isHorizontallyResizable) { - if (info.maxResizeWidth > 0 && info.maxResizeWidth < mDesiredWidgetWidth) { + if (info.maxResizeWidth > 0 + && info.maxResizeWidth >= info.minWidth + && info.maxResizeWidth < mDesiredWidgetWidth) { return rejectWidget( widget, "maxResizeWidth[%d] < mDesiredWidgetWidth[%d]", @@ -227,12 +229,13 @@ public class WidgetPickerActivity extends BaseActivity { mDesiredWidgetWidth); } - final int minWidth = info.minResizeWidth > 0 ? info.minResizeWidth : info.minWidth; + final int minWidth = Math.min(info.minResizeWidth, info.minWidth); if (minWidth > mDesiredWidgetWidth) { return rejectWidget( widget, - "minWidth[%d] > mDesiredWidgetWidth[%d]", - minWidth, + "min(minWidth[%d], minResizeWidth[%d]) > mDesiredWidgetWidth[%d]", + info.minWidth, + info.minResizeWidth, mDesiredWidgetWidth); } } @@ -240,7 +243,9 @@ public class WidgetPickerActivity extends BaseActivity { final boolean isVerticallyResizable = (info.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0; if (mDesiredWidgetHeight > 0 && isVerticallyResizable) { - if (info.maxResizeHeight > 0 && info.maxResizeHeight < mDesiredWidgetHeight) { + if (info.maxResizeHeight > 0 + && info.maxResizeHeight >= info.minHeight + && info.maxResizeHeight < mDesiredWidgetHeight) { return rejectWidget( widget, "maxResizeHeight[%d] < mDesiredWidgetHeight[%d]", @@ -248,20 +253,19 @@ public class WidgetPickerActivity extends BaseActivity { mDesiredWidgetHeight); } - final int minHeight = info.minResizeHeight > 0 ? info.minResizeHeight : info.minHeight; + final int minHeight = Math.min(info.minResizeHeight, info.minHeight); if (minHeight > mDesiredWidgetHeight) { return rejectWidget( widget, - "minHeight[%d] > mDesiredWidgetHeight[%d]", - minHeight, + "min(minHeight[%d], minResizeHeight[%d]) > mDesiredWidgetHeight[%d]", + info.minHeight, + info.minResizeHeight, mDesiredWidgetHeight); } } - if (!isHorizontallyResizable - && !isVerticallyResizable - && (info.minWidth < mDesiredWidgetWidth || info.minHeight < mDesiredWidgetHeight)) { - return rejectWidget(widget, "too small and not resizeable"); + if (!isHorizontallyResizable || !isVerticallyResizable) { + return rejectWidget(widget, "not resizeable"); } return acceptWidget(widget); @@ -271,12 +275,15 @@ public class WidgetPickerActivity extends BaseActivity { WidgetItem widget, String rejectionReason, Object... args) { return new WidgetAcceptabilityVerdict( false, - widget.label, + widget.widgetInfo != null + ? widget.widgetInfo.provider.flattenToShortString() + : widget.label, String.format(Locale.ENGLISH, rejectionReason, args)); } private static WidgetAcceptabilityVerdict acceptWidget(WidgetItem widget) { - return new WidgetAcceptabilityVerdict(true, widget.label, ""); + return new WidgetAcceptabilityVerdict( + true, widget.widgetInfo.provider.flattenToShortString(), ""); } private record WidgetAcceptabilityVerdict( From 1a98a9d8748de9f0fe68b4379854d8d4741b0707 Mon Sep 17 00:00:00 2001 From: Jeremy Sim Date: Wed, 21 Feb 2024 14:24:18 -0800 Subject: [PATCH 4/9] Fix app pair launches with certain apps This CL changes the way app pairs are saved. Previously, we saved the app pair using WorkspaceItemInfos directly from Recents, which caused certain apps to fail on launch because they weren't being launched with the correct Intent. Now, we look up a proper launchable WorkspaceItemInfo from the AllAppsStore and save that to the app pair, which should fix the launch issues. Bug: 323112914 Bug: 323110399 Test: Can launch Slides, Play Store, and Calendar when part of an app pair. Flag: ACONFIG com.android.wm.shell.enable_app_pairs TEAMFOOD Change-Id: I475afa1a237c50ffb0ffaf85af43912a803b8011 --- .../quickstep/util/AppPairsController.java | 84 +++++++++++++++++-- .../launcher3/allapps/AllAppsStore.java | 13 ++- .../android/launcher3/model/data/AppInfo.java | 3 + 3 files changed, 94 insertions(+), 6 deletions(-) diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java index b8463230fc..ce8df9bdff 100644 --- a/quickstep/src/com/android/quickstep/util/AppPairsController.java +++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java @@ -20,6 +20,7 @@ package com.android.quickstep.util; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_PAIR_LAUNCH; +import static com.android.launcher3.model.data.AppInfo.PACKAGE_KEY_COMPARATOR; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; @@ -30,6 +31,7 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.isPersisten import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.content.pm.LauncherApps; import android.util.Log; import android.util.Pair; @@ -42,10 +44,12 @@ import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; +import com.android.launcher3.allapps.AllAppsStore; import com.android.launcher3.apppairs.AppPairIcon; import com.android.launcher3.icons.IconCache; import com.android.launcher3.logging.InstanceId; import com.android.launcher3.logging.StatsLogManager; +import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; @@ -93,14 +97,38 @@ public class AppPairsController { } /** - * Creates a new app pair ItemInfo and adds it to the workspace + * Creates a new app pair ItemInfo and adds it to the workspace. + *
+ * We create WorkspaceItemInfos to save onto the app pair in the following way: + *
1. We verify that the ComponentKey from our Recents tile corresponds to a real + * launchable app in the app store. + *
2. If it doesn't, we search for the underlying launchable app via package name, and use + * that instead. + *
3. If that fails, we re-use the existing WorkspaceItemInfo by cloning it and replacing + * its intent with one from PackageManager. + *
4. If everything fails, we just use the WorkspaceItemInfo as is, with its existing + * intent. This is not preferred, but will still work in most cases (notably it will not work + * well on trampoline apps). */ public void saveAppPair(GroupedTaskView gtv) { TaskView.TaskIdAttributeContainer[] attributes = gtv.getTaskIdAttributeContainers(); - WorkspaceItemInfo app1 = attributes[0].getItemInfo().clone(); - WorkspaceItemInfo app2 = attributes[1].getItemInfo().clone(); - app1.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; - app2.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; + WorkspaceItemInfo recentsInfo1 = attributes[0].getItemInfo(); + WorkspaceItemInfo recentsInfo2 = attributes[0].getItemInfo(); + WorkspaceItemInfo app1 = lookupLaunchableItem(recentsInfo1.getComponentKey()); + WorkspaceItemInfo app2 = lookupLaunchableItem(recentsInfo2.getComponentKey()); + + // If app lookup fails, use the WorkspaceItemInfo that we have, but try to override default + // intent with one from PackageManager. + if (app1 == null) { + Log.w(TAG, "Creating an app pair, but app lookup for " + recentsInfo1.title + + " failed. Falling back to the WorkspaceItemInfo from Recents."); + app1 = convertRecentsItemToAppItem(recentsInfo1); + } + if (app2 == null) { + Log.w(TAG, "Creating an app pair, but app lookup for " + recentsInfo2.title + + " failed. Falling back to the WorkspaceItemInfo from Recents."); + app2 = convertRecentsItemToAppItem(recentsInfo2); + } @PersistentSnapPosition int snapPosition = gtv.getSnapPosition(); if (!isPersistentSnapPosition(snapPosition)) { @@ -188,6 +216,52 @@ public class AppPairsController { ); } + /** + * Creates a new launchable WorkspaceItemInfo of itemType=ITEM_TYPE_APPLICATION by looking the + * ComponentKey up in the AllAppsStore. If no app is found, attempts a lookup by package + * instead. If that lookup fails, returns null. + */ + @Nullable + private WorkspaceItemInfo lookupLaunchableItem(@Nullable ComponentKey key) { + if (key == null) { + return null; + } + + AllAppsStore appsStore = Launcher.getLauncher(mContext).getAppsView().getAppsStore(); + + // Lookup by ComponentKey + AppInfo appInfo = appsStore.getApp(key); + if (appInfo == null) { + // Lookup by package + appInfo = appsStore.getApp(key, PACKAGE_KEY_COMPARATOR); + } + + return appInfo != null ? appInfo.makeWorkspaceItem(mContext) : null; + } + + /** + * Converts a WorkspaceItemInfo of itemType=ITEM_TYPE_TASK (from a Recents task) to a new + * WorkspaceItemInfo of itemType=ITEM_TYPE_APPLICATION. + */ + private WorkspaceItemInfo convertRecentsItemToAppItem(WorkspaceItemInfo recentsItem) { + if (recentsItem.itemType != LauncherSettings.Favorites.ITEM_TYPE_TASK) { + Log.w(TAG, "Expected ItemInfo of type ITEM_TYPE_TASK, but received " + + recentsItem.itemType); + } + + WorkspaceItemInfo launchableItem = recentsItem.clone(); + PackageManager p = mContext.getPackageManager(); + Intent launchIntent = p.getLaunchIntentForPackage(recentsItem.getTargetPackage()); + Log.w(TAG, "Initial intent from Recents: " + launchableItem.intent + "\n" + + "Intent from PackageManager: " + launchIntent); + if (launchIntent != null) { + // If lookup from PackageManager fails, just use the existing intent + launchableItem.intent = launchIntent; + } + launchableItem.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; + return launchableItem; + } + /** * Handles the complicated logic for how to animate an app pair entrance when already inside an * app or app pair. diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java index 009a2aa6b7..9623709348 100644 --- a/src/com/android/launcher3/allapps/AllAppsStore.java +++ b/src/com/android/launcher3/allapps/AllAppsStore.java @@ -40,6 +40,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; @@ -138,12 +139,22 @@ public class AllAppsStore { /** * Returns {@link AppInfo} if any apps matches with provided {@link ComponentKey}, otherwise * null. + * + * Uses {@link AppInfo#COMPONENT_KEY_COMPARATOR} as a default comparator. */ @Nullable public AppInfo getApp(ComponentKey key) { + return getApp(key, COMPONENT_KEY_COMPARATOR); + } + + /** + * Generic version of {@link #getApp(ComponentKey)} that allows comparator to be specified. + */ + @Nullable + public AppInfo getApp(ComponentKey key, Comparator comparator) { mTempInfo.componentName = key.componentName; mTempInfo.user = key.user; - int index = Arrays.binarySearch(mApps, mTempInfo, COMPONENT_KEY_COMPARATOR); + int index = Arrays.binarySearch(mApps, mTempInfo, comparator); return index < 0 ? null : mApps[index]; } diff --git a/src/com/android/launcher3/model/data/AppInfo.java b/src/com/android/launcher3/model/data/AppInfo.java index b213fe306e..210d720bca 100644 --- a/src/com/android/launcher3/model/data/AppInfo.java +++ b/src/com/android/launcher3/model/data/AppInfo.java @@ -52,6 +52,9 @@ public class AppInfo extends ItemInfoWithIcon implements WorkspaceItemFactory { return uc != 0 ? uc : a.componentName.compareTo(b.componentName); }; + public static final Comparator PACKAGE_KEY_COMPARATOR = Comparator.comparingInt( + (AppInfo a) -> a.user.hashCode()).thenComparing(ItemInfo::getTargetPackage); + /** * The intent used to start the application. */ From c71b48c6582380e38c7257c695c2ac70a1f222ee Mon Sep 17 00:00:00 2001 From: Johannes Gallmann Date: Fri, 23 Feb 2024 12:30:46 +0100 Subject: [PATCH 5/9] Transfer coordinate into new Rect instead of in-place Other parts of the animation rely on currentRectF to remain stable, therefore we shouldn't modify the RectF object in place. Bug: 323628523 Flag: NONE Test: Manual, i.e. verifying that widget close (and app close) animation animates correctly regardless of device orientation Change-Id: I48e9ed047b956db654192eef39e5d94fbac53553 --- .../launcher3/QuickstepTransitionManager.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java index 851f2b3d73..2a6e37dc18 100644 --- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java +++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java @@ -2049,7 +2049,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener private final RemoteAnimationTarget[] mAppTargets; private final Matrix mMatrix = new Matrix(); private final Point mTmpPos = new Point(); - private final Rect mCurrentRect = new Rect(); + private final RectF mCurrentRectF = new RectF(); private final float mStartRadius; private final float mEndRadius; private final SurfaceTransactionApplier mSurfaceApplier; @@ -2116,25 +2116,24 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener } if (target.mode == MODE_CLOSING) { - transferRectToTargetCoordinate(target, currentRectF, false, currentRectF); - currentRectF.round(mCurrentRect); + transferRectToTargetCoordinate(target, currentRectF, false, mCurrentRectF); // Scale the target window to match the currentRectF. final float scale; // We need to infer the crop (we crop the window to match the currentRectF). if (mWindowStartBounds.height() > mWindowStartBounds.width()) { - scale = Math.min(1f, currentRectF.width() / mWindowOriginalBounds.width()); + scale = Math.min(1f, mCurrentRectF.width() / mWindowOriginalBounds.width()); - int unscaledHeight = (int) (mCurrentRect.height() * (1f / scale)); + int unscaledHeight = (int) (mCurrentRectF.height() * (1f / scale)); int croppedHeight = mWindowStartBounds.height() - unscaledHeight; mTmpRect.set(0, 0, mWindowOriginalBounds.width(), mWindowStartBounds.height() - croppedHeight); } else { - scale = Math.min(1f, currentRectF.height() + scale = Math.min(1f, mCurrentRectF.height() / mWindowOriginalBounds.height()); - int unscaledWidth = (int) (mCurrentRect.width() * (1f / scale)); + int unscaledWidth = (int) (mCurrentRectF.width() * (1f / scale)); int croppedWidth = mWindowStartBounds.width() - unscaledWidth; mTmpRect.set(0, 0, mWindowStartBounds.width() - croppedWidth, mWindowOriginalBounds.height()); @@ -2142,7 +2141,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener // Match size and position of currentRect. mMatrix.setScale(scale, scale); - mMatrix.postTranslate(mCurrentRect.left, mCurrentRect.top); + mMatrix.postTranslate(mCurrentRectF.left, mCurrentRectF.top); builder.setMatrix(mMatrix) .setWindowCrop(mTmpRect) From a5ac9107b8ab47f51f1d8afb8a269f89f4ec5129 Mon Sep 17 00:00:00 2001 From: Sihua Ma Date: Thu, 22 Feb 2024 14:41:58 -0800 Subject: [PATCH 6/9] Enforce parent outline for widgets This bounds the widget view inside BaseLauncherAppWidgetHostView to reduce the overlap Bug: 322919716 Flag: N/A Test: Manual Change-Id: I270d94e3fff96724e95f9a38ab41f83beebd1271 --- .../widget/BaseLauncherAppWidgetHostView.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java index 580b4f11ea..104209ef53 100644 --- a/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java @@ -34,6 +34,17 @@ import com.android.launcher3.util.Executors; */ public abstract class BaseLauncherAppWidgetHostView extends NavigableAppWidgetHostView { + private static final ViewOutlineProvider VIEW_OUTLINE_PROVIDER = new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + // Since ShortcutAndWidgetContainer sets clipChildren to false, we should restrict the + // outline to be the view bounds, otherwise widgets might draw themselves outside of + // the launcher view. Setting alpha to 0 to match the previous behavior. + outline.setRect(0, 0, view.getWidth(), view.getHeight()); + outline.setAlpha(.0f); + } + }; + protected final LayoutInflater mInflater; private final Rect mEnforcedRectangle = new Rect(); @@ -49,10 +60,13 @@ public abstract class BaseLauncherAppWidgetHostView extends NavigableAppWidgetHo } }; + private boolean mIsCornerRadiusEnforced; + public BaseLauncherAppWidgetHostView(Context context) { super(context); setExecutor(Executors.THREAD_POOL_EXECUTOR); + setClipToOutline(true); mInflater = LayoutInflater.from(context); mEnforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(getContext()); @@ -84,8 +98,8 @@ public abstract class BaseLauncherAppWidgetHostView extends NavigableAppWidgetHo @UiThread private void resetRoundedCorners() { - setOutlineProvider(ViewOutlineProvider.BACKGROUND); - setClipToOutline(false); + setOutlineProvider(VIEW_OUTLINE_PROVIDER); + mIsCornerRadiusEnforced = false; } @UiThread @@ -104,7 +118,7 @@ public abstract class BaseLauncherAppWidgetHostView extends NavigableAppWidgetHo background, mEnforcedRectangle); setOutlineProvider(mCornerRadiusEnforcementOutline); - setClipToOutline(true); + mIsCornerRadiusEnforced = true; invalidateOutline(); } @@ -115,6 +129,6 @@ public abstract class BaseLauncherAppWidgetHostView extends NavigableAppWidgetHo /** Returns true if the corner radius are enforced for this App Widget. */ public boolean hasEnforcedCornerRadius() { - return getClipToOutline(); + return mIsCornerRadiusEnforced; } } From 40fc6eb323d3d42e3b745b528e42b20ef7aa3cb3 Mon Sep 17 00:00:00 2001 From: Saumya Prakash Date: Tue, 20 Feb 2024 22:50:31 +0000 Subject: [PATCH 7/9] Ensure taskbar insets for camera cutout only apply on the needed sides For devices with a camera cutout, we only need the increased inset to accomodate a camera cutout on the sides/orientations where the cutout interrupts the taskbar. This change ensures that the insets are only added when necessary. Fix: 325508089 Test: Open an app and ensure the insets remain unchanged for 0 and 90 degree rotations. Ensure the insets for 180 and 270 degrees are still covering the camera cutout. Flag: N/A Change-Id: I3ee14ffc84d72512acdb750d0f614acacefc7639 --- .../taskbar/TaskbarActivityContext.java | 62 ++++++++++--------- .../taskbar/TaskbarInsetsController.kt | 21 ++----- 2 files changed, 38 insertions(+), 45 deletions(-) diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index 9006df8a1a..50fe6bee69 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -505,52 +505,26 @@ public class TaskbarActivityContext extends BaseTaskbarContext { /** * Creates {@link WindowManager.LayoutParams} for Taskbar, and also sets LP.paramsForRotation - * for taskbar showing as navigation bar + * for taskbar */ private WindowManager.LayoutParams createAllWindowParams() { final int windowType = ENABLE_TASKBAR_NAVBAR_UNIFICATION ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL; WindowManager.LayoutParams windowLayoutParams = createDefaultWindowLayoutParams(windowType, TaskbarActivityContext.WINDOW_TITLE); - if (!isPhoneButtonNavMode()) { - return windowLayoutParams; - } - // Provide WM layout params for all rotations to cache, see NavigationBar#getBarLayoutParams - int width = WindowManager.LayoutParams.MATCH_PARENT; - int height = WindowManager.LayoutParams.MATCH_PARENT; - int gravity = Gravity.BOTTOM; windowLayoutParams.paramsForRotation = new WindowManager.LayoutParams[4]; for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) { WindowManager.LayoutParams lp = createDefaultWindowLayoutParams(windowType, TaskbarActivityContext.WINDOW_TITLE); - switch (rot) { - case Surface.ROTATION_0, Surface.ROTATION_180 -> { - // Defaults are fine - width = WindowManager.LayoutParams.MATCH_PARENT; - height = mLastRequestedNonFullscreenSize; - gravity = Gravity.BOTTOM; - } - case Surface.ROTATION_90 -> { - width = mLastRequestedNonFullscreenSize; - height = WindowManager.LayoutParams.MATCH_PARENT; - gravity = Gravity.END; - } - case Surface.ROTATION_270 -> { - width = mLastRequestedNonFullscreenSize; - height = WindowManager.LayoutParams.MATCH_PARENT; - gravity = Gravity.START; - } - + if (isPhoneButtonNavMode()) { + populatePhoneButtonNavModeWindowLayoutParams(rot, lp); } - lp.width = width; - lp.height = height; - lp.gravity = gravity; windowLayoutParams.paramsForRotation[rot] = lp; } - // Override current layout params + // Override with current layout params WindowManager.LayoutParams currentParams = windowLayoutParams.paramsForRotation[getDisplay().getRotation()]; windowLayoutParams.width = currentParams.width; @@ -560,6 +534,32 @@ public class TaskbarActivityContext extends BaseTaskbarContext { return windowLayoutParams; } + /** + * Update {@link WindowManager.LayoutParams} with values specific to phone and 3 button + * navigation users + */ + private void populatePhoneButtonNavModeWindowLayoutParams(int rot, + WindowManager.LayoutParams lp) { + lp.width = WindowManager.LayoutParams.MATCH_PARENT; + lp.height = WindowManager.LayoutParams.MATCH_PARENT; + lp.gravity = Gravity.BOTTOM; + + // Override with per-rotation specific values + switch (rot) { + case Surface.ROTATION_0, Surface.ROTATION_180 -> { + lp.height = mLastRequestedNonFullscreenSize; + } + case Surface.ROTATION_90 -> { + lp.width = mLastRequestedNonFullscreenSize; + lp.gravity = Gravity.END; + } + case Surface.ROTATION_270 -> { + lp.width = mLastRequestedNonFullscreenSize; + lp.gravity = Gravity.START; + } + } + } + public void onConfigurationChanged(@Config int configChanges) { mControllers.onConfigurationChanged(configChanges); if (!mIsUserSetupComplete) { @@ -920,8 +920,10 @@ public class TaskbarActivityContext extends BaseTaskbarContext { } if (landscapePhoneButtonNav) { mWindowLayoutParams.width = size; + mWindowLayoutParams.paramsForRotation[getDisplay().getRotation()].width = size; } else { mWindowLayoutParams.height = size; + mWindowLayoutParams.paramsForRotation[getDisplay().getRotation()].height = size; } mControllers.taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged(); notifyUpdateLayoutParams(); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt index aa457ca4f1..567fad02ac 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt @@ -118,11 +118,9 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas getProvidedInsets(insetsRoundedCornerFlag) } - if (!context.isGestureNav) { - if (windowLayoutParams.paramsForRotation != null) { - for (layoutParams in windowLayoutParams.paramsForRotation) { - layoutParams.providedInsets = getProvidedInsets(insetsRoundedCornerFlag) - } + if (windowLayoutParams.paramsForRotation != null) { + for (layoutParams in windowLayoutParams.paramsForRotation) { + layoutParams.providedInsets = getProvidedInsets(insetsRoundedCornerFlag) } } @@ -156,19 +154,12 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas ) } - val gravity = windowLayoutParams.gravity - // Pre-calculate insets for different providers across different rotations for this gravity for (rotation in Surface.ROTATION_0..Surface.ROTATION_270) { // Add insets for navbar rotated params - if (windowLayoutParams.paramsForRotation != null) { - val layoutParams = windowLayoutParams.paramsForRotation[rotation] - for (provider in layoutParams.providedInsets) { - setProviderInsets(provider, layoutParams.gravity, rotation) - } - } - for (provider in windowLayoutParams.providedInsets) { - setProviderInsets(provider, gravity, rotation) + val layoutParams = windowLayoutParams.paramsForRotation[rotation] + for (provider in layoutParams.providedInsets) { + setProviderInsets(provider, layoutParams.gravity, rotation) } } context.notifyUpdateLayoutParams() From 5983ac77d8ab1c36abb3f3a874bd4e6f6a573373 Mon Sep 17 00:00:00 2001 From: Saumya Prakash Date: Fri, 23 Feb 2024 20:13:18 +0000 Subject: [PATCH 8/9] Fix jump in app launch when quick switching from Home This change addresses a recent regression that caused quick switch to not scale up to full screen during the launch animation. Fix: 326625526 Test: Launch quick switch from home and ensure it is a smooth transition Flag: N/A Change-Id: Id94d5d3eae0625225f880897e1e5f3cf9e4fea6c --- .../touchcontrollers/NoButtonQuickSwitchTouchController.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java index b7a907fa6b..a92e77aa04 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java @@ -324,12 +324,12 @@ public class NoButtonQuickSwitchTouchController implements TouchController, @Override public void onDragEnd(PointF velocity) { + cancelAnimations(); boolean horizontalFling = mSwipeDetector.isFling(velocity.x); boolean verticalFling = mSwipeDetector.isFling(velocity.y); boolean noFling = !horizontalFling && !verticalFling; if (mMotionPauseDetector.isPaused() && noFling) { // Going to Overview. - cancelAnimations(); InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_QUICK_SWITCH); StateAnimationConfig config = new StateAnimationConfig(); @@ -455,7 +455,6 @@ public class NoButtonQuickSwitchTouchController implements TouchController, nonOverviewAnim.setDuration(Math.max(xDuration, yDuration)); mNonOverviewAnim.setEndAction(() -> onAnimationToStateCompleted(targetState)); - cancelAnimations(); xOverviewAnim.start(); yOverviewAnim.start(); nonOverviewAnim.start(); From e52fee8b8f189964763cff6f5febd16e2b9daf84 Mon Sep 17 00:00:00 2001 From: Fengjiang Li Date: Fri, 23 Feb 2024 14:26:52 -0800 Subject: [PATCH 9/9] Opt-in predicitive back before register the back button The predictive back infra checks the opt-in status when a callback is registered, and drops the registration if the Application / Activity is not opted-in Fix: 326627953 Test: manual Flag: ACONFIG launcher.enable_predictive_back_gesture DISABLED Change-Id: I8f2e2378d0bf966b3784cf08c12045509b6d5682 --- .../com/android/launcher3/uioverrides/QuickstepLauncher.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 722676a19c..a515109044 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -647,10 +647,13 @@ public class QuickstepLauncher extends Launcher { @Override protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + // Back dispatcher is registered in {@link BaseActivity#onCreate}. For predictive back to + // work, we must opt-in BEFORE registering back dispatcher. So we need to call + // setEnableOnBackInvokedCallback() before super.onCreate() if (Utilities.ATLEAST_U && enablePredictiveBackGesture()) { getApplicationInfo().setEnableOnBackInvokedCallback(true); } + super.onCreate(savedInstanceState); if (savedInstanceState != null) { mPendingSplitSelectInfo = ObjectWrapper.unwrap( savedInstanceState.getIBinder(PENDING_SPLIT_SELECT_INFO));