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) 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( 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 9006df8a1a..d12e187fd6 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(); @@ -1485,6 +1487,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/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() diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java index af1bd2a19f..efe1e39f97 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; @@ -361,6 +364,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 d4072313a1..7558b0097e 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -650,10 +650,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)); @@ -1360,6 +1363,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/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(); diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java index a0f81db415..cc5a923e5a 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); 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; + } } 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. */ 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; } }