From 97cbdbcfd3a01a90a61719461205c56d99c61e4c Mon Sep 17 00:00:00 2001 From: Kevin Liu Date: Wed, 19 Jun 2024 15:50:28 +0000 Subject: [PATCH 1/9] Add strict_mode: false As strict mode is in place, the default strict_mode is now set to true. We are encourage teams to write tests in strict_mode to make tests Bivalent (able to run on device and devicelessly) For more info on strict mode: go/roboStrictMode Also fixed some missing deps. Flag: NONE Test: atest Launcher3RoboTests Bug: 334089788 Change-Id: I4b4f2ef9a21926eef63fcada81355b928bbe2870 Merged-In: I4b4f2ef9a21926eef63fcada81355b928bbe2870 --- tests/Android.bp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/Android.bp b/tests/Android.bp index e9111ea44a..1e2caa7aa8 100644 --- a/tests/Android.bp +++ b/tests/Android.bp @@ -241,11 +241,17 @@ android_robolectric_test { java_resource_dirs: ["config"], static_libs: [ "flag-junit-base", + "flag-junit", "com_android_launcher3_flags_lib", "com_android_wm_shell_flags_lib", "androidx.test.uiautomator_uiautomator", "androidx.core_core-animation-testing", "androidx.test.ext.junit", + "androidx.test.espresso.core", + "androidx.test.espresso.contrib", + "androidx.test.espresso.intents", + "androidx.test.rules", + "uiautomator-helpers", "inline-mockito-robolectric-prebuilt", "platform-parametric-runner-lib", "testables", @@ -261,4 +267,5 @@ android_robolectric_test { ], instrumentation_for: "Launcher3", upstream: true, + strict_mode: false, } From 52545edb41b1e50e720ca2dcdb17a9059d1c6bd7 Mon Sep 17 00:00:00 2001 From: Luca Zuccarini Date: Fri, 24 May 2024 14:31:01 +0000 Subject: [PATCH 2/9] Add return animations to Toast search results. This only applies to targets that already use the Animation library for launch animations. Bug: 336719605 Bug: 298089923 Flag: com.android.launcher3.enable_container_return_animations Flag: com.android.systemui.shared.return_animation_framework_library Test: tested manually with flag on and off Change-Id: Ib824e78fa8b1b226b32d23d8325f06b496ba5deb --- aconfig/launcher.aconfig | 7 + .../launcher3/LauncherAnimationRunner.java | 7 + .../launcher3/QuickstepTransitionManager.java | 272 +++++++++++++++--- .../uioverrides/QuickstepLauncher.java | 14 - 4 files changed, 251 insertions(+), 49 deletions(-) diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig index f1f9966f78..15ac9e38fc 100644 --- a/aconfig/launcher.aconfig +++ b/aconfig/launcher.aconfig @@ -310,6 +310,13 @@ flag { } } +flag { + name: "enable_container_return_animations" + namespace: "launcher" + description: "Enables the container return animation mirroring launches." + bug: "341017746" +} + flag { name: "floating_search_bar" namespace: "launcher" diff --git a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java index 15180efbcc..d973149747 100644 --- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java +++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java @@ -238,5 +238,12 @@ public class LauncherAnimationRunner extends RemoteAnimationRunnerCompat { @Override @UiThread default void onAnimationCancelled() {} + + /** + * Returns whether this animation factory supports a tightly coupled return animation. + */ + default boolean supportsReturnTransition() { + return false; + } } } diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java index fae281a70a..5a74f4aafc 100644 --- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java +++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java @@ -43,6 +43,7 @@ import static com.android.launcher3.BaseActivity.INVISIBLE_ALL; import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS; import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS; import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION; +import static com.android.launcher3.Flags.enableContainerReturnAnimations; import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR; @@ -68,6 +69,7 @@ import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITION import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch; import static com.android.quickstep.util.AnimUtils.clampToDuration; import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback; +import static com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary; import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius; import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows; @@ -181,6 +183,9 @@ import java.util.List; */ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener { + private static final String TRANSITION_COOKIE_PREFIX = + "com.android.launcher3.QuickstepTransitionManager_activityLaunch"; + private static final boolean ENABLE_SHELL_STARTING_SURFACE = SystemProperties.getBoolean("persist.debug.shell_starting_surface", true); @@ -333,17 +338,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener restartedListener.register(onEndCallback::executeAllAndDestroy); onEndCallback.add(restartedListener::unregister); - mAppLaunchRunner = new AppLaunchAnimationRunner(v, onEndCallback); - ItemInfo tag = (ItemInfo) v.getTag(); - if (tag != null && tag.shouldUseBackgroundAnimation()) { - ContainerAnimationRunner containerAnimationRunner = ContainerAnimationRunner.from( - v, mLauncher, mStartingWindowListener, onEndCallback); - if (containerAnimationRunner != null) { - mAppLaunchRunner = containerAnimationRunner; - } - } - RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner( - mHandler, mAppLaunchRunner, true /* startAtFrontOfQueue */); + RemoteAnimationRunnerCompat runner = createAppLaunchRunner(v, onEndCallback); // Note that this duration is a guess as we do not know if the animation will be a // recents launch or not for sure until we know the opening app targets. @@ -360,9 +355,94 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener IRemoteCallback endCallback = completeRunnableListCallback(onEndCallback); options.setOnAnimationAbortListener(endCallback); options.setOnAnimationFinishedListener(endCallback); + + IBinder cookie = mAppLaunchRunner.supportsReturnTransition() + ? ((ContainerAnimationRunner) mAppLaunchRunner).getCookie() : null; + addLaunchCookie(cookie, (ItemInfo) v.getTag(), options); + + // Register the return animation so it can be triggered on back from the app to home. + maybeRegisterAppReturnTransition(v); + return new ActivityOptionsWrapper(options, onEndCallback); } + /** + * Selects the appropriate type of launch runner for the given view, builds it, and returns it. + * {@link QuickstepTransitionManager#mAppLaunchRunner} is updated as a by-product of this + * method. + */ + private RemoteAnimationRunnerCompat createAppLaunchRunner(View v, RunnableList onEndCallback) { + ItemInfo tag = (ItemInfo) v.getTag(); + ContainerAnimationRunner containerRunner = null; + if (tag != null && tag.shouldUseBackgroundAnimation()) { + // The cookie should only override the default used by launcher if container return + // animations are enabled. + ActivityTransitionAnimator.TransitionCookie cookie = + checkReturnAnimationsFlags() + ? new ActivityTransitionAnimator.TransitionCookie( + TRANSITION_COOKIE_PREFIX + tag.id) + : null; + ContainerAnimationRunner launchAnimationRunner = + ContainerAnimationRunner.fromView( + v, cookie, true /* forLaunch */, mLauncher, mStartingWindowListener, + onEndCallback); + + if (launchAnimationRunner != null) { + containerRunner = launchAnimationRunner; + } + } + + mAppLaunchRunner = containerRunner != null + ? containerRunner : new AppLaunchAnimationRunner(v, onEndCallback); + return new LauncherAnimationRunner( + mHandler, mAppLaunchRunner, true /* startAtFrontOfQueue */); + } + + /** + * If container return animations are enabled and the current launch runner is itself a + * {@link ContainerAnimationRunner}, registers a matching return animation that de-registers + * itself after it has run once or is made obsolete by the view going away. + */ + private void maybeRegisterAppReturnTransition(View v) { + if (!checkReturnAnimationsFlags() || !mAppLaunchRunner.supportsReturnTransition()) { + return; + } + + ActivityTransitionAnimator.TransitionCookie cookie = + ((ContainerAnimationRunner) mAppLaunchRunner).getCookie(); + RunnableList onEndCallback = new RunnableList(); + ContainerAnimationRunner runner = + ContainerAnimationRunner.fromView( + v, cookie, false /* forLaunch */, mLauncher, mStartingWindowListener, + onEndCallback); + RemoteTransition transition = + new RemoteTransition( + new LauncherAnimationRunner( + mHandler, runner, true /* startAtFrontOfQueue */ + ).toRemoteTransition() + ); + + SystemUiProxy.INSTANCE.get(mLauncher).registerRemoteTransition( + transition, ContainerAnimationRunner.buildBackToHomeFilter(cookie, mLauncher)); + ContainerAnimationRunner.setUpRemoteAnimationCleanup( + v, transition, onEndCallback, mLauncher); + } + + /** + * Adds a new launch cookie for the activity launch if supported. + * Prioritizes the explicitly provided cookie, falling back on extracting one from the given + * {@link ItemInfo} if necessary. + */ + private void addLaunchCookie(IBinder cookie, ItemInfo info, ActivityOptions options) { + if (cookie == null) { + cookie = mLauncher.getLaunchCookie(info); + } + + if (cookie != null) { + options.setLaunchCookie(cookie); + } + } + /** * Whether the launch is a recents app transition and we should do a launch animation * from the recents view. Note that if the remote animation targets are not provided, this @@ -1728,6 +1808,10 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener } } + private static boolean checkReturnAnimationsFlags() { + return enableContainerReturnAnimations() && returnAnimationFrameworkLibrary(); + } + /** * Remote animation runner for animation from the app to Launcher, including recents. */ @@ -1844,38 +1928,45 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener /** The delegate runner that handles the actual animation. */ private final RemoteAnimationDelegate mDelegate; + @Nullable + private final ActivityTransitionAnimator.TransitionCookie mCookie; + private ContainerAnimationRunner( - RemoteAnimationDelegate delegate) { + RemoteAnimationDelegate delegate, + ActivityTransitionAnimator.TransitionCookie cookie) { mDelegate = delegate; + mCookie = cookie; } @Nullable - private static ContainerAnimationRunner from(View v, Launcher launcher, - StartingWindowListener startingWindowListener, RunnableList onEndCallback) { - View viewToUse = findLaunchableViewWithBackground(v); - if (viewToUse == null) { - return null; + ActivityTransitionAnimator.TransitionCookie getCookie() { + return mCookie; + } + + @Nullable + static ContainerAnimationRunner fromView( + View v, + ActivityTransitionAnimator.TransitionCookie cookie, + boolean forLaunch, + Launcher launcher, + StartingWindowListener startingWindowListener, + RunnableList onEndCallback) { + if (!forLaunch && !checkReturnAnimationsFlags()) { + throw new IllegalStateException( + "forLaunch cannot be false when the enableContainerReturnAnimations or " + + "returnAnimationFrameworkLibrary flag is disabled"); } - // The CUJ is logged by the click handler, so we don't log it inside the animation - // library. - ActivityTransitionAnimator.Controller controllerDelegate = - ActivityTransitionAnimator.Controller.fromView(viewToUse, null /* cujType */); - - if (controllerDelegate == null) { - return null; - } - - // This wrapper allows us to override the default value, telling the controller that the - // current window is below the animating window. + // First the controller is created. This is used by the runner to animate the + // origin/target view. ActivityTransitionAnimator.Controller controller = - new DelegateTransitionAnimatorController(controllerDelegate) { - @Override - public boolean isBelowAnimatingWindow() { - return true; - } - }; + buildController(v, cookie, forLaunch); + if (controller == null) { + return null; + } + // The callback is used to make sure that we use the right color to fade between view + // and the window. ActivityTransitionAnimator.Callback callback = task -> { final int backgroundColor = startingWindowListener.mBackgroundColor == Color.TRANSPARENT @@ -1894,7 +1985,52 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener return new ContainerAnimationRunner( new ActivityTransitionAnimator.AnimationDelegate( - MAIN_EXECUTOR, controller, callback, listener)); + MAIN_EXECUTOR, controller, callback, listener), + cookie); + } + + /** + * Constructs a {@link ActivityTransitionAnimator.Controller} that can be used by a + * {@link ContainerAnimationRunner} to animate a view into an opening window or from a + * closing one. + */ + @Nullable + private static ActivityTransitionAnimator.Controller buildController( + View v, ActivityTransitionAnimator.TransitionCookie cookie, boolean isLaunching) { + View viewToUse = findLaunchableViewWithBackground(v); + if (viewToUse == null) { + return null; + } + + // The CUJ is logged by the click handler, so we don't log it inside the animation + // library. TODO: figure out return CUJ. + ActivityTransitionAnimator.Controller controllerDelegate = + ActivityTransitionAnimator.Controller.fromView(viewToUse, null /* cujType */); + + if (controllerDelegate == null) { + return null; + } + + // This wrapper allows us to override the default value, telling the controller that the + // current window is below the animating window as well as information about the return + // animation. + return new DelegateTransitionAnimatorController(controllerDelegate) { + @Override + public boolean isLaunching() { + return isLaunching; + } + + @Override + public boolean isBelowAnimatingWindow() { + return true; + } + + @Nullable + @Override + public ActivityTransitionAnimator.TransitionCookie getTransitionCookie() { + return cookie; + } + }; } /** @@ -1916,6 +2052,67 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener return (T) current; } + /** + * Builds the filter used by WM Shell to match app closing transitions (only back, no home + * button/gesture) to the given launch cookie. + */ + static TransitionFilter buildBackToHomeFilter( + ActivityTransitionAnimator.TransitionCookie cookie, Launcher launcher) { + // Closing activity must include the cookie in its list of launch cookies. + TransitionFilter.Requirement appRequirement = new TransitionFilter.Requirement(); + appRequirement.mActivityType = ACTIVITY_TYPE_STANDARD; + appRequirement.mLaunchCookie = cookie; + appRequirement.mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK}; + // Opening activity must be Launcher. + TransitionFilter.Requirement launcherRequirement = new TransitionFilter.Requirement(); + launcherRequirement.mActivityType = ACTIVITY_TYPE_HOME; + launcherRequirement.mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT}; + launcherRequirement.mTopActivity = launcher.getComponentName(); + // Transition types CLOSE and TO_BACK match the back button/gesture but not the home + // button/gesture. + TransitionFilter filter = new TransitionFilter(); + filter.mTypeSet = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK}; + filter.mRequirements = + new TransitionFilter.Requirement[]{appRequirement, launcherRequirement}; + return filter; + } + + /** + * Creates various conditions to ensure that the given transition is cleaned up correctly + * when necessary: + * - if the transition has run, it is the callback that unregisters it; + * - if the associated view is detached before the transition has had an opportunity to run, + * a {@link View.OnAttachStateChangeListener} allows us to do the same (and removes + * itself). + */ + static void setUpRemoteAnimationCleanup( + View v, RemoteTransition transition, RunnableList callback, Launcher launcher) { + View.OnAttachStateChangeListener listener = new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(@NonNull View v) {} + + @Override + public void onViewDetachedFromWindow(@NonNull View v) { + SystemUiProxy.INSTANCE.get(launcher) + .unregisterRemoteTransition(transition); + v.removeOnAttachStateChangeListener(this); + } + }; + + // Remove the animation as soon as it has run once. + callback.add(() -> { + SystemUiProxy.INSTANCE.get(launcher).unregisterRemoteTransition(transition); + if (v != null) { + v.removeOnAttachStateChangeListener(listener); + } + }); + + // Remove the animation when the view is detached from the hierarchy. + // This is so that if back is not invoked (e.g. if we go back home through the home + // gesture) we don't have obsolete transitions staying registered. + v.addOnAttachStateChangeListener(listener); + } + @Override public void onAnimationStart(int transit, RemoteAnimationTarget[] appTargets, RemoteAnimationTarget[] wallpaperTargets, RemoteAnimationTarget[] nonAppTargets, @@ -1928,6 +2125,11 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener public void onAnimationCancelled() { mDelegate.onAnimationCancelled(); } + + @Override + public boolean supportsReturnTransition() { + return true; + } } /** diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 2168f7a318..6c7f05263e 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -1182,7 +1182,6 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer : Display.DEFAULT_DISPLAY); activityOptions.options.setPendingIntentBackgroundActivityStartMode( ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); - addLaunchCookie(item, activityOptions.options); return activityOptions; } @@ -1206,19 +1205,6 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer mSplitWithKeyboardShortcutController.enterStageSplit(leftOrTop); } - /** - * Adds a new launch cookie for the activity launch if supported. - * - * @param info the item info for the launch - * @param opts the options to set the launchCookie on. - */ - public void addLaunchCookie(ItemInfo info, ActivityOptions opts) { - IBinder launchCookie = getLaunchCookie(info); - if (launchCookie != null) { - opts.setLaunchCookie(launchCookie); - } - } - /** * Return a new launch cookie for the activity launch if supported. * From a34a264af4ec2d5de023f21dddb751f877ecab4e Mon Sep 17 00:00:00 2001 From: Shamali P Date: Thu, 20 Jun 2024 02:21:59 +0000 Subject: [PATCH 3/9] Focus on the right pane when choosing an app on left As before, user can also do 4-finger swipe down to move to right pane. Bug: 345396938 Flag: EXEMPT bugfix Test: Manual - see video in comments Change-Id: If72862af2b05ae54c47e8d446a168252d3fc8194 --- .../picker/WidgetsListItemAnimator.java | 10 ++++ .../WidgetsListTableViewHolderBinder.java | 8 +-- .../widget/picker/WidgetsTwoPaneSheet.java | 55 +++++++++++++++---- 3 files changed, 55 insertions(+), 18 deletions(-) diff --git a/src/com/android/launcher3/widget/picker/WidgetsListItemAnimator.java b/src/com/android/launcher3/widget/picker/WidgetsListItemAnimator.java index 854700fed3..6a1921eb23 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsListItemAnimator.java +++ b/src/com/android/launcher3/widget/picker/WidgetsListItemAnimator.java @@ -16,6 +16,8 @@ package com.android.launcher3.widget.picker; +import static android.animation.ValueAnimator.areAnimatorsEnabled; + import static com.android.launcher3.widget.picker.WidgetsListAdapter.VIEW_TYPE_WIDGETS_LIST; import androidx.recyclerview.widget.DefaultItemAnimator; @@ -26,6 +28,14 @@ public class WidgetsListItemAnimator extends DefaultItemAnimator { public static final int MOVE_DURATION_MS = 90; public static final int ADD_DURATION_MS = 120; + // DefaultItemAnimator runs change and move animations before running add animations (i.e. + // before expanded list item's content start animating to become visible on screen). + public static final int WIDGET_LIST_ITEM_APPEARANCE_START_DELAY = + areAnimatorsEnabled() ? (CHANGE_DURATION_MS + MOVE_DURATION_MS) : 0; + // Delay after which all item animations are ran and list item's content is visible. + public static final int WIDGET_LIST_ITEM_APPEARANCE_DELAY = + WIDGET_LIST_ITEM_APPEARANCE_START_DELAY + ADD_DURATION_MS; + public WidgetsListItemAnimator() { super(); diff --git a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java index 45d733a3a5..679b0f566b 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java +++ b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java @@ -15,10 +15,7 @@ */ package com.android.launcher3.widget.picker; -import static com.android.launcher3.widget.picker.WidgetsListItemAnimator.CHANGE_DURATION_MS; -import static com.android.launcher3.widget.picker.WidgetsListItemAnimator.MOVE_DURATION_MS; - -import static android.animation.ValueAnimator.areAnimatorsEnabled; +import static com.android.launcher3.widget.picker.WidgetsListItemAnimator.WIDGET_LIST_ITEM_APPEARANCE_START_DELAY; import android.content.Context; import android.graphics.Bitmap; @@ -157,8 +154,7 @@ public final class WidgetsListTableViewHolderBinder // Pass resize delay to let the "move" and "change" animations run before resizing the // row. tableRow.setupRow(widgetItems.size(), - /*resizeDelayMs=*/ - areAnimatorsEnabled() ? (CHANGE_DURATION_MS + MOVE_DURATION_MS) : 0); + /*resizeDelayMs=*/ WIDGET_LIST_ITEM_APPEARANCE_START_DELAY); if (tableRow.getChildCount() > widgetItems.size()) { for (int j = widgetItems.size(); j < tableRow.getChildCount(); j++) { tableRow.getChildAt(j).setVisibility(View.GONE); diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java index 5d71db6e39..ae8b5dbd15 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java +++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java @@ -21,6 +21,7 @@ import static com.android.launcher3.UtilitiesKt.CLIP_CHILDREN_FALSE_MODIFIER; import static com.android.launcher3.UtilitiesKt.CLIP_TO_PADDING_FALSE_MODIFIER; import static com.android.launcher3.UtilitiesKt.modifyAttributesOnViewTree; import static com.android.launcher3.UtilitiesKt.restoreAttributesOnViewTree; +import static com.android.launcher3.widget.picker.WidgetsListItemAnimator.WIDGET_LIST_ITEM_APPEARANCE_DELAY; import android.content.Context; import android.graphics.Rect; @@ -31,6 +32,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.ScrollView; @@ -281,10 +283,19 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { mRightPane.removeAllViews(); mRightPane.addView(mWidgetRecommendationsContainer); mRightPaneScrollView.setScrollY(0); - mRightPane.setAccessibilityPaneTitle(suggestionsRightPaneTitle); mSuggestedWidgetsPackageUserKey = PackageUserKey.fromPackageItemInfo(packageItemInfo); final boolean isChangingHeaders = mSelectedHeader == null || !mSelectedHeader.equals(mSuggestedWidgetsPackageUserKey); + // If the initial focus view is still focused, it is likely a programmatic header + // click. + if (mSelectedHeader != null + && !getAccessibilityInitialFocusView().isAccessibilityFocused()) { + post(() -> { + mRightPaneScrollView.setAccessibilityPaneTitle(suggestionsRightPaneTitle); + mRightPaneScrollView.performAccessibilityAction( + AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); + }); + } if (isChangingHeaders) { // If switching from another header, unselect any WidgetCells. This is necessary // because we do not clear/recycle the WidgetCells in the recommendations container @@ -296,7 +307,6 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { mSelectedHeader = mSuggestedWidgetsPackageUserKey; }); mSuggestedWidgetsContainer.addView(mSuggestedWidgetsHeader); - mRightPane.setAccessibilityPaneTitle(suggestionsRightPaneTitle); } @Override @@ -323,12 +333,14 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { mActivePage = currentActivePage; - if (mSuggestedWidgetsHeader == null) { - mAdapters.get(currentActivePage).mWidgetsListAdapter.selectFirstHeaderEntry(); - mAdapters.get(currentActivePage).mWidgetsRecyclerView.scrollToTop(); - } else if (currentActivePage == PERSONAL_TAB || currentActivePage == WORK_TAB) { - mSuggestedWidgetsHeader.callOnClick(); - } + // When using talkback, swiping left while on right pane, should navigate to the widgets + // list on left. + mAdapters.get(mActivePage).mWidgetsRecyclerView.setAccessibilityTraversalBefore( + mRightPaneScrollView.getId()); + + // On page change, select the first item in the list to show in the right pane. + mAdapters.get(currentActivePage).mWidgetsListAdapter.selectFirstHeaderEntry(); + mAdapters.get(currentActivePage).mWidgetsRecyclerView.scrollToTop(); } @Override @@ -383,6 +395,10 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { public void onHeaderChanged(@NonNull PackageUserKey selectedHeader) { final boolean isSameHeader = mSelectedHeader != null && mSelectedHeader.equals(selectedHeader); + // If the initial focus view is still focused, it is likely a programmatic header + // click. + final boolean isUserClick = mSelectedHeader != null + && !getAccessibilityInitialFocusView().isAccessibilityFocused(); mSelectedHeader = selectedHeader; WidgetsListContentEntry contentEntry = mActivityContext.getPopupDataProvider() .getSelectedAppWidgets(selectedHeader); @@ -427,11 +443,14 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { }; mRightPane.removeAllViews(); mRightPane.addView(widgetsRowViewHolder.itemView); + if (isUserClick) { + mRightPaneScrollView.setAccessibilityPaneTitle(getContext().getString( + R.string.widget_picker_right_pane_accessibility_title, + contentEntry.mPkgItem.title)); + postDelayed(() -> focusOnFirstWidgetCell(widgetsRowViewHolder.tableContainer), + WIDGET_LIST_ITEM_APPEARANCE_DELAY); + } mRightPaneScrollView.setScrollY(0); - mRightPane.setAccessibilityPaneTitle( - getContext().getString( - R.string.widget_picker_right_pane_accessibility_title, - contentEntry.mPkgItem.title)); } }; } @@ -445,6 +464,18 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { } } + /** + * Requests focus on the first widget cell in the given widget section. + */ + private static void focusOnFirstWidgetCell(ViewGroup parent) { + if (parent == null) return; + WidgetCell cell = Utilities.findViewByPredicate(parent, v -> v instanceof WidgetCell); + if (cell != null) { + cell.performAccessibilityAction( + AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); + } + } + private static void unselectWidgetCell(ViewGroup parent, WidgetItem item) { if (parent == null || item == null) return; WidgetCell cell = Utilities.findViewByPredicate(parent, v -> v instanceof WidgetCell wc From e4ca19100107ab965bac0a2f0b3df85dd578297e Mon Sep 17 00:00:00 2001 From: Shamali P Date: Mon, 17 Jun 2024 14:37:44 +0000 Subject: [PATCH 4/9] [part-1] Perform doMeasure only once in WidgetsFullSheet We already know the details used to calculate the insets while in onMeasure, so, instead of measuring everything twice, we initialize the maxSpansPerRow in onMeasure and then call doMeasure. Bug: 346341156 Flag: EXEMPT bugfix Test: Manual and screenshot Change-Id: If080f1f91c69d2d54139d876c36eac8743a224ee --- .../launcher3/widget/BaseWidgetSheet.java | 19 +++++++--- .../widget/picker/WidgetsFullSheet.java | 37 +++++++++---------- .../widget/picker/WidgetsTwoPaneSheet.java | 29 ++++++++++++--- 3 files changed, 55 insertions(+), 30 deletions(-) diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java index 13680846f9..c59e295c5f 100644 --- a/src/com/android/launcher3/widget/BaseWidgetSheet.java +++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java @@ -331,8 +331,21 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView * status bar, into account. */ protected void doMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int widthUsed = getInsetsWidth(); + DeviceProfile deviceProfile = mActivityContext.getDeviceProfile(); + measureChildWithMargins(mContent, widthMeasureSpec, + widthUsed, heightMeasureSpec, deviceProfile.bottomSheetTopPadding); + setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), + MeasureSpec.getSize(heightMeasureSpec)); + } + + /** + * Returns the width used on left and right by the insets / padding. + */ + protected int getInsetsWidth() { int widthUsed; + DeviceProfile deviceProfile = mActivityContext.getDeviceProfile(); if (deviceProfile.isTablet) { widthUsed = Math.max(2 * getTabletHorizontalMargin(deviceProfile), 2 * (mInsets.left + mInsets.right)); @@ -343,11 +356,7 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView widthUsed = Math.max(padding.left + padding.right, 2 * (mInsets.left + mInsets.right)); } - - measureChildWithMargins(mContent, widthMeasureSpec, - widthUsed, heightMeasureSpec, deviceProfile.bottomSheetTopPadding); - setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), - MeasureSpec.getSize(heightMeasureSpec)); + return widthUsed; } /** Returns the horizontal margins to be applied to the widget sheet. **/ diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java index 9929892e28..fd15677ab7 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java +++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java @@ -55,7 +55,6 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.launcher3.BaseActivity; import com.android.launcher3.DeviceProfile; -import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.compat.AccessibilityManagerCompat; @@ -416,19 +415,18 @@ public class WidgetsFullSheet extends BaseWidgetSheet @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int availableWidth = MeasureSpec.getSize(widthMeasureSpec); + updateMaxSpansPerRow(availableWidth); doMeasure(widthMeasureSpec, heightMeasureSpec); - - if (updateMaxSpansPerRow()) { - doMeasure(widthMeasureSpec, heightMeasureSpec); - } } - /** Returns {@code true} if the max spans have been updated. */ - private boolean updateMaxSpansPerRow() { - if (getMeasuredWidth() == 0) return false; - - @Px int maxHorizontalSpan = getContentView().getMeasuredWidth() - - (2 * mContentHorizontalMargin); + /** Returns {@code true} if the max spans have been updated. + * + * @param availableWidth Total width available within parent (includes insets). + */ + private void updateMaxSpansPerRow(int availableWidth) { + @Px int maxHorizontalSpan = getAvailableWidthForSuggestions( + availableWidth - getInsetsWidth()); if (mMaxSpanPerRow != maxHorizontalSpan) { mMaxSpanPerRow = maxHorizontalSpan; mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow( @@ -439,16 +437,15 @@ public class WidgetsFullSheet extends BaseWidgetSheet mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow( maxHorizontalSpan); } - onRecommendedWidgetsBound(); - return true; + post(this::onRecommendedWidgetsBound); } - return false; } - protected View getContentView() { - return mHasWorkProfile - ? mViewPager - : mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView; + /** + * Returns the width available to display suggestions. + */ + protected int getAvailableWidthForSuggestions(int pickerAvailableWidth) { + return pickerAvailableWidth - (2 * mContentHorizontalMargin); } @Override @@ -493,7 +490,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet .mWidgetsListAdapter.hasVisibleEntries()); if (mIsNoWidgetsViewNeeded != isNoWidgetsViewNeeded) { mIsNoWidgetsViewNeeded = isNoWidgetsViewNeeded; - onRecommendedWidgetsBound(); + post(this::onRecommendedWidgetsBound); } } @@ -549,7 +546,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.setVisibility(GONE); // Visibility of recommended widgets, recycler views and headers are handled in methods // below. - onRecommendedWidgetsBound(); + post(this::onRecommendedWidgetsBound); onWidgetsBound(); } } diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java index 5d71db6e39..7eaa0f8476 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java +++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java @@ -312,6 +312,30 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { * RECOMMENDATION_SECTION_HEIGHT_RATIO_TWO_PANE; } + @Override + @Px + protected int getAvailableWidthForSuggestions(int pickerAvailableWidth) { + int rightPaneWidth = (int) Math.ceil(0.67 * pickerAvailableWidth); + + if (mDeviceProfile.isTwoPanels && enableUnfoldedTwoPanePicker()) { + // See onLayout + int leftPaneWidth = (int) (0.33 * pickerAvailableWidth); + @Px int minLeftPaneWidthPx = Utilities.dpToPx(MINIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP); + @Px int maxLeftPaneWidthPx = Utilities.dpToPx(MAXIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP); + if (leftPaneWidth < minLeftPaneWidthPx) { + leftPaneWidth = minLeftPaneWidthPx; + } else if (leftPaneWidth > maxLeftPaneWidthPx) { + leftPaneWidth = maxLeftPaneWidthPx; + } + rightPaneWidth = pickerAvailableWidth - leftPaneWidth; + } + + // Since suggestions are shown in right pane, the available width is 2/3 of total width of + // bottom sheet. + return rightPaneWidth - getResources().getDimensionPixelSize( + R.dimen.widget_list_horizontal_margin_two_pane); // right pane end margin. + } + @Override public void onActivePageChanged(int currentActivePage) { super.onActivePageChanged(currentActivePage); @@ -372,11 +396,6 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { } - @Override - protected View getContentView() { - return mRightPane; - } - private HeaderChangeListener getHeaderChangeListener() { return new HeaderChangeListener() { @Override From 558afdafe2134da5da91e955daf655c8a521cec2 Mon Sep 17 00:00:00 2001 From: Shamali P Date: Mon, 24 Jun 2024 16:11:57 +0000 Subject: [PATCH 5/9] [part 2] Update standalone picker to align widget loading and animations In usual picker, widgets are already available before picker opens, so, the work that happens during animations doesn't interrupt animations on the scale it interrupt animations in standalone picker. Bug: 346341156 Flag: EXEMPT bugfix Test: Manual / see before after video Change-Id: I7c8701447bd519b306799b28341d8f2f03daf59f --- .../launcher3/WidgetPickerActivity.java | 27 +++++++++++++------ .../model/WidgetPredictionsRequester.java | 16 +++++++---- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java index 7e52ea1372..7cdca746da 100644 --- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java +++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java @@ -120,10 +120,6 @@ public class WidgetPickerActivity extends BaseActivity { WindowInsetsController wc = mDragLayer.getWindowInsetsController(); wc.hide(navigationBars() + statusBars()); - BaseWidgetSheet widgetSheet = WidgetsFullSheet.show(this, true); - widgetSheet.disableNavBarScrim(true); - widgetSheet.addOnCloseListener(this::finish); - parseIntentExtras(); refreshAndBindWidgets(); } @@ -224,9 +220,10 @@ public class WidgetPickerActivity extends BaseActivity { }; } - /** Updates the model with widgets and provides them after applying the provided filter. */ + /** Updates the model with widgets, applies filters and launches the widgets sheet once + * widgets are available */ private void refreshAndBindWidgets() { - MODEL_EXECUTOR.execute(() -> { + MODEL_EXECUTOR.getHandler().postDelayed(() -> { LauncherAppState app = LauncherAppState.getInstance(this); mModel.update(app, null); final List allWidgets = @@ -240,6 +237,9 @@ public class WidgetPickerActivity extends BaseActivity { } ); bindWidgets(allWidgets); + // Open sheet once widgets are available, so that it doesn't interrupt the open + // animation. + openWidgetsSheet(); if (mUiSurface != null) { Map allWidgetItems = allWidgets.stream() .filter(entry -> entry instanceof WidgetsListContentEntry) @@ -253,15 +253,26 @@ public class WidgetPickerActivity extends BaseActivity { mUiSurface, allWidgetItems); mWidgetPredictionsRequester.request(mAddedWidgets, this::bindRecommendedWidgets); } - }); + }, mDeviceProfile.bottomSheetOpenDuration); } private void bindWidgets(List widgets) { MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setAllWidgets(widgets)); } + private void openWidgetsSheet() { + MAIN_EXECUTOR.execute(() -> { + BaseWidgetSheet widgetSheet = WidgetsFullSheet.show(this, true); + widgetSheet.disableNavBarScrim(true); + widgetSheet.addOnCloseListener(this::finish); + }); + } + private void bindRecommendedWidgets(List recommendedWidgets) { - MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setRecommendedWidgets(recommendedWidgets)); + // Bind recommendations once picker has finished open animation. + MAIN_EXECUTOR.getHandler().postDelayed( + () -> mPopupDataProvider.setRecommendedWidgets(recommendedWidgets), + mDeviceProfile.bottomSheetOpenDuration); } @Override diff --git a/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java b/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java index 5730273d88..41fcf61d81 100644 --- a/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java +++ b/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java @@ -65,6 +65,7 @@ public class WidgetPredictionsRequester { private final Context mContext; @NonNull private final String mUiSurface; + private boolean mPredictionsAvailable; @NonNull private final Map mAllWidgets; @@ -76,8 +77,8 @@ public class WidgetPredictionsRequester { } /** - * Requests predictions from the app predictions manager and registers the provided callback to - * receive updates when predictions are available. + * Requests one time predictions from the app predictions manager and invokes provided callback + * once predictions are available. * * @param existingWidgets widgets that are currently added to the surface; * @param callback consumer of prediction results to be called when predictions are @@ -159,10 +160,14 @@ public class WidgetPredictionsRequester { @WorkerThread private void bindPredictions(List targets, Predicate filter, Consumer> callback) { - List filteredPredictions = filterPredictions(targets, mAllWidgets, filter); - List mappedPredictions = mapWidgetItemsToItemInfo(filteredPredictions); + if (!mPredictionsAvailable) { + mPredictionsAvailable = true; + List filteredPredictions = filterPredictions(targets, mAllWidgets, filter); + List mappedPredictions = mapWidgetItemsToItemInfo(filteredPredictions); - MAIN_EXECUTOR.execute(() -> callback.accept(mappedPredictions)); + MAIN_EXECUTOR.execute(() -> callback.accept(mappedPredictions)); + MODEL_EXECUTOR.execute(this::clear); + } } /** @@ -214,5 +219,6 @@ public class WidgetPredictionsRequester { mAppPredictor.destroy(); mAppPredictor = null; } + mPredictionsAvailable = false; } } From b76356a20241f2ae54d3c4f7c06f42bc8229db54 Mon Sep 17 00:00:00 2001 From: Alex Chau Date: Tue, 25 Jun 2024 13:35:34 +0100 Subject: [PATCH 6/9] Remove unused PreviewPositionHelper.setSplitBounds Bug: 336612373 Test: manual Flag: EXEMPT refactor Change-Id: I916341973c4279e548af59452842bc0a677ef6b0 --- .../quickstep/util/SplitScreenUtils.kt | 24 ++++--------------- .../quickstep/util/TaskViewSimulator.java | 3 --- .../quickstep/views/GroupedTaskView.kt | 20 +--------------- 3 files changed, 5 insertions(+), 42 deletions(-) diff --git a/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt b/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt index 38bbe601b6..4820c3564f 100644 --- a/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt +++ b/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt @@ -31,29 +31,13 @@ class SplitScreenUtils { null } else { SplitConfigurationOptions.SplitBounds( - shellSplitBounds.leftTopBounds, shellSplitBounds.rightBottomBounds, - shellSplitBounds.leftTopTaskId, shellSplitBounds.rightBottomTaskId, + shellSplitBounds.leftTopBounds, + shellSplitBounds.rightBottomBounds, + shellSplitBounds.leftTopTaskId, + shellSplitBounds.rightBottomTaskId, shellSplitBounds.snapPosition ) } } - - /** Converts the launcher version of SplitBounds to the shell version */ - @JvmStatic - fun convertLauncherSplitBoundsToShell( - launcherSplitBounds: SplitConfigurationOptions.SplitBounds? - ): SplitBounds? { - return if (launcherSplitBounds == null) { - null - } else { - SplitBounds( - launcherSplitBounds.leftTopBounds, - launcherSplitBounds.rightBottomBounds, - launcherSplitBounds.leftTopTaskId, - launcherSplitBounds.rightBottomTaskId, - launcherSplitBounds.snapPosition - ) - } - } } } diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java index 49f4e5f701..d9b7d20eac 100644 --- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java +++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java @@ -27,7 +27,6 @@ import static com.android.launcher3.util.SplitConfigurationOptions.StagePosition import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS; import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation; import static com.android.quickstep.util.RecentsOrientedState.preDisplayRotation; -import static com.android.quickstep.util.SplitScreenUtils.convertLauncherSplitBoundsToShell; import android.animation.TimeInterpolator; import android.content.Context; @@ -247,8 +246,6 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { } else { mStagePosition = runningTarget.taskId == splitInfo.leftTopTaskId ? STAGE_POSITION_TOP_OR_LEFT : STAGE_POSITION_BOTTOM_OR_RIGHT; - mPositionHelper.setSplitBounds(convertLauncherSplitBoundsToShell(mSplitBounds), - mStagePosition); } calculateTaskSize(); } diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt index 6296b0e3ea..3e4a953b56 100644 --- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt +++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt @@ -33,10 +33,8 @@ import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_O import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED import com.android.quickstep.TaskOverlayFactory import com.android.quickstep.util.RecentsOrientedState -import com.android.quickstep.util.SplitScreenUtils.Companion.convertLauncherSplitBoundsToShell import com.android.quickstep.util.SplitSelectStateController import com.android.systemui.shared.recents.model.Task -import com.android.systemui.shared.recents.utilities.PreviewPositionHelper import com.android.systemui.shared.system.InteractionJankMonitorWrapper import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition @@ -147,23 +145,7 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu ) taskContainers.forEach { it.bind() } - this.splitBoundsConfig = - splitBoundsConfig?.also { - taskContainers[0] - .thumbnailViewDeprecated - .previewPositionHelper - .setSplitBounds( - convertLauncherSplitBoundsToShell(it), - PreviewPositionHelper.STAGE_POSITION_TOP_OR_LEFT - ) - taskContainers[1] - .thumbnailViewDeprecated - .previewPositionHelper - .setSplitBounds( - convertLauncherSplitBoundsToShell(it), - PreviewPositionHelper.STAGE_POSITION_BOTTOM_OR_RIGHT - ) - } + this.splitBoundsConfig = splitBoundsConfig taskContainers.forEach { it.digitalWellBeingToast?.setSplitBounds(splitBoundsConfig) } setOrientationState(orientedState) } From a07a7c2f91af430a68bf2c5129a63045a230feea Mon Sep 17 00:00:00 2001 From: Brian Isganitis Date: Tue, 25 Jun 2024 17:13:52 -0400 Subject: [PATCH 7/9] Take in test instance as field. This refactor makes it simpler for the rule to keep track of the test instance (immutable). Having the rule be a TestRule instead of a MethodRule also simplifies nesting TestRules within this implementation (future CLs). Test: Taskbar{AllApps,Overlay}ControllerTest Flag: EXEMPT refactor Bug: 230027385 Change-Id: Icf587202242fcb4526946d9c7619e5be4f59154e --- .../launcher3/taskbar/TaskbarUnitTestRule.kt | 17 ++++++++--------- .../allapps/TaskbarAllAppsControllerTest.kt | 3 ++- .../overlay/TaskbarOverlayControllerTest.kt | 3 ++- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt index 1c900b8b77..bbf738e03b 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt @@ -31,14 +31,15 @@ import com.android.quickstep.AllAppsActionManager import com.android.quickstep.TouchInteractionService import com.android.quickstep.TouchInteractionService.TISBinder import org.junit.Assume.assumeTrue -import org.junit.rules.MethodRule -import org.junit.runners.model.FrameworkMethod +import org.junit.rules.TestRule +import org.junit.runner.Description import org.junit.runners.model.Statement /** * Manages the Taskbar lifecycle for unit tests. * - * Tests need to provide their target [context] through the constructor. + * Tests should pass in themselves as [testInstance]. They also need to provide their target + * [context] through the constructor. * * See [InjectController] for grabbing controller(s) under test with minimal boilerplate. * @@ -61,12 +62,11 @@ import org.junit.runners.model.Statement * } * ``` */ -class TaskbarUnitTestRule(private val context: Context) : MethodRule { +class TaskbarUnitTestRule(private val testInstance: Any, private val context: Context) : TestRule { private val instrumentation = InstrumentationRegistry.getInstrumentation() private val serviceTestRule = ServiceTestRule() private lateinit var taskbarManager: TaskbarManager - private lateinit var target: Any val activityContext: TaskbarActivityContext get() { @@ -74,10 +74,9 @@ class TaskbarUnitTestRule(private val context: Context) : MethodRule { ?: throw RuntimeException("Failed to obtain TaskbarActivityContext.") } - override fun apply(base: Statement, method: FrameworkMethod, target: Any): Statement { + override fun apply(base: Statement, description: Description): Statement { return object : Statement() { override fun evaluate() { - this@TaskbarUnitTestRule.target = target instrumentation.runOnMainSync { assumeTrue( @@ -141,11 +140,11 @@ class TaskbarUnitTestRule(private val context: Context) : MethodRule { private fun injectControllers() { val controllers = activityContext.controllers val controllerFieldsByType = controllers.javaClass.fields.associateBy { it.type } - target.javaClass.fields + testInstance.javaClass.fields .filter { it.isAnnotationPresent(InjectController::class.java) } .forEach { it.set( - target, + testInstance, controllerFieldsByType[it.type]?.get(controllers) ?: throw NoSuchElementException("Failed to find controller for ${it.type}"), ) diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt index 9a514bf784..bfad6979cf 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt @@ -42,7 +42,8 @@ import org.junit.runner.RunWith @EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"]) class TaskbarAllAppsControllerTest { - @get:Rule val taskbarUnitTestRule = TaskbarUnitTestRule(getInstrumentation().targetContext) + @get:Rule + val taskbarUnitTestRule = TaskbarUnitTestRule(this, getInstrumentation().targetContext) @get:Rule val animatorTestRule = AnimatorTestRule(this) @InjectController lateinit var allAppsController: TaskbarAllAppsController diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt index 918ec7db7e..72bdc166e2 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt @@ -41,7 +41,8 @@ import org.junit.runner.RunWith @EmulatedDevices(["pixelFoldable2023"]) class TaskbarOverlayControllerTest { - @get:Rule val taskbarUnitTestRule = TaskbarUnitTestRule(getInstrumentation().targetContext) + @get:Rule + val taskbarUnitTestRule = TaskbarUnitTestRule(this, getInstrumentation().targetContext) @InjectController lateinit var overlayController: TaskbarOverlayController private val taskbarContext: TaskbarActivityContext From d06e8dc8a32a49c21fe9108792d39feacd135cbe Mon Sep 17 00:00:00 2001 From: Gustav Sennton Date: Tue, 18 Jun 2024 11:54:38 +0000 Subject: [PATCH 8/9] Ignore minimized Desktop apps in Overview. For each desktop session, Overview shows a single tile with multiple desktop tasks. With this CL avoid showing minimized tasks in that tile. Bug: 333013317 Flag: com.android.window.flags.enable_desktop_windowing_mode Test: manual: ensured minimized desktop tasks are not shown in Overview Change-Id: I48cb6826849abf225c0fe4448ca7b0b13afea44e --- .../android/quickstep/RecentTasksList.java | 14 ++++- .../android/quickstep/views/RecentsView.java | 9 +++- .../quickstep/RecentTasksListTest.java | 53 +++++++++++++++++++ 3 files changed, 72 insertions(+), 4 deletions(-) diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java index 66091d43f1..3d4167a25e 100644 --- a/quickstep/src/com/android/quickstep/RecentTasksList.java +++ b/quickstep/src/com/android/quickstep/RecentTasksList.java @@ -44,6 +44,7 @@ import com.android.wm.shell.util.GroupedRecentTaskInfo; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.function.Consumer; @@ -324,7 +325,9 @@ public class RecentTasksList { // leftover TYPE_FREEFORM tasks created when flag was on should be ignored. if (enableDesktopWindowingMode()) { GroupTask desktopTask = createDesktopTask(rawTask); - allTasks.add(desktopTask); + if (desktopTask != null) { + allTasks.add(desktopTask); + } } continue; } @@ -368,8 +371,13 @@ public class RecentTasksList { return allTasks; } - private DesktopTask createDesktopTask(GroupedRecentTaskInfo recentTaskInfo) { + private @Nullable DesktopTask createDesktopTask(GroupedRecentTaskInfo recentTaskInfo) { ArrayList tasks = new ArrayList<>(recentTaskInfo.getTaskInfoList().size()); + int[] minimizedTaskIds = recentTaskInfo.getMinimizedTaskIds(); + if (minimizedTaskIds.length == recentTaskInfo.getTaskInfoList().size()) { + // All Tasks are minimized -> don't create a DesktopTask + return null; + } for (ActivityManager.RecentTaskInfo taskInfo : recentTaskInfo.getTaskInfoList()) { Task.TaskKey key = new Task.TaskKey(taskInfo); Task task = Task.from(key, taskInfo, false); @@ -377,6 +385,8 @@ public class RecentTasksList { task.positionInParent = taskInfo.positionInParent; task.appBounds = taskInfo.configuration.windowConfiguration.getAppBounds(); task.isVisible = taskInfo.isVisible; + task.isMinimized = + Arrays.stream(minimizedTaskIds).anyMatch(taskId -> taskId == taskInfo.taskId); tasks.add(task); } return new DesktopTask(tasks); diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index d806e3ddc6..c1e112a4c9 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -1826,8 +1826,13 @@ public abstract class RecentsView nonMinimizedTasks = + ((DesktopTask) groupTask).tasks.stream() + .filter(task -> !task.isMinimized) + .toList(); + ((DesktopTaskView) taskView).bind(nonMinimizedTasks, mOrientationState, + mTaskOverlayFactory); mDesktopTaskView = (DesktopTaskView) taskView; } else { Task task = groupTask.task1.key.id == stagedTaskIdToBeRemoved ? groupTask.task2 diff --git a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java index 03244eb0bf..ce16b70a1b 100644 --- a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java +++ b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java @@ -32,6 +32,8 @@ import androidx.test.filters.SmallTest; import com.android.launcher3.util.LooperExecutor; import com.android.quickstep.util.GroupTask; +import com.android.quickstep.views.TaskView; +import com.android.systemui.shared.recents.model.Task; import com.android.wm.shell.util.GroupedRecentTaskInfo; import org.junit.Before; @@ -40,8 +42,11 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; @SmallTest public class RecentTasksListTest { @@ -104,4 +109,52 @@ public class RecentTasksListTest { assertEquals(taskDescription, taskList.get(0).task1.taskDescription.getLabel()); assertNull(taskList.get(0).task2.taskDescription.getLabel()); } + + @Test + public void loadTasksInBackground_freeformTask_createsDesktopTask() { + ActivityManager.RecentTaskInfo[] tasks = { + createRecentTaskInfo(1 /* taskId */), + createRecentTaskInfo(4 /* taskId */), + createRecentTaskInfo(5 /* taskId */)}; + GroupedRecentTaskInfo recentTaskInfos = GroupedRecentTaskInfo.forFreeformTasks( + tasks, Collections.emptySet() /* minimizedTaskIds */); + when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt())) + .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos))); + + List taskList = mRecentTasksList.loadTasksInBackground( + Integer.MAX_VALUE /* numTasks */, -1 /* requestId */, false /* loadKeysOnly */); + + assertEquals(1, taskList.size()); + assertEquals(TaskView.Type.DESKTOP, taskList.get(0).taskViewType); + List actualFreeformTasks = taskList.get(0).getTasks(); + assertEquals(3, actualFreeformTasks.size()); + assertEquals(1, actualFreeformTasks.get(0).key.id); + assertEquals(4, actualFreeformTasks.get(1).key.id); + assertEquals(5, actualFreeformTasks.get(2).key.id); + } + + @Test + public void loadTasksInBackground_freeformTask_onlyMinimizedTasks_doesNotCreateDesktopTask() { + ActivityManager.RecentTaskInfo[] tasks = { + createRecentTaskInfo(1 /* taskId */), + createRecentTaskInfo(4 /* taskId */), + createRecentTaskInfo(5 /* taskId */)}; + Set minimizedTaskIds = + Arrays.stream(new Integer[]{1, 4, 5}).collect(Collectors.toSet()); + GroupedRecentTaskInfo recentTaskInfos = + GroupedRecentTaskInfo.forFreeformTasks(tasks, minimizedTaskIds); + when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt())) + .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos))); + + List taskList = mRecentTasksList.loadTasksInBackground( + Integer.MAX_VALUE /* numTasks */, -1 /* requestId */, false /* loadKeysOnly */); + + assertEquals(0, taskList.size()); + } + + private ActivityManager.RecentTaskInfo createRecentTaskInfo(int taskId) { + ActivityManager.RecentTaskInfo recentTaskInfo = new ActivityManager.RecentTaskInfo(); + recentTaskInfo.taskId = taskId; + return recentTaskInfo; + } } From c06bc41ee003a7e4b5a44ec51bccde36a9f3e82c Mon Sep 17 00:00:00 2001 From: Alex Chau Date: Thu, 30 May 2024 12:28:07 +0100 Subject: [PATCH 9/9] Use snapshotView references in TaskContainer when possible - Moved majority of TaskContainer.thumbnailViewDeprecated usage to TaskContainer.snapshotView, so they'll cover the new TaskThumbnailView as well - There are afew location left that will be handled as a follow-up - Removed TaskThumbnailView's splitTranslation and use translation directly as it's the only translation we use Bug: 336612373 Test: Ensure all Task CUJs still work without refacorting Test: Ensure major Task CUJs work with refactoring Flag: EXEMPT refactor Flag: com.android.launcher3.enable_refactor_task_thumbnail Change-Id: Ieb52236bc503ce0d0a511249978c1821a82d406e --- .../taskbar/TaskbarUIController.java | 4 +- .../TaskViewTouchController.java | 2 +- .../android/quickstep/TaskOverlayFactory.java | 44 +++++----- .../quickstep/TaskShortcutFactory.java | 27 +++--- .../com/android/quickstep/TaskViewUtils.java | 7 +- .../util/SplitAnimationController.kt | 85 +++++++++---------- .../quickstep/views/DesktopTaskView.kt | 6 +- .../views/DigitalWellBeingToast.java | 4 +- .../quickstep/views/GroupedTaskView.kt | 28 ++---- .../android/quickstep/views/TaskMenuView.java | 27 +++--- .../views/TaskThumbnailViewDeprecated.java | 57 ------------- .../com/android/quickstep/views/TaskView.kt | 39 ++++++--- .../util/SplitAnimationControllerTest.kt | 8 +- 13 files changed, 136 insertions(+), 202 deletions(-) diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java index ce281c30e4..f24bc214f0 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java @@ -269,8 +269,8 @@ public class TaskbarUIController { foundTaskView, foundTask, taskContainer.getIconView().getDrawable(), - taskContainer.getThumbnailViewDeprecated(), - taskContainer.getThumbnailViewDeprecated().getThumbnail(), + taskContainer.getSnapshotView(), + taskContainer.getThumbnail(), null /* intent */, null /* user */, info); diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java index 4bc3c1661e..3c7f335325 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java @@ -252,7 +252,7 @@ public abstract class TaskViewTouchController translations = orientationHandler .getDwbLayoutTranslations(mTaskView.getMeasuredWidth(), mTaskView.getMeasuredHeight(), mSplitBounds, deviceProfile, - mTaskView.getThumbnailViews(), mTask.key.id, mBanner); + mTaskView.getSnapshotViews(), mTask.key.id, mBanner); mSplitOffsetTranslationX = translations.first; mSplitOffsetTranslationY = translations.second; updateTranslationY(); diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt index 3e4a953b56..b070244304 100644 --- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt +++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt @@ -68,36 +68,20 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu val initSplitTaskId = getThisTaskCurrentlyInSplitSelection() if (initSplitTaskId == INVALID_TASK_ID) { pagedOrientationHandler.measureGroupedTaskViewThumbnailBounds( - taskContainers[0].thumbnailViewDeprecated, - taskContainers[1].thumbnailViewDeprecated, + taskContainers[0].snapshotView, + taskContainers[1].snapshotView, widthSize, heightSize, splitBoundsConfig, container.deviceProfile, layoutDirection == LAYOUT_DIRECTION_RTL ) - // Should we be having a separate translation step apart from the measuring above? - // The following only applies to large screen for now, but for future reference - // we'd want to abstract this out in PagedViewHandlers to get the primary/secondary - // translation directions - taskContainers[0] - .thumbnailViewDeprecated - .applySplitSelectTranslateX(taskContainers[0].thumbnailViewDeprecated.translationX) - taskContainers[0] - .thumbnailViewDeprecated - .applySplitSelectTranslateY(taskContainers[0].thumbnailViewDeprecated.translationY) - taskContainers[1] - .thumbnailViewDeprecated - .applySplitSelectTranslateX(taskContainers[1].thumbnailViewDeprecated.translationX) - taskContainers[1] - .thumbnailViewDeprecated - .applySplitSelectTranslateY(taskContainers[1].thumbnailViewDeprecated.translationY) } else { // Currently being split with this taskView, let the non-split selected thumbnail // take up full thumbnail area taskContainers .firstOrNull { it.task.key.id != initSplitTaskId } - ?.thumbnailViewDeprecated + ?.snapshotView ?.measure( widthMeasureSpec, MeasureSpec.makeMeasureSpec( @@ -212,8 +196,8 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu taskContainers[0].iconView.asView(), taskContainers[1].iconView.asView(), taskIconHeight, - taskContainers[0].thumbnailViewDeprecated.measuredWidth, - taskContainers[0].thumbnailViewDeprecated.measuredHeight, + taskContainers[0].snapshotView.measuredWidth, + taskContainers[0].snapshotView.measuredHeight, measuredHeight, measuredWidth, isRtl, @@ -308,7 +292,7 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu // Check which of the two apps was selected if ( taskContainers[1].iconView.asView().containsPoint(lastTouchDownPosition) || - taskContainers[1].thumbnailViewDeprecated.containsPoint(lastTouchDownPosition) + taskContainers[1].snapshotView.containsPoint(lastTouchDownPosition) ) { return 1 } diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java index 4f446b203c..8d5ba7766f 100644 --- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java +++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java @@ -238,12 +238,12 @@ public class TaskMenuView extends AbstractFloatingView { mContainer.getDragLayer().getDescendantRectRelativeToSelf( enableOverviewIconMenu() ? getIconView().findViewById(R.id.icon_view_menu_anchor) - : taskContainer.getThumbnailViewDeprecated(), + : taskContainer.getSnapshotView(), sTempRect); Rect insets = mContainer.getDragLayer().getInsets(); BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) getLayoutParams(); params.width = orientationHandler.getTaskMenuWidth( - taskContainer.getThumbnailViewDeprecated(), deviceProfile, + taskContainer.getSnapshotView(), deviceProfile, taskContainer.getStagePosition()); // Gravity set to Left instead of Start as sTempRect.left measures Left distance not Start params.gravity = Gravity.LEFT; @@ -277,10 +277,10 @@ public class TaskMenuView extends AbstractFloatingView { // Margin that insets the menuView inside the taskView float taskInsetMargin = getResources().getDimension(R.dimen.task_card_margin); setTranslationX(orientationHandler.getTaskMenuX(thumbnailAlignedX, - mTaskContainer.getThumbnailViewDeprecated(), deviceProfile, taskInsetMargin, + mTaskContainer.getSnapshotView(), deviceProfile, taskInsetMargin, getIconView())); setTranslationY(orientationHandler.getTaskMenuY( - thumbnailAlignedY, mTaskContainer.getThumbnailViewDeprecated(), + thumbnailAlignedY, mTaskContainer.getSnapshotView(), mTaskContainer.getStagePosition(), this, taskInsetMargin, getIconView())); } @@ -316,7 +316,7 @@ public class TaskMenuView extends AbstractFloatingView { .createRevealAnimator(this, closing, revealAnimationStartProgress); mRevealAnimator.setInterpolator(enableOverviewIconMenu() ? Interpolators.EMPHASIZED : Interpolators.DECELERATE); - + AnimatorSet.Builder openCloseAnimatorBuilder = mOpenCloseAnimator.play(mRevealAnimator); if (enableOverviewIconMenu()) { IconAppChipView iconAppChip = (IconAppChipView) mTaskContainer.getIconView().asView(); @@ -334,11 +334,13 @@ public class TaskMenuView extends AbstractFloatingView { closing ? mMenuTranslationYBeforeOpen : mMenuTranslationYBeforeOpen + additionalTranslationY); translationYAnim.setInterpolator(EMPHASIZED); + openCloseAnimatorBuilder.with(translationYAnim); ObjectAnimator menuTranslationYAnim = ObjectAnimator.ofFloat( iconAppChip.getMenuTranslationY(), MULTI_PROPERTY_VALUE, closing ? 0 : additionalTranslationY); menuTranslationYAnim.setInterpolator(EMPHASIZED); + openCloseAnimatorBuilder.with(menuTranslationYAnim); float additionalTranslationX = 0; if (mContainer.getDeviceProfile().isLandscape @@ -354,20 +356,15 @@ public class TaskMenuView extends AbstractFloatingView { closing ? mMenuTranslationXBeforeOpen : mMenuTranslationXBeforeOpen - additionalTranslationX); translationXAnim.setInterpolator(EMPHASIZED); + openCloseAnimatorBuilder.with(translationXAnim); ObjectAnimator menuTranslationXAnim = ObjectAnimator.ofFloat( iconAppChip.getMenuTranslationX(), MULTI_PROPERTY_VALUE, closing ? 0 : -additionalTranslationX); menuTranslationXAnim.setInterpolator(EMPHASIZED); - - mOpenCloseAnimator.playTogether(translationYAnim, translationXAnim, - menuTranslationXAnim, menuTranslationYAnim); + openCloseAnimatorBuilder.with(menuTranslationXAnim); } - mOpenCloseAnimator.playTogether(mRevealAnimator, - ObjectAnimator.ofFloat( - mTaskContainer.getThumbnailViewDeprecated(), DIM_ALPHA, - closing ? 0 : TaskView.MAX_PAGE_SCRIM_ALPHA), - ObjectAnimator.ofFloat(this, ALPHA, closing ? 0 : 1)); + openCloseAnimatorBuilder.with(ObjectAnimator.ofFloat(this, ALPHA, closing ? 0 : 1)); if (enableRefactorTaskThumbnail()) { mRevealAnimator.addUpdateListener(animation -> { float animatedFraction = animation.getAnimatedFraction(); @@ -375,6 +372,10 @@ public class TaskMenuView extends AbstractFloatingView { mTaskContainer.getTaskContainerData() .getTaskMenuOpenProgress().setValue(openProgress); }); + } else { + openCloseAnimatorBuilder.with(ObjectAnimator.ofFloat( + mTaskContainer.getThumbnailViewDeprecated(), DIM_ALPHA, + closing ? 0 : TaskView.MAX_PAGE_SCRIM_ALPHA)); } mOpenCloseAnimator.addListener(new AnimationSuccessListener() { @Override diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java index 4283d0e4cf..2afb6a6d52 100644 --- a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java +++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java @@ -99,36 +99,6 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab } }; - /** Use to animate thumbnail translationX while first app in split selection is initiated */ - public static final Property SPLIT_SELECT_TRANSLATE_X = - new FloatProperty("splitSelectTranslateX") { - @Override - public void setValue(TaskThumbnailViewDeprecated thumbnail, - float splitSelectTranslateX) { - thumbnail.applySplitSelectTranslateX(splitSelectTranslateX); - } - - @Override - public Float get(TaskThumbnailViewDeprecated thumbnailView) { - return thumbnailView.mSplitSelectTranslateX; - } - }; - - /** Use to animate thumbnail translationY while first app in split selection is initiated */ - public static final Property SPLIT_SELECT_TRANSLATE_Y = - new FloatProperty("splitSelectTranslateY") { - @Override - public void setValue(TaskThumbnailViewDeprecated thumbnail, - float splitSelectTranslateY) { - thumbnail.applySplitSelectTranslateY(splitSelectTranslateY); - } - - @Override - public Float get(TaskThumbnailViewDeprecated thumbnailView) { - return thumbnailView.mSplitSelectTranslateY; - } - }; - private final RecentsViewContainer mContainer; private TaskOverlay mOverlay; private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); @@ -160,8 +130,6 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab private boolean mOverlayEnabled; /** Used as a placeholder when the original thumbnail animates out to. */ private boolean mShowSplashForSplitSelection; - private float mSplitSelectTranslateX; - private float mSplitSelectTranslateY; public TaskThumbnailViewDeprecated(Context context) { this(context, null); @@ -415,31 +383,6 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab } } - /** See {@link #SPLIT_SELECT_TRANSLATE_X} */ - protected void applySplitSelectTranslateX(float splitSelectTranslateX) { - mSplitSelectTranslateX = splitSelectTranslateX; - applyTranslateX(); - } - - /** See {@link #SPLIT_SELECT_TRANSLATE_Y} */ - protected void applySplitSelectTranslateY(float splitSelectTranslateY) { - mSplitSelectTranslateY = splitSelectTranslateY; - applyTranslateY(); - } - - private void applyTranslateX() { - setTranslationX(mSplitSelectTranslateX); - } - - private void applyTranslateY() { - setTranslationY(mSplitSelectTranslateY); - } - - protected void resetViewTransforms() { - mSplitSelectTranslateX = 0; - mSplitSelectTranslateY = 0; - } - public TaskView getTaskView() { return (TaskView) getParent(); } diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt index 7a3b00f197..d4b0040733 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.kt +++ b/quickstep/src/com/android/quickstep/views/TaskView.kt @@ -23,7 +23,9 @@ import android.annotation.IdRes import android.app.ActivityOptions import android.content.Context import android.content.Intent +import android.graphics.Bitmap import android.graphics.Canvas +import android.graphics.Insets import android.graphics.PointF import android.graphics.Rect import android.graphics.drawable.Drawable @@ -138,8 +140,8 @@ constructor( /** Returns a copy of integer array containing taskIds of all tasks in the TaskView. */ get() = taskContainers.map { it.task.key.id }.toIntArray() - val thumbnailViews: Array - get() = taskContainers.map { it.thumbnailViewDeprecated }.toTypedArray() + val snapshotViews: Array + get() = taskContainers.map { it.snapshotView }.toTypedArray() val isGridTask: Boolean /** Returns whether the task is part of overview grid and not being focused. */ @@ -170,6 +172,11 @@ constructor( /** Returns the first thumbnailView of the TaskView. */ get() = taskContainers[0].thumbnailViewDeprecated + @get:Deprecated("Use [taskContainers] instead.") + val firstSnapshotView: View + /** Returns the first snapshotView of the TaskView. */ + get() = taskContainers[0].snapshotView + @get:Deprecated("Use [taskContainers] instead.") val firstItemInfo: ItemInfo get() = taskContainers[0].itemInfo @@ -1197,10 +1204,10 @@ constructor( this, container.task, container.iconView.drawable, - container.thumbnailViewDeprecated, - container.thumbnailViewDeprecated.thumbnail, /* intent */ - null, /* user */ - null, + container.snapshotView, + container.thumbnail, + /* intent */ null, + /* user */ null, container.itemInfo ) } @@ -1512,6 +1519,10 @@ constructor( gridTranslationY = 0f boxTranslationY = 0f nonGridPivotTranslationX = 0f + taskContainers.forEach { + it.snapshotView.translationX = 0f + it.snapshotView.translationY = 0f + } resetViewTransforms() } @@ -1537,10 +1548,6 @@ constructor( alpha = stableAlpha setIconScaleAndDim(1f) setColorTint(0f, 0) - if (!enableRefactorTaskThumbnail()) { - // TODO(b/335399428) add split select functionality to new TTV - taskContainers.forEach { it.thumbnailViewDeprecated.resetViewTransforms() } - } } private fun getGridTrans(endTranslation: Float) = @@ -1624,6 +1631,18 @@ constructor( val snapshotView: View get() = thumbnailView ?: thumbnailViewDeprecated + // TODO(b/349120849): Extract ThumbnailData from TaskContainerData/TaskThumbnailViewModel + val thumbnail: Bitmap? + get() = thumbnailViewDeprecated.thumbnail + + // TODO(b/349120849): Extract ThumbnailData from TaskContainerData/TaskThumbnailViewModel + val isRealSnapshot: Boolean + get() = thumbnailViewDeprecated.isRealSnapshot() + + // TODO(b/349120849): Extract ThumbnailData from TaskContainerData/TaskThumbnailViewModel + val scaledInsets: Insets + get() = thumbnailViewDeprecated.scaledInsets + /** Builds proto for logging */ val itemInfo: WorkspaceItemInfo get() = diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt index d40f8ab389..c8893ad07e 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt @@ -33,7 +33,6 @@ import com.android.launcher3.taskbar.TaskbarActivityContext import com.android.launcher3.util.SplitConfigurationOptions import com.android.quickstep.views.GroupedTaskView import com.android.quickstep.views.IconView -import com.android.quickstep.views.TaskThumbnailViewDeprecated import com.android.quickstep.views.TaskView import com.android.quickstep.views.TaskView.TaskContainer import com.android.systemui.shared.recents.model.Task @@ -59,7 +58,7 @@ class SplitAnimationControllerTest { private val mockSplitSelectStateController: SplitSelectStateController = mock() // TaskView private val mockTaskView: TaskView = mock() - private val mockThumbnailView: TaskThumbnailViewDeprecated = mock() + private val mockSnapshotView: View = mock() private val mockBitmap: Bitmap = mock() private val mockIconView: IconView = mock() private val mockTaskViewDrawable: Drawable = mock() @@ -87,8 +86,8 @@ class SplitAnimationControllerTest { @Before fun setup() { - whenever(mockTaskContainer.thumbnailViewDeprecated).thenReturn(mockThumbnailView) - whenever(mockThumbnailView.thumbnail).thenReturn(mockBitmap) + whenever(mockTaskContainer.snapshotView).thenReturn(mockSnapshotView) + whenever(mockTaskContainer.thumbnail).thenReturn(mockBitmap) whenever(mockTaskContainer.iconView).thenReturn(mockIconView) whenever(mockIconView.drawable).thenReturn(mockTaskViewDrawable) whenever(mockTaskView.taskContainers).thenReturn(List(1) { mockTaskContainer }) @@ -180,7 +179,6 @@ class SplitAnimationControllerTest { whenever(mockTaskContainer.task).thenReturn(mockTask) whenever(mockTaskContainer.iconView).thenReturn(mockIconView) - whenever(mockTaskContainer.thumbnailViewDeprecated).thenReturn(mockThumbnailView) whenever(mockTask.getKey()).thenReturn(mockTaskKey) whenever(mockTaskKey.getId()).thenReturn(taskId) whenever(mockSplitSelectStateController.initialTaskId).thenReturn(taskId)