Merge "Import translations. DO NOT MERGE ANYWHERE" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 7465a4c..d4cea8d 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -615,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/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index a6d3cde..8880abd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -47,6 +47,7 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
import static com.android.window.flags.Flags.enableStartLaunchTransitionFromTaskbarBugfix;
+import static com.android.window.flags.Flags.enableTaskbarConnectedDisplays;
import static com.android.wm.shell.Flags.enableTinyTaskbar;
import static java.lang.invoke.MethodHandles.Lookup.PROTECTED;
@@ -433,7 +434,9 @@
.setIsTransientTaskbar(true)
.build();
}
- mNavMode = DisplayController.getNavigationMode(this);
+ mNavMode = (enableTaskbarConnectedDisplays() && !mIsPrimaryDisplay)
+ ? NavigationMode.THREE_BUTTONS : DisplayController.getNavigationMode(this);
+
}
/** Called when the visibility of the bubble bar changed. */
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 028e9e7..f704254 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -117,7 +117,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;
@@ -247,31 +250,32 @@
AllAppsActionManager allAppsActionManager,
TaskbarNavButtonCallbacks navCallbacks,
RecentsDisplayModel recentsDisplayModel) {
- mParentContext = context;
- createWindowContext(context.getDisplayId());
+ 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);
});
debugWhyTaskbarNotDestroyed("TaskbarManager created");
@@ -284,15 +288,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) {
@@ -302,8 +306,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;
@@ -355,7 +359,6 @@
int displayId = mTaskbars.keyAt(i);
destroyTaskbarForDisplay(displayId);
removeTaskbarRootViewFromWindow(displayId);
- removeWindowContextFromMap(displayId);
}
}
@@ -422,7 +425,7 @@
*/
public void onUserUnlocked() {
mUserUnlocked = true;
- DisplayController.INSTANCE.get(getPrimaryWindowContext()).addChangeListener(
+ DisplayController.INSTANCE.get(mPrimaryWindowContext).addChangeListener(
mRecreationListener);
recreateTaskbar();
addTaskbarRootViewToWindow(getDefaultDisplayId());
@@ -486,8 +489,7 @@
return ql.getUnfoldTransitionProgressProvider();
}
} else {
- return SystemUiProxy.INSTANCE.get(
- getPrimaryWindowContext()).getUnfoldTransitionProvider();
+ return SystemUiProxy.INSTANCE.get(mPrimaryWindowContext).getUnfoldTransitionProvider();
}
return null;
}
@@ -560,7 +562,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;
@@ -740,6 +742,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);
+ }
}
/**
@@ -747,6 +757,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);
+ }
}
/**
@@ -776,19 +794,19 @@
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);
Log.d(TASKBAR_NOT_DESTROYED_TAG, "unregistering component callbacks from destroy().");
- getPrimaryWindowContext().unregisterComponentCallbacks(mDefaultComponentCallbacks);
- mShutdownReceiver.unregisterReceiverSafely(getPrimaryWindowContext());
+ mPrimaryWindowContext.unregisterComponentCallbacks(mDefaultComponentCallbacks);
+ mShutdownReceiver.unregisterReceiverSafely(mPrimaryWindowContext);
destroyAllTaskbars();
}
@@ -893,16 +911,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;
@@ -1003,22 +1021,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);
}
/**
@@ -1028,12 +1050,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;
}
/**
@@ -1042,8 +1065,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);
}
/**
@@ -1070,7 +1102,7 @@
}
private int getDefaultDisplayId() {
- return mParentContext.getDisplayId();
+ return mBaseContext.getDisplayId();
}
/** Temp logs for b/254119092. */
@@ -1114,7 +1146,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/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/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/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/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/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/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/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 02be373..da160f1 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -55,8 +55,8 @@
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.DesktopTask
import com.android.quickstep.util.RecentsOrientedState
-import com.android.systemui.shared.recents.model.Task
/** TaskView that contains all tasks that are part of the desktop. */
class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
@@ -271,10 +271,14 @@
/** Updates this desktop task to the gives task list defined in `tasks` */
fun bind(
- tasks: List<Task>,
+ desktopTask: DesktopTask,
orientedState: RecentsOrientedState,
taskOverlayFactory: TaskOverlayFactory,
) {
+ // TODO(b/370495260): Minimized tasks should not be filtered with desktop exploded view
+ // support.
+ // Minimized tasks should not be shown in Overview.
+ val tasks = desktopTask.tasks.filterNot { it.isMinimized }
if (DEBUG) {
val sb = StringBuilder()
sb.append("bind tasks=").append(tasks.size).append("\n")
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index a76ebdb..88850ab 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -212,6 +212,7 @@
import com.android.quickstep.recents.viewmodel.RecentsViewModel;
import com.android.quickstep.util.ActiveGestureProtoLogProxy;
import com.android.quickstep.util.AnimUtils;
+import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.RecentsAtomicAnimationFactory;
@@ -1981,12 +1982,7 @@
splitTask.getBottomRightTask(), mOrientationState,
mTaskOverlayFactory, splitTask.getSplitBounds());
} else if (taskView instanceof DesktopTaskView desktopTaskView) {
- // Minimized tasks should not be shown in Overview
- List<Task> nonMinimizedTasks =
- groupTask.getTasks().stream()
- .filter(task -> !task.isMinimized)
- .toList();
- desktopTaskView.bind(nonMinimizedTasks, mOrientationState,
+ desktopTaskView.bind((DesktopTask) groupTask, mOrientationState,
mTaskOverlayFactory);
} else if (groupTask instanceof SplitTask splitTask) {
Task task = splitTask.getTopLeftTask().key.id == stagedTaskIdToBeRemoved
@@ -3034,7 +3030,7 @@
final TaskView taskView;
if (needDesktopTask) {
taskView = getTaskViewFromPool(TaskViewType.DESKTOP);
- ((DesktopTaskView) taskView).bind(Arrays.asList(runningTasks),
+ ((DesktopTaskView) taskView).bind(new DesktopTask(Arrays.asList(runningTasks)),
mOrientationState, mTaskOverlayFactory);
} else if (needGroupTaskView) {
taskView = getTaskViewFromPool(TaskViewType.GROUPED);
@@ -6379,7 +6375,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 +6938,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/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/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/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/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/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/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-bs/strings.xml b/res/values-bs/strings.xml
index 8b7033f..ff8a541 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -143,7 +143,7 @@
<string name="title_change_settings" msgid="1376365968844349552">"Promijeni postavke"</string>
<string name="notification_dots_service_title" msgid="4284221181793592871">"Prikaži tačke za obavještenja"</string>
<string name="developer_options_title" msgid="700788437593726194">"Opcije za programere"</string>
- <string name="auto_add_shortcuts_label" msgid="4926805029653694105">"Dodaj ikone aplikacija na početni ekran"</string>
+ <string name="auto_add_shortcuts_label" msgid="4926805029653694105">"Dodavanje ikona aplikacija na početni ekran"</string>
<string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Za nove aplikacije"</string>
<string name="package_state_unknown" msgid="7592128424511031410">"Nepoznato"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Ukloni"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index a1e822a..0c10f0d 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -129,7 +129,7 @@
<string name="app_pair_name_format" msgid="8134106404716224054">"Ζεύγος εφαρμογών: <xliff:g id="APP1">%1$s</xliff:g> και <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Ταπετσαρία και στιλ"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Επεξεργασία αρχικής οθόνης"</string>
- <string name="settings_button_text" msgid="8873672322605444408">"Ρυθμίσεις Αρχ. Οθ."</string>
+ <string name="settings_button_text" msgid="8873672322605444408">"Ρυθμ. Αρχικής οθόνης"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"Απενεργοποιήθηκε από τον διαχειριστή σας"</string>
<string name="allow_rotation_title" msgid="7222049633713050106">"Να επιτρέπεται η περιστροφή της αρχικής οθόνης"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Όταν το τηλέφωνο περιστρέφεται"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index db2b601..8696ff8 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -50,7 +50,7 @@
<string name="add_to_home_screen" msgid="9168649446635919791">"होम स्क्रीन पर जोड़ें"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> विजेट को होम स्क्रीन पर जोड़ा गया"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"सुझाव"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"ज़रूरी ऐप्लिकेशन"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"अहम जानकारी"</string>
<string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"खबरों और पत्रिकाओं वाले ऐप्लिकेशन"</string>
<string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"मनोरंजन से जुड़े ऐप्लिकेशन"</string>
<string name="social_widget_recommendation_category_label" msgid="689147679536384717">"सोशल मीडिया ऐप्लिकेशन"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 9f4ac5a..1a1f405 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -54,7 +54,7 @@
<string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Újságok és magazinok"</string>
<string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Szórakozás"</string>
<string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Közösségi"</string>
- <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Neked javasolt"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Javaslatok"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"A <xliff:g id="SELECTED_HEADER">%1$s</xliff:g>-modulok a jobb, a kereső és a beállítások pedig a bal oldalon találhatók"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# modul}other{# modul}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# gyorsparancs}other{# gyorsparancs}}"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index ec6da15..34bfbad 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -93,7 +93,7 @@
<string name="all_apps_button_personal_label" msgid="1315764287305224468">"רשימת אפליקציות אישיות"</string>
<string name="all_apps_button_work_label" msgid="7270707118948892488">"רשימת אפליקציות עבודה"</string>
<string name="remove_drop_target_label" msgid="7812859488053230776">"הסרה"</string>
- <string name="uninstall_drop_target_label" msgid="4722034217958379417">"להסרת התקנה"</string>
+ <string name="uninstall_drop_target_label" msgid="4722034217958379417">"הסרת ההתקנה"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"פרטי האפליקציה"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"התקנה במרחב הפרטי"</string>
<string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"הסרת האפליקציה"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index b23b97b..bd792ac 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -129,7 +129,7 @@
<string name="app_pair_name_format" msgid="8134106404716224054">"Programų pora: „<xliff:g id="APP1">%1$s</xliff:g>“ ir „<xliff:g id="APP2">%2$s</xliff:g>“"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Ekrano fonas ir stilius"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Redaguoti pagrindinį ekraną"</string>
- <string name="settings_button_text" msgid="8873672322605444408">"„Home“ nustatymai"</string>
+ <string name="settings_button_text" msgid="8873672322605444408">"Pagrindinio ekrano nustatymai"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"Išjungė administratorius"</string>
<string name="allow_rotation_title" msgid="7222049633713050106">"Leisti pasukti pagrindinį ekraną"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Kai telefonas pasukamas"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index e3bde3a..de509df 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -136,7 +136,7 @@
<string name="landscape_mode_title" msgid="5138814555934843926">"ल्यान्डस्केप मोड"</string>
<string name="landscape_mode_desc" msgid="7372569859592816793">"फोनमा ल्यान्डस्केप मोड अन गर्नुहोस्"</string>
<string name="notification_dots_title" msgid="9062440428204120317">"नोटिफिकेसन डट"</string>
- <string name="notification_dots_desc_on" msgid="1679848116452218908">"सक्रिय"</string>
+ <string name="notification_dots_desc_on" msgid="1679848116452218908">"अन छ"</string>
<string name="notification_dots_desc_off" msgid="1760796511504341095">"निष्क्रिय"</string>
<string name="title_missing_notification_access" msgid="7503287056163941064">"सूचनासम्बन्धी पहुँच आवश्यक हुन्छ"</string>
<string name="msg_missing_notification_access" msgid="281113995110910548">"नोटिफिकेसन डट देखाउन <xliff:g id="NAME">%1$s</xliff:g> को एपसम्बन्धी सूचनाहरूलाई अन गर्नुहोस्"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index d771c6f..92b0c6d 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -129,7 +129,7 @@
<string name="app_pair_name_format" msgid="8134106404716224054">"App-paar: <xliff:g id="APP1">%1$s</xliff:g> en <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Achtergrond en stijl"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Startscherm bewerken"</string>
- <string name="settings_button_text" msgid="8873672322605444408">"Instellingen start"</string>
+ <string name="settings_button_text" msgid="8873672322605444408">"Instellingen Start"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"Uitgezet door je beheerder"</string>
<string name="allow_rotation_title" msgid="7222049633713050106">"Draaien van startscherm toestaan"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Als de telefoon gedraaid is"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index ac5f3be..c15e2e9 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -50,7 +50,7 @@
<string name="add_to_home_screen" msgid="9168649446635919791">"ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਸ਼ਾਮਲ ਕਰੋ"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ਵਿਜੇਟ ਨੂੰ ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"ਸੁਝਾਅ"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"ਲੋੜੀਂਦੀਆਂ"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"ਲੋੜੀਂਦੀਆਂ ਐਪਾਂ ਦੇ ਵਿਜੇਟ"</string>
<string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"ਖਬਰਾਂ ਅਤੇ ਰਸਾਲੇ"</string>
<string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"ਮਨੋਰੰਜਨ"</string>
<string name="social_widget_recommendation_category_label" msgid="689147679536384717">"ਸੋਸ਼ਲ"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index b2dd767..f019544 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -49,7 +49,7 @@
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Нажмите на виджет и удерживайте его, чтобы переместить в нужное место на главном экране."</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Добавить на главный экран"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Виджет \"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>\" добавлен на главный экран"</string>
- <string name="suggested_widgets_header_title" msgid="1844314680798145222">"Рекомендованные"</string>
+ <string name="suggested_widgets_header_title" msgid="1844314680798145222">"Рекомендации"</string>
<string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Основное"</string>
<string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Новости и журналы"</string>
<string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Развлечения"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index b2d2a1b..cecfedb 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -46,7 +46,7 @@
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"宽 %1$d,高 %2$d"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"“<xliff:g id="WIDGET_NAME">%1$s</xliff:g>”微件"</string>
<string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"“<xliff:g id="WIDGET_NAME">%1$s</xliff:g>”微件,宽 %2$d,高 %3$d"</string>
- <string name="add_item_request_drag_hint" msgid="8730547755622776606">"轻触并按住此微件即可在主屏幕上随意移动"</string>
+ <string name="add_item_request_drag_hint" msgid="8730547755622776606">"轻触并按住此微件即可将其拖拽到主屏幕上任意位置"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"添加到主屏幕"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"已将“<xliff:g id="WIDGET_NAME">%1$s</xliff:g>”微件添加到主屏幕"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"建议"</string>
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..315301a 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;
/**
@@ -2256,8 +2257,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/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/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/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/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() {