Merge "Import translations. DO NOT MERGE ANYWHERE" into main
diff --git a/OWNERS b/OWNERS
index 22efa33..db8d442 100644
--- a/OWNERS
+++ b/OWNERS
@@ -6,10 +6,8 @@
adamcohen@google.com
hyunyoungs@google.com
-twickham@google.com
vadimt@google.com
winsonc@google.com
-jonmiranda@google.com
awickham@google.com
agvard@google.com
@@ -29,7 +27,6 @@
peanutbutter@google.com
jeremysim@google.com
atsjenk@google.com
-brianji@google.com
hwwang@google.com
# Overview eng team
@@ -46,6 +43,16 @@
shamalip@google.com
zakcohen@google.com
+# System Navigation team
+brianji@google.com
+jonmiranda@google.com
+jagrutdesai@google.com
+randypfohl@google.com
+saumyaprakash@google.com
+sukeshram@google.com
+twickham@google.com
+victortulias@google.com
+
per-file FeatureFlags.java, globs = set noparent
per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, captaincole@google.com
diff --git a/quickstep/res/values-af/strings.xml b/quickstep/res/values-af/strings.xml
index 22125d7..365916e 100644
--- a/quickstep/res/values-af/strings.xml
+++ b/quickstep/res/values-af/strings.xml
@@ -46,7 +46,7 @@
<string name="hotseat_tip_gaps_filled" msgid="3035673010274223538">"Programvoorstelle is in leë spasie bygevoeg"</string>
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Programvoorstelle is geaktiveer"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Programvoorstelle is gedeaktiveer"</string>
- <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Voorspelde program: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+ <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Voorspelde app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Draai jou toestel"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Draai asseblief jou toestel om die tutoriaal oor gebaarnavigasie te voltooi"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Maak seker dat jy van die rand heel regs of heel links af swiep"</string>
@@ -104,10 +104,10 @@
<string name="toast_split_select_app_cancel" msgid="1939025102486630426">"Kanselleer"</string>
<string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Verlaat verdeeldeskermkeuse"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Kies nog ’n app as jy verdeelde skerm wil gebruik"</string>
- <string name="blocked_by_policy" msgid="2071401072261365546">"Jou organisasie laat nie hierdie program toe nie"</string>
+ <string name="blocked_by_policy" msgid="2071401072261365546">"Jou organisasie laat nie hierdie app toe nie"</string>
<string name="split_widgets_not_supported" msgid="1355743038053053866">"Legstukke word nie tans ondersteun nie; kies asseblief ’n ander app"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Slaan navigasietutoriaal oor?"</string>
- <string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Jy kan dit later in die <xliff:g id="NAME">%1$s</xliff:g>-program kry"</string>
+ <string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Jy kan dit later in die <xliff:g id="NAME">%1$s</xliff:g>-app kry"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Kanselleer"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Slaan oor"</string>
<string name="accessibility_rotate_button" msgid="4771825231336502943">"Draai skerm"</string>
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 9ee4b95..1144ac5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -51,7 +51,6 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SHORTCUT_HELPER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
import static com.android.window.flags.Flags.predictiveBackThreeButtonNav;
-import static com.android.wm.shell.Flags.enableBubbleBarInPersistentTaskBar;
import android.animation.Animator;
import android.animation.ArgbEvaluator;
@@ -1348,8 +1347,7 @@
|| mNavButtonsView.getWidth() == 0) {
return;
}
- if (enableBubbleBarInPersistentTaskBar()
- && mControllers.bubbleControllers.isPresent()) {
+ if (mControllers.bubbleControllers.isPresent()) {
if (mBubbleBarTargetLocation == null) {
// only set bubble bar location if it was not set before
mBubbleBarTargetLocation = mControllers.bubbleControllers.get()
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 8e2246b..107facf 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -163,7 +163,6 @@
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import com.android.systemui.unfold.updates.RotationChangeProvider;
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
-import com.android.wm.shell.Flags;
import java.io.PrintWriter;
import java.util.Collections;
@@ -277,12 +276,8 @@
TaskbarScrimView taskbarScrimView = mDragLayer.findViewById(R.id.taskbar_scrim);
NearestTouchFrame navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view);
StashedHandleView stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle);
- BubbleBarView bubbleBarView = null;
- FrameLayout bubbleBarContainer = null;
- if (isTransientTaskbar || Flags.enableBubbleBarInPersistentTaskBar()) {
- bubbleBarView = mDragLayer.findViewById(R.id.taskbar_bubbles);
- bubbleBarContainer = mDragLayer.findViewById(R.id.taskbar_bubbles_container);
- }
+ BubbleBarView bubbleBarView = mDragLayer.findViewById(R.id.taskbar_bubbles);
+ FrameLayout bubbleBarContainer = mDragLayer.findViewById(R.id.taskbar_bubbles_container);
StashedHandleView bubbleHandleView = mDragLayer.findViewById(R.id.stashed_bubble_handle);
mAccessibilityDelegate = new TaskbarShortcutMenuAccessibilityDelegate(this);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index de9eee4..741853e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -772,7 +772,7 @@
* aligned - returns 0.
*/
public float getTranslationXForBubbleBarPosition(BubbleBarLocation location) {
- if (!mControllerCallbacks.isBubbleBarEnabledInPersistentTaskbar()
+ if (!mControllerCallbacks.isBubbleBarEnabled()
|| location == mBubbleBarLocation
|| !mActivityContext.shouldStartAlignTaskbar()
) {
@@ -792,7 +792,7 @@
int iconEnd = centerAlignIconEnd;
if (mShouldTryStartAlign) {
int startSpacingPx = deviceProfile.inlineNavButtonsEndSpacingPx;
- if (mControllerCallbacks.isBubbleBarEnabledInPersistentTaskbar()
+ if (mControllerCallbacks.isBubbleBarEnabled()
&& mBubbleBarLocation != null
&& mActivityContext.shouldStartAlignTaskbar()) {
iconEnd = (int) getTaskBarIconsEndForBubbleBarLocation(mBubbleBarLocation);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
index c7841c1..dddfdfe 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
@@ -36,7 +36,6 @@
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController;
import com.android.launcher3.util.DisplayController;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
-import com.android.wm.shell.Flags;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
/**
@@ -159,10 +158,9 @@
.orElse(0f);
}
- /** Returns true if bubble bar controllers present and enabled in persistent taskbar. */
- public boolean isBubbleBarEnabledInPersistentTaskbar() {
- return Flags.enableBubbleBarInPersistentTaskBar()
- && mControllers.bubbleControllers.isPresent();
+ /** Returns true if bubble bar controllers are present. */
+ public boolean isBubbleBarEnabled() {
+ return mControllers.bubbleControllers.isPresent();
}
/** Returns on click listener for the taskbar overflow view. */
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 89f4f59..438478f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -90,7 +90,6 @@
import com.android.launcher3.util.MultiValueAlpha;
import com.android.quickstep.util.GroupTask;
import com.android.systemui.shared.recents.model.Task;
-import com.android.wm.shell.Flags;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
import java.io.PrintWriter;
@@ -303,8 +302,7 @@
/** Returns whether taskbar should be moved on the bubble bar location update. */
private boolean shouldMoveTaskbarOnBubbleBarLocationUpdate() {
- return Flags.enableBubbleBarInPersistentTaskBar()
- && mControllers.bubbleControllers.isPresent()
+ return mControllers.bubbleControllers.isPresent()
&& mActivity.shouldStartAlignTaskbar()
&& mActivity.isThreeButtonNav();
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index eeac169..3fd9970 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -65,7 +65,6 @@
import static com.android.quickstep.util.SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
import static com.android.wm.shell.Flags.enableBubbleAnything;
-import static com.android.wm.shell.Flags.enableBubbleBarInPersistentTaskBar;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import android.animation.Animator;
@@ -1134,9 +1133,7 @@
/** Provides the translation X for the hotseat item. */
public int getHotseatItemTranslationX(ItemInfo itemInfo) {
int translationX = 0;
- if (isBubbleBarEnabled()
- && enableBubbleBarInPersistentTaskBar()
- && mBubbleBarLocation != null) {
+ if (isBubbleBarEnabled() && mBubbleBarLocation != null) {
boolean isBubblesOnLeft = mBubbleBarLocation.isOnLeft(isRtl(getResources()));
translationX += mDeviceProfile
.getHotseatTranslationXForNavBar(this, isBubblesOnLeft);
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 39f9f85..3740a68 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -33,11 +33,14 @@
import static com.android.launcher3.Flags.enableAdditionalHomeAnimations;
import static com.android.launcher3.Flags.enableGridOnlyOverview;
import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
+import static com.android.launcher3.Flags.msdlFeedback;
import static com.android.launcher3.PagedView.INVALID_PAGE;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_GESTURE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_ENTER_DESKTOP_MODE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_EXIT_DESKTOP_MODE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -121,6 +124,7 @@
import com.android.launcher3.taskbar.TaskbarUIController;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.MSDLPlayerWrapper;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.util.VibratorWrapper;
@@ -149,6 +153,7 @@
import com.android.quickstep.views.RecentsViewContainer;
import com.android.quickstep.views.TaskContainer;
import com.android.quickstep.views.TaskView;
+import com.android.quickstep.views.TaskViewType;
import com.android.systemui.animation.TransitionAnimator;
import com.android.systemui.contextualeducation.GestureType;
import com.android.systemui.shared.recents.model.Task;
@@ -164,6 +169,8 @@
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.shared.startingsurface.SplashScreenExitAnimationUtils;
+import com.google.android.msdl.data.model.MSDLToken;
+
import kotlin.Unit;
import java.util.ArrayList;
@@ -206,6 +213,8 @@
protected MultiStateCallback mStateCallback;
protected boolean mCanceled;
private boolean mRecentsViewScrollLinked = false;
+ // The previous task view type before the user quick switches between tasks
+ private TaskViewType mPreviousTaskViewType;
private final Runnable mLauncherOnDestroyCallback = () -> {
ActiveGestureProtoLogProxy.logLauncherDestroyed();
@@ -362,10 +371,13 @@
@Nullable
private RemoteAnimationTargets.ReleaseCheck mSwipePipToHomeReleaseCheck = null;
+ private final MSDLPlayerWrapper mMSDLPlayerWrapper;
+
public AbsSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
TaskAnimationManager taskAnimationManager, GestureState gestureState,
long touchTimeMs, boolean continuingLastGesture,
- InputConsumerController inputConsumer, RecentsWindowFactory recentsWindowFactory) {
+ InputConsumerController inputConsumer, RecentsWindowFactory recentsWindowFactory,
+ MSDLPlayerWrapper msdlPlayerWrapper) {
super(context, deviceState, gestureState);
mContainerInterface = gestureState.getContainerInterface();
mContextInitListener =
@@ -392,6 +404,8 @@
mSplashMainWindowShiftLength = -res
.getDimensionPixelSize(R.dimen.starting_surface_exit_animation_window_shift_length);
+ mMSDLPlayerWrapper = msdlPlayerWrapper;
+
initTransitionEndpoints(mRemoteTargetHandles[0].getTaskViewSimulator()
.getOrientationState().getLauncherDeviceProfile());
initStateCallbacks();
@@ -695,6 +709,10 @@
return;
}
mRecentsView.onGestureAnimationStart(runningTasks, mDeviceState.getRotationTouchHelper());
+ TaskView currentPageTaskView = mRecentsView.getCurrentPageTaskView();
+ if (currentPageTaskView != null) {
+ mPreviousTaskViewType = currentPageTaskView.getType();
+ }
}
private void launcherFrameDrawn() {
@@ -1478,21 +1496,29 @@
return;
}
- StatsLogManager.EventEnum event;
+ ArrayList<StatsLogManager.EventEnum> events = new ArrayList<>();
switch (endTarget) {
case HOME:
- event = LAUNCHER_HOME_GESTURE;
+ events.add(LAUNCHER_HOME_GESTURE);
break;
case RECENTS:
- event = LAUNCHER_OVERVIEW_GESTURE;
+ events.add(LAUNCHER_OVERVIEW_GESTURE);
break;
case LAST_TASK:
case NEW_TASK:
- event = mLogDirectionUpOrLeft ? LAUNCHER_QUICKSWITCH_LEFT
- : LAUNCHER_QUICKSWITCH_RIGHT;
+ events.add(mLogDirectionUpOrLeft ? LAUNCHER_QUICKSWITCH_LEFT
+ : LAUNCHER_QUICKSWITCH_RIGHT);
+ if (targetTask != null && DesktopModeStatus.canEnterDesktopMode(mContext)
+ && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH.isTrue()) {
+ if (targetTask.getType() == TaskViewType.DESKTOP) {
+ events.add(LAUNCHER_QUICKSWITCH_ENTER_DESKTOP_MODE);
+ } else if (mPreviousTaskViewType == TaskViewType.DESKTOP) {
+ events.add(LAUNCHER_QUICKSWITCH_EXIT_DESKTOP_MODE);
+ }
+ }
break;
default:
- event = IGNORE;
+ events.add(IGNORE);
}
StatsLogger logger = StatsLogManager.newInstance(
mContainer != null ? mContainer.asContext() : mContext).logger()
@@ -1509,7 +1535,7 @@
? LOG_NO_OP_PAGE_INDEX
: mRecentsView.getNextPage();
logger.withRank(pageIndex);
- logger.log(event);
+ events.forEach(logger::log);
}
protected abstract HomeAnimationFactory createHomeAnimationFactory(
@@ -1995,6 +2021,7 @@
@UiThread
private void startNewTask() {
TaskView taskToLaunch = mRecentsView == null ? null : mRecentsView.getNextPageTaskView();
+ doLogGesture(NEW_TASK, taskToLaunch);
startNewTask(success -> {
if (!success) {
reset();
@@ -2003,7 +2030,6 @@
endLauncherTransitionController();
updateSysUiFlags(1 /* windowProgress == overview */);
}
- doLogGesture(NEW_TASK, taskToLaunch);
});
}
@@ -2272,7 +2298,11 @@
}
protected void performHapticFeedback() {
- VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
+ if (msdlFeedback()) {
+ mMSDLPlayerWrapper.playToken(MSDLToken.SWIPE_THRESHOLD_INDICATOR);
+ } else {
+ VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
+ }
}
public Consumer<MotionEvent> getRecentsViewDispatcher(float navbarRotation) {
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index 9b56fd4..b0c69cf 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -62,6 +62,7 @@
import com.android.launcher3.anim.SpringAnimationBuilder;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.MSDLPlayerWrapper;
import com.android.quickstep.fallback.FallbackRecentsView;
import com.android.quickstep.fallback.RecentsState;
import com.android.quickstep.util.RectFSpringAnim;
@@ -102,9 +103,10 @@
public FallbackSwipeHandler(Context context, RecentsAnimationDeviceState deviceState,
TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
- boolean continuingLastGesture, InputConsumerController inputConsumer) {
+ boolean continuingLastGesture, InputConsumerController inputConsumer,
+ MSDLPlayerWrapper msdlPlayerWrapper) {
super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs,
- continuingLastGesture, inputConsumer, null);
+ continuingLastGesture, inputConsumer, null, msdlPlayerWrapper);
mRunningOverHome = mGestureState.getRunningTask() != null
&& mGestureState.getRunningTask().isHomeTask();
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 6087dc2..0ddd87b 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -41,6 +41,7 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.util.MSDLPlayerWrapper;
import com.android.launcher3.util.StableViewInfo;
import com.android.launcher3.views.ClipIconView;
import com.android.launcher3.views.FloatingIconView;
@@ -67,9 +68,10 @@
public LauncherSwipeHandlerV2(Context context, RecentsAnimationDeviceState deviceState,
TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
- boolean continuingLastGesture, InputConsumerController inputConsumer) {
+ boolean continuingLastGesture, InputConsumerController inputConsumer,
+ MSDLPlayerWrapper msdlPlayerWrapper) {
super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs,
- continuingLastGesture, inputConsumer, null);
+ continuingLastGesture, inputConsumer, null, msdlPlayerWrapper);
}
diff --git a/quickstep/src/com/android/quickstep/OWNERS b/quickstep/src/com/android/quickstep/OWNERS
new file mode 100644
index 0000000..868e0ab
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/OWNERS
@@ -0,0 +1,9 @@
+# System Navigation team
+brianji@google.com
+jonmiranda@google.com
+jagrutdesai@google.com
+randypfohl@google.com
+saumyaprakash@google.com
+sukeshram@google.com
+twickham@google.com
+victortulias@google.com
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 2828a84..a5d2f3e 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -208,8 +208,10 @@
OverviewComponentObserver observer = new OverviewComponentObserver(mContext, rads);
try {
RecentsViewContainer container = observer.getContainerInterface().getCreatedContainer();
+ WindowInsets insets = container == null
+ ? null : container.getRootView().getRootWindowInsets();
- return container == null ? null : container.getRootView().getRootWindowInsets();
+ return insets == null ? super.getWindowInsets() : insets;
} finally {
observer.onDestroy();
rads.destroy();
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 564f9a2..4ddbcdb 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -83,6 +83,7 @@
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.LockedUserState;
+import com.android.launcher3.util.MSDLPlayerWrapper;
import com.android.launcher3.util.NavigationMode;
import com.android.launcher3.util.PluginManagerWrapper;
import com.android.launcher3.util.SafeCloseable;
@@ -1274,20 +1275,20 @@
GestureState gestureState, long touchTimeMs) {
return new LauncherSwipeHandlerV2(this, mDeviceState, mTaskAnimationManager,
gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
- mInputConsumer);
+ mInputConsumer, MSDLPlayerWrapper.INSTANCE.get(this));
}
private AbsSwipeUpHandler createFallbackSwipeHandler(
GestureState gestureState, long touchTimeMs) {
return new FallbackSwipeHandler(this, mDeviceState, mTaskAnimationManager,
gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
- mInputConsumer);
+ mInputConsumer, MSDLPlayerWrapper.INSTANCE.get(this));
}
private AbsSwipeUpHandler createRecentsWindowSwipeHandler(
GestureState gestureState, long touchTimeMs) {
return new RecentsWindowSwipeHandler(this, mDeviceState, mTaskAnimationManager,
gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
- mInputConsumer, mRecentsWindowFactory);
+ mInputConsumer, mRecentsWindowFactory, MSDLPlayerWrapper.INSTANCE.get(this));
}
}
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
index b0afe90..b3cc3e9 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
@@ -42,6 +42,7 @@
import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory
import com.android.launcher3.statemanager.StatefulContainer
import com.android.launcher3.taskbar.TaskbarUIController
+import com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL
import com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL
import com.android.launcher3.util.ContextTracker
import com.android.launcher3.util.DisplayController
@@ -88,10 +89,8 @@
* To add new protologs, see [RecentsWindowProtoLogProxy]. To enable logging to logcat, see
* [QuickstepProtoLogGroup.Constants.DEBUG_RECENTS_WINDOW]
*/
-class RecentsWindowManager(
- private val displayId: Int,
- context: Context
-) : RecentsWindowContext(context), RecentsViewContainer, StatefulContainer<RecentsState> {
+class RecentsWindowManager(private val displayId: Int, context: Context) :
+ RecentsWindowContext(context), RecentsViewContainer, StatefulContainer<RecentsState> {
companion object {
private const val HOME_APPEAR_DURATION: Long = 250
@@ -113,7 +112,7 @@
private var layoutInflater: LayoutInflater = LayoutInflater.from(this).cloneInContext(this)
private var stateManager: StateManager<RecentsState, RecentsWindowManager> =
StateManager<RecentsState, RecentsWindowManager>(this, RecentsState.BG_LAUNCHER)
- private var mSystemUiController: SystemUiController? = null
+ private var systemUiController: SystemUiController? = null
private var dragLayer: RecentsDragLayer<RecentsWindowManager>? = null
private var windowView: View? = null
@@ -126,7 +125,7 @@
private var tisBindHelper: TISBindHelper = TISBindHelper(this) {}
// Callback array that corresponds to events defined in @ActivityEvent
- private val mEventCallbacks =
+ private val eventCallbacks =
listOf(RunnableList(), RunnableList(), RunnableList(), RunnableList())
private var onInitListener: Predicate<Boolean>? = null
@@ -184,7 +183,7 @@
}
private fun startHomeInternal() {
- val runner = LauncherAnimationRunner(mainThreadHandler, mAnimationToHomeFactory, true)
+ val runner = LauncherAnimationRunner(mainThreadHandler, animationToHomeFactory, true)
val options =
ActivityOptions.makeRemoteAnimation(
RemoteAnimationAdapter(runner, HOME_APPEAR_DURATION, 0),
@@ -198,7 +197,7 @@
stateManager.moveToRestState()
}
- private val mAnimationToHomeFactory =
+ private val animationToHomeFactory =
RemoteAnimationFactory {
_: Int,
appTargets: Array<RemoteAnimationTarget>?,
@@ -289,7 +288,7 @@
actionsView?.updateDimension(getDeviceProfile(), recentsView?.lastComputedTaskSize)
actionsView?.updateVerticalMargin(DisplayController.getNavigationMode(this))
- mSystemUiController = SystemUiController(windowView)
+ systemUiController = SystemUiController(windowView)
recentsWindowTracker.handleCreate(this)
this.callbacks = callbacks
@@ -359,8 +358,11 @@
if (state == HOME || state == BG_LAUNCHER) {
cleanupRecentsWindow()
}
- if (state === DEFAULT) {
- AccessibilityManagerCompat.sendStateEventToTest(baseContext, OVERVIEW_STATE_ORDINAL)
+ when (state) {
+ HOME ->
+ AccessibilityManagerCompat.sendStateEventToTest(baseContext, NORMAL_STATE_ORDINAL)
+ DEFAULT ->
+ AccessibilityManagerCompat.sendStateEventToTest(baseContext, OVERVIEW_STATE_ORDINAL)
}
}
@@ -378,10 +380,10 @@
}
override fun getSystemUiController(): SystemUiController? {
- if (mSystemUiController == null) {
- mSystemUiController = SystemUiController(rootView)
+ if (systemUiController == null) {
+ systemUiController = SystemUiController(rootView)
}
- return mSystemUiController
+ return systemUiController
}
override fun getContext(): Context {
@@ -432,12 +434,12 @@
/** Adds a callback for the provided activity event */
override fun addEventCallback(@BaseActivity.ActivityEvent event: Int, callback: Runnable?) {
- mEventCallbacks[event].add(callback)
+ eventCallbacks[event].add(callback)
}
/** Removes a previously added callback */
override fun removeEventCallback(@BaseActivity.ActivityEvent event: Int, callback: Runnable?) {
- mEventCallbacks[event].remove(callback)
+ eventCallbacks[event].remove(callback)
}
override fun runOnBindToTouchInteractionService(r: Runnable?) {
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java
index ea1d21b..4a08d12 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java
@@ -62,6 +62,7 @@
import com.android.launcher3.anim.SpringAnimationBuilder;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.MSDLPlayerWrapper;
import com.android.quickstep.AbsSwipeUpHandler;
import com.android.quickstep.GestureState;
import com.android.quickstep.RecentsAnimationController;
@@ -110,9 +111,9 @@
public RecentsWindowSwipeHandler(Context context, RecentsAnimationDeviceState deviceState,
TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
boolean continuingLastGesture, InputConsumerController inputConsumer,
- RecentsWindowFactory recentsWindowFactory) {
+ RecentsWindowFactory recentsWindowFactory, MSDLPlayerWrapper msdlPlayerWrapper) {
super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs,
- continuingLastGesture, inputConsumer, recentsWindowFactory);
+ continuingLastGesture, inputConsumer, recentsWindowFactory, msdlPlayerWrapper);
mRunningOverHome = mGestureState.getRunningTask() != null
&& mGestureState.getRunningTask().isHomeTask();
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt
index 3b59864..53969c5 100644
--- a/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt
@@ -37,6 +37,12 @@
fun getThumbnailById(taskId: Int): Flow<ThumbnailData?>
/**
+ * Gets the [ThumbnailData] associated with a task that has id [taskId]. Flow will settle on
+ * null if the task was not found or is invisible.
+ */
+ fun getCurrentThumbnailById(taskId: Int): ThumbnailData?
+
+ /**
* Sets the tasks that are visible, indicating that properties relating to visuals need to be
* populated e.g. icons/thumbnails etc.
*/
diff --git a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
index 6c627ef..3aae760 100644
--- a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
@@ -72,6 +72,8 @@
override fun getThumbnailById(taskId: Int) =
getTaskDataById(taskId).map { it?.thumbnail }.distinctUntilChangedBy { it?.snapshotId }
+ override fun getCurrentThumbnailById(taskId: Int) = tasks.value[taskId]?.thumbnail
+
override fun setVisibleTasks(visibleTaskIdList: Set<Int>) {
val tasksNoLongerVisible = taskRequests.keys.subtract(visibleTaskIdList)
val newlyVisibleTasks = visibleTaskIdList.subtract(taskRequests.keys)
diff --git a/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCase.kt b/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCase.kt
index f060d7d..bea1d07 100644
--- a/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCase.kt
+++ b/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCase.kt
@@ -24,18 +24,17 @@
import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
import com.android.quickstep.recents.usecase.ThumbnailPositionState.MissingThumbnail
import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
-import kotlinx.coroutines.flow.firstOrNull
/** Use case for retrieving [Matrix] for positioning Thumbnail in a View */
class GetThumbnailPositionUseCase(
private val deviceProfileRepository: RecentsDeviceProfileRepository,
private val rotationStateRepository: RecentsRotationStateRepository,
private val tasksRepository: RecentTasksRepository,
- private val previewPositionHelper: PreviewPositionHelper = PreviewPositionHelper()
+ private val previewPositionHelper: PreviewPositionHelper = PreviewPositionHelper(),
) {
- suspend fun run(taskId: Int, width: Int, height: Int, isRtl: Boolean): ThumbnailPositionState {
+ fun run(taskId: Int, width: Int, height: Int, isRtl: Boolean): ThumbnailPositionState {
val thumbnailData =
- tasksRepository.getThumbnailById(taskId).firstOrNull() ?: return MissingThumbnail
+ tasksRepository.getCurrentThumbnailById(taskId) ?: return MissingThumbnail
val thumbnail = thumbnailData.thumbnail ?: return MissingThumbnail
previewPositionHelper.updateThumbnailMatrix(
Rect(0, 0, thumbnail.width, thumbnail.height),
@@ -44,11 +43,11 @@
height,
deviceProfileRepository.getRecentsDeviceProfile().isLargeScreen,
rotationStateRepository.getRecentsRotationState().activityRotation,
- isRtl
+ isRtl,
)
return MatrixScaling(
previewPositionHelper.matrix,
- previewPositionHelper.isOrientationChanged
+ previewPositionHelper.isOrientationChanged,
)
}
}
diff --git a/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailUseCase.kt b/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailUseCase.kt
index 3aa808e..b9e9e02 100644
--- a/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailUseCase.kt
+++ b/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailUseCase.kt
@@ -18,13 +18,9 @@
import android.graphics.Bitmap
import com.android.quickstep.recents.data.RecentTasksRepository
-import kotlinx.coroutines.flow.firstOrNull
-import kotlinx.coroutines.runBlocking
/** Use case for retrieving thumbnail. */
class GetThumbnailUseCase(private val taskRepository: RecentTasksRepository) {
/** Returns the latest thumbnail associated with [taskId] if loaded, or null otherwise */
- fun run(taskId: Int): Bitmap? = runBlocking {
- taskRepository.getThumbnailById(taskId).firstOrNull()?.thumbnail
- }
+ fun run(taskId: Int): Bitmap? = taskRepository.getCurrentThumbnailById(taskId)?.thumbnail
}
diff --git a/quickstep/src/com/android/quickstep/recents/usecase/SysUiStatusNavFlagsUseCase.kt b/quickstep/src/com/android/quickstep/recents/usecase/SysUiStatusNavFlagsUseCase.kt
index 1d19c7d..5be5f4a 100644
--- a/quickstep/src/com/android/quickstep/recents/usecase/SysUiStatusNavFlagsUseCase.kt
+++ b/quickstep/src/com/android/quickstep/recents/usecase/SysUiStatusNavFlagsUseCase.kt
@@ -22,14 +22,11 @@
import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_NAV
import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_STATUS
import com.android.quickstep.recents.data.RecentTasksRepository
-import kotlinx.coroutines.flow.firstOrNull
-import kotlinx.coroutines.runBlocking
/** UseCase to calculate flags for status bar and navigation bar */
class SysUiStatusNavFlagsUseCase(private val taskRepository: RecentTasksRepository) {
fun getSysUiStatusNavFlags(taskId: Int): Int {
- val thumbnailData =
- runBlocking { taskRepository.getThumbnailById(taskId).firstOrNull() } ?: return 0
+ val thumbnailData = taskRepository.getCurrentThumbnailById(taskId) ?: return 0
val thumbnailAppearance = thumbnailData.appearance
var flags = 0
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt
index 4e13d1c..14359db 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt
@@ -28,7 +28,6 @@
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.runBlocking
/** View model for TaskOverlay */
class TaskOverlayViewModel(
@@ -41,7 +40,7 @@
combine(
recentsViewData.overlayEnabled,
recentsViewData.settledFullyVisibleTaskIds.map { it.contains(task.key.id) },
- recentTasksRepository.getThumbnailById(task.key.id)
+ recentTasksRepository.getThumbnailById(task.key.id),
) { isOverlayEnabled, isFullyVisible, thumbnailData ->
if (isOverlayEnabled && isFullyVisible) {
Enabled(
@@ -55,24 +54,22 @@
.distinctUntilChanged()
fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): ThumbnailPositionState {
- return runBlocking {
- val matrix: Matrix
- val isRotated: Boolean
- when (
- val thumbnailPositionState =
- getThumbnailPositionUseCase.run(task.key.id, width, height, isRtl)
- ) {
- is MatrixScaling -> {
- matrix = thumbnailPositionState.matrix
- isRotated = thumbnailPositionState.isRotated
- }
- is MissingThumbnail -> {
- matrix = Matrix.IDENTITY_MATRIX
- isRotated = false
- }
+ val matrix: Matrix
+ val isRotated: Boolean
+ when (
+ val thumbnailPositionState =
+ getThumbnailPositionUseCase.run(task.key.id, width, height, isRtl)
+ ) {
+ is MatrixScaling -> {
+ matrix = thumbnailPositionState.matrix
+ isRotated = thumbnailPositionState.isRotated
}
- ThumbnailPositionState(matrix, isRotated)
+ is MissingThumbnail -> {
+ matrix = Matrix.IDENTITY_MATRIX
+ isRotated = false
+ }
}
+ return ThumbnailPositionState(matrix, isRotated)
}
data class ThumbnailPositionState(val matrix: Matrix, val isRotated: Boolean)
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
index e5bad67..b5b2fc9 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
@@ -44,7 +44,6 @@
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.runBlocking
@OptIn(ExperimentalCoroutinesApi::class)
class TaskThumbnailViewModelImpl(
@@ -109,17 +108,14 @@
splashProgress.value = splashAlphaUseCase.execute(taskId)
}
- override fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): Matrix {
- return runBlocking {
- when (
- val thumbnailPositionState =
- getThumbnailPositionUseCase.run(taskId, width, height, isRtl)
- ) {
- is ThumbnailPositionState.MatrixScaling -> thumbnailPositionState.matrix
- is ThumbnailPositionState.MissingThumbnail -> Matrix.IDENTITY_MATRIX
- }
+ override fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): Matrix =
+ when (
+ val thumbnailPositionState =
+ getThumbnailPositionUseCase.run(taskId, width, height, isRtl)
+ ) {
+ is ThumbnailPositionState.MatrixScaling -> thumbnailPositionState.matrix
+ is ThumbnailPositionState.MissingThumbnail -> Matrix.IDENTITY_MATRIX
}
- }
private fun isBackgroundOnly(task: Task): Boolean = task.isLocked || task.thumbnail == null
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index 1312aa4..3a0324c 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -28,8 +28,7 @@
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_NONE;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.getIndex;
import static com.android.wm.shell.shared.split.SplitScreenConstants.isPersistentSnapPosition;
import android.content.Context;
@@ -55,6 +54,7 @@
import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.model.data.TaskViewItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.uioverrides.QuickstepLauncher;
@@ -74,6 +74,7 @@
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import java.util.function.Consumer;
/**
@@ -171,20 +172,6 @@
*/
public void saveAppPair(GroupedTaskView gtv) {
InteractionJankMonitorWrapper.begin(gtv, Cuj.CUJ_LAUNCHER_SAVE_APP_PAIR);
- List<TaskContainer> containers = gtv.getTaskContainers();
- WorkspaceItemInfo recentsInfo1 = containers.get(0).getItemInfo();
- WorkspaceItemInfo recentsInfo2 = containers.get(1).getItemInfo();
- WorkspaceItemInfo app1 = resolveAppPairWorkspaceInfo(recentsInfo1);
- WorkspaceItemInfo app2 = resolveAppPairWorkspaceInfo(recentsInfo2);
-
- if (app1 == null || app2 == null) {
- // This shouldn't happen if canSaveAppPair() is called above, but log an error and do
- // not create the app pair if the workspace items can't be resolved
- Log.w(TAG, "Failed to save app pair due to invalid apps ("
- + "app1=" + recentsInfo1.getComponentKey().componentName
- + " app2=" + recentsInfo2.getComponentKey().componentName + ")");
- return;
- }
@PersistentSnapPosition int snapPosition = gtv.getSnapPosition();
if (snapPosition == SNAP_TO_NONE) {
@@ -198,9 +185,30 @@
return;
}
- app1.rank = encodeRank(SPLIT_POSITION_TOP_OR_LEFT, snapPosition);
- app2.rank = encodeRank(SPLIT_POSITION_BOTTOM_OR_RIGHT, snapPosition);
- AppPairInfo newAppPair = new AppPairInfo(app1, app2);
+ List<TaskContainer> containers = gtv.getTaskContainers();
+ List<TaskViewItemInfo> recentsInfos =
+ containers.stream().map(TaskContainer::getItemInfo).toList();
+ List<WorkspaceItemInfo> apps =
+ recentsInfos.stream().map(this::resolveAppPairWorkspaceInfo).toList();
+
+ if (apps.stream().anyMatch(Objects::isNull)) {
+ // This shouldn't happen if canSaveAppPair() is called above, but log an error and do
+ // not create the app pair if the workspace items can't be resolved
+ StringBuilder error =
+ new StringBuilder("Failed to save app pair due to invalid apps (");
+ for (int i = 0; i < recentsInfos.size(); i++) {
+ error.append("app").append(i).append("=")
+ .append(recentsInfos.get(i).getComponentKey().componentName).append(" ");
+ }
+ error.append(")");
+ Log.w(TAG, error.toString());
+ return;
+ }
+
+ for (int i = 0; i < apps.size(); i++) {
+ apps.get(i).rank = encodeRank(getIndex(i), snapPosition);
+ }
+ AppPairInfo newAppPair = new AppPairInfo(apps);
IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
MODEL_EXECUTOR.execute(() -> {
diff --git a/quickstep/src/com/android/quickstep/views/IconView.kt b/quickstep/src/com/android/quickstep/views/IconView.kt
index 583207f..3ee12ab 100644
--- a/quickstep/src/com/android/quickstep/views/IconView.kt
+++ b/quickstep/src/com/android/quickstep/views/IconView.kt
@@ -25,10 +25,13 @@
import android.widget.FrameLayout
import androidx.core.view.updateLayoutParams
import com.android.launcher3.DeviceProfile
+import com.android.launcher3.Flags
import com.android.launcher3.Utilities
+import com.android.launcher3.util.MSDLPlayerWrapper
import com.android.launcher3.util.MultiValueAlpha
import com.android.launcher3.views.ActivityContext
import com.android.quickstep.util.RecentsOrientedState
+import com.google.android.msdl.data.model.MSDLToken
/**
* A view which draws a drawable stretched to fit its size. Unlike ImageView, it avoids relayout
@@ -39,19 +42,32 @@
private var drawable: Drawable? = null
private var drawableWidth = 0
private var drawableHeight = 0
+ private var msdlPlayerWrapper: MSDLPlayerWrapper? = null
- constructor(context: Context) : super(context)
+ constructor(context: Context) : super(context) {
+ msdlPlayerWrapper = MSDLPlayerWrapper.INSTANCE.get(context)
+ }
- constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
+ msdlPlayerWrapper = MSDLPlayerWrapper.INSTANCE.get(context)
+ }
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
- ) : super(context, attrs, defStyleAttr)
+ ) : super(context, attrs, defStyleAttr) {
+ msdlPlayerWrapper = MSDLPlayerWrapper.INSTANCE.get(context)
+ }
init {
multiValueAlpha.setUpdateVisibility(true)
+ // Haptics are handled by the MSDLPlayerWrapper
+ isHapticFeedbackEnabled = !Flags.msdlFeedback() || msdlPlayerWrapper == null
+ }
+
+ override fun setOnLongClickListener(l: OnLongClickListener?) {
+ super.setOnLongClickListener(l?.withFeedback())
}
/** Sets a [Drawable] to be displayed. */
@@ -136,7 +152,7 @@
taskIconMargin = deviceProfile.overviewTaskMarginPx,
taskIconHeight = deviceProfile.overviewTaskIconSizePx,
thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx,
- isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
+ isRtl = layoutDirection == LAYOUT_DIRECTION_RTL,
)
updateLayoutParams<FrameLayout.LayoutParams> {
height = deviceProfile.overviewTaskIconSizePx
@@ -151,6 +167,16 @@
override fun asView(): View = this
+ private fun OnLongClickListener.withFeedback(): OnLongClickListener {
+ val delegate = this
+ return OnLongClickListener { v: View ->
+ if (Flags.msdlFeedback()) {
+ msdlPlayerWrapper?.playToken(MSDLToken.LONG_PRESS)
+ }
+ delegate.onLongClick(v)
+ }
+ }
+
companion object {
private const val NUM_ALPHA_CHANNELS = 2
private const val INDEX_CONTENT_ALPHA = 0
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 3b46367..c2eae66 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -846,6 +846,8 @@
private final Matrix mTmpMatrix = new Matrix();
+ private int mTaskViewCount = 0;
+
@Nullable
public TaskView getFirstTaskView() {
return mUtils.getFirstTaskView(getTaskViews());
@@ -1247,26 +1249,31 @@
// - It's the initial taskview for entering split screen, we only pretend to dismiss the
// task
// - It's the focused task to be moved to the front, we immediately re-add the task
- if (child instanceof TaskView && child != mSplitHiddenTaskView
- && child != mMovingTaskView) {
- TaskView taskView = (TaskView) child;
- for (int i : taskView.getTaskIds()) {
- mHasVisibleTaskData.delete(i);
+ if (child instanceof TaskView) {
+ mTaskViewCount = Math.max(0, --mTaskViewCount);
+ if (child != mSplitHiddenTaskView && child != mMovingTaskView) {
+ TaskView taskView = (TaskView) child;
+ for (int i : taskView.getTaskIds()) {
+ mHasVisibleTaskData.delete(i);
+ }
+ if (child instanceof GroupedTaskView) {
+ mGroupedTaskViewPool.recycle((GroupedTaskView) taskView);
+ } else if (child instanceof DesktopTaskView) {
+ mDesktopTaskViewPool.recycle((DesktopTaskView) taskView);
+ } else {
+ mTaskViewPool.recycle(taskView);
+ }
+ mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
}
- if (child instanceof GroupedTaskView) {
- mGroupedTaskViewPool.recycle((GroupedTaskView) taskView);
- } else if (child instanceof DesktopTaskView) {
- mDesktopTaskViewPool.recycle((DesktopTaskView) taskView);
- } else {
- mTaskViewPool.recycle(taskView);
- }
- mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
}
}
@Override
public void onViewAdded(View child) {
super.onViewAdded(child);
+ if (child instanceof TaskView) {
+ mTaskViewCount++;
+ }
child.setAlpha(mContentAlpha);
// RecentsView is set to RTL in the constructor when system is using LTR. Here we set the
// child direction back to match system settings.
@@ -2081,11 +2088,7 @@
}
public int getTaskViewCount() {
- int taskViewCount = getChildCount();
- if (indexOfChild(mClearAllButton) != -1) {
- taskViewCount--;
- }
- return taskViewCount;
+ return mTaskViewCount;
}
/**
@@ -2291,8 +2294,7 @@
* Updates TaskView scaling and translation required to support variable width.
*/
private void updateTaskSize() {
- final int taskCount = getTaskViewCount();
- if (taskCount == 0) {
+ if (getTaskViewCount() == 0) {
return;
}
@@ -5658,8 +5660,7 @@
throw new IllegalStateException("Another pending animation is still running");
}
- int count = getTaskViewCount();
- if (count == 0) {
+ if (getTaskViewCount() == 0) {
return new PendingAnimation(duration);
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
index c93507a..6dce10b 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
@@ -30,7 +30,9 @@
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.animation.ValueAnimator;
@@ -58,14 +60,18 @@
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulContainer;
+import com.android.launcher3.util.MSDLPlayerWrapper;
import com.android.launcher3.util.SystemUiController;
import com.android.quickstep.fallback.window.RecentsWindowFactory;
import com.android.quickstep.util.ContextInitListener;
+import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.RecentsViewContainer;
import com.android.systemui.shared.Flags;
import com.android.systemui.shared.system.InputConsumerController;
+import com.google.android.msdl.data.model.MSDLToken;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -123,6 +129,7 @@
@Mock protected LauncherRootView mRootView;
@Mock protected SystemUiController mSystemUiController;
@Mock protected GestureState mGestureState;
+ @Mock protected MSDLPlayerWrapper mMSDLPlayerWrapper;
@Rule
public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@@ -305,6 +312,17 @@
});
}
+ @Test
+ @EnableFlags(com.android.launcher3.Flags.FLAG_MSDL_FEEDBACK)
+ public void onMotionPauseDetected_playsSwipeThresholdToken() {
+ SWIPE_HANDLER handler = createSwipeHandler();
+ MotionPauseDetector.OnMotionPauseListener listener = handler.getMotionPauseListener();
+ listener.onMotionPauseDetected();
+
+ verify(mMSDLPlayerWrapper, times(1)).playToken(eq(MSDLToken.SWIPE_THRESHOLD_INDICATOR));
+ verifyNoMoreInteractions(mMSDLPlayerWrapper);
+ }
+
/**
* Verifies that RecentsAnimationController#finish() is called, and captures and runs any
* callback that was passed to it. This ensures that STATE_CURRENT_TASK_FINISHED is correctly
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java
index 88197e5..d4eb8e2 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java
@@ -49,7 +49,8 @@
mGestureState,
touchTimeMs,
continuingLastGesture,
- mInputConsumerController);
+ mInputConsumerController,
+ mMSDLPlayerWrapper);
}
@NonNull
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
index 32b5b85..37accdb 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
@@ -19,11 +19,15 @@
import android.graphics.PointF
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.Flags.enableLauncherOverviewInWindow
import com.android.launcher3.R
import com.android.launcher3.dagger.LauncherAppComponent
import com.android.launcher3.dagger.LauncherAppSingleton
import com.android.launcher3.util.LauncherModelHelper
+import com.android.launcher3.util.MSDLPlayerWrapper
import com.android.quickstep.dagger.QuickStepModule
+import com.android.quickstep.fallback.window.RecentsWindowFactory
import com.android.systemui.contextualeducation.GestureType
import com.android.systemui.shared.system.InputConsumerController
import dagger.BindsInstance
@@ -51,6 +55,8 @@
@Mock private lateinit var systemUiProxy: SystemUiProxy
+ @Mock private lateinit var msdlPlayerWrapper: MSDLPlayerWrapper
+
private lateinit var underTest: LauncherSwipeHandlerV2
@get:Rule val mockitoRule = MockitoJUnit.rule()
@@ -68,6 +74,16 @@
)
val deviceState = mock(RecentsAnimationDeviceState::class.java)
whenever(deviceState.rotationTouchHelper).thenReturn(mock(RotationTouchHelper::class.java))
+
+ if (enableLauncherOverviewInWindow()) {
+ // Initialize an instance of RecentsWindowFactory directly to simulate its
+ // initialization in TouchInteractionService#onCreate.
+ // This instance will then be used in OverviewComponentObserver.
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ RecentsWindowFactory(sandboxContext)
+ }
+ }
+
gestureState = spy(GestureState(OverviewComponentObserver(sandboxContext, deviceState), 0))
underTest =
@@ -79,6 +95,7 @@
0,
false,
inputConsumerController,
+ msdlPlayerWrapper,
)
underTest.onGestureStarted(/* isLikelyToStartNewTask= */ false)
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java
index ec1dc8b..fc6acfd 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java
@@ -78,7 +78,8 @@
mGestureState,
touchTimeMs,
continuingLastGesture,
- mInputConsumerController);
+ mInputConsumerController,
+ mMSDLPlayerWrapper);
}
@NonNull
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java
index 24b2d4e..3287fb5 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java
@@ -65,7 +65,8 @@
touchTimeMs,
continuingLastGesture,
mInputConsumerController,
- mRecentsWindowFactory);
+ mRecentsWindowFactory,
+ mMSDLPlayerWrapper);
}
@Nullable
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
index d6688d6..1c9ce0b 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
@@ -49,6 +49,9 @@
override fun getThumbnailById(taskId: Int): Flow<ThumbnailData?> =
getTaskDataById(taskId).map { it?.thumbnail }
+ override fun getCurrentThumbnailById(taskId: Int): ThumbnailData? =
+ tasks.value.firstOrNull { it.key.id == taskId }?.thumbnail
+
override fun setVisibleTasks(visibleTaskIdList: Set<Int>) {
visibleTasks.value = visibleTaskIdList
tasks.value =
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
index 357df6e..e0e7f28 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
@@ -87,6 +87,48 @@
}
@Test
+ fun getThumbnailByIdReturnsNullWithNoLoadedThumbnails() =
+ testScope.runTest {
+ recentsModel.seedTasks(defaultTaskList)
+ systemUnderTest.getAllTaskData(forceRefresh = true)
+
+ assertThat(systemUnderTest.getThumbnailById(1).first()).isNull()
+ }
+
+ @Test
+ fun getCurrentThumbnailByIdReturnsNullWithNoLoadedThumbnails() =
+ testScope.runTest {
+ recentsModel.seedTasks(defaultTaskList)
+ systemUnderTest.getAllTaskData(forceRefresh = true)
+
+ assertThat(systemUnderTest.getCurrentThumbnailById(1)).isNull()
+ }
+
+ @Test
+ fun getThumbnailByIdReturnsThumbnailWithLoadedThumbnails() =
+ testScope.runTest {
+ recentsModel.seedTasks(defaultTaskList)
+ systemUnderTest.getAllTaskData(forceRefresh = true)
+ val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1]
+
+ systemUnderTest.setVisibleTasks(setOf(1))
+
+ assertThat(systemUnderTest.getThumbnailById(1).first()!!.thumbnail).isEqualTo(bitmap1)
+ }
+
+ @Test
+ fun getCurrentThumbnailByIdReturnsThumbnailWithLoadedThumbnails() =
+ testScope.runTest {
+ recentsModel.seedTasks(defaultTaskList)
+ systemUnderTest.getAllTaskData(forceRefresh = true)
+ val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1]
+
+ systemUnderTest.setVisibleTasks(setOf(1))
+
+ assertThat(systemUnderTest.getCurrentThumbnailById(1)?.thumbnail).isEqualTo(bitmap1)
+ }
+
+ @Test
fun setVisibleTasksPopulatesThumbnails() =
testScope.runTest {
recentsModel.seedTasks(defaultTaskList)
diff --git a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
index c53c177..1c4ce74 100644
--- a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
+++ b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
@@ -42,13 +42,13 @@
import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
+import android.os.Process;
import android.os.UserHandle;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.text.TextUtils;
-import androidx.test.core.content.pm.ApplicationInfoBuilder;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -122,10 +122,10 @@
mLauncherApps = mModelHelper.sandboxContext.spyService(LauncherApps.class);
doAnswer(i -> {
String pkg = i.getArgument(0);
- ApplicationInfo applicationInfo = ApplicationInfoBuilder.newBuilder()
- .setPackageName(pkg)
- .setName("App " + pkg)
- .build();
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.packageName = pkg;
+ applicationInfo.name = "App " + pkg;
+ applicationInfo.uid = Process.myUid();
applicationInfo.category = CATEGORY_PRODUCTIVITY;
applicationInfo.flags = FLAG_INSTALLED;
return applicationInfo;
diff --git a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
index 6a7b6f8..f57c35f 100644
--- a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
+++ b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
@@ -20,18 +20,28 @@
import android.os.SystemProperties;
+import androidx.annotation.NonNull;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.Until;
import com.android.launcher3.tapl.LaunchedAppState;
+import com.android.launcher3.tapl.TestHelpers;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.TestUtil;
+import com.android.launcher3.util.Wait;
+import com.android.quickstep.fallback.RecentsState;
+import com.android.quickstep.fallback.window.RecentsWindowManager;
import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.RecentsViewContainer;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
/**
* Base class for all instrumentation tests that deal with Quickstep.
*/
@@ -54,6 +64,55 @@
}
}
+ // Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call
+ // expecting the results of that gesture because the wait can hide flakeness.
+ protected void waitForRecentsWindowState(String message, Supplier<RecentsState> state) {
+ waitForRecentsWindowCondition(message, recentsWindow ->
+ recentsWindow.getStateManager().getCurrentStableState() == state.get());
+ }
+
+ // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
+ // flakiness.
+ protected void waitForRecentsWindowCondition(String
+ message, Function<RecentsWindowManager, Boolean> condition) {
+ waitForRecentsWindowCondition(message, condition, TestUtil.DEFAULT_UI_TIMEOUT);
+ }
+
+ protected <T> T getFromRecentsWindowManager(Function<RecentsWindowManager, T> f) {
+ if (!TestHelpers.isInLauncherProcess()) return null;
+ return getOnUiThread(
+ () -> f.apply(RecentsWindowManager.getRecentsWindowTracker().getCreatedContext()));
+ }
+
+ protected void executeOnRecentsWindow(Consumer<RecentsWindowManager> f) {
+ getFromRecentsWindowManager(recentsWindow -> {
+ f.accept(recentsWindow);
+ return null;
+ });
+ }
+
+ protected void executeOnRecentsViewContainerInTearDown(
+ @NonNull Consumer<RecentsViewContainer> f) {
+ executeOnRecentsWindow(container -> {
+ if (container != null) f.accept(container);
+ });
+ }
+
+ // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
+ // flakiness.
+ protected void waitForRecentsWindowCondition(
+ String message, Function<RecentsWindowManager, Boolean> condition, long timeout) {
+ verifyKeyguardInvisible();
+ if (!TestHelpers.isInLauncherProcess()) return;
+ Wait.atMost(message, () -> getFromRecentsWindowManager(condition), mLauncher, timeout);
+ }
+
+ protected boolean isInRecentsWindowState(Supplier<RecentsState> state) {
+ if (!TestHelpers.isInLauncherProcess()) return true;
+ return getFromRecentsWindowManager(
+ recentsWindow -> recentsWindow.getStateManager().getState() == state.get());
+ }
+
protected void assertTestActivityIsRunning(int activityNumber, String message) {
assertTrue(message, mDevice.wait(
Until.hasObject(By.pkg(getAppPackageName()).text("TestActivity" + activityNumber)),
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index f1fe2d2..ec6a9c3 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -16,6 +16,7 @@
package com.android.quickstep;
+import static com.android.launcher3.Flags.enableLauncherOverviewInWindow;
import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
import static com.android.quickstep.TaskbarModeSwitchRule.Mode.TRANSIENT;
@@ -30,13 +31,13 @@
import android.content.Intent;
import android.content.res.Configuration;
+import androidx.annotation.NonNull;
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.Until;
-import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.tapl.BaseOverview;
import com.android.launcher3.tapl.LaunchedAppState;
@@ -53,7 +54,9 @@
import com.android.launcher3.util.rule.TestStabilityRule;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
+import com.android.quickstep.fallback.RecentsState;
import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.RecentsViewContainer;
import org.junit.After;
import org.junit.Before;
@@ -61,6 +64,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.function.Consumer;
+
@LargeTest
@RunWith(AndroidJUnit4.class)
public class TaplTestsQuickstep extends AbstractQuickStepTest {
@@ -70,19 +75,33 @@
private static final String READ_DEVICE_CONFIG_PERMISSION =
"android.permission.READ_DEVICE_CONFIG";
+ private enum ExpectedState {
+
+ HOME(LauncherState.NORMAL, RecentsState.HOME),
+ OVERVIEW(LauncherState.OVERVIEW, RecentsState.DEFAULT);
+
+ private final LauncherState mLauncherState;
+ private final RecentsState mRecentsState;
+
+ ExpectedState(LauncherState launcherState, RecentsState recentsState) {
+ this.mLauncherState = launcherState;
+ this.mRecentsState = recentsState;
+ }
+ }
+
@Before
public void setUp() throws Exception {
super.setUp();
- executeOnLauncher(launcher -> {
- RecentsView recentsView = launcher.getOverviewPanel();
+ executeOnRecentsViewContainer(container -> {
+ RecentsView recentsView = container.getOverviewPanel();
recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(true);
});
}
@After
public void tearDown() {
- executeOnLauncherInTearDown(launcher -> {
- RecentsView recentsView = launcher.getOverviewPanel();
+ executeOnRecentsViewContainerInTearDown(container -> {
+ RecentsView recentsView = container.getOverviewPanel();
recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(false);
});
}
@@ -117,28 +136,27 @@
startTestAppsWithCheck();
// mLauncher.pressHome() also tests an important case of pressing home while in background.
Overview overview = mLauncher.goHome().switchToOverview();
- assertTrue("Launcher internal state didn't switch to Overview",
- isInState(() -> LauncherState.OVERVIEW));
- executeOnLauncher(
- launcher -> assertTrue("Don't have at least 3 tasks", getTaskCount(launcher) >= 3));
+ assertIsInState(
+ "Launcher internal state didn't switch to Overview", ExpectedState.OVERVIEW);
+ executeOnRecentsViewContainer(container -> assertTrue(
+ "Don't have at least 3 tasks", getTaskCount(container) >= 3));
// Test flinging forward and backward.
- executeOnLauncher(launcher -> assertEquals("Current task in Overview is not 0",
- 0, getCurrentOverviewPage(launcher)));
+ executeOnRecentsViewContainer(container -> assertEquals("Current task in Overview is not 0",
+ 0, getCurrentOverviewPage(container)));
overview.flingForward();
- assertTrue("Launcher internal state is not Overview",
- isInState(() -> LauncherState.OVERVIEW));
+ assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
final Integer currentTaskAfterFlingForward = getFromLauncher(
launcher -> getCurrentOverviewPage(launcher));
- executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0",
- currentTaskAfterFlingForward > 0));
+ executeOnRecentsViewContainer(container -> assertTrue(
+ "Current task in Overview is still 0", currentTaskAfterFlingForward > 0));
overview.flingBackward();
- assertTrue("Launcher internal state is not Overview",
- isInState(() -> LauncherState.OVERVIEW));
- executeOnLauncher(launcher -> assertTrue("Flinging back in Overview did nothing",
- getCurrentOverviewPage(launcher) < currentTaskAfterFlingForward));
+ assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
+ executeOnRecentsViewContainer(container -> assertTrue(
+ "Flinging back in Overview did nothing",
+ getCurrentOverviewPage(container) < currentTaskAfterFlingForward));
// Test opening a task.
OverviewTask task = mLauncher.goHome().switchToOverview().getCurrentTask();
@@ -154,23 +172,22 @@
// Test dismissing a task.
overview = mLauncher.goHome().switchToOverview();
- assertTrue("Launcher internal state didn't switch to Overview",
- isInState(() -> LauncherState.OVERVIEW));
+ assertIsInState(
+ "Launcher internal state didn't switch to Overview", ExpectedState.OVERVIEW);
final Integer numTasks = getFromLauncher(launcher -> getTaskCount(launcher));
task = overview.getCurrentTask();
assertNotNull("overview.getCurrentTask() returned null (2)", task);
task.dismiss();
- executeOnLauncher(
- launcher -> assertEquals("Dismissing a task didn't remove 1 task from Overview",
- numTasks - 1, getTaskCount(launcher)));
+ executeOnRecentsViewContainer(
+ container -> assertEquals("Dismissing a task didn't remove 1 task from Overview",
+ numTasks - 1, getTaskCount(container)));
// Test dismissing all tasks.
mLauncher.goHome().switchToOverview().dismissAllTasks();
- assertTrue("Launcher internal state is not Home",
- isInState(() -> LauncherState.NORMAL));
- executeOnLauncher(
- launcher -> assertEquals("Still have tasks after dismissing all",
- 0, getTaskCount(launcher)));
+ assertIsInState("Launcher internal state is not Home", ExpectedState.HOME);
+ executeOnRecentsViewContainer(
+ container -> assertEquals("Still have tasks after dismissing all",
+ 0, getTaskCount(container)));
}
/**
@@ -192,12 +209,10 @@
public void testDismissOverviewWithEscKey() throws Exception {
startTestAppsWithCheck();
final Overview overview = mLauncher.goHome().switchToOverview();
- assertTrue("Launcher internal state is not Overview",
- isInState(() -> LauncherState.OVERVIEW));
+ assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
overview.dismissByEscKey();
- assertTrue("Launcher internal state is not Home",
- isInState(() -> LauncherState.NORMAL));
+ assertIsInState("Launcher internal state is not Home", ExpectedState.HOME);
}
@Test
@@ -218,11 +233,9 @@
selectModeButtons.dismissByEscKey();
- assertTrue("Launcher internal state is not Overview",
- isInState(() -> LauncherState.OVERVIEW));
+ assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
overview.dismissByEscKey();
- assertTrue("Launcher internal state is not Home",
- isInState(() -> LauncherState.NORMAL));
+ assertIsInState("Launcher internal state is not Home", ExpectedState.HOME);
}
@Test
@@ -230,11 +243,11 @@
startTestAppsWithCheck();
startAppFast(CALCULATOR_APP_PACKAGE); // Ensure Calculator is last opened app.
Workspace home = mLauncher.goHome();
- assertTrue("Launcher state is not Home", isInState(() -> LauncherState.NORMAL));
+ assertIsInState("Launcher state is not Home", ExpectedState.HOME);
Overview overview = home.openOverviewFromActionPlusTabKeyboardShortcut();
- assertTrue("Launcher state is not Overview", isInState(() -> LauncherState.OVERVIEW));
+ assertIsInState("Launcher state is not Overview", ExpectedState.OVERVIEW);
overview.launchFocusedTaskByEnterKey(CALCULATOR_APP_PACKAGE); // Assert app is focused.
}
@@ -243,28 +256,32 @@
startTestAppsWithCheck();
startAppFast(CALCULATOR_APP_PACKAGE); // Ensure Calculator is last opened app.
Workspace home = mLauncher.goHome();
- assertTrue("Launcher state is not Home", isInState(() -> LauncherState.NORMAL));
+ assertIsInState("Launcher state is not Home", ExpectedState.HOME);
Overview overview = home.openOverviewFromRecentsKeyboardShortcut();
- assertTrue("Launcher state is not Overview", isInState(() -> LauncherState.OVERVIEW));
+ assertIsInState("Launcher state is not Overview", ExpectedState.OVERVIEW);
overview.launchFocusedTaskByEnterKey(CALCULATOR_APP_PACKAGE); // Assert app is focused.
}
- private int getCurrentOverviewPage(Launcher launcher) {
- return launcher.<RecentsView>getOverviewPanel().getCurrentPage();
+ private RecentsView getOverviewPanel(RecentsViewContainer recentsViewContainer) {
+ return recentsViewContainer.getOverviewPanel();
}
- private int getTaskCount(Launcher launcher) {
- return launcher.<RecentsView>getOverviewPanel().getTaskViewCount();
+ private int getCurrentOverviewPage(RecentsViewContainer recentsViewContainer) {
+ return getOverviewPanel(recentsViewContainer).getCurrentPage();
}
- private int getTopRowTaskCountForTablet(Launcher launcher) {
- return launcher.<RecentsView>getOverviewPanel().getTopRowTaskCountForTablet();
+ private int getTaskCount(RecentsViewContainer recentsViewContainer) {
+ return getOverviewPanel(recentsViewContainer).getTaskViewCount();
}
- private int getBottomRowTaskCountForTablet(Launcher launcher) {
- return launcher.<RecentsView>getOverviewPanel().getBottomRowTaskCountForTablet();
+ private int getTopRowTaskCountForTablet(RecentsViewContainer recentsViewContainer) {
+ return getOverviewPanel(recentsViewContainer).getTopRowTaskCountForTablet();
+ }
+
+ private int getBottomRowTaskCountForTablet(RecentsViewContainer recentsViewContainer) {
+ return getOverviewPanel(recentsViewContainer).getBottomRowTaskCountForTablet();
}
@Test
@@ -274,8 +291,8 @@
startTestAppsWithCheck();
assertNotNull("Workspace.switchToOverview() returned null",
mLauncher.goHome().switchToOverview());
- assertTrue("Launcher internal state didn't switch to Overview",
- isInState(() -> LauncherState.OVERVIEW));
+ assertIsInState(
+ "Launcher internal state didn't switch to Overview", ExpectedState.OVERVIEW);
}
@Test
@@ -300,8 +317,8 @@
assertNotNull("Background.switchToOverview() returned null",
launchedAppState.switchToOverview());
- assertTrue("Launcher internal state didn't switch to Overview",
- isInState(() -> LauncherState.OVERVIEW));
+ assertIsInState(
+ "Launcher internal state didn't switch to Overview", ExpectedState.OVERVIEW);
}
private void quickSwitchToPreviousAppAndAssert(boolean toRight) {
@@ -413,11 +430,11 @@
// Debug if we need to goHome to prevent wrong previous state b/315525621
mLauncher.goHome();
mLauncher.getWorkspace().switchToAllApps().pressBackToWorkspace();
- waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
+ waitForState("Launcher internal state didn't switch to Home", ExpectedState.HOME);
startAppFast(CALCULATOR_APP_PACKAGE);
mLauncher.getLaunchedAppState().pressBackToWorkspace();
- waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
+ waitForState("Launcher internal state didn't switch to Home", ExpectedState.HOME);
}
@Test
@@ -432,16 +449,15 @@
}
Overview overview = mLauncher.goHome().switchToOverview();
- executeOnLauncher(
- launcher -> assertTrue("Don't have at least 13 tasks",
- getTaskCount(launcher) >= 13));
+ executeOnRecentsViewContainer(
+ container -> assertTrue("Don't have at least 13 tasks",
+ getTaskCount(container) >= 13));
// Test scroll the first task off screen
overview.scrollCurrentTaskOffScreen();
- assertTrue("Launcher internal state is not Overview",
- isInState(() -> LauncherState.OVERVIEW));
- executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0",
- getCurrentOverviewPage(launcher) > 0));
+ assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
+ executeOnRecentsViewContainer(container -> assertTrue("Current task in Overview is still 0",
+ getCurrentOverviewPage(container) > 0));
// Test opening the task.
overview.getCurrentTask().open();
@@ -454,41 +470,41 @@
// Scroll the task offscreen as it is now first
overview = mLauncher.goHome().switchToOverview();
overview.scrollCurrentTaskOffScreen();
- assertTrue("Launcher internal state is not Overview",
- isInState(() -> LauncherState.OVERVIEW));
- executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0",
- getCurrentOverviewPage(launcher) > 0));
+ assertIsInState(
+ "Launcher internal state is not Overview", ExpectedState.OVERVIEW);
+ executeOnRecentsViewContainer(container -> assertTrue("Current task in Overview is still 0",
+ getCurrentOverviewPage(container) > 0));
// Test dismissing the later task.
final Integer numTasks = getFromLauncher(this::getTaskCount);
overview.getCurrentTask().dismiss();
- executeOnLauncher(
- launcher -> assertEquals("Dismissing a task didn't remove 1 task from Overview",
- numTasks - 1, getTaskCount(launcher)));
- executeOnLauncher(launcher -> assertTrue("Grid did not rebalance after dismissal",
- (Math.abs(getTopRowTaskCountForTablet(launcher) - getBottomRowTaskCountForTablet(
- launcher)) <= 1)));
+ executeOnRecentsViewContainer(
+ container -> assertEquals("Dismissing a task didn't remove 1 task from Overview",
+ numTasks - 1, getTaskCount(container)));
+ executeOnRecentsViewContainer(container -> assertTrue(
+ "Grid did not rebalance after dismissal",
+ (Math.abs(getTopRowTaskCountForTablet(container)
+ - getBottomRowTaskCountForTablet(container)) <= 1)));
// TODO(b/308841019): Re-enable after fixing Overview jank when dismiss
// // Test dismissing more tasks.
-// assertTrue("Launcher internal state didn't remain in Overview",
-// isInState(() -> LauncherState.OVERVIEW));
+// assertIsInState(
+// "Launcher internal state didn't remain in Overview", ExpectedState.OVERVIEW);
// overview.getCurrentTask().dismiss();
-// assertTrue("Launcher internal state didn't remain in Overview",
-// isInState(() -> LauncherState.OVERVIEW));
+// assertIsInState(
+// "Launcher internal state didn't remain in Overview", ExpectedState.OVERVIEW);
// overview.getCurrentTask().dismiss();
-// executeOnLauncher(launcher -> assertTrue("Grid did not rebalance after multiple
-// dismissals",
-// (Math.abs(getTopRowTaskCountForTablet(launcher) - getBottomRowTaskCountForTablet(
-// launcher)) <= 1)));
+// executeOnRecentsViewContainer(container -> assertTrue(
+// "Grid did not rebalance after multiple dismissals",
+// (Math.abs(getTopRowTaskCountForTablet(container)
+// - getBottomRowTaskCountForTablet(container)) <= 1)));
// Test dismissing all tasks.
mLauncher.goHome().switchToOverview().dismissAllTasks();
- assertTrue("Launcher internal state is not Home",
- isInState(() -> LauncherState.NORMAL));
- executeOnLauncher(
- launcher -> assertEquals("Still have tasks after dismissing all",
- 0, getTaskCount(launcher)));
+ assertIsInState("Launcher internal state is not Home", ExpectedState.HOME);
+ executeOnRecentsViewContainer(
+ container -> assertEquals("Still have tasks after dismissing all",
+ 0, getTaskCount(container)));
}
@Test
@@ -497,22 +513,19 @@
startTestAppsWithCheck();
Overview overview = mLauncher.goHome().switchToOverview();
- assertTrue("Launcher internal state should be Overview",
- isInState(() -> LauncherState.OVERVIEW));
- executeOnLauncher(
- launcher -> assertTrue("Should have at least 3 tasks",
- getTaskCount(launcher) >= 3));
+ assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW);
+ executeOnRecentsViewContainer(
+ container -> assertTrue("Should have at least 3 tasks",
+ getTaskCount(container) >= 3));
// It should not dismiss overview when tapping between tasks
overview.touchBetweenTasks();
overview = mLauncher.getOverview();
- assertTrue("Launcher internal state should be Overview",
- isInState(() -> LauncherState.OVERVIEW));
+ assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW);
// Dismiss when tapping to the right of the focused task
overview.touchOutsideFirstTask();
- assertTrue("Launcher internal state should be Home",
- isInState(() -> LauncherState.NORMAL));
+ assertIsInState("Launcher internal state should be Home", ExpectedState.HOME);
}
@Test
@@ -524,34 +537,29 @@
startTestAppsWithCheck();
Overview overview = mLauncher.goHome().switchToOverview();
- assertTrue("Launcher internal state should be Overview",
- isInState(() -> LauncherState.OVERVIEW));
- executeOnLauncher(
- launcher -> assertTrue("Should have at least 3 tasks",
- getTaskCount(launcher) >= 3));
+ assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW);
+ executeOnRecentsViewContainer(
+ container -> assertTrue("Should have at least 3 tasks",
+ getTaskCount(container) >= 3));
if (mLauncher.isTransientTaskbar()) {
// On transient taskbar, it should dismiss when tapping outside taskbar bounds.
overview.touchTaskbarBottomCorner(/* tapRight= */ false);
- assertTrue("Launcher internal state should be Normal",
- isInState(() -> LauncherState.NORMAL));
+ assertIsInState("Launcher internal state should be Normal", ExpectedState.HOME);
overview = mLauncher.getWorkspace().switchToOverview();
// On transient taskbar, it should dismiss when tapping outside taskbar bounds.
overview.touchTaskbarBottomCorner(/* tapRight= */ true);
- assertTrue("Launcher internal state should be Normal",
- isInState(() -> LauncherState.NORMAL));
+ assertIsInState("Launcher internal state should be Normal", ExpectedState.HOME);
} else {
// On persistent taskbar, it should not dismiss when tapping the taskbar
overview.touchTaskbarBottomCorner(/* tapRight= */ false);
- assertTrue("Launcher internal state should be Overview",
- isInState(() -> LauncherState.OVERVIEW));
+ assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW);
// On persistent taskbar, it should not dismiss when tapping the taskbar
overview.touchTaskbarBottomCorner(/* tapRight= */ true);
- assertTrue("Launcher internal state should be Overview",
- isInState(() -> LauncherState.OVERVIEW));
+ assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW);
}
}
@@ -594,4 +602,28 @@
// Presumably the test started with 0 tasks and remains that way after going home.
}
}
+
+ private void assertIsInState(
+ @NonNull String failureMessage, @NonNull ExpectedState expectedState) {
+ assertTrue(failureMessage, enableLauncherOverviewInWindow()
+ ? isInRecentsWindowState(() -> expectedState.mRecentsState)
+ : isInState(() -> expectedState.mLauncherState));
+ }
+
+ private void waitForState(
+ @NonNull String failureMessage, @NonNull ExpectedState expectedState) {
+ if (enableLauncherOverviewInWindow()) {
+ waitForRecentsWindowState(failureMessage, () -> expectedState.mRecentsState);
+ } else {
+ waitForState(failureMessage, () -> expectedState.mLauncherState);
+ }
+ }
+
+ private void executeOnRecentsViewContainer(@NonNull Consumer<RecentsViewContainer> f) {
+ if (enableLauncherOverviewInWindow()) {
+ executeOnRecentsWindow(f::accept);
+ } else {
+ executeOnLauncher(f::accept);
+ }
+ }
}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index f1274dc..5387815 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -33,7 +33,6 @@
import static com.android.launcher3.testing.shared.ResourceUtils.pxFromDp;
import static com.android.launcher3.testing.shared.ResourceUtils.roundPxValueFromFloat;
import static com.android.wm.shell.Flags.enableBubbleBar;
-import static com.android.wm.shell.Flags.enableBubbleBarInPersistentTaskBar;
import static com.android.wm.shell.Flags.enableTinyTaskbar;
import android.annotation.SuppressLint;
@@ -2433,7 +2432,6 @@
*/
public boolean shouldAdjustHotseatOnNavBarLocationUpdate(Context context) {
return enableBubbleBar()
- && enableBubbleBarInPersistentTaskBar()
&& !DisplayController.getNavigationMode(context).hasGestures;
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index cb021c7..24d854c 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -2298,7 +2298,8 @@
if (item.container == CONTAINER_DESKTOP) {
CellLayout cl = mWorkspace.getScreenWithId(presenterPos.screenId);
if (cl != null && cl.isOccupied(presenterPos.cellX, presenterPos.cellY)) {
- Object tag = cl.getChildAt(presenterPos.cellX, presenterPos.cellY).getTag();
+ View occupiedView = cl.getChildAt(presenterPos.cellX, presenterPos.cellY);
+ Object tag = occupiedView == null ? null : occupiedView.getTag();
String desc = "Collision while binding workspace item: " + item
+ ". Collides with " + tag;
if (FeatureFlags.IS_STUDIO_BUILD) {
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 0ec3b79..b05a46d 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -903,12 +903,14 @@
@Override
public void onViewAdded(View child) {
super.onViewAdded(child);
+ mPageScrolls = null;
dispatchPageCountChanged();
}
@Override
public void onViewRemoved(View child) {
super.onViewRemoved(child);
+ mPageScrolls = null;
runOnPageScrollsInitialized(() -> {
mCurrentPage = validateNewPage(mCurrentPage);
mCurrentScrollOverPage = mCurrentPage;
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index a24f3ff..a826b9e 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -69,7 +69,9 @@
public class DragLayer extends BaseDragLayer<Launcher> implements LauncherOverlayCallbacks {
public static final int ALPHA_INDEX_OVERLAY = 0;
- private static final int ALPHA_CHANNEL_COUNT = 1;
+
+ public static final int ALPHA_INDEX_LOADER = 1;
+ private static final int ALPHA_CHANNEL_COUNT = 2;
public static final int ANIMATION_END_DISAPPEAR = 0;
public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index dbab52a..6eb02ab 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -301,6 +301,12 @@
@UiEvent(doc = "User swipes or fling in RIGHT direction on the bottom bazel area.")
LAUNCHER_QUICKSWITCH_RIGHT(572),
+ @UiEvent(doc = "User swipes or fling on the bottom bazel area to enter Desktop mode.")
+ LAUNCHER_QUICKSWITCH_ENTER_DESKTOP_MODE(2025),
+
+ @UiEvent(doc = "User swipes or fling on the bottom bazel area to exit Desktop mode.")
+ LAUNCHER_QUICKSWITCH_EXIT_DESKTOP_MODE(2026),
+
@UiEvent(doc = "User swipes or fling in DOWN direction on the bottom bazel area.")
LAUNCHER_SWIPEDOWN_NAVBAR(573),
diff --git a/src/com/android/launcher3/model/data/AppPairInfo.kt b/src/com/android/launcher3/model/data/AppPairInfo.kt
index 3496c17..82eda36 100644
--- a/src/com/android/launcher3/model/data/AppPairInfo.kt
+++ b/src/com/android/launcher3/model/data/AppPairInfo.kt
@@ -32,9 +32,8 @@
}
/** Convenience constructor, calls primary constructor and init block */
- constructor(app1: WorkspaceItemInfo, app2: WorkspaceItemInfo) : this() {
- add(app1)
- add(app2)
+ constructor(apps: List<WorkspaceItemInfo>) : this() {
+ apps.forEach(this::add)
}
/** Creates a new AppPairInfo that is a copy of the provided one. */
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 150806a..d850fc6 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -24,6 +24,7 @@
import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
import static com.android.launcher3.views.RecyclerViewFastScroller.FastScrollerLocation.WIDGET_SCROLLER;
+import static java.lang.Math.abs;
import static java.util.Collections.emptyList;
import android.animation.Animator;
@@ -41,6 +42,7 @@
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.WindowInsets;
@@ -119,6 +121,10 @@
protected int mRecommendationsCurrentPage = 0;
protected final SparseArray<AdapterHolder> mAdapters = new SparseArray();
+ // Helps with removing focus from searchbar by analyzing motion events.
+ private final SearchClearFocusHelper mSearchClearFocusHelper = new SearchClearFocusHelper();
+ private final float mTouchSlop; // initialized in constructor
+
private final OnAttachStateChangeListener mBindScrollbarInSearchMode =
new OnAttachStateChangeListener() {
@Override
@@ -165,6 +171,7 @@
public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mDeviceProfile = mActivityContext.getDeviceProfile();
mUserCache = UserCache.INSTANCE.get(context);
mHasWorkProfile = mUserCache.getUserProfiles()
@@ -714,10 +721,14 @@
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mNoIntercept = shouldScroll(ev);
- if (mSearchBar.isSearchBarFocused()
- && !getPopupContainer().isEventOverView(mSearchBarContainer, ev)) {
- mSearchBar.clearSearchBarFocus();
- }
+ }
+
+ // Clear focus only if user touched outside of search area and handling focus out ourselves
+ // was necessary (e.g. when it's not predictive back, but other user interaction).
+ if (mSearchBar.isSearchBarFocused()
+ && !getPopupContainer().isEventOverView(mSearchBarContainer, ev)
+ && mSearchClearFocusHelper.shouldClearFocus(ev, mTouchSlop)) {
+ mSearchBar.clearSearchBarFocus();
}
return super.onControllerInterceptTouchEvent(ev);
@@ -1141,4 +1152,53 @@
mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow(mMaxSpanPerRow);
}
}
+
+ /**
+ * Helper to identify if searchbar's focus can be cleared when user performs an action
+ * outside search.
+ */
+ private static class SearchClearFocusHelper {
+ private float mFirstInteractionX = -1f;
+ private float mFirstInteractionY = -1f;
+
+ /**
+ * For a given [MotionEvent] indicates if we should clear focus from search (and hide IME).
+ */
+ boolean shouldClearFocus(MotionEvent ev, float touchSlop) {
+ int action = ev.getAction();
+ boolean clearFocus = false;
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ mFirstInteractionX = ev.getX();
+ mFirstInteractionY = ev.getY();
+ } else if (action == MotionEvent.ACTION_CANCEL) {
+ // This is when user performed a gesture e.g. predictive back
+ // We don't handle it ourselves and let IME handle the close.
+ mFirstInteractionY = -1;
+ mFirstInteractionX = -1;
+ } else if (action == MotionEvent.ACTION_UP) {
+ // Its clear that user action wasn't predictive back - but press / scroll etc. that
+ // should hide the keyboard.
+ clearFocus = true;
+ mFirstInteractionY = -1;
+ mFirstInteractionX = -1;
+ } else if (action == MotionEvent.ACTION_MOVE) {
+ // Sometimes, on move, we may not receive ACTION_UP, but if the move was within
+ // touch slop and we didn't know if its moved or cancelled, we can clear focus.
+ // Example case: Apps list is small and you do a little scroll on list - in such, we
+ // want to still hide the keyboard.
+ if (mFirstInteractionX != -1 && mFirstInteractionY != -1) {
+ float distY = abs(mFirstInteractionY - ev.getY());
+ float distX = abs(mFirstInteractionX - ev.getX());
+ if (distY >= touchSlop || distX >= touchSlop) {
+ clearFocus = true;
+ mFirstInteractionY = -1;
+ mFirstInteractionX = -1;
+ }
+ }
+ }
+
+ return clearFocus;
+ }
+ }
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java b/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java
index deb0ef3..a87a208 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java
@@ -21,6 +21,7 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.os.Bundle;
+import android.os.Process;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -86,6 +87,7 @@
public static AppWidgetProviderInfo createAppWidgetProviderInfo(ComponentName cn) {
ActivityInfo activityInfo = new ActivityInfo();
activityInfo.applicationInfo = new ApplicationInfo();
+ activityInfo.applicationInfo.uid = Process.myUid();
AppWidgetProviderInfo info = new AppWidgetProviderInfo();
info.providerInfo = activityInfo;
info.provider = cn;
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
index 8b6553f..ac67d2b 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
@@ -148,11 +148,12 @@
doAnswer(invocation -> widgetLabel).when(mIconCache).getTitleNoCache(any());
- AppWidgetProviderInfo providerInfo = WidgetUtils.createAppWidgetProviderInfo(ComponentName
- .createRelative(TEST_PACKAGE, widgetClassName));
+ AppWidgetProviderInfo providerInfo = WidgetUtils.createAppWidgetProviderInfo(
+ ComponentName.createRelative(TEST_PACKAGE, widgetClassName));
LauncherAppWidgetProviderInfo launcherAppWidgetProviderInfo =
- LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, providerInfo);
+ spy(LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, providerInfo));
+ doReturn(Process.myUserHandle()).when(launcherAppWidgetProviderInfo).getProfile();
launcherAppWidgetProviderInfo.spanX = 2;
launcherAppWidgetProviderInfo.spanY = 2;
launcherAppWidgetProviderInfo.label = widgetLabel;
diff --git a/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt b/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
index d9af07a..eec6eed 100644
--- a/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
@@ -21,7 +21,7 @@
import android.content.pm.ApplicationInfo
import android.content.pm.LauncherActivityInfo
import android.content.pm.LauncherApps
-import android.os.UserHandle
+import android.os.Process.myUserHandle
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -60,7 +60,7 @@
@get:Rule val setFlagsRule = SetFlagsRule()
- private val mUser = UserHandle(0)
+ private val mUser = myUserHandle()
private val mDataModel: BgDataModel = BgDataModel()
private val mLauncherModelHelper = LauncherModelHelper()
private val mContext: SandboxModelContext = spy(mLauncherModelHelper.sandboxContext)
diff --git a/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java b/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java
index a0f227e..d093bf7 100644
--- a/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java
+++ b/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java
@@ -24,7 +24,6 @@
import android.os.Process;
import androidx.annotation.Nullable;
-import androidx.test.InstrumentationRegistry;
import androidx.test.uiautomator.UiDevice;
import com.android.systemui.shared.system.PackageManagerWrapper;
@@ -92,8 +91,9 @@
* Grants the launcher permission to bind widgets.
*/
public static ShellCommandRule grantWidgetBind() {
- return new ShellCommandRule("appwidget grantbind --package "
- + InstrumentationRegistry.getTargetContext().getPackageName(), null);
+ return new ShellCommandRule(String.format("appwidget grantbind --package %s --user %d",
+ getInstrumentation().getTargetContext().getPackageName(),
+ Process.myUserHandle().getIdentifier()), null);
}
/**