Merge "[CD Taskbar] Force 3 Button Mode on External Display by Default" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index ed370ec..d4cea8d 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -559,6 +559,13 @@
}
flag {
+ name: "enable_launcher_visual_refresh"
+ namespace: "launcher"
+ description: "Adds refresh for font family, app longpress menu icons, and pagination dots"
+ bug: "395145453"
+}
+
+flag {
name: "restore_archived_shortcuts"
namespace: "launcher"
description: "Makes sure pre-archived pinned shortcuts also get restored"
@@ -608,3 +615,10 @@
description: "Enable Strict Mode for the Launcher app"
bug: "394651876"
}
+
+flag {
+ name: "extendible_theme_manager"
+ namespace: "launcher"
+ description: "Enables custom theme manager in Launcher"
+ bug: "381897614"
+}
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index f2f1ebd..05f0695 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -88,6 +88,9 @@
<dimen name="task_thumbnail_header_icon_size">18dp</dimen>
<dimen name="task_thumbnail_header_round_corner_radius">16dp</dimen>
+ <!-- How much a task being dragged for dismissal can undershoot the origin when dragged back to its start position. -->
+ <dimen name="task_dismiss_max_undershoot">25dp</dimen>
+
<dimen name="task_icon_cache_default_icon_size">72dp</dimen>
<item name="overview_modal_max_scale" format="float" type="dimen">1.1</item>
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index 23065b5..5afc5ed 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.taskbar;
+import static com.android.launcher3.Flags.enableAltTabKqsOnConnectedDisplays;
+
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.view.MotionEvent;
@@ -272,11 +274,26 @@
}
private void processLoadedTasksOnDesktop(List<GroupTask> tasks, Set<Integer> taskIdsToExclude) {
- // Find the single desktop task that contains a grouping of desktop tasks
- DesktopTask desktopTask = findDesktopTask(tasks);
+ // Find all desktop tasks.
+ List<DesktopTask> desktopTasks = tasks.stream()
+ .filter(t -> t instanceof DesktopTask)
+ .map(t -> (DesktopTask) t)
+ .toList();
- if (desktopTask != null) {
- mTasks = desktopTask.getTasks().stream()
+ // Apps on the connected displays seem to be in different Desktop tasks even with the
+ // multiple desktops flag disabled. So, until multiple desktops is implemented the following
+ // should help with team-fooding Alt+tab on connected displays. Post multiple desktop,
+ // further changes maybe required to support launching selected desktops.
+ if (enableAltTabKqsOnConnectedDisplays()) {
+ mTasks = desktopTasks.stream()
+ .flatMap(t -> t.getTasks().stream())
+ .map(SingleTask::new)
+ .filter(task -> !shouldExcludeTask(task, taskIdsToExclude))
+ .collect(Collectors.toList());
+
+ mNumHiddenTasks = Math.max(0, tasks.size() - desktopTasks.size());
+ } else if (!desktopTasks.isEmpty()) {
+ mTasks = desktopTasks.get(0).getTasks().stream()
.map(SingleTask::new)
.filter(task -> !shouldExcludeTask(task, taskIdsToExclude))
.collect(Collectors.toList());
@@ -289,14 +306,6 @@
}
}
- @Nullable
- private DesktopTask findDesktopTask(List<GroupTask> tasks) {
- return (DesktopTask) tasks.stream()
- .filter(t -> t instanceof DesktopTask)
- .findFirst()
- .orElse(null);
- }
-
void closeQuickSwitchView() {
closeQuickSwitchView(true);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 78c8e4b..8880abd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -24,6 +24,8 @@
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static android.window.SplashScreen.SPLASH_SCREEN_STYLE_UNDEFINED;
+import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE;
+
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
import static com.android.launcher3.AbstractFloatingView.TYPE_ON_BOARD_POPUP;
import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
@@ -2011,7 +2013,8 @@
return mControllers.uiController.isIconAlignedWithHotseat();
}
- @VisibleForTesting
+ // TODO(b/395061396): Remove `otherwise` when overview in widow is enabled.
+ @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
public TaskbarControllers getControllers() {
return mControllers;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index ea2dec1..66f19eb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -32,6 +32,7 @@
import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
import static com.android.quickstep.util.SystemActionConstants.ACTION_SHOW_TASKBAR;
import static com.android.quickstep.util.SystemActionConstants.SYSTEM_ACTION_ID_TASKBAR;
+import static com.android.window.flags.Flags.enableTaskbarConnectedDisplays;
import android.annotation.SuppressLint;
import android.app.PendingIntent;
@@ -70,7 +71,9 @@
import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.quickstep.AllAppsActionManager;
import com.android.quickstep.RecentsActivity;
+import com.android.quickstep.RecentsAnimationDeviceState;
import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.fallback.window.RecentsDisplayModel;
import com.android.quickstep.fallback.window.RecentsWindowManager;
import com.android.quickstep.util.ContextualSearchInvoker;
import com.android.quickstep.views.RecentsViewContainer;
@@ -82,6 +85,7 @@
import java.io.PrintWriter;
import java.util.StringJoiner;
+import java.util.function.LongConsumer;
/**
* Class to manage taskbar lifecycle
@@ -115,7 +119,10 @@
private static final Uri NAV_BAR_KIDS_MODE = Settings.Secure.getUriFor(
Settings.Secure.NAV_BAR_KIDS_MODE);
- private final Context mParentContext;
+ private final Context mBaseContext;
+ // TODO: Remove this during the connected displays lifecycle refactor.
+ private final Context mPrimaryWindowContext;
+ private WindowManager mPrimaryWindowManager;
private final TaskbarNavButtonController mDefaultNavButtonController;
private final ComponentCallbacks mDefaultComponentCallbacks;
@@ -182,6 +189,7 @@
new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, this::showTaskbarFromBroadcast);
private final AllAppsActionManager mAllAppsActionManager;
+ private final RecentsDisplayModel mRecentsDisplayModel;
private final Runnable mActivityOnDestroyCallback = new Runnable() {
@Override
@@ -238,37 +246,46 @@
}
};
+ private final LongConsumer mSysUiFlagChangeReceiver = this::onSystemUiFlagsChanged;
+
@SuppressLint("WrongConstant")
public TaskbarManager(
Context context,
AllAppsActionManager allAppsActionManager,
- TaskbarNavButtonCallbacks navCallbacks) {
- mParentContext = context;
- createWindowContext(context.getDisplayId());
+ TaskbarNavButtonCallbacks navCallbacks,
+ RecentsDisplayModel recentsDisplayModel) {
+ mBaseContext = context;
mAllAppsActionManager = allAppsActionManager;
+ mRecentsDisplayModel = recentsDisplayModel;
+ mPrimaryWindowContext = createWindowContext(getDefaultDisplayId());
if (enableTaskbarNoRecreate()) {
+ mPrimaryWindowManager = mPrimaryWindowContext.getSystemService(WindowManager.class);
createTaskbarRootLayout(getDefaultDisplayId());
}
mDefaultNavButtonController = createDefaultNavButtonController(context, navCallbacks);
mDefaultComponentCallbacks = createDefaultComponentCallbacks();
- SettingsCache.INSTANCE.get(getPrimaryWindowContext())
+ SettingsCache.INSTANCE.get(mPrimaryWindowContext)
.register(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
- SettingsCache.INSTANCE.get(getPrimaryWindowContext())
+ SettingsCache.INSTANCE.get(mPrimaryWindowContext)
.register(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
Log.d(TASKBAR_NOT_DESTROYED_TAG, "registering component callbacks from constructor.");
- getPrimaryWindowContext().registerComponentCallbacks(mDefaultComponentCallbacks);
- mShutdownReceiver.register(getPrimaryWindowContext(), Intent.ACTION_SHUTDOWN);
+ mPrimaryWindowContext.registerComponentCallbacks(mDefaultComponentCallbacks);
+ mShutdownReceiver.register(mPrimaryWindowContext, Intent.ACTION_SHUTDOWN);
UI_HELPER_EXECUTOR.execute(() -> {
mSharedState.taskbarSystemActionPendingIntent = PendingIntent.getBroadcast(
- getPrimaryWindowContext(),
+ mPrimaryWindowContext,
SYSTEM_ACTION_ID_TASKBAR,
new Intent(ACTION_SHOW_TASKBAR).setPackage(
- getPrimaryWindowContext().getPackageName()),
+ mPrimaryWindowContext.getPackageName()),
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
mTaskbarBroadcastReceiver.register(
- getPrimaryWindowContext(), RECEIVER_NOT_EXPORTED, ACTION_SHOW_TASKBAR);
+ mPrimaryWindowContext, RECEIVER_NOT_EXPORTED, ACTION_SHOW_TASKBAR);
});
+ RecentsAnimationDeviceState deviceState = RecentsAnimationDeviceState.INSTANCE.get(context);
+ onSystemUiFlagsChanged(deviceState.getSystemUiStateFlags());
+ deviceState.addSysUiFlagChangeCallback(mSysUiFlagChangeReceiver);
+
debugWhyTaskbarNotDestroyed("TaskbarManager created");
recreateTaskbar();
}
@@ -279,15 +296,15 @@
return new TaskbarNavButtonController(
context,
navCallbacks,
- SystemUiProxy.INSTANCE.get(getPrimaryWindowContext()),
+ SystemUiProxy.INSTANCE.get(mPrimaryWindowContext),
new Handler(),
- new ContextualSearchInvoker(getPrimaryWindowContext()));
+ new ContextualSearchInvoker(mPrimaryWindowContext));
}
private ComponentCallbacks createDefaultComponentCallbacks() {
return new ComponentCallbacks() {
private Configuration mOldConfig =
- getPrimaryWindowContext().getResources().getConfiguration();
+ mPrimaryWindowContext.getResources().getConfiguration();
@Override
public void onConfigurationChanged(Configuration newConfig) {
@@ -297,8 +314,8 @@
"TaskbarManager#mComponentCallbacks.onConfigurationChanged: " + newConfig);
// TODO: adapt this logic to be specific to different displays.
DeviceProfile dp = mUserUnlocked
- ? LauncherAppState.getIDP(getPrimaryWindowContext()).getDeviceProfile(
- getPrimaryWindowContext())
+ ? LauncherAppState.getIDP(mPrimaryWindowContext).getDeviceProfile(
+ mPrimaryWindowContext)
: null;
int configDiff = mOldConfig.diff(newConfig) & ~SKIP_RECREATE_CONFIG_CHANGES;
@@ -350,7 +367,6 @@
int displayId = mTaskbars.keyAt(i);
destroyTaskbarForDisplay(displayId);
removeTaskbarRootViewFromWindow(displayId);
- removeWindowContextFromMap(displayId);
}
}
@@ -417,7 +433,7 @@
*/
public void onUserUnlocked() {
mUserUnlocked = true;
- DisplayController.INSTANCE.get(getPrimaryWindowContext()).addChangeListener(
+ DisplayController.INSTANCE.get(mPrimaryWindowContext).addChangeListener(
mRecreationListener);
recreateTaskbar();
addTaskbarRootViewToWindow(getDefaultDisplayId());
@@ -481,12 +497,23 @@
return ql.getUnfoldTransitionProgressProvider();
}
} else {
- return SystemUiProxy.INSTANCE.get(
- getPrimaryWindowContext()).getUnfoldTransitionProvider();
+ return SystemUiProxy.INSTANCE.get(mPrimaryWindowContext).getUnfoldTransitionProvider();
}
return null;
}
+ /** Creates a {@link TaskbarUIController} to use with non default displays. */
+ private TaskbarUIController createTaskbarUIControllerForNonDefaultDisplay(int displayId) {
+ if (RecentsDisplayModel.enableOverviewInWindow()) {
+ RecentsViewContainer rvc = mRecentsDisplayModel.getRecentsWindowManager(displayId);
+ if (rvc != null) {
+ return createTaskbarUIControllerForRecentsViewContainer(rvc);
+ }
+ }
+
+ return new TaskbarUIController();
+ }
+
/**
* Creates a {@link TaskbarUIController} to use while the given StatefulActivity is active.
*/
@@ -543,7 +570,7 @@
+ " FLAG_HIDE_NAVBAR_WINDOW=" + ENABLE_TASKBAR_NAVBAR_UNIFICATION
+ " dp.isTaskbarPresent=" + (dp == null ? "null" : dp.isTaskbarPresent));
if (!isTaskbarEnabled || !isLargeScreenTaskbar) {
- SystemUiProxy.INSTANCE.get(getPrimaryWindowContext())
+ SystemUiProxy.INSTANCE.get(mPrimaryWindowContext)
.notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
if (!isTaskbarEnabled) {
return;
@@ -561,7 +588,11 @@
mSharedState.allAppsVisible = mSharedState.allAppsVisible && isLargeScreenTaskbar;
taskbar.init(mSharedState);
- if (mRecentsViewContainer != null) {
+ // Non default displays should not use LauncherTaskbarUIController as they shouldn't
+ // have access to the Launcher activity.
+ if (enableTaskbarConnectedDisplays() && !isDefaultDisplay(displayId)) {
+ taskbar.setUIController(createTaskbarUIControllerForNonDefaultDisplay(displayId));
+ } else if (mRecentsViewContainer != null) {
taskbar.setUIController(
createTaskbarUIControllerForRecentsViewContainer(mRecentsViewContainer));
}
@@ -583,7 +614,7 @@
}
}
- public void onSystemUiFlagsChanged(@SystemUiStateFlags long systemUiStateFlags) {
+ private void onSystemUiFlagsChanged(@SystemUiStateFlags long systemUiStateFlags) {
if (DEBUG) {
Log.d(TAG, "SysUI flags changed: " + formatFlagChange(systemUiStateFlags,
mSharedState.sysuiStateFlags, QuickStepContract::getSystemUiStateString));
@@ -719,6 +750,14 @@
* primary device or a previously mirroring display is switched to extended mode.
*/
public void onDisplayAddSystemDecorations(int displayId) {
+ if (isDefaultDisplay(displayId) || !enableTaskbarConnectedDisplays()) {
+ return;
+ }
+
+ Context newWindowContext = createWindowContext(displayId);
+ if (newWindowContext != null) {
+ addWindowContextToMap(displayId, newWindowContext);
+ }
}
/**
@@ -726,6 +765,14 @@
* removed from the primary device.
*/
public void onDisplayRemoved(int displayId) {
+ if (isDefaultDisplay(displayId) || !enableTaskbarConnectedDisplays()) {
+ return;
+ }
+
+ Context windowContext = getWindowContext(displayId);
+ if (windowContext != null) {
+ removeWindowContextFromMap(displayId);
+ }
}
/**
@@ -755,19 +802,21 @@
mRecentsViewContainer = null;
debugWhyTaskbarNotDestroyed("TaskbarManager#destroy()");
removeActivityCallbacksAndListeners();
- mTaskbarBroadcastReceiver.unregisterReceiverSafely(getPrimaryWindowContext());
+ mTaskbarBroadcastReceiver.unregisterReceiverSafely(mPrimaryWindowContext);
if (mUserUnlocked) {
- DisplayController.INSTANCE.get(getPrimaryWindowContext()).removeChangeListener(
+ DisplayController.INSTANCE.get(mPrimaryWindowContext).removeChangeListener(
mRecreationListener);
}
- SettingsCache.INSTANCE.get(getPrimaryWindowContext())
+ SettingsCache.INSTANCE.get(mPrimaryWindowContext)
.unregister(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
- SettingsCache.INSTANCE.get(getPrimaryWindowContext())
+ SettingsCache.INSTANCE.get(mPrimaryWindowContext)
.unregister(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
+ RecentsAnimationDeviceState.INSTANCE.get(getPrimaryWindowContext())
+ .removeSysUiFlagChangeCallback(mSysUiFlagChangeReceiver);
Log.d(TASKBAR_NOT_DESTROYED_TAG, "unregistering component callbacks from destroy().");
- getPrimaryWindowContext().unregisterComponentCallbacks(mDefaultComponentCallbacks);
- mShutdownReceiver.unregisterReceiverSafely(getPrimaryWindowContext());
+ mPrimaryWindowContext.unregisterComponentCallbacks(mDefaultComponentCallbacks);
+ mShutdownReceiver.unregisterReceiverSafely(mPrimaryWindowContext);
destroyAllTaskbars();
}
@@ -829,6 +878,23 @@
}
/**
+ * Returns the {@link TaskbarUIController} associated with the given display ID.
+ * TODO(b/395061396): Remove this method when overview in widow is enabled.
+ *
+ * @param displayId The ID of the display to retrieve the taskbar for.
+ * @return The {@link TaskbarUIController} for the specified display, or
+ * {@code null} if no taskbar is associated with that display.
+ */
+ @Nullable
+ public TaskbarUIController getUIControllerForDisplay(int displayId) {
+ if (!mTaskbars.contains(displayId)) {
+ return null;
+ }
+
+ return getTaskbarForDisplay(displayId).getControllers().uiController;
+ }
+
+ /**
* Retrieves whether RootLayout was added to window for specific display, or false if no
* such mapping has been made.
*
@@ -855,16 +921,16 @@
* Creates a {@link TaskbarActivityContext} for the given display and adds it to the map.
*/
private TaskbarActivityContext createTaskbarActivityContext(DeviceProfile dp, int displayId) {
- Display display = mParentContext.getSystemService(DisplayManager.class).getDisplay(
+ Display display = mBaseContext.getSystemService(DisplayManager.class).getDisplay(
displayId);
Context navigationBarPanelContext = ENABLE_TASKBAR_NAVBAR_UNIFICATION
- ? mParentContext.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null)
+ ? mBaseContext.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null)
: null;
TaskbarActivityContext newTaskbar = new TaskbarActivityContext(getWindowContext(displayId),
navigationBarPanelContext, dp, mDefaultNavButtonController,
mUnfoldProgressProvider, isDefaultDisplay(displayId),
- SystemUiProxy.INSTANCE.get(getPrimaryWindowContext()));
+ SystemUiProxy.INSTANCE.get(mPrimaryWindowContext));
addTaskbarToMap(displayId, newTaskbar);
return newTaskbar;
@@ -965,22 +1031,26 @@
}
/**
- * Creates {@link Context} for the taskbar on the specified display and›› adds it to map.
+ * Creates {@link Context} for the taskbar on the specified display.
* @param displayId The ID of the display for which to create the window context.
*/
- private void createWindowContext(int displayId) {
- DisplayManager displayManager = mParentContext.getSystemService(DisplayManager.class);
+ private @Nullable Context createWindowContext(int displayId) {
+ DisplayManager displayManager = mBaseContext.getSystemService(DisplayManager.class);
if (displayManager == null) {
- return;
+ return null;
}
Display display = displayManager.getDisplay(displayId);
- if (display != null) {
- int windowType = (ENABLE_TASKBAR_NAVBAR_UNIFICATION && isDefaultDisplay(displayId))
- ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL;
- Context newContext = mParentContext.createWindowContext(display, windowType, null);
- addWindowContextToMap(displayId, newContext);
+ if (display == null) {
+ return null;
}
+
+ int windowType = TYPE_NAVIGATION_BAR_PANEL;
+ if (ENABLE_TASKBAR_NAVBAR_UNIFICATION && isDefaultDisplay(displayId)) {
+ windowType = TYPE_NAVIGATION_BAR;
+ }
+
+ return mBaseContext.createWindowContext(display, windowType, null);
}
/**
@@ -990,12 +1060,13 @@
* @return The Window Context {@link Context} for a given display or {@code null}.
*/
private Context getWindowContext(int displayId) {
- return mWindowContexts.get(displayId);
+ return (isDefaultDisplay(displayId) || !enableTaskbarConnectedDisplays())
+ ? mPrimaryWindowContext : mWindowContexts.get(displayId);
}
@VisibleForTesting
public Context getPrimaryWindowContext() {
- return getWindowContext(getDefaultDisplayId());
+ return mPrimaryWindowContext;
}
/**
@@ -1004,8 +1075,17 @@
* @param displayId The ID of the display for which to retrieve the window manager.
* @return The window manager {@link WindowManager} for a given display or {@code null}.
*/
- private WindowManager getWindowManager(int displayId) {
- return getWindowContext(displayId).getSystemService(WindowManager.class);
+ private @Nullable WindowManager getWindowManager(int displayId) {
+ if (isDefaultDisplay(displayId) || !enableTaskbarConnectedDisplays()) {
+ return mPrimaryWindowManager;
+ }
+
+ Context externalDisplayContext = getWindowContext(displayId);
+ if (externalDisplayContext == null) {
+ return null;
+ }
+
+ return externalDisplayContext.getSystemService(WindowManager.class);
}
/**
@@ -1032,7 +1112,7 @@
}
private int getDefaultDisplayId() {
- return mParentContext.getDisplayId();
+ return mBaseContext.getDisplayId();
}
/** Temp logs for b/254119092. */
@@ -1076,7 +1156,7 @@
log.add("\t\tWindowContext.getResources().getConfiguration()="
+ windowContext.getResources().getConfiguration());
if (mUserUnlocked) {
- log.add("\t\tLauncherAppState.getIDP().getDeviceProfile(getPrimaryWindowContext())"
+ log.add("\t\tLauncherAppState.getIDP().getDeviceProfile(mPrimaryWindowContext)"
+ ".isTaskbarPresent=" + contextTaskbarPresent);
} else {
log.add("\t\tCouldn't get DeviceProfile because !mUserUnlocked");
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 23f4f67..6eec7d5 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -172,10 +172,12 @@
import com.android.quickstep.OverviewCommandHelper;
import com.android.quickstep.OverviewComponentObserver;
import com.android.quickstep.OverviewComponentObserver.OverviewChangeListener;
+import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.RecentsAnimationDeviceState.AssistantVisibilityChangeListener;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.TISBinder;
import com.android.quickstep.TaskUtils;
-import com.android.quickstep.TouchInteractionService.TISBinder;
import com.android.quickstep.util.ActiveGestureProtoLogProxy;
import com.android.quickstep.util.AsyncClockEventDelegate;
import com.android.quickstep.util.LauncherUnfoldAnimationController;
@@ -222,7 +224,7 @@
import java.util.stream.Stream;
public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
- SystemShortcut.BubbleActivityStarter {
+ SystemShortcut.BubbleActivityStarter, AssistantVisibilityChangeListener {
private static final boolean TRACE_LAYOUTS =
SystemProperties.getBoolean("persist.debug.trace_layouts", false);
private static final String TRACE_RELAYOUT_CLASS =
@@ -593,6 +595,8 @@
mHotseatPredictionController.destroy();
if (mViewCapture != null) mViewCapture.close();
removeBackAnimationCallback(mSplitSelectStateController.getSplitBackHandler());
+ RecentsAnimationDeviceState.INSTANCE.get(this)
+ .removeAssistantVisibilityChangeListener(this);
}
@Override
@@ -726,6 +730,7 @@
View.setTracedRequestLayoutClassClass(TRACE_RELAYOUT_CLASS);
OverviewComponentObserver.INSTANCE.get(this)
.addOverviewChangeListener(mOverviewChangeListener);
+ RecentsAnimationDeviceState.INSTANCE.get(this).addAssistantVisibilityChangeListener(this);
}
@Override
@@ -1085,7 +1090,6 @@
if (taskbarManager != null) {
taskbarManager.setActivity(this);
}
- mTISBindHelper.setPredictiveBackToHomeInProgress(mIsPredictiveBackToHomeInProgress);
}
@Override
@@ -1359,7 +1363,8 @@
*/
public void setPredictiveBackToHomeInProgress(boolean isInProgress) {
mIsPredictiveBackToHomeInProgress = isInProgress;
- mTISBindHelper.setPredictiveBackToHomeInProgress(isInProgress);
+ RecentsAnimationDeviceState.INSTANCE.get(this)
+ .setPredictiveBackToHomeInProgress(isInProgress);
}
public boolean getPredictiveBackToHomeInProgress() {
@@ -1501,6 +1506,11 @@
return getStateManager().getState().isRecentsViewVisible;
}
+ @Override
+ public void onAssistantVisibilityChanged(float visibility) {
+ mHotseat.getQsb().setAlpha(1f - visibility);
+ }
+
public boolean isCanShowAllAppsEducationView() {
return mCanShowAllAppsEducationView;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index ff726e6..865e16b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -28,8 +28,6 @@
import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
import static com.android.launcher3.util.NavigationMode.THREE_BUTTONS;
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
@@ -47,7 +45,7 @@
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.VibratorWrapper;
-import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.RecentsAnimationDeviceState;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.OverviewToHomeAnim;
@@ -113,8 +111,8 @@
return false;
}
mDidTouchStartInNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
- boolean isOneHandedModeActive = (SystemUiProxy.INSTANCE.get(mLauncher)
- .getLastSystemUiStateFlags() & SYSUI_STATE_ONE_HANDED_ACTIVE) != 0;
+ boolean isOneHandedModeActive =
+ RecentsAnimationDeviceState.INSTANCE.get(mLauncher).isOneHandedModeActive();
// Reset touch slop multiplier to default 1.0f if one-handed-mode is not active
mDetector.setTouchSlopMultiplier(
isOneHandedModeActive ? ONE_HANDED_ACTIVATED_SLOP_MULTIPLIER : 1f /* default */);
@@ -250,9 +248,8 @@
}
private boolean handlingOverviewAnim() {
- long stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
return mDidTouchStartInNavBar && mStartState == NORMAL
- && (stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0;
+ && !RecentsAnimationDeviceState.INSTANCE.get(mLauncher).isOverviewDisabled();
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 05d12c3..6bca4c7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -55,7 +55,6 @@
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
import static com.android.quickstep.views.RecentsView.TASK_THUMBNAIL_SPLASH_ALPHA;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
@@ -79,7 +78,7 @@
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.util.VibratorWrapper;
-import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.RecentsAnimationDeviceState;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.MotionPauseDetector;
@@ -178,8 +177,7 @@
if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) == 0) {
return false;
}
- long stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
- if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) {
+ if (RecentsAnimationDeviceState.INSTANCE.get(mLauncher).isOverviewDisabled()) {
return false;
}
if (isTrackpadMultiFingerSwipe(ev)) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index f582324..7547f15 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -34,7 +34,6 @@
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import android.view.MotionEvent;
@@ -46,7 +45,7 @@
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.NavigationMode;
-import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.RecentsAnimationDeviceState;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -84,8 +83,7 @@
@Override
protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
- long stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
- if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) {
+ if (RecentsAnimationDeviceState.INSTANCE.get(mLauncher).isOverviewDisabled()) {
return NORMAL;
}
return isDragTowardPositive ? QUICK_SWITCH_FROM_HOME : NORMAL;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
index 99b962b..77a05c1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
@@ -20,6 +20,7 @@
import androidx.dynamicanimation.animation.SpringAnimation
import com.android.app.animation.Interpolators.DECELERATE
import com.android.launcher3.AbstractFloatingView
+import com.android.launcher3.R
import com.android.launcher3.Utilities.EDGE_NAV_BAR
import com.android.launcher3.Utilities.boundToRange
import com.android.launcher3.Utilities.isRtl
@@ -144,7 +145,7 @@
0f,
dismissLength.toFloat(),
0f,
- DISMISS_MAX_UNDERSHOOT,
+ container.resources.getDimension(R.dimen.task_dismiss_max_undershoot),
DECELERATE,
)
taskBeingDragged.secondaryDismissTranslationProperty.setValue(
@@ -207,6 +208,5 @@
companion object {
private const val DISMISS_THRESHOLD_FRACTION = 0.5f
- private const val DISMISS_MAX_UNDERSHOOT = 25f
}
}
diff --git a/quickstep/src/com/android/quickstep/BaseContainerInterface.java b/quickstep/src/com/android/quickstep/BaseContainerInterface.java
index 6d588d9..82fbaa6 100644
--- a/quickstep/src/com/android/quickstep/BaseContainerInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseContainerInterface.java
@@ -83,8 +83,6 @@
public abstract boolean isInLiveTileMode();
- public abstract void onAssistantVisibilityChanged(float assistantVisibility);
-
public abstract boolean isResumed();
public abstract boolean isStarted();
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index d8e0296..d122d24 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -69,14 +69,6 @@
}
}
- /** 5 */
- @Override
- public void onAssistantVisibilityChanged(float visibility) {
- // This class becomes active when the screen is locked.
- // Rather than having it handle assistant visibility changes, the assistant visibility is
- // set to zero prior to this class becoming active.
- }
-
/** 6 */
@Override
public AnimationFactory prepareRecentsUI(
diff --git a/quickstep/src/com/android/quickstep/FallbackWindowInterface.java b/quickstep/src/com/android/quickstep/FallbackWindowInterface.java
index 35630ef..33fe02e 100644
--- a/quickstep/src/com/android/quickstep/FallbackWindowInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackWindowInterface.java
@@ -70,14 +70,6 @@
}
}
- /** 5 */
- @Override
- public void onAssistantVisibilityChanged(float visibility) {
- // This class becomes active when the screen is locked.
- // Rather than having it handle assistant visibility changes, the assistant visibility is
- // set to zero prior to this class becoming active.
- }
-
/** 6 */
@Override
public BaseWindowInterface.AnimationFactory prepareRecentsUI(boolean activityVisible,
diff --git a/quickstep/src/com/android/quickstep/FocusState.kt b/quickstep/src/com/android/quickstep/FocusState.kt
index ba3991f..7c6aa5b 100644
--- a/quickstep/src/com/android/quickstep/FocusState.kt
+++ b/quickstep/src/com/android/quickstep/FocusState.kt
@@ -27,7 +27,10 @@
class FocusState {
var focusedDisplayId = DEFAULT_DISPLAY
- private set
+ private set(value) {
+ field = value
+ listeners.forEach { it.onFocusedDisplayChanged(value) }
+ }
private var listeners = mutableSetOf<FocusChangeListener>()
@@ -40,9 +43,7 @@
transitions?.setFocusTransitionListener(
object : Stub() {
override fun onFocusedDisplayChanged(displayId: Int) {
- Executors.MAIN_EXECUTOR.execute {
- listeners.forEach { it.onFocusedDisplayChanged(displayId) }
- }
+ Executors.MAIN_EXECUTOR.execute { focusedDisplayId = displayId }
}
}
)
diff --git a/quickstep/src/com/android/quickstep/InputConsumerUtils.kt b/quickstep/src/com/android/quickstep/InputConsumerUtils.kt
index c340c92..1a82d3b 100644
--- a/quickstep/src/com/android/quickstep/InputConsumerUtils.kt
+++ b/quickstep/src/com/android/quickstep/InputConsumerUtils.kt
@@ -18,7 +18,6 @@
import android.content.Context
import android.view.MotionEvent
import androidx.annotation.VisibleForTesting
-import com.android.launcher3.anim.AnimatedFloat
import com.android.launcher3.statemanager.BaseState
import com.android.launcher3.statemanager.StatefulContainer
import com.android.launcher3.taskbar.TaskbarManager
@@ -47,7 +46,6 @@
import com.android.systemui.shared.system.InputMonitorCompat
import com.android.wm.shell.Flags
import java.util.function.Consumer
-import java.util.function.Function
/** Utility class for creating input consumers. */
object InputConsumerUtils {
@@ -68,7 +66,6 @@
onCompleteCallback: Consumer<OtherActivityInputConsumer>,
inputEventReceiver: InputChannelCompat.InputEventReceiver,
taskbarManager: TaskbarManager,
- swipeUpProxyProvider: Function<GestureState?, AnimatedFloat?>,
overviewCommandHelper: OverviewCommandHelper,
event: MotionEvent,
): InputConsumer where T : RecentsViewContainer, T : StatefulContainer<S> {
@@ -83,7 +80,7 @@
)
return consumer
}
- val progressProxy = swipeUpProxyProvider.apply(gestureState)
+ val progressProxy = deviceState.getSwipeUpProxy(gestureState)
if (progressProxy != null) {
val consumer: InputConsumer =
ProgressDelegateInputConsumer(
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index ac0aa76..2c37470 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -97,15 +97,6 @@
}
@Override
- public void onAssistantVisibilityChanged(float visibility) {
- QuickstepLauncher launcher = getCreatedContainer();
- if (launcher == null) {
- return;
- }
- launcher.onAssistantVisibilityChanged(visibility);
- }
-
- @Override
public AnimationFactory prepareRecentsUI(
boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback) {
notifyRecentsOfOrientation();
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
index 94d115b..42aa86e 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
@@ -28,6 +28,7 @@
import androidx.annotation.UiThread
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.Cuj
+import com.android.launcher3.Flags.enableAltTabKqsOnConnectedDisplays
import com.android.launcher3.Flags.enableFallbackOverviewInWindow
import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
import com.android.launcher3.Flags.enableLauncherOverviewInWindow
@@ -38,6 +39,8 @@
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_3_BUTTON
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_QUICK_SWITCH
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_SHORTCUT
+import com.android.launcher3.taskbar.TaskbarManager
+import com.android.launcher3.taskbar.TaskbarUIController
import com.android.launcher3.util.Executors
import com.android.launcher3.util.RunnableList
import com.android.launcher3.util.coroutines.DispatcherProvider
@@ -48,6 +51,7 @@
import com.android.quickstep.OverviewCommandHelper.CommandType.KEYBOARD_INPUT
import com.android.quickstep.OverviewCommandHelper.CommandType.SHOW
import com.android.quickstep.OverviewCommandHelper.CommandType.TOGGLE
+import com.android.quickstep.fallback.window.RecentsDisplayModel
import com.android.quickstep.util.ActiveGestureLog
import com.android.quickstep.util.ActiveGestureProtoLogProxy
import com.android.quickstep.views.RecentsView
@@ -72,6 +76,9 @@
private val overviewComponentObserver: OverviewComponentObserver,
private val taskAnimationManager: TaskAnimationManager,
private val dispatcherProvider: DispatcherProvider = ProductionDispatchers,
+ private val recentsDisplayModel: RecentsDisplayModel,
+ private val focusState: FocusState,
+ private val taskbarManager: TaskbarManager,
) {
private val coroutineScope = CoroutineScope(SupervisorJob() + dispatcherProvider.background)
@@ -291,15 +298,51 @@
deviceProfile != null &&
(deviceProfile.isTablet || deviceProfile.isTwoPanels)
+ val focusedDisplayId = focusState.focusedDisplayId
+ val focusedDisplayUIController: TaskbarUIController? =
+ if (RecentsDisplayModel.enableOverviewInWindow()) {
+ Log.d(
+ TAG,
+ "Querying RecentsDisplayModel for TaskbarUIController for display: $focusedDisplayId",
+ )
+ recentsDisplayModel.getRecentsWindowManager(focusedDisplayId)?.taskbarUIController
+ } else {
+ Log.d(
+ TAG,
+ "Querying TaskbarManager for TaskbarUIController for display: $focusedDisplayId",
+ )
+ // TODO(b/395061396): Remove this path when overview in widow is enabled.
+ taskbarManager.getUIControllerForDisplay(focusedDisplayId)
+ }
+ Log.d(
+ TAG,
+ "TaskbarUIController for display $focusedDisplayId was" +
+ "${if (focusedDisplayUIController == null) " not" else ""} found",
+ )
+
when (command.type) {
HIDE -> {
if (!allowQuickSwitch) return true
- keyboardTaskFocusIndex = uiController!!.launchFocusedTask()
+ keyboardTaskFocusIndex =
+ if (
+ enableAltTabKqsOnConnectedDisplays() && focusedDisplayUIController != null
+ ) {
+ focusedDisplayUIController.launchFocusedTask()
+ } else {
+ uiController!!.launchFocusedTask()
+ }
+
if (keyboardTaskFocusIndex == -1) return true
}
KEYBOARD_INPUT ->
if (allowQuickSwitch) {
- uiController!!.openQuickSwitchView()
+ if (
+ enableAltTabKqsOnConnectedDisplays() && focusedDisplayUIController != null
+ ) {
+ focusedDisplayUIController.openQuickSwitchView()
+ } else {
+ uiController!!.openQuickSwitchView()
+ }
return true
} else {
keyboardTaskFocusIndex = 0
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 1f95c41..a34b239 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -57,6 +57,7 @@
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.LongConsumer;
import javax.inject.Inject;
@@ -79,6 +80,7 @@
private final Context mContext;
private final RecentsDisplayModel mRecentsDisplayModel;
+ private final RecentsAnimationDeviceState mDeviceState;
private final Intent mCurrentHomeIntent;
private final Intent mMyHomeIntent;
@@ -100,9 +102,11 @@
public OverviewComponentObserver(
@ApplicationContext Context context,
RecentsDisplayModel recentsDisplayModel,
+ RecentsAnimationDeviceState deviceState,
DaggerSingletonTracker lifecycleTracker) {
mContext = context;
mRecentsDisplayModel = recentsDisplayModel;
+ mDeviceState = deviceState;
mCurrentHomeIntent = createHomeIntent();
mMyHomeIntent = new Intent(mCurrentHomeIntent).setPackage(mContext.getPackageName());
ResolveInfo info = context.getPackageManager().resolveActivity(mMyHomeIntent, 0);
@@ -128,6 +132,12 @@
updateOverviewTargets();
lifecycleTracker.addCloseable(this::onDestroy);
+
+ setHomeDisabled(deviceState.isHomeDisabled());
+ LongConsumer flagChangeCallback = l -> setHomeDisabled(deviceState.isHomeDisabled());
+ deviceState.addSysUiFlagChangeCallback(flagChangeCallback);
+ lifecycleTracker.addCloseable(
+ () -> deviceState.removeSysUiFlagChangeCallback(flagChangeCallback));
}
/** Adds a listener for changes in {@link #isHomeAndOverviewSame()} */
@@ -140,11 +150,7 @@
mOverviewChangeListeners.remove(overviewChangeListener);
}
- /**
- * Called to set home enabled/disabled state via systemUI
- * @param isHomeDisabled
- */
- public void setHomeDisabled(boolean isHomeDisabled) {
+ private void setHomeDisabled(boolean isHomeDisabled) {
if (isHomeDisabled != mIsHomeDisabled) {
mIsHomeDisabled = isHomeDisabled;
updateOverviewTargets();
@@ -175,9 +181,7 @@
// Set assistant visibility to 0 from launcher's perspective, ensures any elements that
// launcher made invisible become visible again before the new activity control helper
// becomes active.
- if (mContainerInterface != null) {
- mContainerInterface.onAssistantVisibilityChanged(0.f);
- }
+ mDeviceState.setAssistantVisibility(0f);
if (SEPARATE_RECENTS_ACTIVITY.get() || Flags.enableLauncherOverviewInWindow()) {
mIsDefaultHome = false;
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 9b0e75c..90221f3 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -172,7 +172,7 @@
return response;
case TestProtocol.REQUEST_REFRESH_OVERVIEW_TARGET:
- runOnTISBinder(TouchInteractionService.TISBinder::refreshOverviewTarget);
+ runOnTISBinder(TISBinder::refreshOverviewTarget);
return response;
case TestProtocol.REQUEST_RECREATE_TASKBAR:
@@ -220,7 +220,7 @@
}
private void enableBlockingTimeout(
- TouchInteractionService.TISBinder tisBinder, boolean enable) {
+ TISBinder tisBinder, boolean enable) {
TaskbarActivityContext context = tisBinder.getTaskbarManager().getCurrentActivityContext();
if (context == null) {
return;
@@ -236,7 +236,7 @@
* Runs the given command on the UI thread, after ensuring we are connected to
* TouchInteractionService.
*/
- protected void runOnTISBinder(Consumer<TouchInteractionService.TISBinder> connectionCallback) {
+ protected void runOnTISBinder(Consumer<TISBinder> connectionCallback) {
try {
CountDownLatch countDownLatch = new CountDownLatch(1);
TISBindHelper helper = MAIN_EXECUTOR.submit(() ->
@@ -252,7 +252,7 @@
}
private <T> Bundle getTISBinderUIProperty(
- BundleSetter<T> bundleSetter, Function<TouchInteractionService.TISBinder, T> provider) {
+ BundleSetter<T> bundleSetter, Function<TISBinder, T> provider) {
Bundle response = new Bundle();
runOnTISBinder(tisBinder -> bundleSetter.set(
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index fca67c3..760130e 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -165,7 +165,7 @@
mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
}
- private void onTISConnected(TouchInteractionService.TISBinder binder) {
+ private void onTISConnected(TISBinder binder) {
TaskbarManager taskbarManager = binder.getTaskbarManager();
if (taskbarManager != null) {
taskbarManager.setActivity(this);
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 090ccdc..322e7c4 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -64,6 +64,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.dagger.ApplicationContext;
import com.android.launcher3.dagger.LauncherAppComponent;
import com.android.launcher3.dagger.LauncherAppSingleton;
@@ -88,6 +89,10 @@
import com.android.systemui.shared.system.TaskStackChangeListeners;
import java.io.PrintWriter;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Function;
+import java.util.function.LongConsumer;
import javax.inject.Inject;
@@ -118,12 +123,16 @@
private final boolean mCanImeRenderGesturalNavButtons =
InputMethodService.canImeRenderGesturalNavButtons();
+ private final List<LongConsumer> mFlagChangeCallbacks = new CopyOnWriteArrayList<>();
private @SystemUiStateFlags long mSystemUiStateFlags = QuickStepContract.SYSUI_STATE_AWAKE;
private NavigationMode mMode = THREE_BUTTONS;
private NavBarPosition mNavBarPosition;
private final Region mDeferredGestureRegion = new Region();
private boolean mAssistantAvailable;
+
+ private final List<AssistantVisibilityChangeListener> mAssistantVisibilityChangeListeners =
+ new CopyOnWriteArrayList<>();
private float mAssistantVisibility;
private boolean mIsUserSetupComplete;
private boolean mIsOneHandedModeEnabled;
@@ -136,6 +145,8 @@
private @NonNull Region mExclusionRegion = GestureExclusionManager.EMPTY_REGION;
private boolean mExclusionListenerRegistered;
+ private Function<GestureState, AnimatedFloat> mSwipeUpProxyProvider = null;
+
@VisibleForTesting
@Inject
RecentsAnimationDeviceState(
@@ -355,8 +366,9 @@
/**
* Updates the system ui state flags from SystemUI.
*/
- public void setSystemUiFlags(@SystemUiStateFlags long stateFlags) {
+ public void setSystemUiStateFlags(@SystemUiStateFlags long stateFlags) {
mSystemUiStateFlags = stateFlags;
+ mFlagChangeCallbacks.forEach(c -> c.accept(stateFlags));
}
/**
@@ -369,6 +381,20 @@
}
/**
+ * Adds a callback to receiver sysui flag changes
+ */
+ public void addSysUiFlagChangeCallback(LongConsumer callback) {
+ mFlagChangeCallbacks.add(callback);
+ }
+
+ /**
+ * Removes a previously added sysui flag change callback
+ */
+ public void removeSysUiFlagChangeCallback(LongConsumer callback) {
+ mFlagChangeCallbacks.remove(callback);
+ }
+
+ /**
* Sets the flag that indicates whether a predictive back-to-home animation is in progress
*/
public void setPredictiveBackToHomeInProgress(boolean isInProgress) {
@@ -383,6 +409,22 @@
}
/**
+ * Sets or clears a function to proxy swipe up transition behavior
+ */
+ public void setSwipeUpProxyProvider(
+ @Nullable Function<GestureState, AnimatedFloat> swipeUpProxyProvider) {
+ mSwipeUpProxyProvider = swipeUpProxyProvider;
+ }
+
+ /**
+ * Returns a proxy animation for swipe up transition if a proxy function was previously set
+ */
+ public AnimatedFloat getSwipeUpProxy(GestureState state) {
+ Function<GestureState, AnimatedFloat> provider = mSwipeUpProxyProvider;
+ return provider != null ? provider.apply(state) : null;
+ }
+
+ /**
* @return whether SystemUI is in a state where we can start a system gesture.
*/
public boolean canStartSystemGesture() {
@@ -535,6 +577,8 @@
*/
public void setAssistantVisibility(float visibility) {
mAssistantVisibility = visibility;
+ mAssistantVisibilityChangeListeners.forEach(
+ l -> l.onAssistantVisibilityChanged(visibility));
}
/**
@@ -544,6 +588,16 @@
return mAssistantVisibility;
}
+ /** Add a listener for assistant visibility changes */
+ public void addAssistantVisibilityChangeListener(AssistantVisibilityChangeListener l) {
+ mAssistantVisibilityChangeListeners.add(l);
+ }
+
+ /** Removes a previously added visibility change listener */
+ public void removeAssistantVisibilityChangeListener(AssistantVisibilityChangeListener l) {
+ mAssistantVisibilityChangeListeners.remove(l);
+ }
+
/**
* @return whether the Assistant gesture can be used in 3 button navigation mode.
*/
@@ -633,6 +687,13 @@
return QuickStepContract.getSystemUiStateString(mSystemUiStateFlags);
}
+ /** Interface for listening assistant visibility change */
+ @FunctionalInterface
+ public interface AssistantVisibilityChangeListener {
+ /** Called when assistant visibility changes */
+ void onAssistantVisibilityChanged(float visibility);
+ }
+
public void dump(PrintWriter pw) {
pw.println("DeviceState:");
pw.println(" canStartSystemGesture=" + canStartSystemGesture());
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 87b58e6..1d83d42 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -38,13 +38,18 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.graphics.ThemeManager;
import com.android.launcher3.graphics.ThemeManager.ThemeChangeListener;
import com.android.launcher3.icons.IconProvider;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
+import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.Executors.SimpleThreadFactory;
import com.android.launcher3.util.LockedUserState;
-import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SafeCloseable;
+import com.android.quickstep.dagger.QuickstepBaseAppComponent;
import com.android.quickstep.recents.data.RecentTasksDataSource;
import com.android.quickstep.recents.data.TaskVisualsChangeNotifier;
import com.android.quickstep.util.DesktopTask;
@@ -65,19 +70,22 @@
import java.util.function.Consumer;
import java.util.function.Predicate;
-import javax.inject.Provider;
+import javax.inject.Inject;
+
+import dagger.Lazy;
/**
* Singleton class to load and manage recents model.
*/
@TargetApi(Build.VERSION_CODES.O)
+@LauncherAppSingleton
public class RecentsModel implements RecentTasksDataSource, TaskStackChangeListener,
TaskVisualsChangeListener, TaskVisualsChangeNotifier,
- ThemeChangeListener, SafeCloseable {
+ ThemeChangeListener {
// We do not need any synchronization for this variable as its only written on UI thread.
- public static final MainThreadInitializedObject<RecentsModel> INSTANCE =
- new MainThreadInitializedObject<>(RecentsModel::new);
+ public static final DaggerSingletonObject<RecentsModel> INSTANCE =
+ new DaggerSingletonObject<>(QuickstepBaseAppComponent::getRecentsModel);
private static final Executor RECENTS_MODEL_EXECUTOR = Executors.newSingleThreadExecutor(
new SimpleThreadFactory("TaskThumbnailIconCache-", THREAD_PRIORITY_BACKGROUND));
@@ -85,53 +93,67 @@
private final ConcurrentLinkedQueue<TaskVisualsChangeListener> mThumbnailChangeListeners =
new ConcurrentLinkedQueue<>();
private final Context mContext;
-
private final RecentTasksList mTaskList;
private final TaskIconCache mIconCache;
private final TaskThumbnailCache mThumbnailCache;
- private final ComponentCallbacks mCallbacks;
- private final TaskStackChangeListeners mTaskStackChangeListeners;
- private final SafeCloseable mIconChangeCloseable;
-
- private final LockedUserState mLockedUserState;
- private final Provider<ThemeManager> mThemeManagerProvider;
- private final Runnable mUnlockCallback;
-
- private RecentsModel(Context context) {
- this(context, new IconProvider(context));
+ @Inject
+ public RecentsModel(@ApplicationContext Context context,
+ SystemUiProxy systemUiProxy,
+ TopTaskTracker topTaskTracker,
+ DisplayController displayController,
+ LockedUserState lockedUserState,
+ Lazy<ThemeManager> themeManagerLazy,
+ DaggerSingletonTracker tracker
+ ) {
+ // Lazily inject the ThemeManager and access themeManager once the device is
+ // unlocked. See b/393248495 for details.
+ this(context, new IconProvider(context), systemUiProxy, topTaskTracker,
+ displayController, lockedUserState,themeManagerLazy, tracker);
}
@SuppressLint("VisibleForTests")
- private RecentsModel(Context context, IconProvider iconProvider) {
+ private RecentsModel(@ApplicationContext Context context,
+ IconProvider iconProvider,
+ SystemUiProxy systemUiProxy,
+ TopTaskTracker topTaskTracker,
+ DisplayController displayController,
+ LockedUserState lockedUserState,
+ Lazy<ThemeManager> themeManagerLazy,
+ DaggerSingletonTracker tracker) {
this(context,
new RecentTasksList(
context,
MAIN_EXECUTOR,
context.getSystemService(KeyguardManager.class),
- SystemUiProxy.INSTANCE.get(context),
- TopTaskTracker.INSTANCE.get(context)),
- new TaskIconCache(context, RECENTS_MODEL_EXECUTOR, iconProvider),
+ systemUiProxy,
+ topTaskTracker),
+ new TaskIconCache(context, RECENTS_MODEL_EXECUTOR, iconProvider, displayController),
new TaskThumbnailCache(context, RECENTS_MODEL_EXECUTOR),
iconProvider,
TaskStackChangeListeners.getInstance(),
- LockedUserState.get(context),
- () -> ThemeManager.INSTANCE.get(context));
+ lockedUserState,
+ themeManagerLazy,
+ tracker);
}
@VisibleForTesting
- RecentsModel(Context context, RecentTasksList taskList, TaskIconCache iconCache,
- TaskThumbnailCache thumbnailCache, IconProvider iconProvider,
+ RecentsModel(@ApplicationContext Context context,
+ RecentTasksList taskList,
+ TaskIconCache iconCache,
+ TaskThumbnailCache thumbnailCache,
+ IconProvider iconProvider,
TaskStackChangeListeners taskStackChangeListeners,
LockedUserState lockedUserState,
- Provider<ThemeManager> themeManagerProvider) {
+ Lazy<ThemeManager> themeManagerLazy,
+ DaggerSingletonTracker tracker) {
mContext = context;
mTaskList = taskList;
mIconCache = iconCache;
mIconCache.registerTaskVisualsChangeListener(this);
mThumbnailCache = thumbnailCache;
if (isCachePreloadingEnabled()) {
- mCallbacks = new ComponentCallbacks() {
+ ComponentCallbacks componentCallbacks = new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration configuration) {
updateCacheSizeAndPreloadIfNeeded();
@@ -141,20 +163,27 @@
public void onLowMemory() {
}
};
- context.registerComponentCallbacks(mCallbacks);
- } else {
- mCallbacks = null;
+ context.registerComponentCallbacks(componentCallbacks);
+ tracker.addCloseable(() -> context.unregisterComponentCallbacks(componentCallbacks));
}
- mTaskStackChangeListeners = taskStackChangeListeners;
- mTaskStackChangeListeners.registerTaskStackListener(this);
- mIconChangeCloseable = iconProvider.registerIconChangeListener(
+ taskStackChangeListeners.registerTaskStackListener(this);
+ SafeCloseable iconChangeCloseable = iconProvider.registerIconChangeListener(
this::onAppIconChanged, MAIN_EXECUTOR.getHandler());
- mLockedUserState = lockedUserState;
- mThemeManagerProvider = themeManagerProvider;
- mUnlockCallback = () -> mThemeManagerProvider.get().addChangeListener(this);
- lockedUserState.runOnUserUnlocked(mUnlockCallback);
+ Runnable unlockCallback = () -> themeManagerLazy.get().addChangeListener(this);
+ lockedUserState.runOnUserUnlocked(unlockCallback);
+
+ tracker.addCloseable(() -> {
+ taskStackChangeListeners.unregisterTaskStackListener(this);
+ iconChangeCloseable.close();
+ mIconCache.removeTaskVisualsChangeListener();
+ if (lockedUserState.isUserUnlocked()) {
+ themeManagerLazy.get().removeChangeListener(this);
+ } else {
+ lockedUserState.removeOnUserUnlockedRunnable(unlockCallback);
+ }
+ });
}
public TaskIconCache getIconCache() {
@@ -407,22 +436,6 @@
}
}
- @Override
- public void close() {
- if (mCallbacks != null) {
- mContext.unregisterComponentCallbacks(mCallbacks);
- }
- mIconCache.removeTaskVisualsChangeListener();
- mTaskStackChangeListeners.unregisterTaskStackListener(this);
- mIconChangeCloseable.close();
-
- if (mLockedUserState.isUserUnlocked()) {
- mThemeManagerProvider.get().removeChangeListener(this);
- } else {
- mLockedUserState.removeOnUserUnlockedRunnable(mUnlockCallback);
- }
- }
-
private boolean isCachePreloadingEnabled() {
return enableGridOnlyOverview() || enableRefactorTaskThumbnail();
}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.kt b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
index d5382ad..32bdcdb 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.kt
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
@@ -65,7 +65,6 @@
import com.android.systemui.shared.recents.ISystemUiProxy
import com.android.systemui.shared.recents.model.ThumbnailData.Companion.wrap
import com.android.systemui.shared.system.QuickStepContract
-import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags
import com.android.systemui.shared.system.RecentsAnimationControllerCompat
import com.android.systemui.shared.system.RecentsAnimationListener
import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController
@@ -149,7 +148,7 @@
private var backToLauncherRunner: IRemoteAnimationRunner? = null
private var dragAndDrop: IDragAndDrop? = null
val homeVisibilityState = HomeVisibilityState()
- private val focusState = FocusState()
+ val focusState = FocusState()
// Used to dedupe calls to SystemUI
private var lastShelfHeight = 0
@@ -162,9 +161,6 @@
private val asyncHandler =
Handler(Executors.UI_HELPER_EXECUTOR.looper) { handleMessageAsync(it) }
- // TODO(141886704): Find a way to remove this
- @SystemUiStateFlags var lastSystemUiStateFlags: Long = 0
-
/**
* This is a singleton pending intent that is used to start recents via Shell (which is a
* different process). It is bare-bones, so it's expected that the component and options will be
@@ -194,7 +190,7 @@
private inline fun executeWithErrorLog(
errorMsg: () -> String,
tag: String = TAG,
- callback: () -> Any?,
+ callback: () -> Unit,
) {
try {
callback.invoke()
@@ -232,36 +228,30 @@
* Sets proxy state, including death linkage, various listeners, and other configuration objects
*/
@MainThread
- fun setProxy(
- proxy: ISystemUiProxy?,
- pip: IPip?,
- bubbles: IBubbles?,
- splitScreen: ISplitScreen?,
- oneHanded: IOneHanded?,
- shellTransitions: IShellTransitions?,
- startingWindow: IStartingWindow?,
- recentTasks: IRecentTasks?,
- sysuiUnlockAnimationController: ISysuiUnlockAnimationController?,
- backAnimation: IBackAnimation?,
- desktopMode: IDesktopMode?,
- unfoldAnimation: IUnfoldAnimation?,
- dragAndDrop: IDragAndDrop?,
- ) {
+ fun setProxy(bundle: Bundle) {
Preconditions.assertUIThread()
unlinkToDeath()
- systemUiProxy = proxy
- this.pip = pip
- this.bubbles = bubbles
- this.splitScreen = splitScreen
- this.oneHanded = oneHanded
- this.shellTransitions = shellTransitions
- this.startingWindow = startingWindow
- this.sysuiUnlockAnimationController = sysuiUnlockAnimationController
- this.recentTasks = recentTasks
- this.backAnimation = backAnimation
- this.desktopMode = desktopMode
- this.unfoldAnimation = if (Flags.enableUnfoldStateAnimation()) null else unfoldAnimation
- this.dragAndDrop = dragAndDrop
+ systemUiProxy = ISystemUiProxy.Stub.asInterface(bundle.getBinder(ISystemUiProxy.DESCRIPTOR))
+ pip = IPip.Stub.asInterface(bundle.getBinder(IPip.DESCRIPTOR))
+ bubbles = IBubbles.Stub.asInterface(bundle.getBinder(IBubbles.DESCRIPTOR))
+ splitScreen = ISplitScreen.Stub.asInterface(bundle.getBinder(ISplitScreen.DESCRIPTOR))
+ oneHanded = IOneHanded.Stub.asInterface(bundle.getBinder(IOneHanded.DESCRIPTOR))
+ shellTransitions =
+ IShellTransitions.Stub.asInterface(bundle.getBinder(IShellTransitions.DESCRIPTOR))
+ startingWindow =
+ IStartingWindow.Stub.asInterface(bundle.getBinder(IStartingWindow.DESCRIPTOR))
+ sysuiUnlockAnimationController =
+ ISysuiUnlockAnimationController.Stub.asInterface(
+ bundle.getBinder(ISysuiUnlockAnimationController.DESCRIPTOR)
+ )
+ recentTasks = IRecentTasks.Stub.asInterface(bundle.getBinder(IRecentTasks.DESCRIPTOR))
+ backAnimation = IBackAnimation.Stub.asInterface(bundle.getBinder(IBackAnimation.DESCRIPTOR))
+ desktopMode = IDesktopMode.Stub.asInterface(bundle.getBinder(IDesktopMode.DESCRIPTOR))
+ unfoldAnimation =
+ if (Flags.enableUnfoldStateAnimation()) null
+ else IUnfoldAnimation.Stub.asInterface(bundle.getBinder(IUnfoldAnimation.DESCRIPTOR))
+ dragAndDrop = IDragAndDrop.Stub.asInterface(bundle.getBinder(IDragAndDrop.DESCRIPTOR))
+
linkToDeath()
// re-attach the listeners once missing due to setProxy has not been initialized yet.
setPipAnimationListener(pipAnimationListener)
@@ -289,15 +279,12 @@
stateChangeCallbacks.forEach { it.run() }
if (unfoldTransitionProvider != null) {
- if (unfoldAnimation != null) {
- try {
- unfoldAnimation.setListener(unfoldTransitionProvider)
+ unfoldTransitionProvider.isActive = false
+ executeWithErrorLog({ "Failed to set unfold anim listener" }) {
+ unfoldAnimation?.apply {
+ setListener(unfoldTransitionProvider)
unfoldTransitionProvider.isActive = true
- } catch (e: RemoteException) {
- // Ignore
}
- } else {
- unfoldTransitionProvider.isActive = false
}
}
}
@@ -305,9 +292,7 @@
/**
* Clear the proxy to release held resources and turn the majority of its operations into no-ops
*/
- @MainThread
- fun clearProxy() =
- setProxy(null, null, null, null, null, null, null, null, null, null, null, null, null)
+ @MainThread fun clearProxy() = setProxy(Bundle.EMPTY)
/** Adds a callback to be notified whenever the active state changes */
fun addOnStateChangeListener(callback: Runnable) = stateChangeCallbacks.add(callback)
diff --git a/quickstep/src/com/android/quickstep/TISBinder.kt b/quickstep/src/com/android/quickstep/TISBinder.kt
new file mode 100644
index 0000000..683012c
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/TISBinder.kt
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep
+
+import android.graphics.Region
+import android.os.Bundle
+import android.os.IRemoteCallback
+import android.os.RemoteException
+import android.util.Log
+import androidx.annotation.BinderThread
+import androidx.annotation.VisibleForTesting
+import com.android.launcher3.taskbar.TaskbarManager
+import com.android.launcher3.testing.TestLogging
+import com.android.launcher3.testing.shared.TestProtocol
+import com.android.launcher3.util.Executors
+import com.android.quickstep.OverviewCommandHelper.CommandType.HIDE
+import com.android.quickstep.OverviewCommandHelper.CommandType.KEYBOARD_INPUT
+import com.android.quickstep.OverviewCommandHelper.CommandType.SHOW
+import com.android.quickstep.OverviewCommandHelper.CommandType.TOGGLE
+import com.android.quickstep.util.ActivityPreloadUtil.preloadOverviewForTIS
+import com.android.quickstep.util.ContextualSearchInvoker
+import com.android.systemui.shared.recents.ILauncherProxy
+import com.android.systemui.shared.statusbar.phone.BarTransitions.TransitionMode
+import com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags
+import java.lang.ref.WeakReference
+
+/** Local ILauncherProxy implementation with some methods for local components */
+private const val TAG = "TISBinder"
+
+class TISBinder internal constructor(tis: TouchInteractionService) : ILauncherProxy.Stub() {
+
+ private val mTis = WeakReference(tis)
+
+ /** Returns the [TaskbarManager] or `null` if TouchInteractionService is not connected */
+ val taskbarManager: TaskbarManager?
+ get() = mTis.get()?.taskbarManager
+
+ /** Returns the [OverviewCommandHelper] or `null` if TouchInteractionService is not connected */
+ val overviewCommandHelper: OverviewCommandHelper?
+ get() = mTis.get()?.overviewCommandHelper
+
+ private val deviceState: RecentsAnimationDeviceState?
+ get() = mTis.get()?.let { RecentsAnimationDeviceState.INSTANCE[it] }
+
+ private inline fun executeForTaskbarManagerOnMain(
+ crossinline callback: TaskbarManager.() -> Unit
+ ) {
+ Executors.MAIN_EXECUTOR.execute { taskbarManager?.let { callback.invoke(it) } }
+ }
+
+ @BinderThread
+ override fun onInitialize(bundle: Bundle) {
+ Executors.MAIN_EXECUTOR.execute {
+ mTis.get()?.let {
+ SystemUiProxy.INSTANCE[it].setProxy(bundle)
+ preloadOverviewForTIS(it, true /* fromInit */)
+ }
+ }
+ }
+
+ @BinderThread
+ override fun onTaskbarToggled() {
+ executeForTaskbarManagerOnMain { currentActivityContext?.toggleTaskbarStash() }
+ }
+
+ @BinderThread
+ override fun onOverviewToggle() {
+ TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle")
+ mTis.get()?.let { tis ->
+ // If currently screen pinning, do not enter overview
+ if (RecentsAnimationDeviceState.INSTANCE[tis].isScreenPinningActive) {
+ return@let
+ }
+ TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS)
+ tis.overviewCommandHelper.addCommand(TOGGLE)
+ }
+ }
+
+ @BinderThread
+ override fun onOverviewShown(triggeredFromAltTab: Boolean) {
+ overviewCommandHelper?.apply {
+ if (triggeredFromAltTab) {
+ TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS)
+ addCommand(KEYBOARD_INPUT)
+ } else {
+ addCommand(SHOW)
+ }
+ }
+ }
+
+ @BinderThread
+ override fun onOverviewHidden(triggeredFromAltTab: Boolean, triggeredFromHomeKey: Boolean) {
+ overviewCommandHelper?.apply {
+ if (triggeredFromAltTab && !triggeredFromHomeKey) {
+ // onOverviewShownFromAltTab hides the overview and ends at the target app
+ addCommand(HIDE)
+ }
+ }
+ }
+
+ @BinderThread
+ override fun onAssistantAvailable(available: Boolean, longPressHomeEnabled: Boolean) {
+ Executors.MAIN_EXECUTOR.execute {
+ deviceState?.setAssistantAvailable(available)
+ taskbarManager?.onLongPressHomeEnabled(longPressHomeEnabled)
+ }
+ }
+
+ @BinderThread
+ override fun onAssistantVisibilityChanged(visibility: Float) {
+ Executors.MAIN_EXECUTOR.execute { deviceState?.assistantVisibility = visibility }
+ }
+
+ /**
+ * Sent when the assistant has been invoked with the given type (defined in AssistManager) and
+ * should be shown. This method is used if SystemUiProxy#setAssistantOverridesRequested was
+ * previously called including this invocation type.
+ */
+ override fun onAssistantOverrideInvoked(invocationType: Int) {
+ mTis.get()?.let { tis ->
+ if (!ContextualSearchInvoker(tis).tryStartAssistOverride(invocationType)) {
+ Log.w(TAG, "Failed to invoke Assist override")
+ }
+ }
+ }
+
+ @BinderThread
+ override fun onSystemUiStateChanged(@SystemUiStateFlags stateFlags: Long) {
+ Executors.MAIN_EXECUTOR.execute { deviceState?.systemUiStateFlags = stateFlags }
+ }
+
+ @BinderThread
+ override fun onActiveNavBarRegionChanges(region: Region) {
+ Executors.MAIN_EXECUTOR.execute { deviceState?.setDeferredGestureRegion(region) }
+ }
+
+ @BinderThread
+ override fun enterStageSplitFromRunningApp(leftOrTop: Boolean) {
+ mTis.get()?.let { tis ->
+ OverviewComponentObserver.INSTANCE[tis]
+ .containerInterface
+ .createdContainer
+ ?.enterStageSplitFromRunningApp(leftOrTop)
+ }
+ }
+
+ @BinderThread
+ override fun onDisplayAddSystemDecorations(displayId: Int) {
+ executeForTaskbarManagerOnMain { onDisplayAddSystemDecorations(displayId) }
+ }
+
+ @BinderThread
+ override fun onDisplayRemoved(displayId: Int) {
+ executeForTaskbarManagerOnMain { onDisplayRemoved(displayId) }
+ }
+
+ @BinderThread
+ override fun onDisplayRemoveSystemDecorations(displayId: Int) {
+ executeForTaskbarManagerOnMain { onDisplayRemoveSystemDecorations(displayId) }
+ }
+
+ @BinderThread
+ override fun updateWallpaperVisibility(displayId: Int, visible: Boolean) {
+ executeForTaskbarManagerOnMain { setWallpaperVisible(displayId, visible) }
+ }
+
+ @BinderThread
+ override fun checkNavBarModes(displayId: Int) {
+ executeForTaskbarManagerOnMain { checkNavBarModes(displayId) }
+ }
+
+ @BinderThread
+ override fun finishBarAnimations(displayId: Int) {
+ executeForTaskbarManagerOnMain { finishBarAnimations(displayId) }
+ }
+
+ @BinderThread
+ override fun touchAutoDim(displayId: Int, reset: Boolean) {
+ executeForTaskbarManagerOnMain { touchAutoDim(displayId, reset) }
+ }
+
+ @BinderThread
+ override fun transitionTo(displayId: Int, @TransitionMode barMode: Int, animate: Boolean) {
+ executeForTaskbarManagerOnMain { transitionTo(displayId, barMode, animate) }
+ }
+
+ @BinderThread
+ override fun appTransitionPending(pending: Boolean) {
+ executeForTaskbarManagerOnMain { appTransitionPending(pending) }
+ }
+
+ override fun onRotationProposal(rotation: Int, isValid: Boolean) {
+ executeForTaskbarManagerOnMain { onRotationProposal(rotation, isValid) }
+ }
+
+ override fun disable(displayId: Int, state1: Int, state2: Int, animate: Boolean) {
+ executeForTaskbarManagerOnMain { disableNavBarElements(displayId, state1, state2, animate) }
+ }
+
+ override fun onSystemBarAttributesChanged(displayId: Int, behavior: Int) {
+ executeForTaskbarManagerOnMain { onSystemBarAttributesChanged(displayId, behavior) }
+ }
+
+ override fun onTransitionModeUpdated(barMode: Int, checkBarModes: Boolean) {
+ executeForTaskbarManagerOnMain { onTransitionModeUpdated(barMode, checkBarModes) }
+ }
+
+ override fun onNavButtonsDarkIntensityChanged(darkIntensity: Float) {
+ executeForTaskbarManagerOnMain { onNavButtonsDarkIntensityChanged(darkIntensity) }
+ }
+
+ override fun onNavigationBarLumaSamplingEnabled(displayId: Int, enable: Boolean) {
+ executeForTaskbarManagerOnMain { onNavigationBarLumaSamplingEnabled(displayId, enable) }
+ }
+
+ override fun onUnbind(reply: IRemoteCallback) {
+ // Run everything in the same main thread block to ensure the cleanup happens before
+ // sending the reply.
+ Executors.MAIN_EXECUTOR.execute {
+ taskbarManager?.destroy()
+ try {
+ reply.sendResult(null)
+ } catch (e: RemoteException) {
+ Log.w(TAG, "onUnbind: Failed to reply to LauncherProxyService", e)
+ }
+ }
+ }
+
+ @VisibleForTesting
+ fun injectFakeTrackpadForTesting() = mTis.get()?.injectFakeTrackpadForTesting()
+
+ @VisibleForTesting fun ejectFakeTrackpadForTesting() = mTis.get()?.ejectFakeTrackpadForTesting()
+
+ @VisibleForTesting fun refreshOverviewTarget() = mTis.get()?.refreshOverviewTarget()
+}
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 63b8aaf..e449048 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -54,22 +54,25 @@
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import java.io.PrintWriter;
import java.util.HashMap;
+import java.util.function.LongConsumer;
public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener {
public static final boolean SHELL_TRANSITIONS_ROTATION =
SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false);
private final Context mCtx;
+ private final RecentsAnimationDeviceState mDeviceState;
+ private final SystemUiProxy mSystemUiProxy;
private RecentsAnimationController mController;
private RecentsAnimationCallbacks mCallbacks;
private RecentsAnimationTargets mTargets;
private TransitionInfo mTransitionInfo;
- private RecentsAnimationDeviceState mDeviceState;
// Temporary until we can hook into gesture state events
private GestureState mLastGestureState;
@@ -102,12 +105,22 @@
}
};
+
+ private @SystemUiStateFlags long mLastSysuiFlags;
+ private final LongConsumer mSysUiFlagChangeCallback = this::onSystemUiFlagsChanged;
+
TaskAnimationManager(Context ctx, RecentsAnimationDeviceState deviceState) {
+ this(ctx, deviceState, SystemUiProxy.INSTANCE.get(ctx));
+ }
+
+ TaskAnimationManager(Context ctx, RecentsAnimationDeviceState deviceState,
+ SystemUiProxy systemUiProxy) {
mCtx = ctx;
mDeviceState = deviceState;
- }
- SystemUiProxy getSystemUiProxy() {
- return SystemUiProxy.INSTANCE.get(mCtx);
+ mSystemUiProxy = systemUiProxy;
+ mLastSysuiFlags = QuickStepContract.SYSUI_STATE_AWAKE;
+ mDeviceState.addSysUiFlagChangeCallback(mSysUiFlagChangeCallback);
+ onSystemUiFlagsChanged(mDeviceState.getSystemUiStateFlags());
}
boolean shouldIgnoreMotionEvents() {
@@ -151,7 +164,7 @@
final BaseContainerInterface containerInterface = gestureState.getContainerInterface();
mLastGestureState = gestureState;
- RecentsAnimationCallbacks newCallbacks = new RecentsAnimationCallbacks(getSystemUiProxy());
+ RecentsAnimationCallbacks newCallbacks = new RecentsAnimationCallbacks(mSystemUiProxy);
mCallbacks = newCallbacks;
mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
@Override
@@ -289,7 +302,7 @@
if(containerInterface.getCreatedContainer() instanceof RecentsWindowManager
&& (Flags.enableFallbackOverviewInWindow()
|| Flags.enableLauncherOverviewInWindow())) {
- mRecentsAnimationStartPending = getSystemUiProxy().startRecentsActivity(intent, options,
+ mRecentsAnimationStartPending = mSystemUiProxy.startRecentsActivity(intent, options,
mCallbacks, gestureState.useSyntheticRecentsTransition());
RecentsDisplayModel.getINSTANCE().get(mCtx)
.getRecentsWindowManager(mDeviceState.getDisplayId())
@@ -321,7 +334,7 @@
});
}
- mRecentsAnimationStartPending = getSystemUiProxy().startRecentsActivity(intent,
+ mRecentsAnimationStartPending = mSystemUiProxy.startRecentsActivity(intent,
options, mCallbacks, false /* useSyntheticRecentsTransition */);
}
@@ -347,8 +360,13 @@
return mCallbacks;
}
- public void onSystemUiFlagsChanged(@QuickStepContract.SystemUiStateFlags long lastSysUIFlags,
- @QuickStepContract.SystemUiStateFlags long newSysUIFlags) {
+ private void onSystemUiFlagsChanged(@SystemUiStateFlags long newSysUIFlags) {
+ onSystemUiFlagsChanged(mLastSysuiFlags, newSysUIFlags);
+ mLastSysuiFlags = newSysUIFlags;
+ }
+
+ private void onSystemUiFlagsChanged(@SystemUiStateFlags long lastSysUIFlags,
+ @SystemUiStateFlags long newSysUIFlags) {
long isShadeExpandedFlagMask =
SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
boolean wasExpanded = hasAnyFlag(lastSysUIFlags, isShadeExpandedFlagMask);
@@ -509,4 +527,8 @@
mLastGestureState.dump(prefix + '\t', pw);
}
}
+
+ public void onDestroy() {
+ mDeviceState.removeSysUiFlagChangeCallback(mSysUiFlagChangeCallback);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.kt b/quickstep/src/com/android/quickstep/TaskIconCache.kt
index b82c110..6a7f1af 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.kt
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.kt
@@ -53,6 +53,7 @@
private val context: Context,
private val bgExecutor: Executor,
private val iconProvider: IconProvider,
+ displayController: DisplayController,
) : TaskIconDataSource, DisplayInfoChangeListener {
private val iconCache =
TaskKeyLruCache<TaskCacheEntry>(
@@ -71,7 +72,7 @@
var taskVisualsChangeListener: TaskVisualsChangeListener? = null
init {
- DisplayController.INSTANCE.get(context).addChangeListener(this)
+ displayController.addChangeListener(this)
}
override fun onDisplayInfoChanged(context: Context, info: DisplayController.Info, flags: Int) {
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 2df4a45..22fc2ea 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -36,7 +36,6 @@
import static com.android.quickstep.InputConsumer.TYPE_CURSOR_HOVER;
import static com.android.quickstep.InputConsumerUtils.newConsumer;
import static com.android.quickstep.InputConsumerUtils.tryCreateAssistantInputConsumer;
-import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import android.app.PendingIntent;
import android.app.Service;
@@ -44,12 +43,9 @@
import android.content.IIntentSender;
import android.content.Intent;
import android.content.res.Configuration;
-import android.graphics.Region;
import android.os.Bundle;
import android.os.IBinder;
-import android.os.IRemoteCallback;
import android.os.Looper;
-import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import android.view.Choreographer;
@@ -57,7 +53,6 @@
import android.view.InputEvent;
import android.view.MotionEvent;
-import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
@@ -67,7 +62,6 @@
import com.android.launcher3.EncryptionType;
import com.android.launcher3.Flags;
import com.android.launcher3.LauncherPrefs;
-import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.desktop.DesktopAppLaunchTransitionManager;
import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.statemanager.StatefulActivity;
@@ -88,6 +82,7 @@
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.OverviewCommandHelper.CommandType;
import com.android.quickstep.OverviewComponentObserver.OverviewChangeListener;
+import com.android.quickstep.fallback.window.RecentsDisplayModel;
import com.android.quickstep.fallback.window.RecentsWindowSwipeHandler;
import com.android.quickstep.inputconsumers.BubbleBarInputConsumer;
import com.android.quickstep.inputconsumers.OneHandedModeInputConsumer;
@@ -97,34 +92,14 @@
import com.android.quickstep.util.ActiveGestureProtoLogProxy;
import com.android.quickstep.util.ActiveTrackpadList;
import com.android.quickstep.util.ActivityPreloadUtil;
-import com.android.quickstep.util.ContextualSearchInvoker;
import com.android.quickstep.util.ContextualSearchStateManager;
import com.android.quickstep.views.RecentsViewContainer;
-import com.android.systemui.shared.recents.ILauncherProxy;
-import com.android.systemui.shared.recents.ISystemUiProxy;
-import com.android.systemui.shared.statusbar.phone.BarTransitions;
import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.InputMonitorCompat;
-import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
-import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController;
-import com.android.systemui.unfold.progress.IUnfoldAnimation;
-import com.android.wm.shell.back.IBackAnimation;
-import com.android.wm.shell.bubbles.IBubbles;
-import com.android.wm.shell.common.pip.IPip;
-import com.android.wm.shell.desktopmode.IDesktopMode;
-import com.android.wm.shell.draganddrop.IDragAndDrop;
-import com.android.wm.shell.onehanded.IOneHanded;
-import com.android.wm.shell.recents.IRecentTasks;
-import com.android.wm.shell.shared.IShellTransitions;
-import com.android.wm.shell.splitscreen.ISplitScreen;
-import com.android.wm.shell.startingsurface.IStartingWindow;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.function.Consumer;
-import java.util.function.Function;
/**
* Service connected by system-UI for handling touch interaction.
@@ -140,371 +115,6 @@
private final TISBinder mTISBinder = new TISBinder(this);
- /**
- * Local ILauncherProxy implementation with some methods for local components
- */
- public static class TISBinder extends ILauncherProxy.Stub {
-
- private final WeakReference<TouchInteractionService> mTis;
-
- private TISBinder(TouchInteractionService tis) {
- mTis = new WeakReference<>(tis);
- }
-
- @BinderThread
- public void onInitialize(Bundle bundle) {
- ISystemUiProxy proxy = ISystemUiProxy.Stub.asInterface(
- bundle.getBinder(ISystemUiProxy.DESCRIPTOR));
- IPip pip = IPip.Stub.asInterface(bundle.getBinder(IPip.DESCRIPTOR));
- IBubbles bubbles = IBubbles.Stub.asInterface(bundle.getBinder(IBubbles.DESCRIPTOR));
- ISplitScreen splitscreen = ISplitScreen.Stub.asInterface(bundle.getBinder(
- ISplitScreen.DESCRIPTOR));
- IOneHanded onehanded = IOneHanded.Stub.asInterface(
- bundle.getBinder(IOneHanded.DESCRIPTOR));
- IShellTransitions shellTransitions = IShellTransitions.Stub.asInterface(
- bundle.getBinder(IShellTransitions.DESCRIPTOR));
- IStartingWindow startingWindow = IStartingWindow.Stub.asInterface(
- bundle.getBinder(IStartingWindow.DESCRIPTOR));
- ISysuiUnlockAnimationController launcherUnlockAnimationController =
- ISysuiUnlockAnimationController.Stub.asInterface(
- bundle.getBinder(ISysuiUnlockAnimationController.DESCRIPTOR));
- IRecentTasks recentTasks = IRecentTasks.Stub.asInterface(
- bundle.getBinder(IRecentTasks.DESCRIPTOR));
- IBackAnimation backAnimation = IBackAnimation.Stub.asInterface(
- bundle.getBinder(IBackAnimation.DESCRIPTOR));
- IDesktopMode desktopMode = IDesktopMode.Stub.asInterface(
- bundle.getBinder(IDesktopMode.DESCRIPTOR));
- IUnfoldAnimation unfoldTransition = IUnfoldAnimation.Stub.asInterface(
- bundle.getBinder(IUnfoldAnimation.DESCRIPTOR));
- IDragAndDrop dragAndDrop = IDragAndDrop.Stub.asInterface(
- bundle.getBinder(IDragAndDrop.DESCRIPTOR));
- MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
- SystemUiProxy.INSTANCE.get(tis).setProxy(proxy, pip,
- bubbles, splitscreen, onehanded, shellTransitions, startingWindow,
- recentTasks, launcherUnlockAnimationController, backAnimation, desktopMode,
- unfoldTransition, dragAndDrop);
- tis.initInputMonitor("TISBinder#onInitialize()");
- ActivityPreloadUtil.preloadOverviewForTIS(tis, true /* fromInit */);
- }));
- }
-
- @BinderThread
- @Override
- public void onTaskbarToggled() {
- MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
- TaskbarActivityContext activityContext =
- tis.mTaskbarManager.getCurrentActivityContext();
-
- if (activityContext != null) {
- activityContext.toggleTaskbarStash();
- }
- }));
- }
-
- @BinderThread
- public void onOverviewToggle() {
- TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle");
- executeForTouchInteractionService(tis -> {
- // If currently screen pinning, do not enter overview
- if (tis.mDeviceState.isScreenPinningActive()) {
- return;
- }
- TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
- tis.mOverviewCommandHelper.addCommand(CommandType.TOGGLE);
- });
- }
-
- @BinderThread
- @Override
- public void onOverviewShown(boolean triggeredFromAltTab) {
- executeForTouchInteractionService(tis -> {
- if (triggeredFromAltTab) {
- TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
- tis.mOverviewCommandHelper.addCommand(CommandType.KEYBOARD_INPUT);
- } else {
- tis.mOverviewCommandHelper.addCommand(CommandType.SHOW);
- }
- });
- }
-
- @BinderThread
- @Override
- public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
- executeForTouchInteractionService(tis -> {
- if (triggeredFromAltTab && !triggeredFromHomeKey) {
- // onOverviewShownFromAltTab hides the overview and ends at the target app
- tis.mOverviewCommandHelper.addCommand(CommandType.HIDE);
- }
- });
- }
-
- @BinderThread
- @Override
- public void onAssistantAvailable(boolean available, boolean longPressHomeEnabled) {
- MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
- tis.mDeviceState.setAssistantAvailable(available);
- tis.onAssistantVisibilityChanged();
- executeForTaskbarManager(taskbarManager -> taskbarManager
- .onLongPressHomeEnabled(longPressHomeEnabled));
- }));
- }
-
- @BinderThread
- @Override
- public void onAssistantVisibilityChanged(float visibility) {
- MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
- tis.mDeviceState.setAssistantVisibility(visibility);
- tis.onAssistantVisibilityChanged();
- }));
- }
-
- /**
- * Sent when the assistant has been invoked with the given type (defined in AssistManager)
- * and should be shown. This method is used if SystemUiProxy#setAssistantOverridesRequested
- * was previously called including this invocation type.
- */
- @Override
- public void onAssistantOverrideInvoked(int invocationType) {
- executeForTouchInteractionService(tis -> {
- if (!new ContextualSearchInvoker(tis).tryStartAssistOverride(invocationType)) {
- Log.w(TAG, "Failed to invoke Assist override");
- }
- });
- }
-
- @BinderThread
- public void onSystemUiStateChanged(@SystemUiStateFlags long stateFlags) {
- MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
- long lastFlags = tis.mDeviceState.getSystemUiStateFlags();
- tis.mDeviceState.setSystemUiFlags(stateFlags);
- tis.onSystemUiFlagsChanged(lastFlags);
- }));
- }
-
- @BinderThread
- public void onActiveNavBarRegionChanges(Region region) {
- MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
- tis -> tis.mDeviceState.setDeferredGestureRegion(region)));
- }
-
- @BinderThread
- @Override
- public void enterStageSplitFromRunningApp(boolean leftOrTop) {
- executeForTouchInteractionService(tis -> {
- RecentsViewContainer container = tis.mOverviewComponentObserver
- .getContainerInterface().getCreatedContainer();
- if (container != null) {
- container.enterStageSplitFromRunningApp(leftOrTop);
- }
- });
- }
-
- @BinderThread
- @Override
- public void onDisplayAddSystemDecorations(int displayId) {
- executeForTaskbarManager(taskbarManager ->
- taskbarManager.onDisplayAddSystemDecorations(displayId));
- }
-
- @BinderThread
- @Override
- public void onDisplayRemoved(int displayId) {
- executeForTaskbarManager(taskbarManager ->
- taskbarManager.onDisplayRemoved(displayId));
- }
-
- @BinderThread
- @Override
- public void onDisplayRemoveSystemDecorations(int displayId) {
- executeForTaskbarManager(taskbarManager ->
- taskbarManager.onDisplayRemoveSystemDecorations(displayId));
- }
-
- @BinderThread
- @Override
- public void updateWallpaperVisibility(int displayId, boolean visible) {
- executeForTaskbarManager(taskbarManager ->
- taskbarManager.setWallpaperVisible(displayId, visible));
- }
-
- @BinderThread
- @Override
- public void checkNavBarModes(int displayId) {
- executeForTaskbarManager(taskbarManager ->
- taskbarManager.checkNavBarModes(displayId));
- }
-
- @BinderThread
- @Override
- public void finishBarAnimations(int displayId) {
- executeForTaskbarManager(taskbarManager ->
- taskbarManager.finishBarAnimations(displayId));
- }
-
- @BinderThread
- @Override
- public void touchAutoDim(int displayId, boolean reset) {
- executeForTaskbarManager(taskbarManager ->
- taskbarManager.touchAutoDim(displayId, reset));
- }
-
- @BinderThread
- @Override
- public void transitionTo(int displayId, @BarTransitions.TransitionMode int barMode,
- boolean animate) {
- executeForTaskbarManager(taskbarManager ->
- taskbarManager.transitionTo(displayId, barMode, animate));
- }
-
- @BinderThread
- @Override
- public void appTransitionPending(boolean pending) {
- executeForTaskbarManager(taskbarManager ->
- taskbarManager.appTransitionPending(pending));
- }
-
- @Override
- public void onRotationProposal(int rotation, boolean isValid) {
- executeForTaskbarManager(taskbarManager ->
- taskbarManager.onRotationProposal(rotation, isValid));
- }
-
- @Override
- public void disable(int displayId, int state1, int state2, boolean animate) {
- executeForTaskbarManager(taskbarManager ->
- taskbarManager.disableNavBarElements(displayId, state1, state2, animate));
- }
-
- @Override
- public void onSystemBarAttributesChanged(int displayId, int behavior) {
- executeForTaskbarManager(taskbarManager ->
- taskbarManager.onSystemBarAttributesChanged(displayId, behavior));
- }
-
- @Override
- public void onTransitionModeUpdated(int barMode, boolean checkBarModes) {
- executeForTaskbarManager(taskbarManager ->
- taskbarManager.onTransitionModeUpdated(barMode, checkBarModes));
- }
-
- @Override
- public void onNavButtonsDarkIntensityChanged(float darkIntensity) {
- executeForTaskbarManager(taskbarManager ->
- taskbarManager.onNavButtonsDarkIntensityChanged(darkIntensity));
- }
-
- @Override
- public void onNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {
- executeForTaskbarManager(taskbarManager ->
- taskbarManager.onNavigationBarLumaSamplingEnabled(displayId, enable));
- }
-
- @Override
- public void onUnbind(IRemoteCallback reply) {
- // Run everything in the same main thread block to ensure the cleanup happens before
- // sending the reply.
- MAIN_EXECUTOR.execute(() -> {
- executeForTaskbarManager(TaskbarManager::destroy);
- try {
- reply.sendResult(null);
- } catch (RemoteException e) {
- Log.w(TAG, "onUnbind: Failed to reply to LauncherProxyService", e);
- }
- });
- }
-
- private void executeForTouchInteractionService(
- @NonNull Consumer<TouchInteractionService> tisConsumer) {
- TouchInteractionService tis = mTis.get();
- if (tis == null) return;
- tisConsumer.accept(tis);
- }
-
- private void executeForTaskbarManager(
- @NonNull Consumer<TaskbarManager> taskbarManagerConsumer) {
- MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
- TaskbarManager taskbarManager = tis.mTaskbarManager;
- if (taskbarManager == null) return;
- taskbarManagerConsumer.accept(taskbarManager);
- }));
- }
-
- /**
- * Returns the {@link TaskbarManager}.
- * <p>
- * Returns {@code null} if TouchInteractionService is not connected
- */
- @Nullable
- public TaskbarManager getTaskbarManager() {
- TouchInteractionService tis = mTis.get();
- if (tis == null) return null;
- return tis.mTaskbarManager;
- }
-
- @VisibleForTesting
- public void injectFakeTrackpadForTesting() {
- TouchInteractionService tis = mTis.get();
- if (tis == null) return;
- tis.mTrackpadsConnected.add(1000);
- tis.initInputMonitor("tapl testing");
- }
-
- @VisibleForTesting
- public void ejectFakeTrackpadForTesting() {
- TouchInteractionService tis = mTis.get();
- if (tis == null) return;
- tis.mTrackpadsConnected.clear();
- // This method destroys the current input monitor if set up, and only init a new one
- // in 3-button mode if {@code mTrackpadsConnected} is not empty. So in other words,
- // it will destroy the input monitor.
- tis.initInputMonitor("tapl testing");
- }
-
- /**
- * Sets whether a predictive back-to-home animation is in progress in the device state
- */
- public void setPredictiveBackToHomeInProgress(boolean isInProgress) {
- executeForTouchInteractionService(tis ->
- tis.mDeviceState.setPredictiveBackToHomeInProgress(isInProgress));
- }
-
- /**
- * Returns the {@link OverviewCommandHelper}.
- * <p>
- * Returns {@code null} if TouchInteractionService is not connected
- */
- @Nullable
- public OverviewCommandHelper getOverviewCommandHelper() {
- TouchInteractionService tis = mTis.get();
- if (tis == null) return null;
- return tis.mOverviewCommandHelper;
- }
-
- /**
- * Sets a proxy to bypass swipe up behavior
- */
- public void setSwipeUpProxy(Function<GestureState, AnimatedFloat> proxy) {
- executeForTouchInteractionService(
- tis -> tis.mSwipeUpProxyProvider = proxy != null ? proxy : (i -> null));
- }
-
- /**
- * Sets the task id where gestures should be blocked
- */
- public void setGestureBlockedTaskId(int taskId) {
- executeForTouchInteractionService(
- tis -> tis.mDeviceState.setGestureBlockingTaskId(taskId));
- }
-
- /** Refreshes the current overview target. */
- public void refreshOverviewTarget() {
- executeForTouchInteractionService(tis -> {
- tis.mAllAppsActionManager.onDestroy();
- tis.onOverviewTargetChanged(tis.mOverviewComponentObserver.isHomeAndOverviewSame());
- });
- }
- }
-
private RotationTouchHelper mRotationTouchHelper;
private final AbsSwipeUpHandler.Factory mLauncherSwipeHandlerFactory =
@@ -520,6 +130,13 @@
private final ScreenOnTracker.ScreenOnListener mScreenOnListener = this::onScreenOnChanged;
private final OverviewChangeListener mOverviewChangeListener = this::onOverviewTargetChanged;
+ private final Runnable mSysUiProxyStateChangeCallback =
+ () -> {
+ if (SystemUiProxy.INSTANCE.get(this).isActive()) {
+ initInputMonitor("TISBinder#onInitialize()");
+ }
+ };
+
private final TaskbarNavButtonCallbacks mNavCallbacks = new TaskbarNavButtonCallbacks() {
@Override
public void onNavigateHome() {
@@ -553,7 +170,6 @@
private InputEventReceiver mInputEventReceiver;
private TaskbarManager mTaskbarManager;
- private Function<GestureState, AnimatedFloat> mSwipeUpProxyProvider = i -> null;
private AllAppsActionManager mAllAppsActionManager;
private ActiveTrackpadList mTrackpadsConnected;
@@ -584,7 +200,8 @@
initInputMonitor("onTrackpadConnected()");
});
- mTaskbarManager = new TaskbarManager(this, mAllAppsActionManager, mNavCallbacks);
+ mTaskbarManager = new TaskbarManager(this, mAllAppsActionManager, mNavCallbacks,
+ RecentsDisplayModel.getINSTANCE().get(this));
mDesktopAppLaunchTransitionManager =
new DesktopAppLaunchTransitionManager(this, SystemUiProxy.INSTANCE.get(this));
mDesktopAppLaunchTransitionManager.registerTransitions();
@@ -595,6 +212,7 @@
mDisplayInfoChangeListener =
mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
ScreenOnTracker.INSTANCE.get(this).addListener(mScreenOnListener);
+ SystemUiProxy.INSTANCE.get(this).addOnStateChangeListener(mSysUiProxyStateChangeCallback);
}
private void disposeEventHandlers(String reason) {
@@ -641,12 +259,12 @@
mTaskAnimationManager = new TaskAnimationManager(this, mDeviceState);
mOverviewComponentObserver = OverviewComponentObserver.INSTANCE.get(this);
mOverviewCommandHelper = new OverviewCommandHelper(this,
- mOverviewComponentObserver, mTaskAnimationManager);
+ mOverviewComponentObserver, mTaskAnimationManager,
+ RecentsDisplayModel.getINSTANCE().get(this),
+ SystemUiProxy.INSTANCE.get(this).getFocusState(), mTaskbarManager);
mResetGestureInputConsumer = new ResetGestureInputConsumer(
mTaskAnimationManager, mTaskbarManager::getCurrentActivityContext);
mInputConsumer.registerInputConsumer();
- onSystemUiFlagsChanged(mDeviceState.getSystemUiStateFlags());
- onAssistantVisibilityChanged();
// Initialize the task tracker
TopTaskTracker.INSTANCE.get(this);
@@ -665,6 +283,10 @@
return mOverviewCommandHelper;
}
+ public TaskbarManager getTaskbarManager() {
+ return mTaskbarManager;
+ }
+
private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
if (!LockedUserState.get(this).isUserUnlocked() || mDeviceState.isButtonNavMode()) {
// Skip if not yet unlocked (can't read user shared prefs) or if the current navigation
@@ -706,34 +328,18 @@
});
}
- @UiThread
- private void onSystemUiFlagsChanged(@SystemUiStateFlags long lastSysUIFlags) {
- if (LockedUserState.get(this).isUserUnlocked()) {
- long systemUiStateFlags = mDeviceState.getSystemUiStateFlags();
- SystemUiProxy.INSTANCE.get(this).setLastSystemUiStateFlags(systemUiStateFlags);
- mOverviewComponentObserver.setHomeDisabled(mDeviceState.isHomeDisabled());
- mTaskbarManager.onSystemUiFlagsChanged(systemUiStateFlags);
- mTaskAnimationManager.onSystemUiFlagsChanged(lastSysUIFlags, systemUiStateFlags);
- }
- }
-
- @UiThread
- private void onAssistantVisibilityChanged() {
- if (LockedUserState.get(this).isUserUnlocked()) {
- mOverviewComponentObserver.getContainerInterface().onAssistantVisibilityChanged(
- mDeviceState.getAssistantVisibility());
- }
- }
-
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy: user=" + getUserId()
+ " instance=" + System.identityHashCode(this));
if (LockedUserState.get(this).isUserUnlocked()) {
mInputConsumer.unregisterInputConsumer();
- mOverviewComponentObserver.setHomeDisabled(false);
mOverviewComponentObserver.removeOverviewChangeListener(mOverviewChangeListener);
}
+ if (mTaskAnimationManager != null) {
+ mTaskAnimationManager.onDestroy();
+ }
+
disposeEventHandlers("TouchInteractionService onDestroy()");
SystemUiProxy.INSTANCE.get(this).clearProxy();
@@ -748,6 +354,8 @@
mDeviceState.removeDisplayInfoChangeListener(mDisplayInfoChangeListener);
LockedUserState.get(this).removeOnUserUnlockedRunnable(mUserUnlockedRunnable);
ScreenOnTracker.INSTANCE.get(this).removeListener(mScreenOnListener);
+ SystemUiProxy.INSTANCE.get(this)
+ .removeOnStateChangeListener(mSysUiProxyStateChangeCallback);
super.onDestroy();
}
@@ -875,7 +483,6 @@
this::onConsumerInactive,
mInputEventReceiver,
mTaskbarManager,
- mSwipeUpProxyProvider,
mOverviewCommandHelper,
event);
mUncheckedConsumer = mConsumer;
@@ -1154,4 +761,26 @@
gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
mInputConsumer, MSDLPlayerWrapper.INSTANCE.get(this));
}
+
+ @VisibleForTesting
+ public void injectFakeTrackpadForTesting() {
+ mTrackpadsConnected.add(1000);
+ initInputMonitor("tapl testing");
+ }
+
+ @VisibleForTesting
+ public void ejectFakeTrackpadForTesting() {
+ mTrackpadsConnected.clear();
+ // This method destroys the current input monitor if set up, and only init a new one
+ // in 3-button mode if {@code mTrackpadsConnected} is not empty. So in other words,
+ // it will destroy the input monitor.
+ initInputMonitor("tapl testing");
+ }
+
+ /** Refreshes the current overview target. */
+ @VisibleForTesting
+ public void refreshOverviewTarget() {
+ mAllAppsActionManager.onDestroy();
+ onOverviewTargetChanged(mOverviewComponentObserver.isHomeAndOverviewSame());
+ }
}
diff --git a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
index adc45ae..d79a8ea 100644
--- a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
+++ b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
@@ -22,6 +22,7 @@
import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.quickstep.OverviewComponentObserver;
import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.RecentsModel;
import com.android.quickstep.RotationTouchHelper;
import com.android.quickstep.SimpleOrientationTouchTransformer;
import com.android.quickstep.SystemUiProxy;
@@ -29,6 +30,7 @@
import com.android.quickstep.fallback.window.RecentsDisplayModel;
import com.android.quickstep.logging.SettingsChangeLogger;
import com.android.quickstep.util.AsyncClockEventDelegate;
+import com.android.quickstep.util.ContextualSearchHapticManager;
import com.android.quickstep.util.ContextualSearchStateManager;
/**
@@ -57,10 +59,14 @@
RotationTouchHelper getRotationTouchHelper();
+ ContextualSearchHapticManager getContextualSearchHapticManager();
+
ContextualSearchStateManager getContextualSearchStateManager();
RecentsAnimationDeviceState getRecentsAnimationDeviceState();
+ RecentsModel getRecentsModel();
+
SettingsChangeLogger getSettingsChangeLogger();
SimpleOrientationTouchTransformer getSimpleOrientationTouchTransformer();
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt
index 31a1be8..95a3ec2 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt
@@ -50,10 +50,14 @@
DaggerSingletonObject<RecentsDisplayModel>(
QuickstepBaseAppComponent::getRecentsDisplayModel
)
+
+ @JvmStatic
+ fun enableOverviewInWindow() =
+ Flags.enableFallbackOverviewInWindow() || Flags.enableLauncherOverviewInWindow()
}
init {
- if (Flags.enableFallbackOverviewInWindow() || Flags.enableLauncherOverviewInWindow()) {
+ if (enableOverviewInWindow()) {
displayManager.registerDisplayListener(displayListener, Executors.MAIN_EXECUTOR.handler)
// In the scenario where displays were added before this display listener was
// registered, we should store the RecentsDisplayResources for those displays
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index c986b88..39a9dff 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -72,7 +72,8 @@
import com.android.quickstep.GestureState;
import com.android.quickstep.OverviewComponentObserver;
import com.android.quickstep.OverviewComponentObserver.OverviewChangeListener;
-import com.android.quickstep.TouchInteractionService.TISBinder;
+import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.TISBinder;
import com.android.quickstep.util.ActivityPreloadUtil;
import com.android.quickstep.util.LottieAnimationColorUtils;
import com.android.quickstep.util.TISBindHelper;
@@ -283,16 +284,13 @@
protected void onResume() {
super.onResume();
maybeResumeOrPauseBackgroundAnimation();
- TISBinder binder = mTISBindHelper.getBinder();
- if (binder != null) {
- setSetupUIVisible(true);
- binder.setSwipeUpProxy(this::createSwipeUpProxy);
- }
+ RecentsAnimationDeviceState.INSTANCE.get(this)
+ .setSwipeUpProxyProvider(this::createSwipeUpProxy);
+ setSetupUIVisible(true);
}
private void onTISConnected(TISBinder binder) {
setSetupUIVisible(isResumed());
- binder.setSwipeUpProxy(isResumed() ? this::createSwipeUpProxy : null);
TaskbarManager taskbarManager = binder.getTaskbarManager();
if (taskbarManager != null) {
mLauncherStartAnim = taskbarManager.createLauncherStartFromSuwAnim(MAX_SWIPE_DURATION);
@@ -306,7 +304,8 @@
@Override
protected void onPause() {
super.onPause();
- clearBinderOverride();
+ setSetupUIVisible(false);
+ RecentsAnimationDeviceState.INSTANCE.get(this).setSwipeUpProxyProvider(null);
maybeResumeOrPauseBackgroundAnimation();
if (mSwipeProgress.value >= 1) {
finishAndRemoveTask();
@@ -314,14 +313,6 @@
}
}
- private void clearBinderOverride() {
- TISBinder binder = mTISBindHelper.getBinder();
- if (binder != null) {
- setSetupUIVisible(false);
- binder.setSwipeUpProxy(null);
- }
- }
-
/**
* Should be called when we have successfully reached Launcher, so we dispatch to animation
* listeners to ensure the state matches the visual animation that just occurred.
@@ -338,8 +329,9 @@
protected void onDestroy() {
super.onDestroy();
getIDP().removeOnChangeListener(mOnIDPChangeListener);
+ // In case bindHelper was not connected during onPause, clear UIVisible flag again
+ setSetupUIVisible(false);
mTISBindHelper.onDestroy();
- clearBinderOverride();
if (mBackgroundAnimatorListener != null) {
mAnimatedBackground.removeAnimatorListener(mBackgroundAnimatorListener);
}
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
index 0365f89..2114c30 100644
--- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
@@ -38,10 +38,9 @@
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.logging.StatsLogManager;
-import com.android.quickstep.TouchInteractionService.TISBinder;
+import com.android.quickstep.RecentsAnimationDeviceState;
import com.android.quickstep.interaction.TutorialController.TutorialType;
import com.android.quickstep.util.LayoutUtils;
-import com.android.quickstep.util.TISBindHelper;
import java.util.ArrayList;
import java.util.Arrays;
@@ -64,7 +63,6 @@
private SharedPreferences mSharedPrefs;
private StatsLogManager mStatsLogManager;
- private TISBindHelper mTISBindHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -99,7 +97,6 @@
.commit();
correctUserOrientation();
- mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
initWindowInsets();
}
@@ -349,10 +346,6 @@
updateServiceState(true);
}
- private void onTISConnected(TISBinder binder) {
- updateServiceState(isResumed());
- }
-
@Override
protected void onPause() {
super.onPause();
@@ -360,16 +353,13 @@
}
private void updateServiceState(boolean isEnabled) {
- TISBinder binder = mTISBindHelper.getBinder();
- if (binder != null) {
- binder.setGestureBlockedTaskId(isEnabled ? getTaskId() : -1);
- }
+ RecentsAnimationDeviceState.INSTANCE.get(this)
+ .setGestureBlockingTaskId(isEnabled ? getTaskId() : -1);
}
@Override
protected void onDestroy() {
super.onDestroy();
- mTISBindHelper.onDestroy();
updateServiceState(false);
}
}
diff --git a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
index 002a4e8..b1a5920 100644
--- a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
@@ -66,14 +66,10 @@
)
tasks.value = MapForStateFlow(recentTasks)
- // Request data for completed tasks to prevent stale data.
- // This will prevent thumbnail and icon from being replaced and
- // null due to race condition.
- taskRequests.values.forEach { (taskKey, job) ->
- if (job.isCompleted) {
- requestTaskData(taskKey.id)
- }
- }
+ // Request data for tasks to prevent stale data.
+ // This will prevent thumbnail and icon from being replaced and null due to
+ // race condition. The new request will hit the cache and return immediately.
+ taskRequests.keys.forEach(::requestTaskData)
}
}
return tasks.map { it.values.toList() }
diff --git a/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt
index 162d14d..0b299ee 100644
--- a/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt
+++ b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt
@@ -77,8 +77,7 @@
.flowOn(dispatcherProvider.background)
fun bind(vararg taskId: TaskId) {
- Log.d(TAG, "bind: $taskId")
- taskIds.value = taskId.toSet()
+ taskIds.value = taskId.toSet().also { Log.d(TAG, "bind: $it") }
}
fun isThumbnailValid(thumbnail: ThumbnailData?, width: Int, height: Int): Boolean =
diff --git a/quickstep/src/com/android/quickstep/util/ContextualSearchHapticManager.kt b/quickstep/src/com/android/quickstep/util/ContextualSearchHapticManager.kt
index 286b77a..7ec605d 100644
--- a/quickstep/src/com/android/quickstep/util/ContextualSearchHapticManager.kt
+++ b/quickstep/src/com/android/quickstep/util/ContextualSearchHapticManager.kt
@@ -21,18 +21,25 @@
import android.os.VibrationEffect.Composition
import android.os.Vibrator
import com.android.launcher3.dagger.ApplicationContext
-import com.android.launcher3.util.MainThreadInitializedObject
-import com.android.launcher3.util.SafeCloseable
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.util.DaggerSingletonObject
import com.android.launcher3.util.VibratorWrapper
import com.android.quickstep.DeviceConfigWrapper.Companion.get
+import com.android.quickstep.dagger.QuickstepBaseAppComponent
+import javax.inject.Inject
import kotlin.math.pow
/** Manages haptics relating to Contextual Search invocations. */
+@LauncherAppSingleton
class ContextualSearchHapticManager
-internal constructor(@ApplicationContext private val context: Context) : SafeCloseable {
+@Inject
+internal constructor(
+ @ApplicationContext private val context: Context,
+ private val contextualSearchStateManager: ContextualSearchStateManager,
+ private val vibratorWrapper: VibratorWrapper,
+) {
private var searchEffect = createSearchEffect()
- private var contextualSearchStateManager = ContextualSearchStateManager.INSTANCE[context]
private fun createSearchEffect() =
if (
@@ -50,7 +57,7 @@
/** Indicates that search has been invoked. */
fun vibrateForSearch() {
- searchEffect.let { VibratorWrapper.INSTANCE[context].vibrate(it) }
+ searchEffect.let { vibratorWrapper.vibrate(it) }
}
/** Indicates that search will be invoked if the current gesture is maintained. */
@@ -93,13 +100,13 @@
composition.addPrimitive(Composition.PRIMITIVE_LOW_TICK, scale)
}
}
- VibratorWrapper.INSTANCE[context].vibrate(composition.compose())
+ vibratorWrapper.vibrate(composition.compose())
}
}
- override fun close() {}
-
companion object {
- @JvmField val INSTANCE = MainThreadInitializedObject { ContextualSearchHapticManager(it) }
+ @JvmField
+ val INSTANCE =
+ DaggerSingletonObject(QuickstepBaseAppComponent::getContextualSearchHapticManager)
}
}
diff --git a/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt b/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt
index d00a39c..969aa0f 100644
--- a/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt
+++ b/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt
@@ -21,7 +21,6 @@
import android.app.contextualsearch.ContextualSearchManager.FEATURE_CONTEXTUAL_SEARCH
import android.content.Context
import android.util.Log
-import androidx.annotation.VisibleForTesting
import com.android.internal.app.AssistUtils
import com.android.launcher3.logging.StatsLogManager
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_ASSISTANT_FAILED_SERVICE_ERROR
@@ -34,7 +33,7 @@
import com.android.quickstep.BaseContainerInterface
import com.android.quickstep.DeviceConfigWrapper
import com.android.quickstep.OverviewComponentObserver
-import com.android.quickstep.SystemUiProxy
+import com.android.quickstep.RecentsAnimationDeviceState
import com.android.quickstep.TopTaskTracker
import com.android.quickstep.views.RecentsView
import com.android.systemui.shared.system.QuickStepContract
@@ -45,9 +44,10 @@
private val context: Context,
private val contextualSearchStateManager: ContextualSearchStateManager,
private val topTaskTracker: TopTaskTracker,
- private val systemUiProxy: SystemUiProxy,
+ private val deviceState: RecentsAnimationDeviceState,
private val statsLogManager: StatsLogManager,
private val contextualSearchHapticManager: ContextualSearchHapticManager,
+ private val overviewComponentObserver: OverviewComponentObserver,
private val contextualSearchManager: ContextualSearchManager?,
) {
constructor(
@@ -56,9 +56,10 @@
context,
ContextualSearchStateManager.INSTANCE[context],
TopTaskTracker.INSTANCE[context],
- SystemUiProxy.INSTANCE[context],
+ RecentsAnimationDeviceState.INSTANCE[context],
StatsLogManager.newInstance(context),
ContextualSearchHapticManager.INSTANCE[context],
+ OverviewComponentObserver.INSTANCE[context],
context.getSystemService(ContextualSearchManager::class.java),
)
@@ -189,7 +190,7 @@
if (contextualSearchManager == null) {
return false
}
- val recentsContainerInterface = getRecentsContainerInterface()
+ val recentsContainerInterface = overviewComponentObserver.containerInterface
if (recentsContainerInterface?.isInLiveTileMode() == true) {
Log.i(TAG, "Contextual Search invocation attempted: live tile")
endLiveTileMode(recentsContainerInterface) {
@@ -202,27 +203,20 @@
}
private fun isFakeLandscape(): Boolean =
- getRecentsContainerInterface()
- ?.getCreatedContainer()
+ overviewComponentObserver.containerInterface
+ ?.createdContainer
?.getOverviewPanel<RecentsView<*, *>>()
- ?.getPagedOrientationHandler()
+ ?.pagedOrientationHandler
?.isLayoutNaturalToLauncher == false
- private fun isInSplitscreen(): Boolean {
- return topTaskTracker.getRunningSplitTaskIds().isNotEmpty()
- }
+ private fun isInSplitscreen(): Boolean = topTaskTracker.runningSplitTaskIds.isNotEmpty()
private fun isNotificationShadeShowing(): Boolean {
- return systemUiProxy.lastSystemUiStateFlags and SHADE_EXPANDED_SYSUI_FLAGS != 0L
+ return deviceState.systemUiStateFlags and SHADE_EXPANDED_SYSUI_FLAGS != 0L
}
private fun isKeyguardShowing(): Boolean {
- return systemUiProxy.lastSystemUiStateFlags and KEYGUARD_SHOWING_SYSUI_FLAGS != 0L
- }
-
- @VisibleForTesting
- fun getRecentsContainerInterface(): BaseContainerInterface<*, *>? {
- return OverviewComponentObserver.INSTANCE.get(context).containerInterface
+ return deviceState.systemUiStateFlags and KEYGUARD_SHOWING_SYSUI_FLAGS != 0L
}
/**
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 99255e8..88d14b7 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -65,6 +65,7 @@
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource
import com.android.launcher3.views.BaseDragLayer
import com.android.quickstep.TaskViewUtils
+import com.android.quickstep.util.SplitScreenUtils.Companion.extractTopParentAndChildren
import com.android.quickstep.views.FloatingAppPairView
import com.android.quickstep.views.FloatingTaskView
import com.android.quickstep.views.GroupedTaskView
@@ -981,35 +982,15 @@
progressUpdater.setDuration(QuickstepTransitionManager.APP_LAUNCH_DURATION)
progressUpdater.interpolator = Interpolators.EMPHASIZED
- var rootCandidate: Change? = null
-
- for (change in transitionInfo.changes) {
- val taskInfo: RunningTaskInfo = change.taskInfo ?: continue
-
- // TODO (b/316490565): Replace this logic when SplitBounds is available to
- // startAnimation() and we can know the precise taskIds of launching tasks.
- if (
- taskInfo.windowingMode == windowingMode &&
- (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT)
- ) {
- // Found one!
- rootCandidate = change
- break
- }
- }
-
- // If we could not find a proper root candidate, something went wrong.
- check(rootCandidate != null) { "Could not find a split root candidate" }
-
- // Recurse up the tree until parent is null, then we've found our root.
- var parentToken: WindowContainerToken? = rootCandidate.parent
- while (parentToken != null) {
- rootCandidate = transitionInfo.getChange(parentToken) ?: break
- parentToken = rootCandidate.parent
- }
-
- // Make sure nothing weird happened, like getChange() returning null.
- check(rootCandidate != null) { "Failed to find a root leash" }
+ val splitTree: Pair<Change, List<Change>>? = extractTopParentAndChildren(transitionInfo)
+ check(splitTree != null) { "Could not find a split root candidate" }
+ val rootCandidate = splitTree.first
+ val stageRootTaskIds: Set<Int> = splitTree.second
+ .map { it.taskInfo!!.taskId }
+ .toSet()
+ val leafTasks: List<Change> = transitionInfo.changes
+ .filter { it.taskInfo != null && it.taskInfo!!.parentTaskId in stageRootTaskIds}
+ .toList()
// Starting position is a 34% size tile centered in the middle of the screen.
// Ending position is the full device screen.
@@ -1043,6 +1024,29 @@
override fun onAnimationEnd(animation: Animator) {
finishCallback.run()
}
+
+ override fun onAnimationStart(animation: Animator) {
+ // Reset leaf and stage root tasks, animation can begin from freeform windows
+ for (leaf in leafTasks) {
+ val endAbsBounds = leaf.endAbsBounds
+
+ t.setAlpha(leaf.leash, 1f)
+ t.setCrop(leaf.leash, 0f, 0f,
+ endAbsBounds.width().toFloat(), endAbsBounds.height().toFloat())
+ t.setPosition(leaf.leash, 0f, 0f)
+ }
+
+ for (stageRoot in splitTree.second) {
+ val endAbsBounds = stageRoot.endAbsBounds
+
+ t.setAlpha(stageRoot.leash, 1f)
+ t.setCrop(stageRoot.leash, 0f, 0f,
+ endAbsBounds.width().toFloat(), endAbsBounds.height().toFloat())
+ t.setPosition(stageRoot.leash, endAbsBounds.left.toFloat(),
+ endAbsBounds.top.toFloat())
+ }
+ t.apply()
+ }
}
)
diff --git a/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt b/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
index 4005c5a..7787e30 100644
--- a/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
@@ -46,8 +46,8 @@
* Given a TransitionInfo, generates the tree structure for those changes and extracts out
* the top most root and it's two immediate children. Changes can be provided in any order.
*
- * @return a [Pair] where first -> top most split root, second -> [List] of 2,
- * leftTop/bottomRight stage roots
+ * @return null if no root is found, otherwise a [Pair] where first -> top most split root,
+ * second -> [List] of 2, leftTop/bottomRight stage roots
*/
fun extractTopParentAndChildren(
transitionInfo: TransitionInfo
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index 828322b..7d5b471 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -169,9 +169,6 @@
if (sourceRectHint.isEmpty()) {
mSourceRectHint.set(getEnterPipWithOverlaySrcRectHint(appBounds, aspectRatio));
- // Create a new overlay layer. We do not call detach on this instance, it's propagated
- // to other classes like PipTaskOrganizer / RecentsAnimationController to complete
- // the cleanup.
mPipContentOverlay = new PipContentOverlay.PipAppIconOverlay(view.getContext(),
mAppBounds, mDestinationBounds,
new IconProvider(context).getIcon(mActivityInfo), appIconSizePx);
diff --git a/quickstep/src/com/android/quickstep/util/TISBindHelper.java b/quickstep/src/com/android/quickstep/util/TISBindHelper.java
index 027dc08..3716efa 100644
--- a/quickstep/src/com/android/quickstep/util/TISBindHelper.java
+++ b/quickstep/src/com/android/quickstep/util/TISBindHelper.java
@@ -28,8 +28,8 @@
import com.android.launcher3.taskbar.TaskbarManager;
import com.android.quickstep.OverviewCommandHelper;
+import com.android.quickstep.TISBinder;
import com.android.quickstep.TouchInteractionService;
-import com.android.quickstep.TouchInteractionService.TISBinder;
import java.util.ArrayList;
import java.util.function.Consumer;
@@ -109,15 +109,6 @@
return mBinder == null ? null : mBinder.getTaskbarManager();
}
- /**
- * Sets flag whether a predictive back-to-home animation is in progress
- */
- public void setPredictiveBackToHomeInProgress(boolean isInProgress) {
- if (mBinder != null) {
- mBinder.setPredictiveBackToHomeInProgress(isInProgress);
- }
- }
-
@Nullable
public OverviewCommandHelper getOverviewCommandHelper() {
return mBinder == null ? null : mBinder.getOverviewCommandHelper();
diff --git a/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java b/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java
index ceffbe4..da26622 100644
--- a/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java
+++ b/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java
@@ -103,23 +103,28 @@
}
case DIRECTION_RIGHT: {
int boundedIndex =
- cycle ? (nextIndex < 0 ? maxSize - 1 : nextIndex) : Math.max(
- nextIndex, 0);
+ cycle ? (nextIndex < 0 ? maxSize - 1 : nextIndex)
+ : Math.max(nextIndex, 0);
boolean inOriginalTop = mOriginalTopRowIds.contains(currentPageTaskViewId);
return inOriginalTop ? mTopRowIds.get(boundedIndex)
: mBottomRowIds.get(boundedIndex);
}
case DIRECTION_TAB: {
int boundedIndex =
- cycle ? nextIndex < 0 ? maxSize - 1 : nextIndex % maxSize : Math.min(
- nextIndex, maxSize - 1);
+ cycle ? (nextIndex < 0 ? maxSize - 1 : nextIndex % maxSize)
+ : Math.min(nextIndex, maxSize - 1);
if (delta >= 0) {
return inTop && mTopRowIds.get(index) != mBottomRowIds.get(index)
? mBottomRowIds.get(index)
: mTopRowIds.get(boundedIndex);
} else {
if (mTopRowIds.contains(currentPageTaskViewId)) {
- return mBottomRowIds.get(boundedIndex);
+ if (boundedIndex < 0) {
+ // If no cycling, always return the first task.
+ return mTopRowIds.get(0);
+ } else {
+ return mBottomRowIds.get(boundedIndex);
+ }
} else {
// Go up to top if there is task above
return mTopRowIds.get(index) != mBottomRowIds.get(index)
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
index bb6829a..02be373 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -53,6 +53,7 @@
import com.android.quickstep.recents.di.get
import com.android.quickstep.recents.domain.model.DesktopTaskBoundsData
import com.android.quickstep.recents.ui.viewmodel.DesktopTaskViewModel
+import com.android.quickstep.recents.ui.viewmodel.TaskData
import com.android.quickstep.task.thumbnail.TaskThumbnailView
import com.android.quickstep.util.RecentsOrientedState
import com.android.systemui.shared.recents.model.Task
@@ -364,6 +365,10 @@
taskContainer.snapshotView.contentDescription = taskContainer.task.titleDescription
}
+ override fun setIconState(container: TaskContainer, state: TaskData?) {
+ container.snapshotView.contentDescription = (state as? TaskData.Data)?.titleDescription
+ }
+
// Ignoring [onIconUnloaded] as all tasks shares the same Desktop icon
override fun onIconUnloaded(taskContainer: TaskContainer) {}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index a76ebdb..51980f0 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -6379,7 +6379,7 @@
}
/**
- * @return true if the task in on the top of the grid
+ * @return true if the task in on the bottom of the grid
*/
public boolean isOnGridBottomRow(TaskView taskView) {
return showAsGrid()
@@ -6942,7 +6942,8 @@
* Creates the spring animations which run as a task settles back into its place in overview.
*
* <p>When a task dismiss is cancelled, the task will return to its original position via a
- * spring animation.
+ * spring animation. As it passes the threshold of its settling state, its neighbors will
+ * spring in response to the perceived impact of the settling task.
*/
public SpringAnimation createTaskDismissSettlingSpringAnimation(TaskView draggedTaskView,
float velocity, boolean isDismissing, SingleAxisSwipeDetector detector,
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
index f610335..d37a3f9 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
@@ -20,6 +20,7 @@
import android.view.View
import androidx.core.view.children
import androidx.dynamicanimation.animation.FloatPropertyCompat
+import androidx.dynamicanimation.animation.FloatValueHolder
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
@@ -29,6 +30,7 @@
import com.android.launcher3.util.DynamicResource
import com.android.launcher3.util.IntArray
import com.android.quickstep.util.GroupTask
+import com.android.quickstep.util.TaskGridNavHelper
import com.android.quickstep.util.isExternalDisplay
import com.android.quickstep.views.RecentsView.RUNNING_TASK_ATTACH_ALPHA
import com.android.systemui.shared.recents.model.ThumbnailData
@@ -305,7 +307,8 @@
* Creates the spring animations which run when a dragged task view in overview is released.
*
* <p>When a task dismiss is cancelled, the task will return to its original position via a
- * spring animation.
+ * spring animation. As it passes the threshold of its settling state, its neighbors will spring
+ * in response to the perceived impact of the settling task.
*/
fun createTaskDismissSettlingSpringAnimation(
draggedTaskView: TaskView?,
@@ -320,37 +323,181 @@
FloatPropertyCompat.createFloatPropertyCompat(
draggedTaskView.secondaryDismissTranslationProperty
)
- val rp = DynamicResource.provider(recentsView.mContainer)
- return SpringAnimation(draggedTaskView, taskDismissFloatProperty)
- .setSpring(
- SpringForce()
- .setDampingRatio(rp.getFloat(R.dimen.dismiss_task_trans_y_damping_ratio))
- .setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_y_stiffness))
- )
- .setStartVelocity(if (detector.isFling(velocity)) velocity else 0f)
- .addUpdateListener { animation, value, _ ->
- if (isDismissing && abs(value) >= abs(dismissLength)) {
- // TODO(b/393553524): Remove 0 alpha, instead animate task fully off screen.
- draggedTaskView.alpha = 0f
- animation.cancel()
- } else if (draggedTaskView.isRunningTask && recentsView.enableDrawingLiveTile) {
- recentsView.runActionOnRemoteHandles { remoteTargetHandle ->
- remoteTargetHandle.taskViewSimulator.taskSecondaryTranslation.value =
- taskDismissFloatProperty.getValue(draggedTaskView)
+ // Animate dragged task towards dismissal or rest state.
+ val draggedTaskViewSpringAnimation =
+ SpringAnimation(draggedTaskView, taskDismissFloatProperty)
+ .setSpring(createExpressiveDismissSpringForce())
+ .setStartVelocity(if (detector.isFling(velocity)) velocity else 0f)
+ .addUpdateListener { animation, value, _ ->
+ if (isDismissing && abs(value) >= abs(dismissLength)) {
+ // TODO(b/393553524): Remove 0 alpha, instead animate task fully off screen.
+ draggedTaskView.alpha = 0f
+ animation.cancel()
+ } else if (draggedTaskView.isRunningTask && recentsView.enableDrawingLiveTile) {
+ recentsView.runActionOnRemoteHandles { remoteTargetHandle ->
+ remoteTargetHandle.taskViewSimulator.taskSecondaryTranslation.value =
+ taskDismissFloatProperty.getValue(draggedTaskView)
+ }
+ recentsView.redrawLiveTile()
}
- recentsView.redrawLiveTile()
}
+ .addEndListener { _, _, _, _ ->
+ if (isDismissing) {
+ recentsView.dismissTask(
+ draggedTaskView,
+ /* animateTaskView = */ false,
+ /* removeTask = */ true,
+ )
+ } else {
+ recentsView.onDismissAnimationEnds()
+ }
+ onEndRunnable()
+ }
+ if (!isDismissing) {
+ addNeighboringSpringAnimationsForDismissCancel(
+ draggedTaskView,
+ draggedTaskViewSpringAnimation,
+ recentsView.pageCount,
+ )
+ }
+ return draggedTaskViewSpringAnimation
+ }
+
+ private fun addNeighboringSpringAnimationsForDismissCancel(
+ draggedTaskView: TaskView,
+ draggedTaskViewSpringAnimation: SpringAnimation,
+ taskCount: Int,
+ ) {
+ // Empty spring animation exists for conditional start, and to drive neighboring springs.
+ val neighborsToSettle =
+ SpringAnimation(FloatValueHolder()).setSpring(createExpressiveDismissSpringForce())
+ var lastPosition = 0f
+ var startSettling = false
+ draggedTaskViewSpringAnimation.addUpdateListener { _, value, velocity ->
+ // Start the settling animation the first time the dragged task passes the origin (from
+ // negative displacement to positive displacement). We do not check for an exact value
+ // to compare to, as the update listener does not necessarily hit every value (e.g. a
+ // value of zero). Do not check again once it has started settling, as a spring can
+ // bounce past the origin multiple times depending on the stifness and damping ratio.
+ if (startSettling) return@addUpdateListener
+ if (lastPosition < 0 && value >= 0) {
+ startSettling = true
}
- .addEndListener { _, _, _, _ ->
- if (isDismissing) {
- recentsView.dismissTask(
- draggedTaskView,
- /* animateTaskView = */ false,
- /* removeTask = */ true,
+ lastPosition = value
+ if (startSettling) {
+ neighborsToSettle.setStartVelocity(velocity).animateToFinalPosition(0f)
+ }
+ }
+
+ // Add tasks before dragged index, fanning out from the dragged task.
+ // The order they are added matters, as each spring drives the next.
+ var previousNeighbor = neighborsToSettle
+ getTasksAdjacentToDraggedTask(draggedTaskView, towardsStart = true).forEach {
+ previousNeighbor = createNeighboringTaskViewSpringAnimation(it, previousNeighbor)
+ }
+ // Add tasks after dragged index, fanning out from the dragged task.
+ // The order they are added matters, as each spring drives the next.
+ previousNeighbor = neighborsToSettle
+ getTasksAdjacentToDraggedTask(draggedTaskView, towardsStart = false).forEach {
+ previousNeighbor = createNeighboringTaskViewSpringAnimation(it, previousNeighbor)
+ }
+ }
+
+ /** Gets adjacent tasks either before or after the dragged task in visual order. */
+ private fun getTasksAdjacentToDraggedTask(
+ draggedTaskView: TaskView,
+ towardsStart: Boolean,
+ ): Sequence<TaskView> {
+ if (recentsView.showAsGrid()) {
+ return gridTaskViewInTabOrderSequence(draggedTaskView, towardsStart)
+ } else {
+ val taskViewList = taskViews.toList()
+ val draggedTaskViewIndex = taskViewList.indexOf(draggedTaskView)
+
+ return if (towardsStart) {
+ taskViewList.take(draggedTaskViewIndex).reversed().asSequence()
+ } else {
+ taskViewList.takeLast(taskViewList.size - draggedTaskViewIndex - 1).asSequence()
+ }
+ }
+ }
+
+ /**
+ * Returns a sequence of TaskViews in the grid, ordered according to tab navigation, starting
+ * from the dragged TaskView, in the direction of the provided delta.
+ *
+ * <p>A positive delta moves forward in the tab order towards the end of the grid, while a
+ * negative value moves backward towards the beginning.
+ */
+ private fun gridTaskViewInTabOrderSequence(
+ draggedTaskView: TaskView,
+ towardsStart: Boolean,
+ ): Sequence<TaskView> = sequence {
+ val taskGridNavHelper =
+ TaskGridNavHelper(
+ recentsView.topRowIdArray,
+ recentsView.bottomRowIdArray,
+ getLargeTaskViewIds(),
+ /* hasAddDesktopButton= */ false,
+ )
+ var nextTaskView: TaskView? = draggedTaskView
+ var previousTaskView: TaskView? = null
+ while (nextTaskView != previousTaskView && nextTaskView != null) {
+ previousTaskView = nextTaskView
+ nextTaskView =
+ recentsView.getTaskViewFromTaskViewId(
+ taskGridNavHelper.getNextGridPage(
+ nextTaskView.taskViewId,
+ if (towardsStart) -1 else 1,
+ TaskGridNavHelper.DIRECTION_TAB,
+ /* cycle = */ false,
)
- }
- onEndRunnable()
+ )
+ if (nextTaskView != null && nextTaskView != previousTaskView) {
+ yield(nextTaskView)
}
+ }
+ }
+
+ /** Creates a neighboring task view spring, driven by the spring of its neighbor. */
+ private fun createNeighboringTaskViewSpringAnimation(
+ taskView: TaskView,
+ previousNeighborSpringAnimation: SpringAnimation,
+ ): SpringAnimation {
+ val neighboringTaskViewSpringAnimation =
+ SpringAnimation(
+ taskView,
+ FloatPropertyCompat.createFloatPropertyCompat(
+ taskView.secondaryDismissTranslationProperty
+ ),
+ )
+ .setSpring(createExpressiveDismissSpringForce())
+ // Update live tile on spring animation.
+ if (taskView.isRunningTask && recentsView.enableDrawingLiveTile) {
+ neighboringTaskViewSpringAnimation.addUpdateListener { _, _, _ ->
+ recentsView.runActionOnRemoteHandles { remoteTargetHandle ->
+ remoteTargetHandle.taskViewSimulator.taskSecondaryTranslation.value =
+ taskView.secondaryDismissTranslationProperty.get(taskView)
+ }
+ recentsView.redrawLiveTile()
+ }
+ }
+ // Drive current neighbor's spring with the previous neighbor's.
+ previousNeighborSpringAnimation.addUpdateListener { _, value, _ ->
+ neighboringTaskViewSpringAnimation.animateToFinalPosition(value)
+ }
+ return neighboringTaskViewSpringAnimation
+ }
+
+ private fun createExpressiveDismissSpringForce(): SpringForce {
+ val resourceProvider = DynamicResource.provider(recentsView.mContainer)
+ return SpringForce()
+ .setDampingRatio(
+ resourceProvider.getFloat(R.dimen.expressive_dismiss_task_trans_y_damping_ratio)
+ )
+ .setStiffness(
+ resourceProvider.getFloat(R.dimen.expressive_dismiss_task_trans_y_stiffness)
+ )
}
companion object {
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 5093259..609262f 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -81,6 +81,7 @@
import com.android.quickstep.recents.di.RecentsDependencies
import com.android.quickstep.recents.di.get
import com.android.quickstep.recents.di.inject
+import com.android.quickstep.recents.ui.viewmodel.TaskData
import com.android.quickstep.recents.ui.viewmodel.TaskTileUiState
import com.android.quickstep.recents.ui.viewmodel.TaskViewModel
import com.android.quickstep.util.ActiveGestureErrorDetector
@@ -773,12 +774,17 @@
// Updating containers
val mapOfTasks = state.tasks.associateBy { it.taskId }
taskContainers.forEach { container ->
+ val containerState = mapOfTasks[container.task.key.id]
container.setState(
- state = mapOfTasks[container.task.key.id],
+ state = containerState,
liveTile = state.isLiveTile,
hasHeader = type == TaskViewType.DESKTOP,
)
updateThumbnailValidity(container)
+
+ if (enableOverviewIconMenu()) {
+ setIconState(container, containerState)
+ }
}
}
@@ -1028,7 +1034,7 @@
}
}
}
- if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
+ if (needsUpdate(changes, FLAG_UPDATE_ICON) && !enableOverviewIconMenu()) {
taskContainers.forEach {
if (visible) {
recentsModel.iconCache
@@ -1059,10 +1065,23 @@
pendingIconLoadRequests.clear()
}
+ protected open fun setIconState(container: TaskContainer, state: TaskData?) {
+ if (enableOverviewIconMenu()) {
+ if (state is TaskData.Data) {
+ setIcon(container.iconView, state.icon)
+ container.iconView.setText(state.title)
+ container.digitalWellBeingToast?.initialize()
+ } else {
+ setIcon(container.iconView, null)
+ container.iconView.setText(null)
+ }
+ }
+ }
+
protected open fun onIconLoaded(taskContainer: TaskContainer) {
setIcon(taskContainer.iconView, taskContainer.task.icon)
if (enableOverviewIconMenu()) {
- setText(taskContainer.iconView, taskContainer.task.title)
+ taskContainer.iconView.setText(taskContainer.task.title)
}
taskContainer.digitalWellBeingToast?.initialize()
}
@@ -1070,7 +1089,7 @@
protected open fun onIconUnloaded(taskContainer: TaskContainer) {
setIcon(taskContainer.iconView, null)
if (enableOverviewIconMenu()) {
- setText(taskContainer.iconView, null)
+ taskContainer.iconView.setText(null)
}
}
@@ -1095,10 +1114,6 @@
}
}
- protected fun setText(iconView: TaskViewIcon, text: CharSequence?) {
- iconView.setText(text)
- }
-
@JvmOverloads
open fun setShouldShowScreenshot(
shouldShowScreenshot: Boolean,
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt
index 785e585..50d6aff 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt
@@ -22,6 +22,7 @@
import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING
import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_IN_LAUNCHER
import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_TOUCHING
+import com.android.launcher3.taskbar.rules.SandboxParams
import com.android.launcher3.taskbar.rules.TaskbarModeRule
import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.TRANSIENT
import com.android.launcher3.taskbar.rules.TaskbarModeRule.TaskbarMode
@@ -46,15 +47,15 @@
@get:Rule(order = 0)
val context =
- TaskbarWindowSandboxContext.create { builder ->
- builder.bindSystemUiProxy(
+ TaskbarWindowSandboxContext.create(
+ SandboxParams({
spy(SystemUiProxy(ApplicationProvider.getApplicationContext())) { proxy ->
doAnswer { latestSuspendNotification = it.getArgument(0) }
.whenever(proxy)
.notifyTaskbarAutohideSuspend(anyOrNull())
}
- )
- }
+ })
+ )
@get:Rule(order = 1) val animatorTestRule = AnimatorTestRule(this)
@get:Rule(order = 2) val taskbarModeRule = TaskbarModeRule(context)
@get:Rule(order = 3) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
index 9ca8a1b..bfd53ef 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
@@ -26,21 +26,29 @@
import com.android.launcher3.Flags.FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR
import com.android.launcher3.Flags.FLAG_TASKBAR_OVERFLOW
import com.android.launcher3.R
+import com.android.launcher3.dagger.LauncherAppSingleton
import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
import com.android.launcher3.taskbar.TaskbarViewTestUtil.createHotseatItems
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController
+import com.android.launcher3.taskbar.rules.DisplayControllerModule
+import com.android.launcher3.taskbar.rules.MockedRecentsModelHelper
import com.android.launcher3.taskbar.rules.MockedRecentsModelTestRule
+import com.android.launcher3.taskbar.rules.SandboxParams
import com.android.launcher3.taskbar.rules.TaskbarModeRule
import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.PINNED
import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.TRANSIENT
import com.android.launcher3.taskbar.rules.TaskbarModeRule.TaskbarMode
+import com.android.launcher3.taskbar.rules.TaskbarSandboxComponent
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
+import com.android.launcher3.util.AllModulesForTest
+import com.android.launcher3.util.FakePrefsModule
import com.android.launcher3.util.LauncherMultivalentJUnit
import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
import com.android.launcher3.util.TestUtil.getOnUiThread
+import com.android.quickstep.RecentsModel
import com.android.quickstep.SystemUiProxy
import com.android.quickstep.util.DesktopTask
import com.android.systemui.shared.recents.model.Task
@@ -49,6 +57,8 @@
import com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR
import com.android.wm.shell.desktopmode.IDesktopTaskListener
import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -70,19 +80,25 @@
class TaskbarOverflowTest {
@get:Rule(order = 0) val setFlagsRule = SetFlagsRule()
+ val mockRecentsModelHelper: MockedRecentsModelHelper = MockedRecentsModelHelper()
+
@get:Rule(order = 1)
val context =
- TaskbarWindowSandboxContext.create { builder ->
- builder.bindSystemUiProxy(
- spy(SystemUiProxy(ApplicationProvider.getApplicationContext())) { proxy ->
- doAnswer { desktopTaskListener = it.getArgument(0) }
- .whenever(proxy)
- .setDesktopTaskListener(anyOrNull())
- }
+ TaskbarWindowSandboxContext.create(
+ SandboxParams(
+ {
+ spy(SystemUiProxy(ApplicationProvider.getApplicationContext())) { proxy ->
+ doAnswer { desktopTaskListener = it.getArgument(0) }
+ .whenever(proxy)
+ .setDesktopTaskListener(anyOrNull())
+ }
+ },
+ DaggerTaskbarOverflowComponent.builder()
+ .bindRecentsModel(mockRecentsModelHelper.mockRecentsModel),
)
- }
+ )
- @get:Rule(order = 2) val recentsModel = MockedRecentsModelTestRule(context)
+ @get:Rule(order = 2) val recentsModel = MockedRecentsModelTestRule(mockRecentsModelHelper)
@get:Rule(order = 3) val taskbarModeRule = TaskbarModeRule(context)
@@ -404,3 +420,18 @@
return maxNumIconViews
}
}
+
+/** TaskbarOverflowComponent used to bind the RecentsModel. */
+@LauncherAppSingleton
+@Component(
+ modules = [AllModulesForTest::class, FakePrefsModule::class, DisplayControllerModule::class]
+)
+interface TaskbarOverflowComponent : TaskbarSandboxComponent {
+
+ @Component.Builder
+ interface Builder : TaskbarSandboxComponent.Builder {
+ @BindsInstance fun bindRecentsModel(model: RecentsModel): Builder
+
+ override fun build(): TaskbarOverflowComponent
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt
index 360f019..ba53dcd 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt
@@ -25,6 +25,7 @@
import androidx.test.core.app.ApplicationProvider
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController
+import com.android.launcher3.taskbar.rules.SandboxParams
import com.android.launcher3.taskbar.rules.TaskbarModeRule
import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.PINNED
import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.TRANSIENT
@@ -55,13 +56,14 @@
@get:Rule(order = 0) val setFlagsRule = SetFlagsRule()
@get:Rule(order = 1)
val context =
- TaskbarWindowSandboxContext.create { builder ->
- builder.bindSystemUiProxy(
+ TaskbarWindowSandboxContext.create(
+ SandboxParams({
spy(SystemUiProxy(ApplicationProvider.getApplicationContext())) {
doAnswer { backPressed = true }.whenever(it).onBackEvent(anyOrNull())
}
- )
- }
+ })
+ )
+
@get:Rule(order = 2) val taskbarModeRule = TaskbarModeRule(context)
@get:Rule(order = 3) val animatorTestRule = AnimatorTestRule(this)
@get:Rule(order = 4) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt
index 021e1e4..8395d79 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt
@@ -50,8 +50,11 @@
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.UserSetupMode
import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
import com.android.launcher3.util.LauncherMultivalentJUnit
import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
+import com.android.launcher3.util.TestUtil
+import com.android.quickstep.RecentsAnimationDeviceState
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE
import com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR
@@ -60,6 +63,8 @@
import org.junit.After
import org.junit.Rule
import org.junit.Test
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
import org.junit.runner.RunWith
@RunWith(LauncherMultivalentJUnit::class)
@@ -68,7 +73,16 @@
class TaskbarStashControllerTest {
@get:Rule(order = 0) val setFlagsRule = SetFlagsRule()
@get:Rule(order = 1) val context = TaskbarWindowSandboxContext.create()
- @get:Rule(order = 2) val taskbarModeRule = TaskbarModeRule(context)
+ @get:Rule(order = 2)
+ val initSingleton =
+ object : TestWatcher() {
+ override fun starting(description: Description?) {
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {
+ RecentsAnimationDeviceState.INSTANCE[context].systemUiStateFlags = 0
+ }
+ }
+ }
+ @get:Rule(order = 3) val taskbarModeRule = TaskbarModeRule(context)
@get:Rule(order = 4) val animatorTestRule = AnimatorTestRule(this)
@get:Rule(order = 5) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelHelper.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelHelper.kt
new file mode 100644
index 0000000..a7bfa9a
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelHelper.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.rules
+
+import com.android.quickstep.RecentsModel
+import com.android.quickstep.RecentsModel.RecentTasksChangedListener
+import com.android.quickstep.TaskIconCache
+import com.android.quickstep.util.GroupTask
+import java.util.function.Consumer
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+
+/** Helper class to mock the {@link RecentsModel} object in test */
+class MockedRecentsModelHelper {
+ private val mockIconCache: TaskIconCache = mock()
+ var taskListId = 0
+ var recentTasksChangedListener: RecentTasksChangedListener? = null
+ var taskRequests: MutableList<(List<GroupTask>) -> Unit> = mutableListOf()
+
+ val mockRecentsModel: RecentsModel = mock {
+ on { iconCache } doReturn mockIconCache
+
+ on { unregisterRecentTasksChangedListener() } doAnswer { recentTasksChangedListener = null }
+
+ on { registerRecentTasksChangedListener(any<RecentTasksChangedListener>()) } doAnswer
+ {
+ recentTasksChangedListener = it.getArgument<RecentTasksChangedListener>(0)
+ }
+
+ on { getTasks(anyOrNull(), anyOrNull()) } doAnswer
+ {
+ val request = it.getArgument<Consumer<List<GroupTask>>?>(0)
+ if (request != null) {
+ taskRequests.add { response -> request.accept(response) }
+ }
+ taskListId
+ }
+
+ on { getTasks(anyOrNull()) } doAnswer
+ {
+ val request = it.getArgument<Consumer<List<GroupTask>>?>(0)
+ if (request != null) {
+ taskRequests.add { response -> request.accept(response) }
+ }
+ taskListId
+ }
+
+ on { isTaskListValid(any()) } doAnswer { taskListId == it.getArgument(0) }
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelTestRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelTestRule.kt
index ed1443d..359b876 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelTestRule.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelTestRule.kt
@@ -16,64 +16,17 @@
package com.android.launcher3.taskbar.rules
-import com.android.quickstep.RecentsModel
-import com.android.quickstep.RecentsModel.RecentTasksChangedListener
-import com.android.quickstep.TaskIconCache
import com.android.quickstep.util.GroupTask
-import java.util.function.Consumer
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
-import org.mockito.kotlin.any
-import org.mockito.kotlin.anyOrNull
-import org.mockito.kotlin.doAnswer
-import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.mock
-class MockedRecentsModelTestRule(private val context: TaskbarWindowSandboxContext) : TestRule {
-
- private val mockIconCache: TaskIconCache = mock()
-
- private val mockRecentsModel: RecentsModel = mock {
- on { iconCache } doReturn mockIconCache
-
- on { unregisterRecentTasksChangedListener() } doAnswer { recentTasksChangedListener = null }
-
- on { registerRecentTasksChangedListener(any<RecentTasksChangedListener>()) } doAnswer
- {
- recentTasksChangedListener = it.getArgument<RecentTasksChangedListener>(0)
- }
-
- on { getTasks(anyOrNull(), anyOrNull()) } doAnswer
- {
- val request = it.getArgument<Consumer<List<GroupTask>>?>(0)
- if (request != null) {
- taskRequests.add { response -> request.accept(response) }
- }
- taskListId
- }
-
- on { getTasks(anyOrNull()) } doAnswer
- {
- val request = it.getArgument<Consumer<List<GroupTask>>?>(0)
- if (request != null) {
- taskRequests.add { response -> request.accept(response) }
- }
- taskListId
- }
-
- on { isTaskListValid(any()) } doAnswer { taskListId == it.getArgument(0) }
- }
-
+class MockedRecentsModelTestRule(private val modelHelper: MockedRecentsModelHelper) : TestRule {
private var recentTasks: List<GroupTask> = emptyList()
- private var taskListId = 0
- private var recentTasksChangedListener: RecentTasksChangedListener? = null
- private var taskRequests: MutableList<(List<GroupTask>) -> Unit> = mutableListOf()
override fun apply(base: Statement?, description: Description?): Statement {
return object : Statement() {
override fun evaluate() {
- context.putObject(RecentsModel.INSTANCE, mockRecentsModel)
base?.evaluate()
}
}
@@ -82,15 +35,15 @@
// NOTE: For the update to take effect, `resolvePendingTaskRequests()` needs to be called, so
// calbacks to any pending `RecentsModel.getTasks()` get called with the updated task list.
fun updateRecentTasks(tasks: List<GroupTask>) {
- ++taskListId
+ ++modelHelper.taskListId
recentTasks = tasks
- recentTasksChangedListener?.onRecentTasksChanged()
+ modelHelper.recentTasksChangedListener?.onRecentTasksChanged()
}
fun resolvePendingTaskRequests() {
val requests = mutableListOf<(List<GroupTask>) -> Unit>()
- requests.addAll(taskRequests)
- taskRequests.clear()
+ requests.addAll(modelHelper.taskRequests)
+ modelHelper.taskRequests.clear()
requests.forEach { it(recentTasks) }
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
index 90c9553..0204b2d 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
@@ -33,6 +33,7 @@
import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
import com.android.launcher3.util.TestUtil
import com.android.quickstep.AllAppsActionManager
+import com.android.quickstep.fallback.window.RecentsDisplayModel
import java.lang.reflect.Field
import java.lang.reflect.ParameterizedType
import java.util.Locale
@@ -110,6 +111,7 @@
PendingIntent(IIntentSender.Default())
},
object : TaskbarNavButtonCallbacks {},
+ RecentsDisplayModel.INSTANCE.get(context),
) {
override fun recreateTaskbar() {
super.recreateTaskbar()
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
index e6dc2a2..95e8980 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
@@ -47,10 +47,6 @@
import org.junit.runner.Description
import org.junit.runners.model.Statement
-/** Include additional bindings when building a [TaskbarSandboxComponent]. */
-typealias TaskbarComponentBinder =
- TaskbarWindowSandboxContext.(TaskbarSandboxComponent.Builder) -> Unit
-
/**
* [SandboxApplication] for running Taskbar tests.
*
@@ -61,7 +57,7 @@
private constructor(
private val base: SandboxApplication,
val virtualDisplay: VirtualDisplay,
- private val componentBinder: TaskbarComponentBinder?,
+ private val params: SandboxParams,
) : ContextWrapper(base), ObjectSandbox by base, TestRule {
val settingsCacheSandbox = SettingsCacheSandbox()
@@ -76,10 +72,9 @@
override fun before() {
val context = this@TaskbarWindowSandboxContext
val builder =
- DaggerTaskbarSandboxComponent.builder()
- .bindSystemUiProxy(SystemUiProxy(context))
+ params.builderBase
+ .bindSystemUiProxy(params.systemUiProxyProvider.invoke(context))
.bindSettingsCache(settingsCacheSandbox.cache)
- componentBinder?.invoke(context, builder)
base.initDaggerComponent(builder)
}
}
@@ -95,10 +90,9 @@
private const val VIRTUAL_DISPLAY_NAME = "TaskbarSandboxDisplay"
/** Creates a [SandboxApplication] for Taskbar tests. */
- fun create(componentBinder: TaskbarComponentBinder? = null): TaskbarWindowSandboxContext {
+ fun create(params: SandboxParams = SandboxParams()): TaskbarWindowSandboxContext {
val base = ApplicationProvider.getApplicationContext<Context>()
val displayManager = checkNotNull(base.getSystemService(DisplayManager::class.java))
-
// Create virtual display to avoid clashing with Taskbar on default display.
val virtualDisplay =
base.resources.displayMetrics.let {
@@ -115,7 +109,7 @@
return TaskbarWindowSandboxContext(
SandboxApplication(base.createDisplayContext(virtualDisplay.display)),
virtualDisplay,
- componentBinder,
+ params,
)
}
}
@@ -157,3 +151,9 @@
override fun build(): TaskbarSandboxComponent
}
}
+
+/** Include additional bindings when building a [TaskbarSandboxComponent]. */
+data class SandboxParams(
+ val systemUiProxyProvider: (Context) -> SystemUiProxy = { SystemUiProxy(it) },
+ val builderBase: TaskbarSandboxComponent.Builder = DaggerTaskbarSandboxComponent.builder(),
+)
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/OverviewCommandHelperTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/OverviewCommandHelperTest.kt
index 0ae710f..56c01f9 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/OverviewCommandHelperTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/OverviewCommandHelperTest.kt
@@ -68,7 +68,10 @@
touchInteractionService = mock(),
overviewComponentObserver = mock(),
taskAnimationManager = mock(),
- dispatcherProvider = TestDispatcherProvider(dispatcher)
+ dispatcherProvider = TestDispatcherProvider(dispatcher),
+ recentsDisplayModel = mock(),
+ focusState = mock(),
+ taskbarManager = mock(),
)
)
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
index c399bdb..8b17958 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
@@ -97,6 +97,8 @@
// Set desktop mode supported
when(mContext.getResources()).thenReturn(mResources);
when(mResources.getBoolean(R.bool.config_isDesktopModeSupported)).thenReturn(true);
+ when(mResources.getBoolean(R.bool.config_canInternalDisplayHostDesktops))
+ .thenReturn(true);
mRecentTasksList = new RecentTasksList(mContext, mockMainThreadExecutor,
mockKeyguardManager, mSystemUiProxy, mTopTaskTracker);
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
index b652ee8..63115bf 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
@@ -150,7 +150,7 @@
allSysUiStates().forEach { state ->
val canStartGesture = !disablingStates.contains(state)
- underTest.setSystemUiFlags(state)
+ underTest.systemUiStateFlags = state
assertThat(underTest.canStartTrackpadGesture()).isEqualTo(canStartGesture)
}
}
@@ -166,7 +166,7 @@
)
stateToExpectedResult.forEach { (state, allowed) ->
- underTest.setSystemUiFlags(state)
+ underTest.systemUiStateFlags = state
assertThat(underTest.canStartTrackpadGesture()).isEqualTo(allowed)
}
}
@@ -177,7 +177,7 @@
allSysUiStates().forEach { state ->
val canStartGesture = !disablingStates.contains(state)
- underTest.setSystemUiFlags(state)
+ underTest.systemUiStateFlags = state
assertThat(underTest.canStartSystemGesture()).isEqualTo(canStartGesture)
}
}
@@ -197,7 +197,7 @@
)
stateToExpectedResult.forEach { (state, gestureAllowed) ->
- underTest.setSystemUiFlags(state)
+ underTest.systemUiStateFlags = state
assertThat(underTest.canStartSystemGesture()).isEqualTo(gestureAllowed)
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java
index 722e1da..2eb2e4c 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java
@@ -43,6 +43,7 @@
import com.android.launcher3.R;
import com.android.launcher3.graphics.ThemeManager;
import com.android.launcher3.icons.IconProvider;
+import com.android.launcher3.util.DaggerSingletonTracker;
import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.quickstep.util.GroupTask;
@@ -109,7 +110,7 @@
mRecentsModel = new RecentsModel(mContext, mTasksList, mock(TaskIconCache.class),
mThumbnailCache, mock(IconProvider.class), mock(TaskStackChangeListeners.class),
- mLockedUserState, () -> mThemeManager);
+ mLockedUserState, () -> mThemeManager, mock(DaggerSingletonTracker.class));
mResource = mock(Resources.class);
when(mResource.getInteger((R.integer.recentsThumbnailCacheSize))).thenReturn(3);
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskAnimationManagerTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskAnimationManagerTest.java
index 6e9885a..b730f5b 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskAnimationManagerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskAnimationManagerTest.java
@@ -54,12 +54,7 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
mTaskAnimationManager = new TaskAnimationManager(mContext,
- RecentsAnimationDeviceState.INSTANCE.get(mContext)) {
- @Override
- SystemUiProxy getSystemUiProxy() {
- return mSystemUiProxy;
- }
- };
+ RecentsAnimationDeviceState.INSTANCE.get(mContext), mSystemUiProxy);
}
@Test
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ContextualSearchInvokerTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ContextualSearchInvokerTest.java
index 61971b1..4e49aec 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ContextualSearchInvokerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ContextualSearchInvokerTest.java
@@ -50,7 +50,8 @@
import com.android.launcher3.logging.StatsLogManager;
import com.android.quickstep.BaseContainerInterface;
import com.android.quickstep.DeviceConfigWrapper;
-import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.OverviewComponentObserver;
+import com.android.quickstep.RecentsAnimationDeviceState;
import com.android.quickstep.TopTaskTracker;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.views.RecentsView;
@@ -75,7 +76,7 @@
private @Mock PackageManager mMockPackageManager;
private @Mock ContextualSearchStateManager mMockStateManager;
private @Mock TopTaskTracker mMockTopTaskTracker;
- private @Mock SystemUiProxy mMockSystemUiProxy;
+ private @Mock RecentsAnimationDeviceState mMockDeviceState;
private @Mock StatsLogManager mMockStatsLogManager;
private @Mock StatsLogManager.StatsLogger mMockStatsLogger;
private @Mock ContextualSearchHapticManager mMockContextualSearchHapticManager;
@@ -84,6 +85,7 @@
private @Mock RecentsViewContainer mMockRecentsViewContainer;
private @Mock RecentsView mMockRecentsView;
private @Mock RecentsPagedOrientationHandler mMockOrientationHandler;
+ private @Mock OverviewComponentObserver mMockOverviewComponentObserver;
private ContextualSearchInvoker mContextualSearchInvoker;
@Before
@@ -92,20 +94,21 @@
when(mMockPackageManager.hasSystemFeature(FEATURE_CONTEXTUAL_SEARCH)).thenReturn(true);
Context context = spy(getApplicationContext());
doReturn(mMockPackageManager).when(context).getPackageManager();
- when(mMockSystemUiProxy.getLastSystemUiStateFlags()).thenReturn(0L);
+ when(mMockDeviceState.getSystemUiStateFlags()).thenReturn(0L);
when(mMockTopTaskTracker.getRunningSplitTaskIds()).thenReturn(new int[]{});
when(mMockStateManager.isContextualSearchIntentAvailable()).thenReturn(true);
when(mMockStateManager.isContextualSearchSettingEnabled()).thenReturn(true);
when(mMockStatsLogManager.logger()).thenReturn(mMockStatsLogger);
+
+ doReturn(mMockContainerInterface).when(mMockOverviewComponentObserver)
+ .getContainerInterface();
when(mMockContainerInterface.getCreatedContainer()).thenReturn(mMockRecentsViewContainer);
when(mMockRecentsViewContainer.getOverviewPanel()).thenReturn(mMockRecentsView);
- mContextualSearchInvoker = spy(new ContextualSearchInvoker(context, mMockStateManager,
- mMockTopTaskTracker, mMockSystemUiProxy, mMockStatsLogManager,
- mMockContextualSearchHapticManager, mMockContextualSearchManager
- ));
- doReturn(mMockContainerInterface).when(mContextualSearchInvoker)
- .getRecentsContainerInterface();
+ mContextualSearchInvoker = new ContextualSearchInvoker(context, mMockStateManager,
+ mMockTopTaskTracker, mMockDeviceState, mMockStatsLogManager,
+ mMockContextualSearchHapticManager, mMockOverviewComponentObserver,
+ mMockContextualSearchManager);
}
@Test
@@ -148,7 +151,7 @@
@Test
public void runContextualSearchInvocationChecksAndLogFailures_notificationShadeIsShowing() {
- when(mMockSystemUiProxy.getLastSystemUiStateFlags()).thenReturn(SHADE_EXPANDED_SYSUI_FLAGS);
+ when(mMockDeviceState.getSystemUiStateFlags()).thenReturn(SHADE_EXPANDED_SYSUI_FLAGS);
assertFalse("Expected invocation checks to fail when notification shade is showing",
mContextualSearchInvoker.runContextualSearchInvocationChecksAndLogFailures());
@@ -158,7 +161,7 @@
@Test
public void runContextualSearchInvocationChecksAndLogFailures_keyguardIsShowing() {
- when(mMockSystemUiProxy.getLastSystemUiStateFlags()).thenReturn(
+ when(mMockDeviceState.getSystemUiStateFlags()).thenReturn(
KEYGUARD_SHOWING_SYSUI_FLAGS);
assertFalse("Expected invocation checks to fail when keyguard is showing",
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.kt
index 7066d21..f2fa0c5 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.kt
@@ -447,6 +447,37 @@
}
/*
+ 5 3 [1]
+ CLEAR_ALL
+ 6 4 2
+ */
+ @Test
+ fun equalLengthRows_noFocused_onTop_pressTabWithShift_noCycle_staysOnTop() {
+ assertThat(
+ getNextGridPage(currentPageTaskViewId = 1, DIRECTION_TAB, delta = -1, cycle = false)
+ )
+ .isEqualTo(1)
+ }
+
+ /*
+ 5 3 1
+ [CLEAR_ALL]
+ 6 4 2
+ */
+ @Test
+ fun equalLengthRows_noFocused_onClearAll_pressTab_noCycle_staysOnClearAll() {
+ assertThat(
+ getNextGridPage(
+ currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID,
+ DIRECTION_TAB,
+ delta = 1,
+ cycle = false,
+ )
+ )
+ .isEqualTo(CLEAR_ALL_PLACEHOLDER_ID)
+ }
+
+ /*
5 3 1
CLEAR_ALL FOCUSED_TASK←--DESKTOP
6 4 2
@@ -783,10 +814,11 @@
bottomIds: IntArray = IntArray.wrap(2, 4, 6),
largeTileIds: List<Int> = emptyList(),
hasAddDesktopButton: Boolean = false,
+ cycle: Boolean = true,
): Int {
val taskGridNavHelper =
TaskGridNavHelper(topIds, bottomIds, largeTileIds, hasAddDesktopButton)
- return taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, true)
+ return taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle)
}
private companion object {
diff --git a/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java b/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
index e2ca91a..a164dad 100644
--- a/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
@@ -89,7 +89,6 @@
import org.mockito.junit.MockitoRule;
import java.util.Optional;
-import java.util.function.Function;
import javax.inject.Provider;
@@ -104,7 +103,6 @@
private TaskAnimationManager mTaskAnimationManager;
private InputChannelCompat.InputEventReceiver mInputEventReceiver;
@Nullable private ResetGestureInputConsumer mResetGestureInputConsumer;
- @NonNull private Function<GestureState, AnimatedFloat> mSwipeUpProxyProvider = (state) -> null;
@NonNull @Mock private TaskbarActivityContext mTaskbarActivityContext;
@NonNull @Mock private OverviewComponentObserver mOverviewComponentObserver;
@@ -446,7 +444,7 @@
@Test
public void testNewConsumer_withSwipeUpProxyProvider_returnsProgressDelegateInputConsumer() {
- mSwipeUpProxyProvider = (state) -> new AnimatedFloat();
+ doReturn(new AnimatedFloat()).when(mDeviceState).getSwipeUpProxy(any());
assertCorrectInputConsumer(
this::createInputConsumer,
@@ -496,7 +494,6 @@
otherActivityInputConsumer -> {},
mInputEventReceiver,
mTaskbarManager,
- mSwipeUpProxyProvider,
mOverviewCommandHelper,
event);
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index e0560e2..79d3c19 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -539,6 +539,24 @@
}
}
+ @Test
+ @PortraitLandscape
+ public void testDismissCancel() throws Exception {
+ startTestAppsWithCheck();
+ Overview overview = mLauncher.goHome().switchToOverview();
+ assertIsInState("Launcher internal state didn't switch to Overview",
+ ExpectedState.OVERVIEW);
+ final Integer numTasks = getFromRecentsView(RecentsView::getTaskViewCount);
+ OverviewTask task = overview.getCurrentTask();
+ assertNotNull("overview.getCurrentTask() returned null (2)", task);
+
+ task.dismissCancel();
+
+ runOnRecentsView(recentsView -> assertEquals(
+ "Canceling dismissing a task removed a task from Overview",
+ numTasks == null ? 0 : numTasks, recentsView.getTaskViewCount()));
+ }
+
private void startTestAppsWithCheck() throws Exception {
startTestApps();
expectLaunchedAppState();
diff --git a/res/values/config.xml b/res/values/config.xml
index a545f0c..07f97bc 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -117,6 +117,10 @@
<item name="swipe_up_rect_y_damping_ratio" type="dimen" format="float">0.95</item>
<item name="swipe_up_rect_y_stiffness" type="dimen" format="float">400</item>
+ <!-- Expressive Dismiss -->
+ <item name="expressive_dismiss_task_trans_y_damping_ratio" type="dimen" format="float">0.6</item>
+ <item name="expressive_dismiss_task_trans_y_stiffness" type="dimen" format="float">900</item>
+
<!-- Taskbar -->
<!-- This is a float because it is converted to dp later in DeviceProfile -->
<item name="taskbar_icon_size" type="dimen" format="float">0</item>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index c48f140..c3cb31d 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -480,6 +480,7 @@
<dimen name="task_thumbnail_icon_drawable_size_grid">0dp</dimen>
<dimen name="task_thumbnail_icon_menu_drawable_touch_size">0dp</dimen>
<dimen name="task_menu_edge_padding">0dp</dimen>
+ <dimen name="task_dismiss_max_undershoot">0dp</dimen>
<dimen name="overview_task_margin">0dp</dimen>
<dimen name="overview_actions_height">0dp</dimen>
<dimen name="overview_actions_button_spacing">0dp</dimen>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index d3684b2..fb847f9 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -77,7 +77,6 @@
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.graphics.PreloadIconDrawable;
-import com.android.launcher3.graphics.ThemeManager;
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
@@ -484,9 +483,7 @@
}
private void setNonPendingIcon(ItemInfoWithIcon info) {
- ThemeManager themeManager = ThemeManager.INSTANCE.get(getContext());
- int flags = (shouldUseTheme()
- && themeManager.isMonoThemeEnabled()) ? FLAG_THEMED : 0;
+ int flags = shouldUseTheme() ? FLAG_THEMED : 0;
// Remove badge on icons smaller than 48dp.
if (mHideBadge || mDisplay == DISPLAY_SEARCH_RESULT_SMALL) {
flags |= FLAG_NO_BADGE;
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index add8a05..900f74d 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -409,8 +409,9 @@
private List<DisplayOption> filterByColumnCount(
List<DisplayOption> allOptions, int numColumns) {
- return allOptions.stream().filter(
- option -> option.grid.numColumns == numColumns).toList();
+ return allOptions.stream()
+ .filter(option -> option.grid.numColumns == numColumns)
+ .collect(Collectors.toList());
}
/**
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 58fd154..04e699c 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -288,6 +288,7 @@
import java.util.Set;
import java.util.function.Predicate;
import java.util.function.Supplier;
+import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
@@ -348,8 +349,7 @@
private final int[] mTmpAddItemCellCoordinates = new int[2];
- @Thunk
- Hotseat mHotseat;
+ protected Hotseat mHotseat;
private DropTargetBar mDropTargetBar;
@@ -806,10 +806,6 @@
);
}
- public void onAssistantVisibilityChanged(float visibility) {
- mHotseat.getQsb().setAlpha(1f - visibility);
- }
-
/**
* Returns {@code true} if a new DeviceProfile is initialized, and {@code false} otherwise.
*/
@@ -2256,8 +2252,9 @@
*/
@Override
public void bindItems(final List<ItemInfo> items, final boolean forceAnimateIcons) {
- bindInflatedItems(items.stream().map(i -> Pair.create(
- i, getItemInflater().inflateItem(i, getModelWriter()))).toList(),
+ bindInflatedItems(items.stream()
+ .map(i -> Pair.create(i, getItemInflater().inflateItem(i, getModelWriter())))
+ .collect(Collectors.toList()),
forceAnimateIcons ? new AnimatorSet() : null);
}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index d93c07f..cb3a0bc 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -658,9 +658,9 @@
appState.getInvariantDeviceProfile().fillResIconDpi);
// Only fetch badge if the icon is on workspace
if (info.id != ItemInfo.NO_ID && badge == null) {
- badge = appState.getIconCache().getShortcutInfoBadge(si)
- .newIcon(context, ThemeManager.INSTANCE.get(context)
- .isMonoThemeEnabled() ? FLAG_THEMED : 0);
+ badge = appState.getIconCache().getShortcutInfoBadge(si).newIcon(
+ context, ThemeManager.INSTANCE.get(context).isIconThemeEnabled()
+ ? FLAG_THEMED : 0);
}
}
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index b0001af..260ff9f 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -273,12 +273,7 @@
mFastScroller = findViewById(R.id.fast_scroller);
mFastScroller.setPopupView(findViewById(R.id.fast_scroller_popup));
mFastScrollLetterLayout = findViewById(R.id.scroll_letter_layout);
- if (Flags.letterFastScroller()) {
- // Set clip children to false otherwise the scroller letters will be clipped.
- setClipChildren(false);
- } else {
- setClipChildren(true);
- }
+ setClipChildren(false);
mSearchContainer = inflateSearchBar();
if (!isSearchBarFloating()) {
diff --git a/src/com/android/launcher3/celllayout/ReorderAlgorithm.java b/src/com/android/launcher3/celllayout/ReorderAlgorithm.java
index c303783..043c3be 100644
--- a/src/com/android/launcher3/celllayout/ReorderAlgorithm.java
+++ b/src/com/android/launcher3/celllayout/ReorderAlgorithm.java
@@ -26,6 +26,7 @@
import java.util.Comparator;
import java.util.List;
import java.util.Map.Entry;
+import java.util.stream.Collectors;
/**
* Contains the logic of a reorder.
@@ -143,12 +144,14 @@
// and not by the views hash which is "random".
// The views are sorted twice, once for the X position and a second time for the Y position
// to ensure same order everytime.
- Comparator comparator = Comparator.comparing(
- view -> ((CellLayoutLayoutParams) ((View) view).getLayoutParams()).getCellX()
+ Comparator<View> comparator = Comparator.comparing(
+ (View view) -> ((CellLayoutLayoutParams) view.getLayoutParams()).getCellX()
).thenComparing(
- view -> ((CellLayoutLayoutParams) ((View) view).getLayoutParams()).getCellY()
+ (View view) -> ((CellLayoutLayoutParams) view.getLayoutParams()).getCellY()
);
- List<View> views = solution.map.keySet().stream().sorted(comparator).toList();
+ List<View> views = solution.map.keySet().stream()
+ .sorted(comparator)
+ .collect(Collectors.toList());
for (View child : views) {
if (child == ignoreView) continue;
CellAndSpan c = solution.map.get(child);
diff --git a/src/com/android/launcher3/graphics/ThemeManager.kt b/src/com/android/launcher3/graphics/ThemeManager.kt
index 9f35e4a..242220a 100644
--- a/src/com/android/launcher3/graphics/ThemeManager.kt
+++ b/src/com/android/launcher3/graphics/ThemeManager.kt
@@ -40,8 +40,8 @@
open class ThemeManager
@Inject
constructor(
- @ApplicationContext private val context: Context,
- private val prefs: LauncherPrefs,
+ @ApplicationContext protected val context: Context,
+ protected val prefs: LauncherPrefs,
lifecycle: DaggerSingletonTracker,
) {
@@ -53,9 +53,11 @@
set(value) = prefs.put(THEMED_ICONS, value)
get() = prefs.get(THEMED_ICONS)
- var themeController: IconThemeController? =
- if (isMonoThemeEnabled) MonoIconThemeController() else null
- private set
+ val themeController: IconThemeController?
+ get() = iconState.themeController
+
+ val isIconThemeEnabled: Boolean
+ get() = themeController != null
private val listeners = CopyOnWriteArrayList<ThemeChangeListener>()
@@ -77,12 +79,10 @@
}
}
- private fun verifyIconState() {
+ protected fun verifyIconState() {
val newState = parseIconState()
if (newState == iconState) return
-
iconState = newState
- themeController = if (isMonoThemeEnabled) MonoIconThemeController() else null
listeners.forEach { it.onThemeChanged() }
}
@@ -105,15 +105,19 @@
return IconState(
iconMask = iconMask,
folderShapeMask = shapeModel?.folderPathString ?: iconMask,
- isMonoTheme = isMonoThemeEnabled,
+ themeController = createThemeController(),
)
}
+ protected open fun createThemeController(): IconThemeController? {
+ return if (isMonoThemeEnabled) MONO_THEME_CONTROLLER else null
+ }
+
data class IconState(
val iconMask: String,
val folderShapeMask: String,
- val isMonoTheme: Boolean,
- val themeCode: String = if (isMonoTheme) "with-theme" else "no-theme",
+ val themeController: IconThemeController?,
+ val themeCode: String = themeController?.themeID ?: "no-theme",
) {
fun toUniqueId() = "${iconMask.hashCode()},$themeCode"
}
@@ -135,5 +139,8 @@
private const val ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED"
private val CONFIG_ICON_MASK_RES_ID: Int =
Resources.getSystem().getIdentifier("config_icon_mask", "string", "android")
+
+ // Use a constant to allow equality check in verifyIconState
+ private val MONO_THEME_CONTROLLER = MonoIconThemeController()
}
}
diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
index de74ae8..003bef3 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -390,8 +390,9 @@
ModelWriter writer = mApp.getModel()
.getWriter(false /* verifyChanges */, CellPosMapper.DEFAULT, null);
- List<Pair<ItemInfo, View>> bindItems = items.stream().map(i ->
- Pair.create(i, inflater.inflateItem(i, writer, null))).toList();
+ List<Pair<ItemInfo, View>> bindItems = items.stream()
+ .map(i -> Pair.create(i, inflater.inflateItem(i, writer, null)))
+ .collect(Collectors.toList());
executeCallbacksTask(c -> c.bindInflatedItems(bindItems), executor);
}
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index ddc775d..eab28b7 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -46,6 +46,7 @@
import com.android.launcher3.BuildConfig;
import com.android.launcher3.Workspace;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.CollectionInfo;
import com.android.launcher3.model.data.ItemInfo;
@@ -210,7 +211,7 @@
}
/**
- * Updates the deep shortucts state in system to match out internal model, pinning any missing
+ * Updates the deep shortcuts state in system to match out internal model, pinning any missing
* shortcuts and unpinning any extra shortcuts.
*/
public void updateShortcutPinnedState(Context context) {
@@ -266,6 +267,8 @@
|| !systemShortcuts.containsAll(modelShortcuts)) {
// Update system state for this package
try {
+ FileLog.d(TAG, "updateShortcutPinnedState:"
+ + " Pinning Shortcuts: " + entry.getKey() + ": " + modelShortcuts);
context.getSystemService(LauncherApps.class).pinShortcuts(
entry.getKey(), new ArrayList<>(modelShortcuts), user);
} catch (SecurityException | IllegalStateException e) {
@@ -278,6 +281,9 @@
systemMap.keySet().forEach(packageName -> {
// Update system state
try {
+ FileLog.d(TAG, "updateShortcutPinnedState:"
+ + " Unpinning extra Shortcuts for package: " + packageName
+ + ": " + systemMap.get(packageName));
context.getSystemService(LauncherApps.class).pinShortcuts(
packageName, Collections.emptyList(), user);
} catch (SecurityException | IllegalStateException e) {
diff --git a/src/com/android/launcher3/model/GridSizeMigrationDBController.java b/src/com/android/launcher3/model/GridSizeMigrationDBController.java
index b291421..47f13bd 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationDBController.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationDBController.java
@@ -193,7 +193,8 @@
List<DbEntry> filteredDstHotseatItems = dstHotseatItems;
if (srcHotseatSize < destHotseatSize) {
filteredDstHotseatItems = filteredDstHotseatItems.stream()
- .filter(entry -> entry.screenId < srcHotseatSize).toList();
+ .filter(entry -> entry.screenId < srcHotseatSize)
+ .collect(Collectors.toList());
}
final List<DbEntry> dstWorkspaceItems = destReader.loadAllWorkspaceEntries();
final List<DbEntry> hotseatToBeAdded = new ArrayList<>(1);
@@ -237,9 +238,12 @@
Collections.sort(hotseatToBeAdded);
Collections.sort(workspaceToBeAdded);
- List<Integer> idsInUse = dstWorkspaceItems.stream().map(entry -> entry.id).collect(
- Collectors.toList());
- idsInUse.addAll(dstHotseatItems.stream().map(entry -> entry.id).toList());
+ List<Integer> idsInUse = dstWorkspaceItems.stream()
+ .map(entry -> entry.id)
+ .collect(Collectors.toList());
+ idsInUse.addAll(dstHotseatItems.stream()
+ .map(entry -> entry.id)
+ .collect(Collectors.toList()));
// Migrate hotseat
solveHotseatPlacement(helper, destHotseatSize,
@@ -269,7 +273,8 @@
int screenId = destReader.mLastScreenId + 1;
while (!workspaceToBeAdded.isEmpty()) {
solveGridPlacement(helper, srcReader, destReader, screenId, trgX, trgY,
- workspaceToBeAdded, srcWorkspaceItems.stream().map(entry -> entry.id).toList());
+ workspaceToBeAdded,
+ srcWorkspaceItems.stream().map(entry -> entry.id).collect(Collectors.toList()));
screenId++;
}
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 6a8d86b..bd8c36b 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -24,6 +24,7 @@
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
import android.content.ComponentName;
import android.content.ContentValues;
@@ -307,7 +308,7 @@
* Make an WorkspaceItemInfo object for a restored application or shortcut item that points
* to a package that is not yet installed on the system.
*/
- public WorkspaceItemInfo getRestoredItemInfo(Intent intent) {
+ public WorkspaceItemInfo getRestoredItemInfo(Intent intent, boolean isArchived) {
final WorkspaceItemInfo info = new WorkspaceItemInfo();
info.user = user;
info.intent = intent;
@@ -317,7 +318,7 @@
mIconCache.getTitleAndIcon(info, DEFAULT_LOOKUP_FLAG);
}
- if (hasRestoreFlag(WorkspaceItemInfo.FLAG_RESTORED_ICON)) {
+ if (hasRestoreFlag(WorkspaceItemInfo.FLAG_RESTORED_ICON) || isArchived) {
String title = getTitle();
if (!TextUtils.isEmpty(title)) {
info.title = Utilities.trim(title);
@@ -333,6 +334,7 @@
info.contentDescription = mIconCache.getUserBadgedLabel(info.title, info.user);
info.itemType = itemType;
info.status = restoreFlag;
+ if (isArchived) info.runtimeStatusFlags |= FLAG_ARCHIVED;
return info;
}
diff --git a/src/com/android/launcher3/model/ModelDbController.java b/src/com/android/launcher3/model/ModelDbController.java
index 0138390..3a55aa7 100644
--- a/src/com/android/launcher3/model/ModelDbController.java
+++ b/src/com/android/launcher3/model/ModelDbController.java
@@ -90,6 +90,7 @@
import java.io.InputStream;
import java.io.StringReader;
import java.util.List;
+import java.util.stream.Collectors;
/**
* Utility class which maintains an instance of Launcher database and provides utility methods
@@ -377,7 +378,7 @@
// to run in grid migration based on if that grid already existed before migration or not.
List<String> existingDBs = LauncherFiles.GRID_DB_FILES.stream()
.filter(dbName -> mContext.getDatabasePath(dbName).exists())
- .toList();
+ .collect(Collectors.toList());
mOpenHelper = (mContext instanceof SandboxContext) ? oldHelper
: createDatabaseHelper(true, new DeviceGridState(idp).getDbFile());
@@ -460,7 +461,7 @@
// to run in grid migration based on if that grid already existed before migration or not.
List<String> existingDBs = LauncherFiles.GRID_DB_FILES.stream()
.filter(dbName -> mContext.getDatabasePath(dbName).exists())
- .toList();
+ .collect(Collectors.toList());
mOpenHelper = (mContext instanceof SandboxContext) ? oldHelper
: createDatabaseHelper(true /* forMigration */, targetDbName);
try {
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 6bef292..d1eceb9 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.model;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED;
@@ -39,7 +40,6 @@
import com.android.launcher3.Flags;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel.ModelUpdateTask;
-import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.IconCache;
@@ -238,19 +238,22 @@
if (itemInfo.isPromise() && isNewApkAvailable) {
boolean isTargetValid = !cn.getClassName().equals(
IconCache.EMPTY_CLASS_NAME);
- if (itemInfo.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+ if (itemInfo.itemType == ITEM_TYPE_DEEP_SHORTCUT) {
List<ShortcutInfo> shortcut =
new ShortcutRequest(context, mUser)
.forPackage(cn.getPackageName(),
itemInfo.getDeepShortcutId())
.query(ShortcutRequest.PINNED);
- if (shortcut.isEmpty()) {
+ if (shortcut.isEmpty()
+ && !(Flags.restoreArchivedShortcuts()
+ && !itemInfo.isArchived())
+ ) {
isTargetValid = false;
if (DEBUG) {
Log.d(TAG, "Pinned Shortcut not found for updated"
+ " package=" + itemInfo.getTargetPackage());
}
- } else {
+ } else if (!shortcut.isEmpty()) {
if (DEBUG) {
Log.d(TAG, "Found pinned shortcut for updated"
+ " package=" + itemInfo.getTargetPackage()
@@ -269,7 +272,7 @@
|| itemInfo.isArchived())) {
if (updateWorkspaceItemIntent(context, itemInfo, packageName)) {
infoUpdated = true;
- } else if (itemInfo.hasPromiseIconUi()) {
+ } else if (shouldRemoveRestoredShortcut(itemInfo)) {
removedShortcuts.add(itemInfo.id);
if (DEBUG) {
FileLog.w(TAG, "Removing restored shortcut promise icon"
@@ -436,7 +439,7 @@
*/
private boolean updateWorkspaceItemIntent(Context context,
WorkspaceItemInfo si, String packageName) {
- if (si.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+ if (si.itemType == ITEM_TYPE_DEEP_SHORTCUT) {
// Do not update intent for deep shortcuts as they contain additional information
// about the shortcut.
return false;
@@ -452,6 +455,15 @@
return false;
}
+ private boolean shouldRemoveRestoredShortcut(WorkspaceItemInfo itemInfo) {
+ if (itemInfo.hasPromiseIconUi() && !Flags.restoreArchivedShortcuts()) {
+ return true;
+ }
+ return Flags.restoreArchivedShortcuts()
+ && !itemInfo.isArchived()
+ && itemInfo.itemType == ITEM_TYPE_DEEP_SHORTCUT;
+ }
+
private String getOpString() {
return switch (mOp) {
case OP_NONE -> "NONE";
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.kt b/src/com/android/launcher3/model/ShortcutsChangedTask.kt
index 2e4f75f..56e9e43 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.kt
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.kt
@@ -17,6 +17,7 @@
import android.content.pm.ShortcutInfo
import android.os.UserHandle
+import com.android.launcher3.Flags
import com.android.launcher3.LauncherModel.ModelUpdateTask
import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
import com.android.launcher3.icons.CacheableShortcutInfo
@@ -59,8 +60,11 @@
val infoWrapper = ApplicationInfoWrapper(context, packageName, user)
if (shortcuts.isEmpty()) {
// Verify that the app is indeed installed.
- if (!infoWrapper.isInstalled() && !infoWrapper.isArchived()) {
- // App is not installed or archived, ignoring package events
+ if (
+ (!infoWrapper.isInstalled() && !infoWrapper.isArchived()) ||
+ (Flags.restoreArchivedShortcuts() && infoWrapper.isArchived())
+ ) {
+ // App is not installed or is archived, ignoring package events
return
}
}
@@ -75,7 +79,7 @@
val nonPinnedIds: MutableSet<String> = HashSet(allLauncherKnownIds)
val updatedWorkspaceItemInfos = ArrayList<WorkspaceItemInfo>()
for (fullDetails in shortcuts) {
- if (!fullDetails.isPinned) {
+ if (!fullDetails.isPinned && !Flags.restoreArchivedShortcuts()) {
continue
}
val shortcutId = fullDetails.id
diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
index 90f11a3..3919eb7 100644
--- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
+++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
@@ -194,27 +194,36 @@
if (intent.`package` == null) {
intent.`package` = targetPkg
}
+ val isPreArchived = appInfoWrapper.isArchived() && c.restoreFlag != 0
+
// else if cn == null => can't infer much, leave it
// else if !validPkg => could be restored icon or missing sd-card
when {
- !TextUtils.isEmpty(targetPkg) && !validTarget -> {
+ !TextUtils.isEmpty(targetPkg) && (!validTarget || isPreArchived) -> {
// Points to a valid app (superset of cn != null) but the apk
// is not available.
when {
- c.restoreFlag != 0 -> {
+ c.restoreFlag != 0 || isPreArchived -> {
// Package is not yet available but might be
// installed later.
- FileLog.d(TAG, "package not yet restored: $targetPkg")
+ FileLog.d(
+ TAG,
+ "package not yet restored: $targetPkg, itemType=${c.itemType}" +
+ "isPreArchived=$isPreArchived, restoreFlag=${c.restoreFlag}",
+ )
tempPackageKey.update(targetPkg, c.user)
when {
c.hasRestoreFlag(WorkspaceItemInfo.FLAG_RESTORE_STARTED) -> {
// Restore has started once.
}
- installingPkgs.containsKey(tempPackageKey) -> {
+ installingPkgs.containsKey(tempPackageKey) || isPreArchived -> {
// App restore has started. Update the flag
c.restoreFlag =
c.restoreFlag or WorkspaceItemInfo.FLAG_RESTORE_STARTED
- FileLog.d(TAG, "restore started for installing app: $targetPkg")
+ FileLog.d(
+ TAG,
+ "restore started for installing app: $targetPkg, itemType=${c.itemType}",
+ )
c.updater().put(Favorites.RESTORED, c.restoreFlag).commit()
}
else -> {
@@ -253,9 +262,18 @@
}
}
if (c.restoreFlag and WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI != 0) {
+ FileLog.d(
+ TAG,
+ "restore flag set AND WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI != 0, setting valid target to false: $targetPkg, itemType=${c.itemType}, restoreFlag=${c.restoreFlag}",
+ )
validTarget = false
}
- if (validTarget) {
+ if (validTarget && !isPreArchived) {
+ FileLog.d(
+ TAG,
+ "valid target true, marking restored: $targetPkg," +
+ " itemType=${c.itemType}, restoreFlag=${c.restoreFlag}",
+ )
// The shortcut points to a valid target (either no target
// or something which is ready to be used)
c.markRestored()
@@ -265,7 +283,7 @@
when {
c.restoreFlag != 0 -> {
// Already verified above that user is same as default user
- info = c.getRestoredItemInfo(intent)
+ info = c.getRestoredItemInfo(intent, isPreArchived)
}
c.itemType == Favorites.ITEM_TYPE_APPLICATION ->
info = c.getAppShortcutInfo(intent, allowMissingTarget, useLowResIcon, false)
diff --git a/src/com/android/launcher3/model/data/AppPairInfo.kt b/src/com/android/launcher3/model/data/AppPairInfo.kt
index e620ac9..c0fe4fd 100644
--- a/src/com/android/launcher3/model/data/AppPairInfo.kt
+++ b/src/com/android/launcher3/model/data/AppPairInfo.kt
@@ -23,6 +23,7 @@
import com.android.launcher3.icons.IconCache
import com.android.launcher3.logger.LauncherAtom
import com.android.launcher3.views.ActivityContext
+import java.util.stream.Collectors
/** A type of app collection that launches multiple apps into split screen. */
class AppPairInfo() : CollectionInfo() {
@@ -54,7 +55,7 @@
/** Returns the app pair's member apps as an ArrayList of [ItemInfo]. */
override fun getContents(): ArrayList<ItemInfo> =
- ArrayList(contents.stream().map { it as ItemInfo }.toList())
+ ArrayList(contents.stream().map { it as ItemInfo }.collect(Collectors.toList()))
/** Returns the app pair's member apps as an ArrayList of [WorkspaceItemInfo]. */
override fun getAppContents(): ArrayList<WorkspaceItemInfo> = contents
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index 7fb0152..ff40f30 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -323,7 +323,7 @@
* Returns a FastBitmapDrawable with the icon and context theme applied
*/
public FastBitmapDrawable newIcon(Context context, @DrawableCreationFlags int creationFlags) {
- if (!ThemeManager.INSTANCE.get(context).isMonoThemeEnabled()) {
+ if (!ThemeManager.INSTANCE.get(context).isIconThemeEnabled()) {
creationFlags &= ~FLAG_THEMED;
}
FastBitmapDrawable drawable = bitmap.newIcon(context, creationFlags);
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index f56888b..dc42920 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -176,7 +176,7 @@
// At this point idp.dbFile contains the name of the dbFile from the previous phone
return LauncherFiles.GRID_DB_FILES.stream()
.filter(dbName -> context.getDatabasePath(dbName).exists())
- .toList();
+ .collect(Collectors.toList());
}
/**
diff --git a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
index d042b1d..4ccf16b 100644
--- a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
@@ -222,7 +222,8 @@
if (shouldShowFullPageView(recommendations)) {
// Show all widgets in single page with unlimited available height.
return setRecommendations(
- recommendations.values().stream().flatMap(Collection::stream).toList(),
+ recommendations.values().stream().flatMap(Collection::stream)
+ .collect(Collectors.toList()),
deviceProfile, /*availableHeight=*/ Float.MAX_VALUE, availableWidth,
cellPadding);
@@ -369,7 +370,7 @@
// Show only those widgets that were displayed when user first opened the picker.
if (!mDisplayedWidgets.isEmpty()) {
filteredRecommendedWidgets = recommendedWidgets.stream().filter(
- w -> mDisplayedWidgets.contains(w.componentName)).toList();
+ w -> mDisplayedWidgets.contains(w.componentName)).collect(Collectors.toList());
}
Context context = getContext();
LayoutInflater inflater = LayoutInflater.from(context);
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index ab0f9a7..7a218ae 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -87,6 +87,7 @@
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
+import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
@@ -650,7 +651,7 @@
mRecommendedWidgets = mActivityContext.getWidgetPickerDataProvider().get()
.getRecommendations()
.values().stream()
- .flatMap(Collection::stream).toList();
+ .flatMap(Collection::stream).collect(Collectors.toList());
mRecommendedWidgetsCount = mWidgetRecommendationsView.setRecommendations(
mRecommendedWidgets,
mDeviceProfile,
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
index 0bcab60..216f4d4 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
@@ -41,6 +41,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.stream.Collectors;
/** A {@link TableLayout} for showing recommended widgets. */
public final class WidgetsRecommendationTableLayout extends TableLayout {
@@ -163,6 +164,7 @@
}
// Perform re-ordering once we have filtered out recommendations that fit.
- return filteredRows.stream().sorted(WIDGETS_TABLE_ROW_COUNT_COMPARATOR).toList();
+ return filteredRows.stream().sorted(WIDGETS_TABLE_ROW_COUNT_COMPARATOR)
+ .collect(Collectors.toList());
}
}
diff --git a/src/com/android/launcher3/widget/util/WidgetsTableUtils.java b/src/com/android/launcher3/widget/util/WidgetsTableUtils.java
index df72f07..1134781 100644
--- a/src/com/android/launcher3/widget/util/WidgetsTableUtils.java
+++ b/src/com/android/launcher3/widget/util/WidgetsTableUtils.java
@@ -95,7 +95,7 @@
List<ArrayList<WidgetItem>> rows = groupWidgetItemsUsingRowPxWithoutReordering(
sortedWidgetItems, context, dp, rowPx,
cellPadding);
- return rows.stream().sorted(WIDGETS_TABLE_ROW_SIZE_COMPARATOR).toList();
+ return rows.stream().sorted(WIDGETS_TABLE_ROW_SIZE_COMPARATOR).collect(Collectors.toList());
}
/**
diff --git a/tests/multivalentTests/src/com/android/launcher3/graphics/ThemeManagerTest.kt b/tests/multivalentTests/src/com/android/launcher3/graphics/ThemeManagerTest.kt
index 43b7b68..85c1156 100644
--- a/tests/multivalentTests/src/com/android/launcher3/graphics/ThemeManagerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/graphics/ThemeManagerTest.kt
@@ -21,11 +21,13 @@
import com.android.launcher3.FakeLauncherPrefs
import com.android.launcher3.dagger.LauncherAppComponent
import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.icons.mono.MonoIconThemeController
import com.android.launcher3.util.AllModulesForTest
import com.android.launcher3.util.Executors.MAIN_EXECUTOR
import com.android.launcher3.util.FakePrefsModule
import com.android.launcher3.util.SandboxApplication
import com.android.launcher3.util.TestUtil
+import com.google.common.truth.Truth.assertThat
import dagger.Component
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@@ -55,12 +57,13 @@
themeManager.isMonoThemeEnabled = true
TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
assertTrue(themeManager.isMonoThemeEnabled)
- assertTrue(themeManager.iconState.isMonoTheme)
+ assertThat(themeManager.iconState.themeController)
+ .isInstanceOf(MonoIconThemeController::class.java)
themeManager.isMonoThemeEnabled = false
TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
assertFalse(themeManager.isMonoThemeEnabled)
- assertFalse(themeManager.iconState.isMonoTheme)
+ assertThat(themeManager.iconState.themeController).isNull()
}
@Test
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/ShortcutsChangedTaskTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/ShortcutsChangedTaskTest.kt
index fb6d038..c6863f4 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/ShortcutsChangedTaskTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/ShortcutsChangedTaskTest.kt
@@ -24,8 +24,12 @@
import android.content.pm.ShortcutInfo
import android.os.Process.myUserHandle
import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.launcher3.Flags
import com.android.launcher3.LauncherAppState
import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
import com.android.launcher3.icons.BitmapInfo
@@ -42,6 +46,7 @@
import java.util.function.Predicate
import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
@@ -55,6 +60,8 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class ShortcutsChangedTaskTest {
+ @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule()
+
private lateinit var shortcutsChangedTask: ShortcutsChangedTask
private lateinit var modelHelper: LauncherModelHelper
private lateinit var context: SandboxModelContext
@@ -131,7 +138,8 @@
}
@Test
- fun `When installed unpinned shortcut is found then remove from workspace`() {
+ @DisableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS)
+ fun `When installed unpinned shortcut is found with Flag off then remove from workspace`() {
// Given
shortcuts =
listOf(
@@ -163,6 +171,37 @@
}
@Test
+ @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS)
+ fun `When installed unpinned shortcut is found with Flag on then keep in workspace`() {
+ // Given
+ shortcuts =
+ listOf(
+ mock<ShortcutInfo>().apply {
+ whenever(isPinned).thenReturn(false)
+ whenever(id).thenReturn(expectedShortcutId)
+ }
+ )
+ val items: IntSparseArrayMap<ItemInfo> = modelHelper.bgDataModel.itemsIdMap
+ items.put(expectedWai.id, expectedWai)
+ doReturn(
+ ApplicationInfo().apply {
+ enabled = true
+ flags = flags or FLAG_INSTALLED
+ isArchived = false
+ }
+ )
+ .whenever(launcherApps)
+ .getApplicationInfo(eq(expectedPackage), any(), eq(user))
+ doReturn(shortcuts).whenever(launcherApps).getShortcuts(any(), eq(user))
+ // When
+ shortcutsChangedTask.execute(mockTaskController, modelHelper.bgDataModel, mockAllApps)
+ // Then
+ verify(mockAppState.iconCache)
+ .getShortcutIcon(eq(expectedWai), any<CacheableShortcutInfo>())
+ verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWai))
+ }
+
+ @Test
fun `When shortcut app is uninstalled then skip handling`() {
// Given
shortcuts =
@@ -192,7 +231,8 @@
}
@Test
- fun `When archived pinned shortcut is found then keep in workspace`() {
+ @DisableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS)
+ fun `When archived pinned shortcut is found with flag off then keep in workspace`() {
// Given
shortcuts =
listOf(
@@ -222,7 +262,8 @@
}
@Test
- fun `When archived unpinned shortcut is found then keep in workspace`() {
+ @DisableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS)
+ fun `When archived unpinned shortcut is found with flag off then keep in workspace`() {
// Given
shortcuts =
listOf(
@@ -310,4 +351,34 @@
assertThat(modelHelper.bgDataModel.deepShortcutMap).doesNotContainKey(expectedKey)
verify(mockTaskController, times(0)).bindDeepShortcuts(eq(modelHelper.bgDataModel))
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS)
+ fun `When restoring archived shortcut with flag on then skip handling`() {
+ // Given
+ shortcuts =
+ listOf(
+ mock<ShortcutInfo>().apply {
+ whenever(isPinned).thenReturn(true)
+ whenever(id).thenReturn(expectedShortcutId)
+ }
+ )
+ val items: IntSparseArrayMap<ItemInfo> = modelHelper.bgDataModel.itemsIdMap
+ items.put(expectedWai.id, expectedWai)
+ doReturn(
+ ApplicationInfo().apply {
+ enabled = true
+ flags = flags or FLAG_INSTALLED
+ isArchived = true
+ }
+ )
+ .whenever(launcherApps)
+ .getApplicationInfo(eq(expectedPackage), any(), eq(user))
+ doReturn(shortcuts).whenever(launcherApps).getShortcuts(any(), eq(user))
+ // When
+ shortcutsChangedTask.execute(mockTaskController, modelHelper.bgDataModel, mockAllApps)
+ // Then
+ verify(mockTaskController, times(0)).deleteAndBindComponentsRemoved(any(), any())
+ verify(mockTaskController, times(0)).bindUpdatedWorkspaceItems(any())
+ }
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
index da87dfc..7a403e1 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
@@ -18,16 +18,19 @@
import android.appwidget.AppWidgetProviderInfo
import android.content.ComponentName
-import android.content.Context
import android.content.Intent
+import android.content.pm.ApplicationInfo
import android.content.pm.LauncherApps
import android.content.pm.PackageInstaller
import android.content.pm.ShortcutInfo
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.util.LongSparseArray
-import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.Flags
import com.android.launcher3.LauncherAppState
import com.android.launcher3.LauncherSettings.Favorites
import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
@@ -44,9 +47,14 @@
import com.android.launcher3.model.data.LauncherAppWidgetInfo
import com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_UI_NOT_READY
import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_RESTORED_ICON
+import com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_RESTORE_STARTED
import com.android.launcher3.pm.UserCache
import com.android.launcher3.shortcuts.ShortcutKey
import com.android.launcher3.util.ComponentKey
+import com.android.launcher3.util.ContentWriter
+import com.android.launcher3.util.LauncherModelHelper
+import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
import com.android.launcher3.util.PackageManagerHelper
import com.android.launcher3.util.PackageUserKey
import com.android.launcher3.util.UserIconInfo
@@ -55,6 +63,7 @@
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
@@ -66,6 +75,7 @@
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@@ -73,20 +83,23 @@
@RunWith(AndroidJUnit4::class)
class WorkspaceItemProcessorTest {
+ @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule()
+
@Mock private lateinit var mockIconRequestInfo: IconRequestInfo<WorkspaceItemInfo>
@Mock private lateinit var mockWorkspaceInfo: WorkspaceItemInfo
@Mock private lateinit var mockBgDataModel: BgDataModel
- @Mock private lateinit var mockContext: Context
@Mock private lateinit var mockAppState: LauncherAppState
@Mock private lateinit var mockPmHelper: PackageManagerHelper
- @Mock private lateinit var mockLauncherApps: LauncherApps
@Mock private lateinit var mockCursor: LoaderCursor
@Mock private lateinit var mockUserCache: UserCache
@Mock private lateinit var mockUserManagerState: UserManagerState
@Mock private lateinit var mockWidgetInflater: WidgetInflater
- private var intent: Intent = Intent()
- private var mUserHandle: UserHandle = UserHandle(0)
+ lateinit var mModelHelper: LauncherModelHelper
+ lateinit var mContext: SandboxModelContext
+ lateinit var mLauncherApps: LauncherApps
+ private var mIntent: Intent = Intent()
+ private var mUserHandle: UserHandle = Process.myUserHandle()
private var mIconRequestInfos: MutableList<IconRequestInfo<WorkspaceItemInfo>> = mutableListOf()
private var mComponentName: ComponentName = ComponentName("package", "class")
private var mUnlockedUsersArray: LongSparseArray<Boolean> = LongSparseArray()
@@ -101,40 +114,35 @@
@Before
fun setup() {
- mUserHandle = UserHandle(0)
+ mModelHelper = LauncherModelHelper()
+ mContext = mModelHelper.sandboxContext
+ mLauncherApps =
+ mContext.spyService(LauncherApps::class.java).apply {
+ doReturn(true).whenever(this).isPackageEnabled("package", mUserHandle)
+ doReturn(true).whenever(this).isActivityEnabled(mComponentName, mUserHandle)
+ }
+ mUserHandle = Process.myUserHandle()
mockIconRequestInfo = mock<IconRequestInfo<WorkspaceItemInfo>>()
mockWorkspaceInfo = mock<WorkspaceItemInfo>()
mockBgDataModel = mock<BgDataModel>()
mComponentName = ComponentName("package", "class")
mUnlockedUsersArray = LongSparseArray<Boolean>(1).apply { put(101, true) }
- intent =
+ mIntent =
Intent().apply {
component = mComponentName
`package` = "pkg"
putExtra(ShortcutKey.EXTRA_SHORTCUT_ID, "")
}
- mockLauncherApps =
- mock<LauncherApps>().apply {
- whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true)
- whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(true)
- }
- mockContext =
- mock<Context>().apply {
- whenever(packageManager).thenReturn(mock())
- whenever(packageManager.getUserBadgedLabel(any(), any())).thenReturn("")
- whenever(applicationContext).thenReturn(ApplicationProvider.getApplicationContext())
- whenever(getSystemService(LauncherApps::class.java)).thenReturn(mockLauncherApps)
- }
mockAppState =
mock<LauncherAppState>().apply {
- whenever(context).thenReturn(mockContext)
+ whenever(context).thenReturn(mContext)
whenever(iconCache).thenReturn(mock())
whenever(iconCache.getShortcutIcon(any(), any(), any())).then {}
}
mockPmHelper =
mock<PackageManagerHelper>().apply {
whenever(getAppLaunchIntent(mComponentName.packageName, mUserHandle))
- .thenReturn(intent)
+ .thenReturn(mIntent)
}
mockCursor =
mock(LoaderCursor::class.java, RETURNS_DEEP_STUBS).apply {
@@ -143,9 +151,9 @@
id = 1
restoreFlag = 1
serialNumber = 101
- whenever(parseIntent()).thenReturn(intent)
+ whenever(parseIntent()).thenReturn(mIntent)
whenever(markRestored()).doAnswer { restoreFlag = 0 }
- whenever(updater().put(Favorites.INTENT, intent.toUri(0)).commit()).thenReturn(1)
+ whenever(updater().put(Favorites.INTENT, mIntent.toUri(0)).commit()).thenReturn(1)
whenever(getAppShortcutInfo(any(), any(), any(), any()))
.thenReturn(mockWorkspaceInfo)
whenever(createIconRequestInfo(any(), any())).thenReturn(mockIconRequestInfo)
@@ -177,7 +185,7 @@
memoryLogger: LoaderMemoryLogger? = null,
userCache: UserCache = mockUserCache,
userManagerState: UserManagerState = mockUserManagerState,
- launcherApps: LauncherApps = mockLauncherApps,
+ launcherApps: LauncherApps = mLauncherApps,
shortcutKeyToPinnedShortcuts: Map<ShortcutKey, ShortcutInfo> = mKeyToPinnedShortcutsMap,
app: LauncherAppState = mockAppState,
bgDataModel: BgDataModel = mockBgDataModel,
@@ -244,7 +252,7 @@
fun `When app has null target package then mark deleted`() {
// Given
- intent.apply {
+ mIntent.apply {
component = null
`package` = null
}
@@ -264,8 +272,8 @@
// Given
mComponentName = ComponentName("", "")
- intent.component = mComponentName
- intent.`package` = ""
+ mIntent.component = mComponentName
+ mIntent.`package` = ""
// When
itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest()
@@ -298,15 +306,14 @@
fun `When fallback Activity found for app then mark restored`() {
// Given
- mockLauncherApps =
- mock<LauncherApps>().apply {
- whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true)
- whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(false)
- }
+ mLauncherApps.apply {
+ whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true)
+ whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(false)
+ }
mockPmHelper =
mock<PackageManagerHelper>().apply {
whenever(getAppLaunchIntent(mComponentName.packageName, mUserHandle))
- .thenReturn(intent)
+ .thenReturn(mIntent)
}
// When
@@ -317,7 +324,7 @@
assertWithMessage("item restoreFlag should be set to 0")
.that(mockCursor.restoreFlag)
.isEqualTo(0)
- verify(mockCursor.updater().put(Favorites.INTENT, intent.toUri(0))).commit()
+ verify(mockCursor.updater().put(Favorites.INTENT, mIntent.toUri(0))).commit()
assertThat(mIconRequestInfos).containsExactly(mockIconRequestInfo)
verify(mockCursor).checkAndAddItem(mockWorkspaceInfo, mockBgDataModel, null)
}
@@ -326,11 +333,10 @@
fun `When app with disabled activity and no fallback found then mark deleted`() {
// Given
- mockLauncherApps =
- mock<LauncherApps>().apply {
- whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true)
- whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(false)
- }
+ mLauncherApps.apply {
+ whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true)
+ whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(false)
+ }
mockPmHelper =
mock<PackageManagerHelper>().apply {
whenever(getAppLaunchIntent(mComponentName.packageName, mUserHandle))
@@ -358,11 +364,11 @@
@Test
fun `When valid Pinned Deep Shortcut then mark restored`() {
-
// Given
mockCursor.itemType = ITEM_TYPE_DEEP_SHORTCUT
val expectedShortcutInfo =
mock<ShortcutInfo>().apply {
+ whenever(userHandle).thenReturn(mUserHandle)
whenever(id).thenReturn("")
whenever(`package`).thenReturn("")
whenever(activity).thenReturn(mock())
@@ -372,7 +378,7 @@
whenever(disabledReason).thenReturn(0)
whenever(persons).thenReturn(EMPTY_PERSON_ARRAY)
}
- val shortcutKey = ShortcutKey.fromIntent(intent, mockCursor.user)
+ val shortcutKey = ShortcutKey.fromIntent(mIntent, mockCursor.user)
mKeyToPinnedShortcutsMap[shortcutKey] = expectedShortcutInfo
mIconRequestInfos = mutableListOf()
@@ -393,6 +399,67 @@
}
@Test
+ @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS)
+ fun `When Archived Deep Shortcut with flag on then mark restored`() {
+ // Given
+ val mockContentWriter: ContentWriter = mock()
+ val mockAppInfo: ApplicationInfo =
+ mock<ApplicationInfo>().apply {
+ isArchived = true
+ enabled = true
+ }
+ val expectedRestoreFlag = FLAG_RESTORED_ICON or FLAG_RESTORE_STARTED
+ doReturn(mockAppInfo).whenever(mLauncherApps).getApplicationInfo(any(), any(), any())
+ whenever(mockContentWriter.put(Favorites.RESTORED, expectedRestoreFlag))
+ .thenReturn(mockContentWriter)
+ whenever(mockContentWriter.commit()).thenReturn(1)
+ mockCursor.apply {
+ itemType = ITEM_TYPE_DEEP_SHORTCUT
+ restoreFlag = restoreFlag or FLAG_RESTORED_ICON
+ whenever(updater()).thenReturn(mockContentWriter)
+ }
+ mIconRequestInfos = mutableListOf()
+
+ // When
+ itemProcessorUnderTest =
+ createWorkspaceItemProcessorUnderTest(allDeepShortcuts = mAllDeepShortcuts)
+ itemProcessorUnderTest.processItem()
+
+ // Then
+ assertThat(mockCursor.restoreFlag and FLAG_RESTORED_ICON).isEqualTo(FLAG_RESTORED_ICON)
+ assertThat(mockCursor.restoreFlag and FLAG_RESTORE_STARTED).isEqualTo(FLAG_RESTORE_STARTED)
+ assertThat(mIconRequestInfos).isNotEmpty()
+ assertThat(mAllDeepShortcuts).isEmpty()
+ verify(mockContentWriter).put(Favorites.RESTORED, expectedRestoreFlag)
+ verify(mockCursor).checkAndAddItem(any(), eq(mockBgDataModel), eq(null))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS)
+ fun `When Archived Deep Shortcut with flag off then remove`() {
+ // Given
+ mockCursor.itemType = ITEM_TYPE_DEEP_SHORTCUT
+ mIconRequestInfos = mutableListOf()
+
+ // When
+ itemProcessorUnderTest =
+ createWorkspaceItemProcessorUnderTest(allDeepShortcuts = mAllDeepShortcuts)
+ itemProcessorUnderTest.processItem()
+
+ // Then
+ assertWithMessage("item restoreFlag should be set to 0")
+ .that(mockCursor.restoreFlag)
+ .isEqualTo(0)
+ assertThat(mIconRequestInfos).isEmpty()
+ assertThat(mAllDeepShortcuts).isEmpty()
+ verify(mockCursor)
+ .markDeleted(
+ "Pinned shortcut not found from request. package=pkg, user=UserHandle{0}",
+ "shortcut_not_found",
+ )
+ }
+
+ @Test
fun `When Pinned Deep Shortcut is not stored in ShortcutManager re-query by Shortcut ID`() {
// Given
mockCursor.itemType = ITEM_TYPE_DEEP_SHORTCUT
@@ -406,8 +473,9 @@
whenever(disabledMessage).thenReturn("")
whenever(disabledReason).thenReturn(0)
whenever(persons).thenReturn(EMPTY_PERSON_ARRAY)
+ whenever(userHandle).thenReturn(mUserHandle)
}
- whenever(mockLauncherApps.getShortcuts(any(), any())).thenReturn(listOf(si))
+ doReturn(listOf(si)).whenever(mLauncherApps).getShortcuts(any(), any())
mKeyToPinnedShortcutsMap.clear()
mIconRequestInfos = mutableListOf()
@@ -417,12 +485,12 @@
itemProcessorUnderTest.processItem()
// Then
- verify(mockLauncherApps).getShortcuts(any(), any())
+ verify(mLauncherApps).getShortcuts(any(), any())
assertWithMessage("item restoreFlag should be set to 0")
.that(mockCursor.restoreFlag)
.isEqualTo(0)
verify(mockCursor).markRestored()
- verify(mockCursor).checkAndAddItem(any(), any(), anyOrNull())
+ verify(mockCursor).checkAndAddItem(any(), any(), eq(null))
}
@Test
@@ -469,11 +537,11 @@
}
mIconRequestInfos = mutableListOf()
// Make sure shortcuts map has expected key from expected package
- intent.`package` = mComponentName.packageName
- val shortcutKey = ShortcutKey.fromIntent(intent, mockCursor.user)
+ mIntent.`package` = mComponentName.packageName
+ val shortcutKey = ShortcutKey.fromIntent(mIntent, mockCursor.user)
mKeyToPinnedShortcutsMap[shortcutKey] = expectedShortcutInfo
// set intent package back to null to test scenario
- intent.`package` = null
+ mIntent.`package` = null
// When
itemProcessorUnderTest =
@@ -656,7 +724,7 @@
itemProcessorUnderTest.processItem()
// Then
- verify(mockCursor).checkAndAddItem(any(), any(), anyOrNull())
+ verify(mockCursor).checkAndAddItem(any(), eq(mockBgDataModel), eq(null))
}
@Test
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 2431ef5..1158521 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -211,6 +211,36 @@
}
/**
+ * Starts dismissing the task by swiping up, then cancels, and task springs back to start.
+ */
+ public void dismissCancel() {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "want to start dismissing an overview task then cancel")) {
+ verifyActiveContainer();
+ int taskCountBeforeDismiss = mOverview.getTaskCount();
+ mLauncher.assertNotEquals("Unable to find a task", 0, taskCountBeforeDismiss);
+
+ final Rect taskBounds = mLauncher.getVisibleBounds(mTask);
+ final int centerX = taskBounds.centerX();
+ final int centerY = taskBounds.bottom - 1;
+ final int endCenterY = centerY - (taskBounds.height() / 4);
+ mLauncher.executeAndWaitForLauncherEvent(
+ // Set slowDown to true so we do not fling the task at the end of the drag, as
+ // we want it to cancel and return back to the origin. We use 30 steps to
+ // perform the gesture slowly as well, to avoid flinging.
+ () -> mLauncher.linearGesture(centerX, centerY, centerX, endCenterY,
+ /* steps= */ 30, /* slowDown= */ true,
+ LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER),
+ event -> TestProtocol.DISMISS_ANIMATION_ENDS_MESSAGE.equals(
+ event.getClassName()),
+ () -> "Canceling swipe to dismiss did not end with task at origin.",
+ "cancel swiping to dismiss");
+
+ }
+ }
+
+ /**
* Clicks the task.
*/
public LaunchedAppState open() {