Merge "[2/n] Hide desktop button in overview for incompatible tasks" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 06809d7..1726eca 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -564,3 +564,23 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "restore_archived_shortcuts"
+ namespace: "launcher"
+ description: "Makes sure pre-archived pinned shortcuts also get restored"
+ bug: "375414891"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "restore_archived_app_icons_from_db"
+ namespace: "launcher"
+ description: "Restores pre-archived icons from db when available, mimicing promise icons"
+ bug: "391913214"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/quickstep/dagger/com/android/launcher3/dagger/AppModule.kt b/quickstep/dagger/com/android/launcher3/dagger/AppModule.kt
new file mode 100644
index 0000000..29586c4
--- /dev/null
+++ b/quickstep/dagger/com/android/launcher3/dagger/AppModule.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.dagger
+
+import dagger.Module
+
+/**
+ * Module containing bindings for the final derivative app, an implementation of this module should
+ * be included in the final app code.
+ */
+@Module abstract class AppModule {}
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index 1f33e08..a530325 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -34,7 +34,6 @@
<string name="launcher_restore_event_logger_class" translatable="false">com.android.quickstep.LauncherRestoreEventLoggerImpl</string>
<string name="taskbar_edu_tooltip_controller_class" translatable="false">com.android.launcher3.taskbar.TaskbarEduTooltipController</string>
<string name="nav_handle_long_press_handler_class" translatable="false"></string>
- <string name="contextual_search_state_manager_class" translatable="false"></string>
<!-- The number of thumbnails and icons to keep in the cache. The thumbnail cache size also
determines how many thumbnails will be fetched in the background. -->
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 0474000..d2a7029 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -304,10 +304,6 @@
<string name="taskbar_a11y_shown_with_bubbles_left_title">Taskbar & bubbles left shown</string>
<!-- Accessibility title for the Taskbar window appearing together with bubble bar on right. [CHAR_LIMIT=30] -->
<string name="taskbar_a11y_shown_with_bubbles_right_title">Taskbar & bubbles right shown</string>
- <!-- Accessibility title for the Taskbar window being closed. [CHAR_LIMIT=30] -->
- <string name="taskbar_a11y_hidden_title">Taskbar hidden</string>
- <!-- Accessibility title for the Taskbar window being closed together with bubble bar. [CHAR_LIMIT=30] -->
- <string name="taskbar_a11y_hidden_with_bubbles_title">Taskbar & bubbles hidden</string>
<!-- Accessibility title for the Taskbar window on phones. [CHAR_LIMIT=NONE] -->
<string name="taskbar_phone_a11y_title">Navigation bar</string>
<!-- Text in popup dialog for user to switch between always showing Taskbar or not. [CHAR LIMIT=30] -->
diff --git a/quickstep/src/com/android/launcher3/dagger/Modules.kt b/quickstep/src/com/android/launcher3/dagger/Modules.kt
index 04c1d5e..52be413 100644
--- a/quickstep/src/com/android/launcher3/dagger/Modules.kt
+++ b/quickstep/src/com/android/launcher3/dagger/Modules.kt
@@ -21,9 +21,11 @@
import com.android.launcher3.util.ApiWrapper
import com.android.launcher3.util.PluginManagerWrapper
import com.android.launcher3.util.window.WindowManagerProxy
+import com.android.quickstep.util.GestureExclusionManager
import com.android.quickstep.util.SystemWindowManagerProxy
import dagger.Binds
import dagger.Module
+import dagger.Provides
private object Modules {}
@@ -42,3 +44,11 @@
@Binds
abstract fun bindPluginManagerWrapper(impl: PluginManagerWrapperImpl): PluginManagerWrapper
}
+
+@Module
+object StaticObjectModule {
+
+ @Provides
+ @JvmStatic
+ fun provideGestureExclusionManager(): GestureExclusionManager = GestureExclusionManager.INSTANCE
+}
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
index 56945ba..70868c5 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
@@ -17,7 +17,6 @@
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
import static com.android.launcher3.model.PredictionHelper.getAppTargetFromItemInfo;
-import static com.android.launcher3.model.PredictionHelper.isTrackedForHotseatPrediction;
import static com.android.launcher3.model.PredictionHelper.wrapAppTargetWithItemLocation;
import android.app.prediction.AppTarget;
@@ -27,9 +26,12 @@
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
+import com.android.launcher3.model.PredictionHelper;
import com.android.launcher3.model.data.ItemInfo;
import java.util.ArrayList;
+import java.util.Objects;
+import java.util.stream.Collectors;
/**
* Model helper for app predictions in workspace
@@ -43,13 +45,18 @@
*/
public static Bundle convertDataModelToAppTargetBundle(Context context, BgDataModel dataModel) {
Bundle bundle = new Bundle();
- ArrayList<AppTargetEvent> events = new ArrayList<>();
- ArrayList<ItemInfo> workspaceItems = dataModel.getAllWorkspaceItems();
- for (ItemInfo item : workspaceItems) {
- AppTarget target = getAppTargetFromItemInfo(context, item);
- if (target != null && !isTrackedForHotseatPrediction(item)) continue;
- events.add(wrapAppTargetWithItemLocation(target, AppTargetEvent.ACTION_PIN, item));
- }
+ ArrayList<AppTargetEvent> events = dataModel.itemsIdMap
+ .stream()
+ .filter(PredictionHelper::isTrackedForHotseatPrediction)
+ .map(item -> {
+ AppTarget target = getAppTargetFromItemInfo(context, item);
+ return target != null
+ ? wrapAppTargetWithItemLocation(target, AppTargetEvent.ACTION_PIN, item)
+ : null;
+ })
+ .filter(Objects::nonNull)
+ .collect(Collectors.toCollection(ArrayList::new));
+
ArrayList<AppTarget> currentTargets = new ArrayList<>();
FixedContainerItems hotseatItems = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION);
if (hotseatItems != null) {
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index 25e1813..40e8fc2 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -462,7 +462,7 @@
private Bundle getBundleForWidgetsOnWorkspace(Context context, BgDataModel dataModel) {
Bundle bundle = new Bundle();
ArrayList<AppTargetEvent> widgetEvents =
- dataModel.getAllWorkspaceItems().stream()
+ dataModel.itemsIdMap.stream()
.filter(PredictionHelper::isTrackedForWidgetPrediction)
.map(item -> {
AppTarget target = getAppTargetFromItemInfo(context, item);
diff --git a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
index 9d9054e..40e1c10 100644
--- a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
+++ b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
import static com.android.launcher3.Flags.enableTieredWidgetsByDefaultInPicker;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
+import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toMap;
@@ -69,9 +70,11 @@
@NonNull AllAppsList apps) {
Predicate<WidgetItem> predictedWidgetsFilter = enableTieredWidgetsByDefaultInPicker()
? dataModel.widgetsModel.getPredictedWidgetsFilter() : null;
- Set<ComponentKey> widgetsInWorkspace = dataModel.appWidgets.stream().map(
- widget -> new ComponentKey(widget.providerName, widget.user)).collect(
- Collectors.toSet());
+ Set<ComponentKey> widgetsInWorkspace = dataModel.itemsIdMap
+ .stream()
+ .filter(WIDGET_FILTER)
+ .map(item -> new ComponentKey(item.getTargetComponent(), item.user))
+ .collect(Collectors.toSet());
// Widgets (excluding shortcuts & already added widgets) that belong to apps eligible for
// being in predictions.
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index b928de0..3736e6d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -33,6 +33,7 @@
import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.SingleTask;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -275,7 +276,7 @@
if (desktopTask != null) {
mTasks = desktopTask.getTasks().stream()
- .map(GroupTask::new)
+ .map(SingleTask::new)
.filter(task -> !shouldExcludeTask(task, taskIdsToExclude))
.collect(Collectors.toList());
// All other tasks, apart from the grouped desktop task, are hidden
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 1144ac5..289e720 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -42,7 +42,7 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
@@ -129,7 +129,9 @@
private final Rect mTempRect = new Rect();
- private static final int FLAG_SWITCHER_SHOWING = 1 << 0;
+ /** Whether the IME Switcher button is visible. */
+ private static final int FLAG_IME_SWITCHER_BUTTON_VISIBLE = 1 << 0;
+ /** Whether the IME is visible. */
private static final int FLAG_IME_VISIBLE = 1 << 1;
private static final int FLAG_ROTATION_BUTTON_VISIBLE = 1 << 2;
private static final int FLAG_A11Y_VISIBLE = 1 << 3;
@@ -301,7 +303,7 @@
isThreeButtonNav ? mStartContextualContainer : mEndContextualContainer,
mControllers.navButtonController, R.id.ime_switcher);
mPropertyHolders.add(new StatePropertyHolder(mImeSwitcherButton,
- flags -> ((flags & FLAG_SWITCHER_SHOWING) != 0)
+ flags -> ((flags & FLAG_IME_SWITCHER_BUTTON_VISIBLE) != 0)
&& ((flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0)));
}
@@ -507,8 +509,9 @@
private void parseSystemUiFlags(@SystemUiStateFlags long sysUiStateFlags) {
mSysuiStateFlags = sysUiStateFlags;
+ boolean isImeSwitcherButtonVisible =
+ (sysUiStateFlags & SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING) != 0;
boolean isImeVisible = (sysUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0;
- boolean isImeSwitcherShowing = (sysUiStateFlags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0;
boolean a11yVisible = (sysUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
boolean isHomeDisabled = (sysUiStateFlags & SYSUI_STATE_HOME_DISABLED) != 0;
boolean isRecentsDisabled = (sysUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0;
@@ -522,9 +525,8 @@
boolean isKeyboardShortcutHelperShowing =
(sysUiStateFlags & SYSUI_STATE_SHORTCUT_HELPER_SHOWING) != 0;
- // TODO(b/202218289) we're getting IME as not visible on lockscreen from system
+ updateStateForFlag(FLAG_IME_SWITCHER_BUTTON_VISIBLE, isImeSwitcherButtonVisible);
updateStateForFlag(FLAG_IME_VISIBLE, isImeVisible);
- updateStateForFlag(FLAG_SWITCHER_SHOWING, isImeSwitcherShowing);
updateStateForFlag(FLAG_A11Y_VISIBLE, a11yVisible);
updateStateForFlag(FLAG_DISABLE_HOME, isHomeDisabled);
updateStateForFlag(FLAG_DISABLE_RECENTS, isRecentsDisabled);
@@ -1226,7 +1228,8 @@
private static String getStateString(int flags) {
StringJoiner str = new StringJoiner("|");
- appendFlag(str, flags, FLAG_SWITCHER_SHOWING, "FLAG_SWITCHER_SHOWING");
+ appendFlag(str, flags, FLAG_IME_SWITCHER_BUTTON_VISIBLE,
+ "FLAG_IME_SWITCHER_BUTTON_VISIBLE");
appendFlag(str, flags, FLAG_IME_VISIBLE, "FLAG_IME_VISIBLE");
appendFlag(str, flags, FLAG_ROTATION_BUTTON_VISIBLE, "FLAG_ROTATION_BUTTON_VISIBLE");
appendFlag(str, flags, FLAG_A11Y_VISIBLE, "FLAG_A11Y_VISIBLE");
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 978ffdb..e8a0c45 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -86,7 +86,6 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.BubbleTextView.RunningAppState;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Flags;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.R;
@@ -1330,7 +1329,7 @@
mControllers.uiController.onTaskbarIconLaunched(api);
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
}
- } else if (tag instanceof TaskItemInfo info && !Flags.enableMultiInstanceMenuTaskbar()) {
+ } else if (tag instanceof TaskItemInfo info) {
RemoteTransition remoteTransition = canUnminimizeDesktopTask(info.getTaskId())
? createDesktopAppLaunchRemoteTransition(
AppLaunchType.UNMINIMIZE, Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_ICON)
@@ -1341,7 +1340,8 @@
taskView = recents.getTaskViewByTaskId(info.getTaskId());
}
- if (areDesktopTasksVisible() && taskView != null) {
+ if (areDesktopTasksVisible() && taskView != null
+ && mControllers.uiController.isInOverviewUi()) {
RunnableList runnableList = taskView.launchWithAnimation();
if (runnableList != null) {
runnableList.add(() ->
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 21c8255..b46b0dc 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -89,6 +89,8 @@
public class TaskbarManager {
private static final String TAG = "TaskbarManager";
private static final boolean DEBUG = false;
+ // TODO(b/382378283) remove all logs with this tag
+ public static final String NULL_TASKBAR_ROOT_LAYOUT_TAG = "b/382378283";
/**
* All the configurations which do not initiate taskbar recreation.
@@ -151,6 +153,20 @@
private class RecreationListener implements DisplayController.DisplayInfoChangeListener {
@Override
public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
+
+ if ((flags & CHANGE_DENSITY) != 0) {
+ Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "Display density changed");
+ }
+ if ((flags & CHANGE_NAVIGATION_MODE) != 0) {
+ Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "Navigation mode changed");
+ }
+ if ((flags & CHANGE_DESKTOP_MODE) != 0) {
+ Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "Desktop mode changed");
+ }
+ if ((flags & CHANGE_TASKBAR_PINNING) != 0) {
+ Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "Taskbar pinning changed");
+ }
+
if ((flags & (CHANGE_DENSITY | CHANGE_NAVIGATION_MODE | CHANGE_DESKTOP_MODE
| CHANGE_TASKBAR_PINNING)) != 0) {
recreateTaskbar();
@@ -345,13 +361,13 @@
private void destroyTaskbarForDisplay(int displayId) {
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
- debugWhyTaskbarNotDestroyed(
- "destroyTaskbarForDisplay: " + taskbar + " displayId=" + displayId);
+ debugWhyTaskbarNotDestroyed("destroyTaskbarForDisplay: " + taskbar, displayId);
if (taskbar != null) {
taskbar.onDestroy();
// remove all defaults that we store
removeTaskbarFromMap(displayId);
}
+ // make this display-specific
DeviceProfile dp = mUserUnlocked ?
LauncherAppState.getIDP(mWindowContext).getDeviceProfile(mWindowContext) : null;
if (dp == null || !isTaskbarEnabled(dp)) {
@@ -506,6 +522,7 @@
private void recreateTaskbarForDisplay(int displayId) {
Trace.beginSection("recreateTaskbar");
try {
+ // TODO: make this code display specific
DeviceProfile dp = mUserUnlocked ?
LauncherAppState.getIDP(mWindowContext).getDeviceProfile(mWindowContext) : null;
@@ -547,9 +564,14 @@
if (enableTaskbarNoRecreate()) {
addTaskbarRootViewToWindow(displayId);
FrameLayout taskbarRootLayout = getTaskbarRootLayoutForDisplay(displayId);
- taskbarRootLayout.removeAllViews();
- taskbarRootLayout.addView(taskbar.getDragLayer());
- taskbar.notifyUpdateLayoutParams();
+ if (taskbarRootLayout != null) {
+ taskbarRootLayout.removeAllViews();
+ taskbarRootLayout.addView(taskbar.getDragLayer());
+ taskbar.notifyUpdateLayoutParams();
+ } else {
+ Log.e(NULL_TASKBAR_ROOT_LAYOUT_TAG,
+ "taskbarRootLayout is null for displayId=" + displayId);
+ }
}
} finally {
Trace.endSection();
@@ -744,17 +766,25 @@
public void dumpLogs(String prefix, PrintWriter pw) {
pw.println(prefix + "TaskbarManager:");
- TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
- if (taskbar == null) {
- pw.println(prefix + "\tTaskbarActivityContext: null");
- } else {
- taskbar.dumpLogs(prefix + "\t", pw);
+ // iterate through taskbars and do the dump for each
+ for (int i = 0; i < mTaskbars.size(); i++) {
+ int displayId = mTaskbars.keyAt(i);
+ TaskbarActivityContext taskbar = mTaskbars.get(i);
+ pw.println(prefix + "\tTaskbar at display " + displayId + ":");
+ if (taskbar == null) {
+ pw.println(prefix + "\t\tTaskbarActivityContext: null");
+ } else {
+ taskbar.dumpLogs(prefix + "\t\t", pw);
+ }
}
+
}
private void addTaskbarRootViewToWindow(int displayId) {
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
if (!enableTaskbarNoRecreate() || taskbar == null) {
+ Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG,
+ "addTaskbarRootViewToWindow - taskbar null | displayId=" + displayId);
return;
}
@@ -762,6 +792,10 @@
mWindowManager.addView(getTaskbarRootLayoutForDisplay(displayId),
taskbar.getWindowLayoutParams());
mAddedRootLayouts.put(displayId, true);
+ } else {
+ Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG,
+ "addTaskbarRootViewToWindow - root layout already added | displayId="
+ + displayId);
}
}
@@ -854,6 +888,7 @@
}
};
addTaskbarRootLayoutToMap(displayId, newTaskbarRootLayout);
+ Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "created new root layout - displayId=" + displayId);
}
private boolean isDefaultDisplay(int displayId) {
@@ -867,7 +902,14 @@
* @return The taskbar root layout {@link FrameLayout} for a given display or {@code null}.
*/
private FrameLayout getTaskbarRootLayoutForDisplay(int displayId) {
- return mRootLayouts.get(displayId);
+ FrameLayout frameLayout = mRootLayouts.get(displayId);
+ if (frameLayout != null) {
+ return frameLayout;
+ } else {
+ Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG,
+ "getTaskbarRootLayoutForDisplay == null | displayId=" + displayId);
+ return null;
+ }
}
/**
@@ -880,6 +922,8 @@
if (!mRootLayouts.contains(displayId) && rootLayout != null) {
mRootLayouts.put(displayId, rootLayout);
}
+
+ Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "mRootLayouts.size()=" + mRootLayouts.size());
}
/**
@@ -892,6 +936,8 @@
mAddedRootLayouts.delete(displayId);
mRootLayouts.delete(displayId);
}
+
+ Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "mRootLayouts.size()=" + mRootLayouts.size());
}
private int getDefaultDisplayId() {
@@ -900,11 +946,17 @@
/** Temp logs for b/254119092. */
public void debugWhyTaskbarNotDestroyed(String debugReason) {
+ debugWhyTaskbarNotDestroyed(debugReason, getDefaultDisplayId());
+ }
+
+ /** Temp logs for b/254119092. */
+ public void debugWhyTaskbarNotDestroyed(String debugReason, int displayId) {
StringJoiner log = new StringJoiner("\n");
- log.add(debugReason);
+ log.add(debugReason + " displayId=" + displayId);
boolean activityTaskbarPresent = mActivity != null
&& mActivity.getDeviceProfile().isTaskbarPresent;
+ // TODO: make this display specific
boolean contextTaskbarPresent = mUserUnlocked && LauncherAppState.getIDP(mWindowContext)
.getDeviceProfile(mWindowContext).isTaskbarPresent;
if (activityTaskbarPresent == contextTaskbarPresent) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
index 5eb92d8..6047999 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
@@ -30,6 +30,7 @@
import com.android.quickstep.RecentsModel
import com.android.quickstep.util.DesktopTask
import com.android.quickstep.util.GroupTask
+import com.android.quickstep.util.SingleTask
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import java.io.PrintWriter
@@ -218,7 +219,7 @@
// Kind of hacky, we wrap each single task in the Desktop as a GroupTask.
val orderFromId = orderedRunningTaskIds.withIndex().associate { (index, id) -> id to index }
val sortedTasks = tasks.sortedWith(compareBy(nullsLast()) { orderFromId[it.key.id] })
- return sortedTasks.map { GroupTask(it) }
+ return sortedTasks.map { SingleTask(it) }
}
private fun reloadRecentTasksIfNeeded() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 025ad87..68114f1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -35,7 +35,7 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
@@ -259,8 +259,10 @@
private @Nullable AnimatorSet mAnimator;
private boolean mIsSystemGestureInProgress;
+ /** Whether the IME is visible. */
private boolean mIsImeShowing;
- private boolean mIsImeSwitcherShowing;
+ /** Whether the IME Switcher button is visible. */
+ private boolean mIsImeSwitcherButtonShowing;
private final Alarm mTimeoutAlarm = new Alarm();
private boolean mEnableBlockingTimeoutDuringTests = false;
@@ -1147,7 +1149,8 @@
SystemUiFlagUtils.isLocked(systemUiStateFlags));
mIsImeShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SHOWING);
- mIsImeSwitcherShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SWITCHER_SHOWING);
+ mIsImeSwitcherButtonShowing =
+ hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING);
if (updateStateForFlag(FLAG_STASHED_IME, shouldStashForIme())) {
animDuration = TASKBAR_STASH_DURATION_FOR_IME;
startDelay = getTaskbarStashStartDelayForIme();
@@ -1199,7 +1202,7 @@
return false;
}
- return mIsImeShowing || mIsImeSwitcherShowing;
+ return mIsImeShowing || mIsImeSwitcherButtonShowing;
}
/**
@@ -1375,7 +1378,7 @@
pw.println(prefix + "\tmState=" + getStateString(mState));
pw.println(prefix + "\tmIsSystemGestureInProgress=" + mIsSystemGestureInProgress);
pw.println(prefix + "\tmIsImeShowing=" + mIsImeShowing);
- pw.println(prefix + "\tmIsImeSwitcherShowing=" + mIsImeSwitcherShowing);
+ pw.println(prefix + "\tmIsImeSwitcherButtonShowing=" + mIsImeSwitcherButtonShowing);
}
private static String getStateString(long flags) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index e4e97e5..457ba3d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -33,7 +33,6 @@
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.os.Bundle;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.view.DisplayCutout;
@@ -41,7 +40,6 @@
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import androidx.annotation.LayoutRes;
@@ -311,16 +309,6 @@
mShouldTryStartAlign = mActivityContext.shouldStartAlignTaskbar();
}
- @Override
- public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
- if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) {
- announceTaskbarShown();
- } else if (action == AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS) {
- announceTaskbarHidden();
- }
- return super.performAccessibilityActionInternal(action, arguments);
- }
-
private void announceTaskbarShown() {
BubbleBarLocation bubbleBarLocation = mControllerCallbacks.getBubbleBarLocationIfVisible();
if (bubbleBarLocation == null) {
@@ -334,21 +322,12 @@
}
}
- private void announceTaskbarHidden() {
- BubbleBarLocation bubbleBarLocation = mControllerCallbacks.getBubbleBarLocationIfVisible();
- if (bubbleBarLocation == null) {
- announceForAccessibility(mContext.getString(R.string.taskbar_a11y_hidden_title));
- } else {
- announceForAccessibility(
- mContext.getString(R.string.taskbar_a11y_hidden_with_bubbles_title));
- }
- }
-
protected void announceAccessibilityChanges() {
- this.performAccessibilityAction(
- isVisibleToUser() ? AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS
- : AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
-
+ // Only announce taskbar window shown. Window disappearing is generally not announce.
+ // This also aligns with talkback guidelines and unnecessary announcement to users.
+ if (isVisibleToUser()) {
+ announceTaskbarShown();
+ }
ActivityContext.lookupContext(getContext()).getDragLayer()
.sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 7d39bf8..aff879e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -20,7 +20,7 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
@@ -34,6 +34,7 @@
import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.Log;
+import android.widget.Toast;
import com.android.launcher3.taskbar.TaskbarSharedState;
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController;
@@ -94,7 +95,7 @@
| SYSUI_STATE_IME_SHOWING
| SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
| SYSUI_STATE_QUICK_SETTINGS_EXPANDED
- | SYSUI_STATE_IME_SWITCHER_SHOWING;
+ | SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING;
private static final long MASK_HIDE_HANDLE_VIEW = SYSUI_STATE_BOUNCER_SHOWING
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
@@ -589,6 +590,18 @@
});
}
+ @Override
+ public void onDragItemOverBubbleBarDragZone(BubbleBarLocation location) {
+ //TODO(b/388894910): add meaningful implementation
+ MAIN_EXECUTOR.execute(() ->
+ Toast.makeText(mContext, "onDragItemOver " + location, Toast.LENGTH_SHORT).show());
+ }
+
+ @Override
+ public void onItemDraggedOutsideBubbleBarDropZone() {
+
+ }
+
/** Notifies WMShell to show the expanded view. */
void showExpandedView() {
mSystemUiProxy.showExpandedView();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 58ebc50..f672840 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -23,6 +23,7 @@
import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.internal.jank.Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE;
+import static com.android.launcher3.Flags.enableExpressiveDismissTaskMotion;
import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.PENDING_SPLIT_SELECT_INFO;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE;
@@ -149,7 +150,9 @@
import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.QuickSwitchTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewRecentsTouchContext;
import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchControllerDeprecated;
import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarTouchController;
import com.android.launcher3.util.ActivityOptionsWrapper;
@@ -265,6 +268,25 @@
private final OverviewChangeListener mOverviewChangeListener = this::onOverviewTargetChanged;
+ private final TaskViewRecentsTouchContext mTaskViewRecentsTouchContext =
+ new TaskViewRecentsTouchContext() {
+ @Override
+ public boolean isRecentsInteractive() {
+ return isInState(OVERVIEW) || isInState(OVERVIEW_MODAL_TASK);
+ }
+
+ @Override
+ public boolean isRecentsModal() {
+ return isInState(OVERVIEW_MODAL_TASK);
+ }
+
+ @Override
+ public void onUserControlledAnimationCreated(
+ AnimatorPlaybackController animController) {
+ getStateManager().setCurrentUserControlledAnimation(animController);
+ }
+ };
+
public static QuickstepLauncher getLauncher(Context context) {
return fromContext(context);
}
@@ -665,7 +687,11 @@
list.add(new StatusBarTouchController(this));
}
- list.add(new LauncherTaskViewController(this));
+ if (enableExpressiveDismissTaskMotion()) {
+ list.add(new TaskViewTouchController<>(this, mTaskViewRecentsTouchContext));
+ } else {
+ list.add(new TaskViewTouchControllerDeprecated<>(this, mTaskViewRecentsTouchContext));
+ }
return list.toArray(new TouchController[list.size()]);
}
@@ -1442,29 +1468,6 @@
mBubbleBarLocation = bubbleBarLocation;
}
- private static final class LauncherTaskViewController extends
- TaskViewTouchController<QuickstepLauncher> {
-
- LauncherTaskViewController(QuickstepLauncher activity) {
- super(activity);
- }
-
- @Override
- protected boolean isRecentsInteractive() {
- return mContainer.isInState(OVERVIEW) || mContainer.isInState(OVERVIEW_MODAL_TASK);
- }
-
- @Override
- protected boolean isRecentsModal() {
- return mContainer.isInState(OVERVIEW_MODAL_TASK);
- }
-
- @Override
- protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
- mContainer.getStateManager().setCurrentUserControlledAnimation(animController);
- }
- }
-
@Override
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
super.dump(prefix, fd, writer, args);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewRecentsTouchContext.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewRecentsTouchContext.java
new file mode 100644
index 0000000..e8d31c1
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewRecentsTouchContext.java
@@ -0,0 +1,32 @@
+/*
+ * 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.uioverrides.touchcontrollers;
+
+import com.android.launcher3.anim.AnimatorPlaybackController;
+
+/** Interface providing context about the RecentsView state to a {@link TaskViewTouchController}. */
+public interface TaskViewRecentsTouchContext {
+ /** Returns whether Recents is interactive for touch. */
+ boolean isRecentsInteractive();
+
+ /** Returns if Recents is showing a single task in a modal way. */
+ boolean isRecentsModal();
+
+ /** Runs when a user controlled animation is created. */
+ default void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.kt b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.kt
new file mode 100644
index 0000000..c996f34
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.kt
@@ -0,0 +1,394 @@
+/*
+ * 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.uioverrides.touchcontrollers
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.content.Context
+import android.graphics.Rect
+import android.os.VibrationEffect
+import android.view.MotionEvent
+import android.view.animation.Interpolator
+import com.android.app.animation.Interpolators
+import com.android.launcher3.AbstractFloatingView
+import com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS
+import com.android.launcher3.LauncherAnimUtils.blockedFlingDurationFactor
+import com.android.launcher3.R
+import com.android.launcher3.Utilities
+import com.android.launcher3.anim.AnimatorPlaybackController
+import com.android.launcher3.anim.PendingAnimation
+import com.android.launcher3.touch.BaseSwipeDetector
+import com.android.launcher3.touch.SingleAxisSwipeDetector
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.FlingBlockCheck
+import com.android.launcher3.util.TouchController
+import com.android.launcher3.util.VibratorWrapper
+import com.android.quickstep.util.VibrationConstants
+import com.android.quickstep.views.RecentsView
+import com.android.quickstep.views.RecentsViewContainer
+import com.android.quickstep.views.TaskView
+import kotlin.math.abs
+
+/** Touch controller for handling task view card swipes */
+class TaskViewTouchController<CONTAINER>(
+ private val container: CONTAINER,
+ private val taskViewRecentsTouchContext: TaskViewRecentsTouchContext,
+) : AnimatorListenerAdapter(), TouchController, SingleAxisSwipeDetector.Listener where
+CONTAINER : Context,
+CONTAINER : RecentsViewContainer {
+ private val recentsView: RecentsView<*, *> = container.getOverviewPanel()
+ private val detector: SingleAxisSwipeDetector =
+ SingleAxisSwipeDetector(
+ container as Context,
+ this,
+ recentsView.pagedOrientationHandler.upDownSwipeDirection,
+ )
+ private val tempRect = Rect()
+ private val isRtl = Utilities.isRtl(container.resources)
+ private val flingBlockCheck = FlingBlockCheck()
+
+ private var currentAnimation: AnimatorPlaybackController? = null
+ private var currentAnimationIsGoingUp = false
+ private var allowGoingUp = false
+ private var allowGoingDown = false
+ private var noIntercept = false
+ private var displacementShift = 0f
+ private var progressMultiplier = 0f
+ private var endDisplacement = 0f
+ private var draggingEnabled = true
+ private var overrideVelocity: Float? = null
+ private var taskBeingDragged: TaskView? = null
+ private var isDismissHapticRunning = false
+
+ private fun canInterceptTouch(ev: MotionEvent): Boolean {
+ val currentAnimation = currentAnimation
+ return when {
+ (ev.edgeFlags and Utilities.EDGE_NAV_BAR) != 0 -> {
+ // Don't intercept swipes on the nav bar, as user might be trying to go home
+ // during a task dismiss animation.
+ currentAnimation?.animationPlayer?.end()
+ false
+ }
+ currentAnimation != null -> {
+ currentAnimation.forceFinishIfCloseToEnd()
+ true
+ }
+ AbstractFloatingView.getTopOpenViewWithType(
+ container,
+ AbstractFloatingView.TYPE_TOUCH_CONTROLLER_NO_INTERCEPT,
+ ) != null -> false
+ else -> taskViewRecentsTouchContext.isRecentsInteractive
+ }
+ }
+
+ override fun onAnimationCancel(animation: Animator) {
+ if (animation === currentAnimation?.target) {
+ clearState()
+ }
+ }
+
+ override fun onControllerInterceptTouchEvent(ev: MotionEvent): Boolean {
+ if (
+ (ev.action == MotionEvent.ACTION_UP || ev.action == MotionEvent.ACTION_CANCEL) &&
+ currentAnimation == null
+ ) {
+ clearState()
+ }
+ if (ev.action == MotionEvent.ACTION_DOWN) {
+ // Disable swiping up and down if the task overlay is modal.
+ if (taskViewRecentsTouchContext.isRecentsModal) {
+ noIntercept = true
+ return false
+ }
+ noIntercept = !canInterceptTouch(ev)
+ if (noIntercept) {
+ return false
+ }
+ // Now figure out which direction scroll events the controller will start
+ // calling the callbacks.
+ var directionsToDetectScroll = 0
+ var ignoreSlopWhenSettling = false
+ if (currentAnimation != null) {
+ directionsToDetectScroll = SingleAxisSwipeDetector.DIRECTION_BOTH
+ ignoreSlopWhenSettling = true
+ } else {
+ taskBeingDragged = null
+ recentsView.taskViews.forEach { taskView ->
+ if (
+ recentsView.isTaskViewVisible(taskView) &&
+ container.dragLayer.isEventOverView(taskView, ev)
+ ) {
+ taskBeingDragged = taskView
+ val upDirection = recentsView.pagedOrientationHandler.getUpDirection(isRtl)
+
+ // The task can be dragged up to dismiss it
+ allowGoingUp = true
+
+ // The task can be dragged down to open it if:
+ // - It's the current page
+ // - We support gestures to enter overview
+ // - It's the focused task if in grid view
+ // - The task is snapped
+ allowGoingDown =
+ taskView === recentsView.currentPageTaskView &&
+ DisplayController.getNavigationMode(container).hasGestures &&
+ (!recentsView.showAsGrid() || taskView.isLargeTile) &&
+ recentsView.isTaskInExpectedScrollPosition(taskView)
+
+ directionsToDetectScroll =
+ if (allowGoingDown) SingleAxisSwipeDetector.DIRECTION_BOTH
+ else upDirection
+ return@forEach
+ }
+ }
+ if (taskBeingDragged == null) {
+ noIntercept = true
+ return false
+ }
+ }
+ detector.setDetectableScrollConditions(directionsToDetectScroll, ignoreSlopWhenSettling)
+ }
+ if (noIntercept) {
+ return false
+ }
+ onControllerTouchEvent(ev)
+ return detector.isDraggingOrSettling
+ }
+
+ override fun onControllerTouchEvent(ev: MotionEvent): Boolean = detector.onTouchEvent(ev)
+
+ private fun reInitAnimationController(goingUp: Boolean) {
+ if (currentAnimation != null && currentAnimationIsGoingUp == goingUp) {
+ // No need to init
+ return
+ }
+ if ((goingUp && !allowGoingUp) || (!goingUp && !allowGoingDown)) {
+ // Trying to re-init in an unsupported direction.
+ return
+ }
+ val taskBeingDragged = taskBeingDragged ?: return
+ currentAnimation?.setPlayFraction(0f)
+ currentAnimation?.target?.removeListener(this)
+ currentAnimation?.dispatchOnCancel()
+
+ val orientationHandler = recentsView.pagedOrientationHandler
+ currentAnimationIsGoingUp = goingUp
+ val dl = container.dragLayer
+ val secondaryLayerDimension = orientationHandler.getSecondaryDimension(dl)
+ val maxDuration = 2L * secondaryLayerDimension
+ val verticalFactor = orientationHandler.getTaskDragDisplacementFactor(isRtl)
+ val secondaryTaskDimension = orientationHandler.getSecondaryDimension(taskBeingDragged)
+ // The interpolator controlling the most prominent visual movement. We use this to determine
+ // whether we passed SUCCESS_TRANSITION_PROGRESS.
+ val currentInterpolator: Interpolator
+ val pa: PendingAnimation
+ if (goingUp) {
+ currentInterpolator = Interpolators.LINEAR
+ pa = PendingAnimation(maxDuration)
+ recentsView.createTaskDismissAnimation(
+ pa,
+ taskBeingDragged,
+ true, /* animateTaskView */
+ true, /* removeTask */
+ maxDuration,
+ false, /* dismissingForSplitSelection*/
+ )
+
+ endDisplacement = -secondaryTaskDimension.toFloat()
+ } else {
+ currentInterpolator = Interpolators.ZOOM_IN
+ pa =
+ recentsView.createTaskLaunchAnimation(
+ taskBeingDragged,
+ maxDuration,
+ currentInterpolator,
+ )
+
+ // Since the thumbnail is what is filling the screen, based the end displacement on it.
+ taskBeingDragged.getThumbnailBounds(tempRect, /* relativeToDragLayer= */ true)
+ endDisplacement = (secondaryLayerDimension - tempRect.bottom).toFloat()
+ }
+ endDisplacement *= verticalFactor.toFloat()
+ currentAnimation =
+ pa.createPlaybackController().apply {
+ // Setting this interpolator doesn't affect the visual motion, but is used to
+ // determine whether we successfully reached the target state in onDragEnd().
+ target.interpolator = currentInterpolator
+ taskViewRecentsTouchContext.onUserControlledAnimationCreated(this)
+ target.addListener(this@TaskViewTouchController)
+ dispatchOnStart()
+ }
+ progressMultiplier = 1 / endDisplacement
+ }
+
+ override fun onDragStart(start: Boolean, startDisplacement: Float) {
+ if (!draggingEnabled) return
+ val currentAnimation = currentAnimation
+
+ val orientationHandler = recentsView.pagedOrientationHandler
+ if (currentAnimation == null) {
+ reInitAnimationController(orientationHandler.isGoingUp(startDisplacement, isRtl))
+ displacementShift = 0f
+ } else {
+ displacementShift = currentAnimation.progressFraction / progressMultiplier
+ currentAnimation.pause()
+ }
+ flingBlockCheck.unblockFling()
+ overrideVelocity = null
+ }
+
+ override fun onDrag(displacement: Float): Boolean {
+ if (!draggingEnabled) return true
+ val taskBeingDragged = taskBeingDragged ?: return true
+ val currentAnimation = currentAnimation ?: return true
+
+ val orientationHandler = recentsView.pagedOrientationHandler
+ val totalDisplacement = displacement + displacementShift
+ val isGoingUp =
+ if (totalDisplacement == 0f) currentAnimationIsGoingUp
+ else orientationHandler.isGoingUp(totalDisplacement, isRtl)
+ if (isGoingUp != currentAnimationIsGoingUp) {
+ reInitAnimationController(isGoingUp)
+ flingBlockCheck.blockFling()
+ } else {
+ flingBlockCheck.onEvent()
+ }
+
+ if (isGoingUp) {
+ if (currentAnimation.progressFraction < ANIMATION_PROGRESS_FRACTION_MIDPOINT) {
+ // Halve the value when dismissing, as we are animating the drag across the full
+ // length for only the first half of the progress
+ currentAnimation.setPlayFraction(
+ Utilities.boundToRange(totalDisplacement * progressMultiplier / 2, 0f, 1f)
+ )
+ } else {
+ // Set mOverrideVelocity to control task dismiss velocity in onDragEnd
+ var velocityDimenId = R.dimen.default_task_dismiss_drag_velocity
+ if (recentsView.showAsGrid()) {
+ velocityDimenId =
+ if (taskBeingDragged.isLargeTile) {
+ R.dimen.default_task_dismiss_drag_velocity_grid_focus_task
+ } else {
+ R.dimen.default_task_dismiss_drag_velocity_grid
+ }
+ }
+ overrideVelocity = -taskBeingDragged.resources.getDimension(velocityDimenId)
+
+ // Once halfway through task dismissal interpolation, switch from reversible
+ // dragging-task animation to playing the remaining task translation animations,
+ // while this is in progress disable dragging.
+ draggingEnabled = false
+ }
+ } else {
+ currentAnimation.setPlayFraction(
+ Utilities.boundToRange(totalDisplacement * progressMultiplier, 0f, 1f)
+ )
+ }
+
+ return true
+ }
+
+ override fun onDragEnd(velocity: Float) {
+ val taskBeingDragged = taskBeingDragged ?: return
+ val currentAnimation = currentAnimation ?: return
+
+ // Limit velocity, as very large scalar values make animations play too quickly
+ val maxTaskDismissDragVelocity =
+ taskBeingDragged.resources.getDimension(R.dimen.max_task_dismiss_drag_velocity)
+ val endVelocity =
+ Utilities.boundToRange(
+ overrideVelocity ?: velocity,
+ -maxTaskDismissDragVelocity,
+ maxTaskDismissDragVelocity,
+ )
+ overrideVelocity = null
+
+ var fling = draggingEnabled && detector.isFling(endVelocity)
+ val goingToEnd: Boolean
+ val blockedFling = fling && flingBlockCheck.isBlocked
+ if (blockedFling) {
+ fling = false
+ }
+ val orientationHandler = recentsView.pagedOrientationHandler
+ val goingUp = orientationHandler.isGoingUp(endVelocity, isRtl)
+ val progress = currentAnimation.progressFraction
+ val interpolatedProgress = currentAnimation.interpolatedProgress
+ goingToEnd =
+ if (fling) {
+ goingUp == currentAnimationIsGoingUp
+ } else {
+ interpolatedProgress > SUCCESS_TRANSITION_PROGRESS
+ }
+ var animationDuration =
+ BaseSwipeDetector.calculateDuration(
+ endVelocity,
+ if (goingToEnd) (1 - progress) else progress,
+ )
+ if (blockedFling && !goingToEnd) {
+ animationDuration *= blockedFlingDurationFactor(endVelocity).toLong()
+ }
+ // Due to very high or low velocity dismissals, animation durations can be inconsistently
+ // long or short. Bound the duration for animation of task translations for a more
+ // standardized feel.
+ animationDuration =
+ Utilities.boundToRange(
+ animationDuration,
+ MIN_TASK_DISMISS_ANIMATION_DURATION,
+ MAX_TASK_DISMISS_ANIMATION_DURATION,
+ )
+
+ currentAnimation.setEndAction { this.clearState() }
+ currentAnimation.startWithVelocity(
+ container,
+ goingToEnd,
+ abs(endVelocity.toDouble()).toFloat(),
+ endDisplacement,
+ animationDuration,
+ )
+ if (goingUp && goingToEnd && !isDismissHapticRunning) {
+ VibratorWrapper.INSTANCE.get(container)
+ .vibrate(
+ TASK_DISMISS_VIBRATION_PRIMITIVE,
+ TASK_DISMISS_VIBRATION_PRIMITIVE_SCALE,
+ TASK_DISMISS_VIBRATION_FALLBACK,
+ )
+ isDismissHapticRunning = true
+ }
+
+ draggingEnabled = true
+ }
+
+ private fun clearState() {
+ detector.finishedScrolling()
+ detector.setDetectableScrollConditions(0, false)
+ draggingEnabled = true
+ taskBeingDragged = null
+ currentAnimation = null
+ isDismissHapticRunning = false
+ }
+
+ companion object {
+ private const val ANIMATION_PROGRESS_FRACTION_MIDPOINT = 0.5f
+ private const val MIN_TASK_DISMISS_ANIMATION_DURATION: Long = 300
+ private const val MAX_TASK_DISMISS_ANIMATION_DURATION: Long = 600
+
+ private const val TASK_DISMISS_VIBRATION_PRIMITIVE: Int =
+ VibrationEffect.Composition.PRIMITIVE_TICK
+ private const val TASK_DISMISS_VIBRATION_PRIMITIVE_SCALE: Float = 1f
+ private val TASK_DISMISS_VIBRATION_FALLBACK: VibrationEffect =
+ VibrationConstants.EFFECT_TEXTURE_TICK
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchControllerDeprecated.java
similarity index 94%
rename from quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
rename to quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchControllerDeprecated.java
index d622987..b1a36c7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchControllerDeprecated.java
@@ -49,10 +49,13 @@
/**
* Touch controller for handling task view card swipes
+ *
+ * @deprecated This class will be replaced by the new {@link TaskViewTouchController}.
*/
-public abstract class TaskViewTouchController<CONTAINER extends Context & RecentsViewContainer>
- extends AnimatorListenerAdapter implements TouchController,
- SingleAxisSwipeDetector.Listener {
+@Deprecated
+public class TaskViewTouchControllerDeprecated<
+ CONTAINER extends Context & RecentsViewContainer> extends AnimatorListenerAdapter
+ implements TouchController, SingleAxisSwipeDetector.Listener {
private static final float ANIMATION_PROGRESS_FRACTION_MIDPOINT = 0.5f;
private static final long MIN_TASK_DISMISS_ANIMATION_DURATION = 300;
@@ -65,6 +68,7 @@
VibrationConstants.EFFECT_TEXTURE_TICK;
protected final CONTAINER mContainer;
+ private final TaskViewRecentsTouchContext mTaskViewRecentsTouchContext;
private final SingleAxisSwipeDetector mDetector;
private final RecentsView<?, ?> mRecentsView;
private final Rect mTempRect = new Rect();
@@ -88,8 +92,10 @@
private boolean mIsDismissHapticRunning = false;
- public TaskViewTouchController(CONTAINER container) {
+ public TaskViewTouchControllerDeprecated(CONTAINER container,
+ TaskViewRecentsTouchContext taskViewRecentsTouchContext) {
mContainer = container;
+ mTaskViewRecentsTouchContext = taskViewRecentsTouchContext;
mRecentsView = container.getOverviewPanel();
mIsRtl = Utilities.isRtl(container.getResources());
SingleAxisSwipeDetector.Direction dir =
@@ -117,15 +123,7 @@
mContainer, TYPE_TOUCH_CONTROLLER_NO_INTERCEPT) != null) {
return false;
}
- return isRecentsInteractive();
- }
-
- protected abstract boolean isRecentsInteractive();
-
- /** Is recents view showing a single task in a modal way. */
- protected abstract boolean isRecentsModal();
-
- protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
+ return mTaskViewRecentsTouchContext.isRecentsInteractive();
}
@Override
@@ -161,7 +159,7 @@
if (mRecentsView.isTaskViewVisible(taskView) && mContainer.getDragLayer()
.isEventOverView(taskView, ev)) {
// Disable swiping up and down if the task overlay is modal.
- if (isRecentsModal()) {
+ if (mTaskViewRecentsTouchContext.isRecentsModal()) {
mTaskBeingDragged = null;
break;
}
@@ -259,7 +257,7 @@
// Setting this interpolator doesn't affect the visual motion, but is used to determine
// whether we successfully reached the target state in onDragEnd().
mCurrentAnimation.getTarget().setInterpolator(currentInterpolator);
- onUserControlledAnimationCreated(mCurrentAnimation);
+ mTaskViewRecentsTouchContext.onUserControlledAnimationCreated(mCurrentAnimation);
mCurrentAnimation.getTarget().addListener(this);
mCurrentAnimation.dispatchOnStart();
mProgressMultiplier = 1 / mEndDisplacement;
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
index 66f307c..6ad9a2c 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
@@ -28,7 +28,9 @@
import androidx.annotation.UiThread
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.Cuj
+import com.android.launcher3.Flags.enableFallbackOverviewInWindow
import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
+import com.android.launcher3.Flags.enableLauncherOverviewInWindow
import com.android.launcher3.Flags.enableOverviewCommandHelperTimeout
import com.android.launcher3.PagedView
import com.android.launcher3.logger.LauncherAtom
@@ -344,9 +346,12 @@
return false
}
- val activity = containerInterface.getCreatedContainer()
- if (activity != null) {
- InteractionJankMonitorWrapper.begin(activity.rootView, Cuj.CUJ_LAUNCHER_QUICK_SWITCH)
+ val recentsInWindowFlagSet =
+ enableFallbackOverviewInWindow() || enableLauncherOverviewInWindow()
+ if (!recentsInWindowFlagSet) {
+ containerInterface.getCreatedContainer()?.rootView?.let { view ->
+ InteractionJankMonitorWrapper.begin(view, Cuj.CUJ_LAUNCHER_QUICK_SWITCH)
+ }
}
val gestureState =
@@ -373,6 +378,12 @@
transitionInfo: TransitionInfo,
) {
Log.d(TAG, "recents animation started: $command")
+ if (recentsInWindowFlagSet) {
+ containerInterface.getCreatedContainer()?.rootView?.let { view ->
+ InteractionJankMonitorWrapper.begin(view, Cuj.CUJ_LAUNCHER_QUICK_SWITCH)
+ }
+ }
+
updateRecentsViewFocus(command)
logShowOverviewFrom(command.type)
containerInterface.runOnInitBackgroundStateUI {
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 379d71b4..01ced75 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -39,6 +39,8 @@
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
+import com.android.quickstep.util.SplitTask;
import com.android.systemui.shared.recents.model.Task;
import com.android.wm.shell.Flags;
import com.android.wm.shell.recents.IRecentTasksListener;
@@ -50,6 +52,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -360,22 +363,19 @@
final Task.TaskKey task1Key = new Task.TaskKey(taskInfo1);
final Task task1 = Task.from(task1Key, taskInfo1,
tmpLockedUsers.get(task1Key.userId) /* isLocked */);
- final Task task2;
- final SplitConfigurationOptions.SplitBounds launcherSplitBounds;
if (rawTask.isBaseType(TYPE_SPLIT)) {
final TaskInfo taskInfo2 = rawTask.getBaseGroupedTask().getTaskInfo2();
final Task.TaskKey task2Key = new Task.TaskKey(taskInfo2);
- task2 = Task.from(task2Key, taskInfo2,
+ final Task task2 = Task.from(task2Key, taskInfo2,
tmpLockedUsers.get(task2Key.userId) /* isLocked */);
- launcherSplitBounds =
+ final SplitConfigurationOptions.SplitBounds launcherSplitBounds =
convertShellSplitBoundsToLauncher(
rawTask.getBaseGroupedTask().getSplitBounds());
+ allTasks.add(new SplitTask(task1, task2, launcherSplitBounds));
} else {
- task2 = null;
- launcherSplitBounds = null;
+ allTasks.add(new SingleTask(task1));
}
- allTasks.add(new GroupTask(task1, task2, launcherSplitBounds));
} else {
TaskInfo taskInfo1 = rawTask.getTaskInfo1();
TaskInfo taskInfo2 = rawTask.getTaskInfo2();
@@ -407,9 +407,14 @@
if (taskInfo1.isVisible) {
numVisibleTasks++;
}
- final SplitConfigurationOptions.SplitBounds launcherSplitBounds =
- convertShellSplitBoundsToLauncher(rawTask.getSplitBounds());
- allTasks.add(new GroupTask(task1, task2, launcherSplitBounds));
+ if (task2 != null) {
+ Objects.requireNonNull(rawTask.getSplitBounds());
+ final SplitConfigurationOptions.SplitBounds launcherSplitBounds =
+ convertShellSplitBoundsToLauncher(rawTask.getSplitBounds());
+ allTasks.add(new SplitTask(task1, task2, launcherSplitBounds));
+ } else {
+ allTasks.add(new SingleTask(task1));
+ }
}
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index d4305a5..0c89a80 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -64,12 +64,15 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
import com.android.launcher3.util.DisplayController.Info;
-import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.NavigationMode;
-import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.SettingsCache;
import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
import com.android.quickstep.util.ActiveGestureLog;
@@ -85,13 +88,14 @@
import com.android.systemui.shared.system.TaskStackChangeListeners;
import java.io.PrintWriter;
-import java.util.ArrayList;
+
+import javax.inject.Inject;
/**
* Manages the state of the system during a swipe up gesture.
*/
-public class RecentsAnimationDeviceState implements DisplayInfoChangeListener, ExclusionListener,
- SafeCloseable {
+@LauncherAppSingleton
+public class RecentsAnimationDeviceState implements DisplayInfoChangeListener, ExclusionListener {
static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode";
@@ -99,8 +103,8 @@
private static final float QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON = 3f;
private static final float QUICKSTEP_TOUCH_SLOP_RATIO_GESTURAL = 1.414f;
- public static MainThreadInitializedObject<RecentsAnimationDeviceState> INSTANCE =
- new MainThreadInitializedObject<>(RecentsAnimationDeviceState::new);
+ public static DaggerSingletonObject<RecentsAnimationDeviceState> INSTANCE =
+ new DaggerSingletonObject<>(LauncherAppComponent::getRecentsAnimationDeviceState);
private final Context mContext;
private final DisplayController mDisplayController;
@@ -110,12 +114,11 @@
private final RotationTouchHelper mRotationTouchHelper;
private final TaskStackChangeListener mPipListener;
+ private final DaggerSingletonTracker mLifeCycle;
// Cache for better performance since it doesn't change at runtime.
private final boolean mCanImeRenderGesturalNavButtons =
InputMethodService.canImeRenderGesturalNavButtons();
- private final ArrayList<Runnable> mOnDestroyActions = new ArrayList<>();
-
private @SystemUiStateFlags long mSystemUiStateFlags = QuickStepContract.SYSUI_STATE_AWAKE;
private NavigationMode mMode = THREE_BUTTONS;
private NavBarPosition mNavBarPosition;
@@ -134,35 +137,39 @@
private @NonNull Region mExclusionRegion = GestureExclusionManager.EMPTY_REGION;
private boolean mExclusionListenerRegistered;
- private RecentsAnimationDeviceState(Context context) {
- this(context, GestureExclusionManager.INSTANCE);
- }
-
@VisibleForTesting
- RecentsAnimationDeviceState(Context context, GestureExclusionManager exclusionManager) {
+ @Inject
+ RecentsAnimationDeviceState(
+ @ApplicationContext Context context,
+ GestureExclusionManager exclusionManager,
+ DisplayController displayController,
+ ContextualSearchStateManager contextualSearchStateManager,
+ RotationTouchHelper rotationTouchHelper,
+ SettingsCache settingsCache,
+ DaggerSingletonTracker lifeCycle) {
mContext = context;
- mDisplayController = DisplayController.INSTANCE.get(context);
+ mDisplayController = displayController;
mExclusionManager = exclusionManager;
- mContextualSearchStateManager = ContextualSearchStateManager.INSTANCE.get(context);
+ mContextualSearchStateManager = contextualSearchStateManager;
+ mRotationTouchHelper = rotationTouchHelper;
+ mLifeCycle = lifeCycle;
mIsOneHandedModeSupported = SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false);
- mRotationTouchHelper = RotationTouchHelper.INSTANCE.get(context);
// Register for exclusion updates
- runOnDestroy(this::unregisterExclusionListener);
+ mLifeCycle.addCloseable(this::unregisterExclusionListener);
// Register for display changes changes
mDisplayController.addChangeListener(this);
onDisplayInfoChanged(context, mDisplayController.getInfo(), CHANGE_ALL);
- runOnDestroy(() -> mDisplayController.removeChangeListener(this));
+ mLifeCycle.addCloseable(() -> mDisplayController.removeChangeListener(this));
- SettingsCache settingsCache = SettingsCache.INSTANCE.get(mContext);
if (mIsOneHandedModeSupported) {
Uri oneHandedUri = Settings.Secure.getUriFor(ONE_HANDED_ENABLED);
SettingsCache.OnChangeListener onChangeListener =
enabled -> mIsOneHandedModeEnabled = enabled;
settingsCache.register(oneHandedUri, onChangeListener);
mIsOneHandedModeEnabled = settingsCache.getValue(oneHandedUri);
- runOnDestroy(() -> settingsCache.unregister(oneHandedUri, onChangeListener));
+ mLifeCycle.addCloseable(() -> settingsCache.unregister(oneHandedUri, onChangeListener));
} else {
mIsOneHandedModeEnabled = false;
}
@@ -173,14 +180,16 @@
enabled -> mIsSwipeToNotificationEnabled = enabled;
settingsCache.register(swipeBottomNotificationUri, onChangeListener);
mIsSwipeToNotificationEnabled = settingsCache.getValue(swipeBottomNotificationUri);
- runOnDestroy(() -> settingsCache.unregister(swipeBottomNotificationUri, onChangeListener));
+ mLifeCycle.addCloseable(
+ () -> settingsCache.unregister(swipeBottomNotificationUri, onChangeListener));
Uri setupCompleteUri = Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE);
mIsUserSetupComplete = settingsCache.getValue(setupCompleteUri, 0);
if (!mIsUserSetupComplete) {
SettingsCache.OnChangeListener userSetupChangeListener = e -> mIsUserSetupComplete = e;
settingsCache.register(setupCompleteUri, userSetupChangeListener);
- runOnDestroy(() -> settingsCache.unregister(setupCompleteUri, userSetupChangeListener));
+ mLifeCycle.addCloseable(
+ () -> settingsCache.unregister(setupCompleteUri, userSetupChangeListener));
}
try {
@@ -201,21 +210,10 @@
}
};
TaskStackChangeListeners.getInstance().registerTaskStackListener(mPipListener);
- runOnDestroy(() ->
+ mLifeCycle.addCloseable(() ->
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mPipListener));
}
- private void runOnDestroy(Runnable action) {
- mOnDestroyActions.add(action);
- }
-
- @Override
- public void close() {
- for (Runnable r : mOnDestroyActions) {
- r.run();
- }
- }
-
/**
* Adds a listener for the nav mode change, guaranteed to be called after the device state's
* mode has changed.
@@ -228,7 +226,7 @@
};
mDisplayController.addChangeListener(listener);
callback.run();
- runOnDestroy(() -> mDisplayController.removeChangeListener(listener));
+ mLifeCycle.addCloseable(() -> mDisplayController.removeChangeListener(listener));
}
@Override
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
index 8edbacb..ef63b9b 100644
--- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -142,7 +142,9 @@
if (mSplitBounds == null) {
SplitBounds shellSplitBounds = targets.extras.getParcelable(KEY_EXTRA_SPLIT_BOUNDS,
SplitBounds.class);
- mSplitBounds = convertShellSplitBoundsToLauncher(shellSplitBounds);
+ if (shellSplitBounds != null) {
+ mSplitBounds = convertShellSplitBoundsToLauncher(shellSplitBounds);
+ }
}
boolean containsSplitTargets = mSplitBounds != null;
diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
index f54b655..a614327 100644
--- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -33,13 +33,16 @@
import android.view.MotionEvent;
import android.view.OrientationEventListener;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
import com.android.launcher3.util.DisplayController.Info;
-import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.NavigationMode;
-import com.android.launcher3.util.SafeCloseable;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.systemui.shared.Flags;
import com.android.systemui.shared.system.QuickStepContract;
@@ -48,16 +51,20 @@
import java.io.PrintWriter;
+import javax.inject.Inject;
+
/**
* Helper class for transforming touch events
*/
-public class RotationTouchHelper implements DisplayInfoChangeListener, SafeCloseable {
+@LauncherAppSingleton
+public class RotationTouchHelper implements DisplayInfoChangeListener {
- public static final MainThreadInitializedObject<RotationTouchHelper> INSTANCE =
- new MainThreadInitializedObject<>(RotationTouchHelper::new);
+ public static final DaggerSingletonObject<RotationTouchHelper> INSTANCE =
+ new DaggerSingletonObject<>(LauncherAppComponent::getRotationTouchHelper);
private final OrientationTouchTransformer mOrientationTouchTransformer;
private final DisplayController mDisplayController;
+ private final SystemUiProxy mSystemUiProxy;
private final int mDisplayId;
private int mDisplayRotation;
@@ -127,12 +134,17 @@
private boolean mTaskListFrozen;
private final Context mContext;
- private RotationTouchHelper(Context context) {
+ @Inject
+ RotationTouchHelper(@ApplicationContext Context context,
+ DisplayController displayController,
+ SystemUiProxy systemUiProxy,
+ DaggerSingletonTracker lifeCycle) {
mContext = context;
- mDisplayController = DisplayController.INSTANCE.get(mContext);
- Resources resources = mContext.getResources();
+ mDisplayController = displayController;
+ mSystemUiProxy = systemUiProxy;
mDisplayId = DEFAULT_DISPLAY;
+ Resources resources = mContext.getResources();
mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
() -> QuickStepContract.getWindowCornerRadius(mContext));
@@ -160,14 +172,13 @@
}
}
};
- }
- @Override
- public void close() {
- mDisplayController.removeChangeListener(this);
- mOrientationListener.disable();
- TaskStackChangeListeners.getInstance()
- .unregisterTaskStackListener(mFrozenTaskListener);
+ lifeCycle.addCloseable(() -> {
+ mDisplayController.removeChangeListener(this);
+ mOrientationListener.disable();
+ TaskStackChangeListeners.getInstance()
+ .unregisterTaskStackListener(mFrozenTaskListener);
+ });
}
public boolean isTaskListFrozen() {
@@ -340,8 +351,7 @@
}
private void notifySysuiOfCurrentRotation(int rotation) {
- UI_HELPER_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(mContext)
- .notifyPrioritizedRotation(rotation));
+ UI_HELPER_EXECUTOR.execute(() -> mSystemUiProxy.notifyPrioritizedRotation(rotation));
}
/**
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.kt b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
index 94ef4dd..87953c7 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.kt
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
@@ -1073,6 +1073,19 @@
//
// Desktop Mode
//
+ /** Calls shell to create a new desk (if possible) on the display whose ID is `displayId`. */
+ fun createDesktop(displayId: Int) =
+ executeWithErrorLog({ "Failed call createDesk" }) { desktopMode?.createDesk(displayId) }
+
+ /**
+ * Calls shell to activate the desk whose ID is `deskId` on whatever display it exists on. This
+ * will bring all tasks on this desk to the front.
+ */
+ fun activateDesktop(deskId: Int, transition: RemoteTransition?) =
+ executeWithErrorLog({ "Failed call activateDesk" }) {
+ desktopMode?.activateDesk(deskId, transition)
+ }
+
/** Call shell to show all apps active on the desktop */
fun showDesktopApps(displayId: Int, transition: RemoteTransition?) =
executeWithErrorLog({ "Failed call showDesktopApps" }) {
@@ -1089,14 +1102,6 @@
desktopMode?.showDesktopApp(taskId, transition, toFrontReason)
}
- /** Call shell to get number of visible freeform tasks */
- fun getVisibleDesktopTaskCount(displayId: Int): Int {
- executeWithErrorLog({ "Failed call getVisibleDesktopTaskCount" }) {
- return desktopMode?.getVisibleTaskCount(displayId) ?: 0
- }
- return 0
- }
-
/** Set a listener on shell to get updates about desktop task state */
fun setDesktopTaskListener(listener: IDesktopTaskListener?) {
desktopTaskListener = listener
diff --git a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
index 1d40d76..fe25f32 100644
--- a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
+++ b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
@@ -21,10 +21,13 @@
import com.android.launcher3.model.WellbeingModel;
import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.quickstep.OverviewComponentObserver;
+import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.RotationTouchHelper;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TopTaskTracker;
import com.android.quickstep.fallback.window.RecentsDisplayModel;
import com.android.quickstep.util.AsyncClockEventDelegate;
+import com.android.quickstep.util.ContextualSearchStateManager;
/**
* Launcher Quickstep base component for Dagger injection.
@@ -49,4 +52,10 @@
DesktopVisibilityController getDesktopVisibilityController();
TopTaskTracker getTopTaskTracker();
+
+ RotationTouchHelper getRotationTouchHelper();
+
+ ContextualSearchStateManager getContextualSearchStateManager();
+
+ RecentsAnimationDeviceState getRecentsAnimationDeviceState();
}
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index d9209bf..fff7e9b 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -33,7 +33,6 @@
import com.android.launcher3.Flags;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.desktop.DesktopRecentsTransitionController;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.statehandlers.DesktopVisibilityController;
@@ -47,6 +46,7 @@
import com.android.quickstep.GestureState;
import com.android.quickstep.fallback.window.RecentsDisplayModel;
import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.views.OverviewActionsView;
@@ -210,7 +210,7 @@
if (!found) {
ArrayList<GroupTask> newList = new ArrayList<>(taskGroups.size() + 1);
newList.addAll(taskGroups);
- newList.add(new GroupTask(mHomeTask, null, null));
+ newList.add(new SingleTask(mHomeTask));
taskGroups = newList;
}
}
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsDragLayer.java b/quickstep/src/com/android/quickstep/fallback/RecentsDragLayer.java
index a2884b6..7e5afc3 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsDragLayer.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsDragLayer.java
@@ -15,9 +15,15 @@
*/
package com.android.quickstep.fallback;
+import static com.android.launcher3.Flags.enableExpressiveDismissTaskMotion;
+
import android.content.Context;
import android.util.AttributeSet;
+import com.android.launcher3.statemanager.StatefulContainer;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewRecentsTouchContext;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchControllerDeprecated;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.views.RecentsViewContainer;
@@ -25,16 +31,33 @@
/**
* Drag layer for fallback recents activity
*/
-public class RecentsDragLayer<T extends Context & RecentsViewContainer> extends BaseDragLayer<T> {
+public class RecentsDragLayer<T extends Context & RecentsViewContainer
+ & StatefulContainer<RecentsState>> extends BaseDragLayer<T> {
+
+ private final TaskViewRecentsTouchContext mTaskViewRecentsTouchContext =
+ new TaskViewRecentsTouchContext() {
+ @Override
+ public boolean isRecentsInteractive() {
+ return mContainer.getRootView().hasWindowFocus()
+ || mContainer.getStateManager().getState().hasLiveTile();
+ }
+
+ @Override
+ public boolean isRecentsModal() {
+ return false;
+ }
+ };
+
public RecentsDragLayer(Context context, AttributeSet attrs) {
super(context, attrs, 1 /* alphaChannelCount */);
}
@Override
public void recreateControllers() {
- mControllers = new TouchController[] {
- new RecentsTaskController(mContainer),
- new FallbackNavBarTouchController(mContainer),
- };
+ mControllers = new TouchController[]{
+ enableExpressiveDismissTaskMotion() ? new TaskViewTouchController<>(mContainer,
+ mTaskViewRecentsTouchContext) : new TaskViewTouchControllerDeprecated<>(
+ mContainer, mTaskViewRecentsTouchContext),
+ new FallbackNavBarTouchController(mContainer)};
}
}
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java b/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java
deleted file mode 100644
index 07da379..0000000
--- a/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.fallback;
-
-import android.content.Context;
-
-import com.android.launcher3.statemanager.StatefulContainer;
-import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
-import com.android.quickstep.views.RecentsViewContainer;
-
-public class RecentsTaskController<T extends Context & RecentsViewContainer &
- StatefulContainer<RecentsState>> extends TaskViewTouchController<T> {
- public RecentsTaskController(T container) {
- super(container);
- }
-
- @Override
- protected boolean isRecentsInteractive() {
- return mContainer.getRootView().hasWindowFocus()
- || mContainer.getStateManager().getState().hasLiveTile();
- }
-
- @Override
- protected boolean isRecentsModal() {
- return false;
- }
-}
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 9bfe71f..b2a30ca 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -31,15 +31,15 @@
import static com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers.ContainerCase.DEVICE_SEARCH_RESULT_CONTAINER;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORKSPACE_SNAPSHOT;
import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_0;
-import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_90;
import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_180;
import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_270;
+import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_90;
import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__ALLAPPS;
import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__BACKGROUND;
import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__HOME;
import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__OVERVIEW;
-import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__RECENTS_ORIENTATION_HANDLER__PORTRAIT;
import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__RECENTS_ORIENTATION_HANDLER__LANDSCAPE;
+import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__RECENTS_ORIENTATION_HANDLER__PORTRAIT;
import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__RECENTS_ORIENTATION_HANDLER__SEASCAPE;
import android.content.Context;
@@ -69,6 +69,7 @@
import com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.model.data.CollectionInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.Executors;
@@ -386,7 +387,8 @@
// and then write to StatsLog.
app.getModel().enqueueModelUpdateTask((taskController, dataModel, apps) ->
write(event, applyOverwrites(mItemInfo.buildProto(
- dataModel.collections.get(mItemInfo.container), mContext))));
+ (CollectionInfo) dataModel.itemsIdMap.get(mItemInfo.container),
+ mContext))));
})) {
// Write log on the model thread so that logs do not go out of order
// (for eg: drop comes after drag)
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index 639d3a7..02baa39 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -146,8 +146,8 @@
}
}
- override fun onDetachedFromWindow() {
- super.onDetachedFromWindow()
+ // TODO(b/391842220): Cancel scope in onDetach instead of having a specific method for this.
+ fun destroyScopes() {
val scopeToCancel = viewAttachedScope
recentsCoroutineScope.launch(dispatcherProvider.background) {
scopeToCancel.cancel("TaskThumbnailView detaching from window")
diff --git a/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java b/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java
index f75d3b3..ed96399 100644
--- a/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java
+++ b/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java
@@ -21,7 +21,6 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_SYSTEM_ACTION;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
import static com.android.quickstep.util.SystemActionConstants.SYSTEM_ACTION_ID_SEARCH_SCREEN;
import android.app.PendingIntent;
@@ -44,11 +43,13 @@
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.R;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
import com.android.launcher3.util.EventLogArray;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.ResourceBasedOverride;
-import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.quickstep.DeviceConfigWrapper;
@@ -58,12 +59,14 @@
import java.io.PrintWriter;
import java.util.Optional;
-/** Long-lived class to manage Contextual Search states like the user setting and availability. */
-public class ContextualSearchStateManager implements ResourceBasedOverride, SafeCloseable {
+import javax.inject.Inject;
- public static final MainThreadInitializedObject<ContextualSearchStateManager> INSTANCE =
- forOverride(ContextualSearchStateManager.class,
- R.string.contextual_search_state_manager_class);
+/** Long-lived class to manage Contextual Search states like the user setting and availability. */
+@LauncherAppSingleton
+public class ContextualSearchStateManager {
+
+ public static final DaggerSingletonObject<ContextualSearchStateManager> INSTANCE =
+ new DaggerSingletonObject<>(LauncherAppComponent::getContextualSearchStateManager);
private static final String TAG = "ContextualSearchStMgr";
private static final int MAX_DEBUG_EVENT_SIZE = 20;
@@ -73,23 +76,29 @@
private final Runnable mSysUiStateChangeListener = this::updateOverridesToSysUi;
private final SimpleBroadcastReceiver mContextualSearchPackageReceiver =
new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, (unused) -> requestUpdateProperties());
- private final SettingsCache.OnChangeListener mContextualSearchSettingChangedListener =
- this::onContextualSearchSettingChanged;
protected final EventLogArray mEventLogArray = new EventLogArray(TAG, MAX_DEBUG_EVENT_SIZE);
// Cached value whether the ContextualSearch intent filter matched any enabled components.
private boolean mIsContextualSearchIntentAvailable;
private boolean mIsContextualSearchSettingEnabled;
- protected Context mContext;
- protected String mContextualSearchPackage;
+ protected final Context mContext;
+ protected final String mContextualSearchPackage;
+ protected final SystemUiProxy mSystemUiProxy;
+ protected final TopTaskTracker mTopTaskTracker;
- public ContextualSearchStateManager() {}
-
- public ContextualSearchStateManager(Context context) {
+ @Inject
+ public ContextualSearchStateManager(
+ @ApplicationContext Context context,
+ SettingsCache settingsCache,
+ SystemUiProxy systemUiProxy,
+ TopTaskTracker topTaskTracker,
+ DaggerSingletonTracker lifeCycle) {
mContext = context;
mContextualSearchPackage = mContext.getResources().getString(
com.android.internal.R.string.config_defaultContextualSearchPackageName);
+ mSystemUiProxy = systemUiProxy;
+ mTopTaskTracker = topTaskTracker;
if (areAllContextualSearchFlagsDisabled()
|| !context.getPackageManager().hasSystemFeature(FEATURE_CONTEXTUAL_SEARCH)) {
@@ -106,11 +115,20 @@
context, mContextualSearchPackage, Intent.ACTION_PACKAGE_ADDED,
Intent.ACTION_PACKAGE_CHANGED, Intent.ACTION_PACKAGE_REMOVED);
- SettingsCache.INSTANCE.get(context).register(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI,
- mContextualSearchSettingChangedListener);
- onContextualSearchSettingChanged(
- SettingsCache.INSTANCE.get(context).getValue(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI));
- SystemUiProxy.INSTANCE.get(mContext).addOnStateChangeListener(mSysUiStateChangeListener);
+ SettingsCache.OnChangeListener settingChangedListener =
+ isEnabled -> mIsContextualSearchSettingEnabled = isEnabled;
+ settingsCache.register(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI, settingChangedListener);
+ mIsContextualSearchSettingEnabled =
+ settingsCache.getValue(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI);
+
+ systemUiProxy.addOnStateChangeListener(mSysUiStateChangeListener);
+
+ lifeCycle.addCloseable(() -> {
+ mContextualSearchPackageReceiver.unregisterReceiverSafely(mContext);
+ unregisterSearchScreenSystemAction();
+ settingsCache.unregister(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI, settingChangedListener);
+ systemUiProxy.removeOnStateChangeListener(mSysUiStateChangeListener);
+ });
}
/** Return {@code true} if the Settings toggle is enabled. */
@@ -118,10 +136,6 @@
return mIsContextualSearchSettingEnabled;
}
- private void onContextualSearchSettingChanged(boolean isEnabled) {
- mIsContextualSearchSettingEnabled = isEnabled;
- }
-
/** Whether search supports showing on the lockscreen. */
protected boolean supportsShowWhenLocked() {
return false;
@@ -208,7 +222,7 @@
protected final void updateOverridesToSysUi() {
// LPH commit haptic is always enabled
- SystemUiProxy.INSTANCE.get(mContext).setOverrideHomeButtonLongPress(
+ mSystemUiProxy.setOverrideHomeButtonLongPress(
getLPHDurationMillis().orElse(0L), getLPHCustomSlopMultiplier().orElse(0f), true);
Log.i(TAG, "Sent LPH override to sysui: " + getLPHDurationMillis().orElse(0L) + ";"
+ getLPHCustomSlopMultiplier().orElse(0f));
@@ -227,10 +241,8 @@
new ContextualSearchInvoker(mContext).show(
ENTRYPOINT_SYSTEM_ACTION);
if (contextualSearchInvoked) {
- String runningPackage =
- TopTaskTracker.INSTANCE.get(mContext).getCachedTopTask(
- /* filterOnlyVisibleRecents */
- true).getPackageName();
+ String runningPackage = mTopTaskTracker.getCachedTopTask(
+ /* filterOnlyVisibleRecents */ true).getPackageName();
StatsLogManager.newInstance(mContext).logger()
.withPackageName(runningPackage)
.log(LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_SYSTEM_ACTION);
@@ -259,15 +271,6 @@
}
}
- @Override
- public void close() {
- mContextualSearchPackageReceiver.unregisterReceiverSafely(mContext);
- unregisterSearchScreenSystemAction();
- SettingsCache.INSTANCE.get(mContext).unregister(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI,
- mContextualSearchSettingChangedListener);
- SystemUiProxy.INSTANCE.get(mContext).removeOnStateChangeListener(mSysUiStateChangeListener);
- }
-
protected final void addEventLog(String event) {
synchronized (mEventLogArray) {
mEventLogArray.addLog(event);
diff --git a/quickstep/src/com/android/quickstep/util/DesktopTask.kt b/quickstep/src/com/android/quickstep/util/DesktopTask.kt
index 1cee2d2..5463cf7 100644
--- a/quickstep/src/com/android/quickstep/util/DesktopTask.kt
+++ b/quickstep/src/com/android/quickstep/util/DesktopTask.kt
@@ -17,7 +17,6 @@
import com.android.quickstep.views.TaskViewType
import com.android.systemui.shared.recents.model.Task
-import java.util.Objects
/**
* A [Task] container that can contain N number of tasks that are part of the desktop in recent
@@ -39,9 +38,6 @@
override fun equals(o: Any?): Boolean {
if (this === o) return true
if (o !is DesktopTask) return false
- if (!super.equals(o)) return false
- return tasks == o.tasks
+ return super.equals(o)
}
-
- override fun hashCode() = Objects.hash(super.hashCode(), tasks)
}
diff --git a/quickstep/src/com/android/quickstep/util/GroupTask.kt b/quickstep/src/com/android/quickstep/util/GroupTask.kt
index 0bee5f6..d5bbcd3 100644
--- a/quickstep/src/com/android/quickstep/util/GroupTask.kt
+++ b/quickstep/src/com/android/quickstep/util/GroupTask.kt
@@ -22,10 +22,10 @@
import java.util.Objects
/**
- * A [Task] container that can contain one or two tasks, depending on if the two tasks are
- * represented as an app-pair in the recents task list.
+ * An abstract class for creating [Task] containers that can be [SingleTask]s, [SplitTask]s, or
+ * [DesktopTask]s in the recent tasks list.
*/
-open class GroupTask
+abstract class GroupTask
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
constructor(
@Deprecated("Prefer using `getTasks()` instead") @JvmField val task1: Task,
@@ -33,13 +33,16 @@
@JvmField val mSplitBounds: SplitConfigurationOptions.SplitBounds?,
@JvmField val taskViewType: TaskViewType,
) {
- constructor(task: Task) : this(task, null, null)
-
- constructor(
- t1: Task,
- t2: Task?,
+ protected constructor(
+ task1: Task,
+ task2: Task?,
splitBounds: SplitConfigurationOptions.SplitBounds?,
- ) : this(t1, t2, splitBounds, if (t2 != null) TaskViewType.GROUPED else TaskViewType.SINGLE)
+ ) : this(
+ task1,
+ task2,
+ splitBounds,
+ if (task2 != null) TaskViewType.GROUPED else TaskViewType.SINGLE,
+ )
open fun containsTask(taskId: Int) =
task1.key.id == taskId || (task2 != null && task2.key.id == taskId)
@@ -59,18 +62,50 @@
get() = listOfNotNull(task1, task2)
/** Creates a copy of this instance */
- open fun copy() = GroupTask(Task(task1), if (task2 != null) Task(task2) else null, mSplitBounds)
+ abstract fun copy(): GroupTask
override fun toString() = "type=$taskViewType task1=$task1 task2=$task2"
override fun equals(o: Any?): Boolean {
if (this === o) return true
if (o !is GroupTask) return false
- return taskViewType == o.taskViewType &&
- task1 == o.task1 &&
- task2 == o.task2 &&
- mSplitBounds == o.mSplitBounds
+ return taskViewType == o.taskViewType && tasks == o.tasks
}
- override fun hashCode() = Objects.hash(task1, task2, mSplitBounds, taskViewType)
+ override fun hashCode() = Objects.hash(tasks, taskViewType)
+}
+
+/** A [Task] container that must contain exactly one task in the recent tasks list. */
+class SingleTask(task: Task) :
+ GroupTask(task, task2 = null, mSplitBounds = null, TaskViewType.SINGLE) {
+ override fun copy() = SingleTask(task1)
+
+ override fun toString() = "type=$taskViewType task=$task1"
+
+ override fun equals(o: Any?): Boolean {
+ if (this === o) return true
+ if (o !is SingleTask) return false
+ return super.equals(o)
+ }
+}
+
+/**
+ * A [Task] container that must contain exactly two tasks and split bounds to represent an app-pair
+ * in the recent tasks list.
+ */
+class SplitTask(task1: Task, task2: Task, splitBounds: SplitConfigurationOptions.SplitBounds) :
+ GroupTask(task1, task2, splitBounds, TaskViewType.GROUPED) {
+
+ override fun copy() = SplitTask(task1, task2!!, mSplitBounds!!)
+
+ override fun toString() = "type=$taskViewType task1=$task1 task2=$task2"
+
+ override fun equals(o: Any?): Boolean {
+ if (this === o) return true
+ if (o !is SplitTask) return false
+ if (mSplitBounds!! != o.mSplitBounds!!) return false
+ return super.equals(o)
+ }
+
+ override fun hashCode() = Objects.hash(super.hashCode(), mSplitBounds)
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt b/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
index d982e81..4005c5a 100644
--- a/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
@@ -33,38 +33,34 @@
// TODO(b/254378592): Remove these methods when the two classes are reunited
/** Converts the shell version of SplitBounds to the launcher version */
@JvmStatic
- fun convertShellSplitBoundsToLauncher(
- shellSplitBounds: SplitBounds?
- ): SplitConfigurationOptions.SplitBounds? {
- return if (shellSplitBounds == null) {
- null
- } else {
- SplitConfigurationOptions.SplitBounds(
- shellSplitBounds.leftTopBounds,
- shellSplitBounds.rightBottomBounds,
- shellSplitBounds.leftTopTaskId,
- shellSplitBounds.rightBottomTaskId,
- shellSplitBounds.snapPosition
- )
- }
- }
+ fun convertShellSplitBoundsToLauncher(shellSplitBounds: SplitBounds) =
+ SplitConfigurationOptions.SplitBounds(
+ shellSplitBounds.leftTopBounds,
+ shellSplitBounds.rightBottomBounds,
+ shellSplitBounds.leftTopTaskId,
+ shellSplitBounds.rightBottomTaskId,
+ shellSplitBounds.snapPosition,
+ )
/**
* 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.
+ * 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 a [Pair] where first -> top most split root, second -> [List] of 2,
+ * leftTop/bottomRight stage roots
*/
- fun extractTopParentAndChildren(transitionInfo: TransitionInfo):
- Pair<Change, List<Change>>? {
+ fun extractTopParentAndChildren(
+ transitionInfo: TransitionInfo
+ ): Pair<Change, List<Change>>? {
val parentToChildren = mutableMapOf<Change, MutableList<Change>>()
val hasParent = mutableSetOf<Change>()
// filter out anything that isn't opening and the divider
- val taskChanges: List<Change> = transitionInfo.changes
- .filter { change -> (change.mode == TRANSIT_OPEN ||
- change.mode == TRANSIT_TO_FRONT) && change.flags < FLAG_FIRST_CUSTOM}
+ val taskChanges: List<Change> =
+ transitionInfo.changes
+ .filter { change ->
+ (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT) &&
+ change.flags < FLAG_FIRST_CUSTOM
+ }
.toList()
// 1. Build Parent-Child Relationships
@@ -73,8 +69,8 @@
// startAnimation() and we can know the precise taskIds of launching tasks.
change.parent?.let { parent ->
parentToChildren
- .getOrPut(transitionInfo.getChange(parent)!!) { mutableListOf() }
- .add(change)
+ .getOrPut(transitionInfo.getChange(parent)!!) { mutableListOf() }
+ .add(change)
hasParent.add(change)
}
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 684e84a..c0b026b 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -2693,16 +2693,17 @@
}
private void onReset() {
- if (enableRefactorTaskThumbnail()) {
- mRecentsViewModel.onReset();
- removeAllViews();
- }
unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
setCurrentPage(0);
LayoutUtils.setViewEnabled(mActionsView, true);
if (mOrientationState.setGestureActive(false)) {
updateOrientationHandler(/* forceRecreateDragLayerControllers = */ false);
}
+ if (enableRefactorTaskThumbnail()) {
+ mRecentsViewModel.onReset();
+ // TODO(b/391842220) Remove TaskViews rather than calling specific logic to cancel scope
+ getTaskViews().forEach(TaskView::destroyScopes);
+ }
}
public int getRunningTaskViewId() {
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index 5de8d1c..7ac0946 100644
--- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -141,6 +141,11 @@
}
}
+ // TODO(b/391842220): Cancel scope in onDetach instead of having a specific method for this.
+ fun destroyScopes() {
+ thumbnailView.destroyScopes()
+ }
+
fun bindThumbnailView() {
taskThumbnailViewModel.bind(task.key.id)
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 99df84c..e7a395f 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -617,6 +617,11 @@
taskContainers.forEach { it.destroy() }
}
+ fun destroyScopes() {
+ // TODO(b/391842220): Cancel scope in onDetach instead of having a specific method for this.
+ taskContainers.forEach { it.destroyScopes() }
+ }
+
// TODO: Clip-out the icon region from the thumbnail, since they are overlapping.
override fun hasOverlappingRendering() = false
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
index 6eccc36..c792783 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
@@ -21,6 +21,7 @@
import android.content.Context
import android.content.Intent
import android.content.res.Resources
+import android.graphics.Rect
import android.os.Process
import android.os.UserHandle
import android.platform.test.annotations.EnableFlags
@@ -36,12 +37,16 @@
import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.taskbar.TaskbarRecentAppsController.TaskState
import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.util.SplitConfigurationOptions
import com.android.quickstep.RecentsModel
import com.android.quickstep.RecentsModel.RecentTasksChangedListener
import com.android.quickstep.TaskIconCache
import com.android.quickstep.util.DesktopTask
import com.android.quickstep.util.GroupTask
+import com.android.quickstep.util.SingleTask
+import com.android.quickstep.util.SplitTask
import com.android.systemui.shared.recents.model.Task
+import com.android.wm.shell.shared.split.SplitScreenConstants
import com.google.common.truth.Truth.assertThat
import java.util.function.Consumer
import org.junit.Before
@@ -913,15 +918,21 @@
return packageNames.map { packageName ->
if (packageName.startsWith("split")) {
val splitPackages = packageName.split("_")
- GroupTask(
+ SplitTask(
createTask(100, splitPackages[0]),
createTask(101, splitPackages[1]),
- /* splitBounds = */ null,
+ SplitConfigurationOptions.SplitBounds(
+ /* leftTopBounds = */ Rect(),
+ /* rightBottomBounds = */ Rect(),
+ /* leftTopTaskId = */ -1,
+ /* rightBottomTaskId = */ -1,
+ /* snapPosition = */ SplitScreenConstants.SNAP_TO_2_50_50,
+ ),
)
} else {
// Use the number at the end of the test packageName as the id.
val id = 1000 + packageName[packageName.length - 1].code
- GroupTask(createTask(id, packageName))
+ SingleTask(createTask(id, packageName))
}
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt
index e7f3523..df70b10 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt
@@ -28,6 +28,7 @@
import com.android.launcher3.taskbar.TaskbarIconType.OVERFLOW
import com.android.launcher3.taskbar.TaskbarIconType.RECENT
import com.android.quickstep.util.GroupTask
+import com.android.quickstep.util.SingleTask
import com.android.systemui.shared.recents.model.Task
import com.android.systemui.shared.recents.model.Task.TaskKey
import com.google.common.truth.FailureMetadata
@@ -56,7 +57,7 @@
/** Creates a list of fake recent tasks. */
fun createRecents(size: Int): List<GroupTask> {
return List(size) {
- GroupTask(
+ SingleTask(
Task().apply {
key =
TaskKey(
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
index 542eb64..0c74610 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
@@ -18,6 +18,8 @@
import static com.android.quickstep.AbsSwipeUpHandler.STATE_HANDLER_INVALIDATED;
import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION;
+import static com.android.wm.shell.shared.split.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertTrue;
@@ -68,6 +70,7 @@
import com.android.quickstep.views.RecentsViewContainer;
import com.android.systemui.shared.Flags;
import com.android.systemui.shared.system.InputConsumerController;
+import com.android.wm.shell.shared.split.SplitBounds;
import com.google.android.msdl.data.model.MSDLToken;
@@ -140,6 +143,12 @@
public void setUpAnimationTargets() {
Bundle extras = new Bundle();
extras.putBoolean(KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION, true);
+ extras.putParcelable(KEY_EXTRA_SPLIT_BOUNDS, new SplitBounds(
+ /* leftTopBounds = */ new Rect(),
+ /* rightBottomBounds = */ new Rect(),
+ /* leftTopTaskId = */ -1,
+ /* rightBottomTaskId = */ -1,
+ /* snapPosition = */ SNAP_TO_2_50_50));
mRecentsAnimationTargets = new RecentsAnimationTargets(
new RemoteAnimationTarget[] {mRemoteAnimationTarget},
new RemoteAnimationTarget[] {mRemoteAnimationTarget},
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
index f05b422..af741f6 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
@@ -86,17 +86,11 @@
whenever(displayManager.displays).thenReturn(arrayOf(display))
sandboxContext.initDaggerComponent(
- DaggerTestComponent.builder().bindSystemUiProxy(systemUiProxy)
+ DaggerTestComponent.builder()
+ .bindSystemUiProxy(systemUiProxy)
+ .bindRotationHelper(mock(RotationTouchHelper::class.java))
+ .bindRecentsState(mock(RecentsAnimationDeviceState::class.java))
)
- sandboxContext.putObject(
- RotationTouchHelper.INSTANCE,
- mock(RotationTouchHelper::class.java),
- )
- sandboxContext.putObject(
- RecentsAnimationDeviceState.INSTANCE,
- mock(RecentsAnimationDeviceState::class.java),
- )
-
gestureState = spy(GestureState(OverviewComponentObserver.INSTANCE.get(sandboxContext), 0))
underTest =
@@ -117,20 +111,14 @@
gestureState.setTrackpadGestureType(GestureState.TrackpadGestureType.THREE_FINGER)
underTest.onGestureEnded(flingSpeed, PointF())
verify(systemUiProxy)
- .updateContextualEduStats(
- /* isTrackpadGesture= */ eq(true),
- eq(GestureType.HOME),
- )
+ .updateContextualEduStats(/* isTrackpadGesture= */ eq(true), eq(GestureType.HOME))
}
@Test
fun goHomeFromAppByTouch_updateEduStats() {
underTest.onGestureEnded(flingSpeed, PointF())
verify(systemUiProxy)
- .updateContextualEduStats(
- /* isTrackpadGesture= */ eq(false),
- eq(GestureType.HOME),
- )
+ .updateContextualEduStats(/* isTrackpadGesture= */ eq(false), eq(GestureType.HOME))
}
}
@@ -141,6 +129,10 @@
interface Builder : LauncherAppComponent.Builder {
@BindsInstance fun bindSystemUiProxy(proxy: SystemUiProxy): Builder
+ @BindsInstance fun bindRotationHelper(helper: RotationTouchHelper): Builder
+
+ @BindsInstance fun bindRecentsState(state: RecentsAnimationDeviceState): Builder
+
override fun build(): TestComponent
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
index 4a7c537..cad3b99 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
@@ -35,6 +35,7 @@
import android.app.TaskInfo;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Rect;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -45,6 +46,8 @@
import com.android.quickstep.views.TaskViewType;
import com.android.systemui.shared.recents.model.Task;
import com.android.wm.shell.shared.GroupedTaskInfo;
+import com.android.wm.shell.shared.split.SplitBounds;
+import com.android.wm.shell.shared.split.SplitScreenConstants;
import org.junit.Before;
import org.junit.Test;
@@ -99,7 +102,12 @@
@Test
public void loadTasksInBackground_onlyKeys_noValidTaskDescription() throws Exception {
GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forSplitTasks(
- new RecentTaskInfo(), new RecentTaskInfo(), null);
+ new RecentTaskInfo(), new RecentTaskInfo(), new SplitBounds(
+ /* leftTopBounds = */ new Rect(),
+ /* rightBottomBounds = */ new Rect(),
+ /* leftTopTaskId = */ -1,
+ /* rightBottomTaskId = */ -1,
+ /* snapPosition = */ SplitScreenConstants.SNAP_TO_2_50_50));
when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
.thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
@@ -129,7 +137,13 @@
task1.taskDescription = new ActivityManager.TaskDescription(taskDescription);
RecentTaskInfo task2 = new RecentTaskInfo();
task2.taskDescription = new ActivityManager.TaskDescription();
- GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forSplitTasks(task1, task2, null);
+ GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forSplitTasks(task1, task2,
+ new SplitBounds(
+ /* leftTopBounds = */ new Rect(),
+ /* rightBottomBounds = */ new Rect(),
+ /* leftTopTaskId = */ -1,
+ /* rightBottomTaskId = */ -1,
+ /* snapPosition = */ SplitScreenConstants.SNAP_TO_2_50_50));
when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
.thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
index 0245908..b652ee8 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
@@ -1,9 +1,8 @@
package com.android.quickstep
-import android.content.Context
import androidx.test.annotation.UiThreadTest
-import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.SmallTest
+import com.android.launcher3.dagger.LauncherComponentProvider
import com.android.launcher3.util.DisplayController.CHANGE_DENSITY
import com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE
import com.android.launcher3.util.DisplayController.CHANGE_ROTATION
@@ -12,6 +11,7 @@
import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
import com.android.launcher3.util.LauncherMultivalentJUnit
import com.android.launcher3.util.NavigationMode
+import com.android.launcher3.util.SandboxApplication
import com.android.quickstep.util.GestureExclusionManager
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING
@@ -27,6 +27,7 @@
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
@@ -43,21 +44,32 @@
@RunWith(LauncherMultivalentJUnit::class)
class RecentsAnimationDeviceStateTest {
+ @get:Rule val context = SandboxApplication()
+
@Mock private lateinit var exclusionManager: GestureExclusionManager
@Mock private lateinit var info: Info
- private val context = ApplicationProvider.getApplicationContext() as Context
private lateinit var underTest: RecentsAnimationDeviceState
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- underTest = RecentsAnimationDeviceState(context, exclusionManager)
+
+ val component = LauncherComponentProvider.get(context)
+ underTest =
+ RecentsAnimationDeviceState(
+ context,
+ exclusionManager,
+ component.displayController,
+ component.contextualSearchStateManager,
+ component.rotationTouchHelper,
+ component.settingsCache,
+ component.daggerSingletonTracker,
+ )
}
@After
fun tearDown() {
- underTest.close()
UI_HELPER_EXECUTOR.submit {}.get()
MAIN_EXECUTOR.submit {}.get()
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java
index a5c60ce..99a1c59 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java
@@ -32,6 +32,7 @@
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Rect;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.annotation.UiThreadTest;
@@ -42,9 +43,12 @@
import com.android.launcher3.R;
import com.android.launcher3.graphics.ThemeManager;
import com.android.launcher3.icons.IconProvider;
+import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SplitTask;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.TaskStackChangeListeners;
+import com.android.wm.shell.shared.split.SplitScreenConstants;
import org.junit.Before;
import org.junit.Rule;
@@ -173,7 +177,13 @@
Task.TaskKey taskKey2 = new Task.TaskKey(taskInfo2);
Task task2 = Task.from(taskKey2, taskInfo2, false);
- allTasks.add(new GroupTask(task1, task2, null));
+ allTasks.add(
+ new SplitTask(task1, task2, new SplitConfigurationOptions.SplitBounds(
+ /* leftTopBounds = */ new Rect(),
+ /* rightBottomBounds = */ new Rect(),
+ /* leftTopTaskId = */ -1,
+ /* rightBottomTaskId = */ -1,
+ /* snapPosition = */ SplitScreenConstants.SNAP_TO_2_50_50)));
return allTasks;
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
index b6cf5bd..823f808 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
@@ -19,13 +19,17 @@
import android.content.ComponentName
import android.content.Intent
import android.graphics.Bitmap
+import android.graphics.Rect
import android.graphics.drawable.Drawable
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.util.SplitConfigurationOptions
import com.android.launcher3.util.TestDispatcherProvider
import com.android.quickstep.util.DesktopTask
-import com.android.quickstep.util.GroupTask
+import com.android.quickstep.util.SingleTask
+import com.android.quickstep.util.SplitTask
import com.android.systemui.shared.recents.model.Task
import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
@@ -48,8 +52,18 @@
private val tasks = (0..5).map(::createTaskWithId)
private val defaultTaskList =
listOf(
- GroupTask(tasks[0]),
- GroupTask(tasks[1], tasks[2], null),
+ SingleTask(tasks[0]),
+ SplitTask(
+ tasks[1],
+ tasks[2],
+ SplitConfigurationOptions.SplitBounds(
+ /* leftTopBounds = */ Rect(),
+ /* rightBottomBounds = */ Rect(),
+ /* leftTopTaskId = */ -1,
+ /* rightBottomTaskId = */ -1,
+ /* snapPosition = */ SNAP_TO_2_50_50,
+ ),
+ ),
DesktopTask(tasks.subList(3, 6)),
)
private val recentsModel = FakeRecentTasksDataSource()
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
index 108cfb5..fa043b9 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
@@ -21,7 +21,6 @@
import android.graphics.Rect
import com.android.launcher3.util.LauncherMultivalentJUnit
import com.android.launcher3.util.SplitConfigurationOptions
-import com.android.quickstep.views.TaskViewType
import com.android.systemui.shared.recents.model.Task
import com.android.wm.shell.shared.split.SplitScreenConstants
import com.google.common.truth.Truth.assertThat
@@ -33,28 +32,28 @@
@Test
fun testGroupTask_sameInstance_isEqual() {
- val task = GroupTask(createTask(1))
+ val task = SingleTask(createTask(1))
assertThat(task).isEqualTo(task)
}
@Test
fun testGroupTask_identicalConstructor_isEqual() {
- val task1 = GroupTask(createTask(1))
- val task2 = GroupTask(createTask(1))
+ val task1 = SingleTask(createTask(1))
+ val task2 = SingleTask(createTask(1))
assertThat(task1).isEqualTo(task2)
}
@Test
fun testGroupTask_copy_isEqual() {
- val task1 = GroupTask(createTask(1))
+ val task1 = SingleTask(createTask(1))
val task2 = task1.copy()
assertThat(task1).isEqualTo(task2)
}
@Test
fun testGroupTask_differentId_isNotEqual() {
- val task1 = GroupTask(createTask(1))
- val task2 = GroupTask(createTask(2))
+ val task1 = SingleTask(createTask(1))
+ val task2 = SingleTask(createTask(2))
assertThat(task1).isNotEqualTo(task2)
}
@@ -66,10 +65,10 @@
Rect(),
1,
2,
- SplitScreenConstants.SNAP_TO_2_50_50
+ SplitScreenConstants.SNAP_TO_2_50_50,
)
- val task1 = GroupTask(createTask(1), createTask(2), splitBounds, TaskViewType.GROUPED)
- val task2 = GroupTask(createTask(1), createTask(2), splitBounds, TaskViewType.GROUPED)
+ val task1 = SplitTask(createTask(1), createTask(2), splitBounds)
+ val task2 = SplitTask(createTask(1), createTask(2), splitBounds)
assertThat(task1).isEqualTo(task2)
}
@@ -81,7 +80,7 @@
Rect(),
1,
2,
- SplitScreenConstants.SNAP_TO_2_50_50
+ SplitScreenConstants.SNAP_TO_2_50_50,
)
val splitBounds2 =
SplitConfigurationOptions.SplitBounds(
@@ -89,17 +88,17 @@
Rect(),
1,
2,
- SplitScreenConstants.SNAP_TO_2_33_66
+ SplitScreenConstants.SNAP_TO_2_33_66,
)
- val task1 = GroupTask(createTask(1), createTask(2), splitBounds1, TaskViewType.GROUPED)
- val task2 = GroupTask(createTask(1), createTask(2), splitBounds2, TaskViewType.GROUPED)
+ val task1 = SplitTask(createTask(1), createTask(2), splitBounds1)
+ val task2 = SplitTask(createTask(1), createTask(2), splitBounds2)
assertThat(task1).isNotEqualTo(task2)
}
@Test
fun testGroupTask_differentType_isNotEqual() {
- val task1 = GroupTask(createTask(1), null, null, TaskViewType.SINGLE)
- val task2 = GroupTask(createTask(1), null, null, TaskViewType.DESKTOP)
+ val task1 = SingleTask(createTask(1))
+ val task2 = DesktopTask(listOf(createTask(1)))
assertThat(task1).isNotEqualTo(task2)
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
index 708273e..0491c07 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
@@ -660,10 +660,16 @@
intent.component = task2ComponentName
taskInfo.baseIntent = intent
task2.key = Task.TaskKey(taskInfo)
- return GroupTask(
+ return SplitTask(
task1,
task2,
- SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_2_50_50),
+ SplitConfigurationOptions.SplitBounds(
+ /* leftTopBounds = */ Rect(),
+ /* rightBottomBounds = */ Rect(),
+ /* leftTopTaskId = */ -1,
+ /* rightBottomTaskId = */ -1,
+ /* snapPosition = */ SNAP_TO_2_50_50,
+ ),
)
}
@@ -692,10 +698,16 @@
intent.component = task2ComponentName
taskInfo.baseIntent = intent
task2.key = Task.TaskKey(taskInfo)
- return GroupTask(
+ return SplitTask(
task1,
task2,
- SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_2_50_50),
+ SplitConfigurationOptions.SplitBounds(
+ /* leftTopBounds = */ Rect(),
+ /* rightBottomBounds = */ Rect(),
+ /* leftTopTaskId = */ -1,
+ /* rightBottomTaskId = */ -1,
+ /* snapPosition = */ SNAP_TO_2_50_50,
+ ),
)
}
}
diff --git a/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java b/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
index 3c5e1e8..e2ca91a 100644
--- a/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
@@ -16,8 +16,6 @@
package com.android.quickstep;
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
-
import static com.android.quickstep.InputConsumerUtils.newBaseConsumer;
import static com.android.quickstep.InputConsumerUtils.newConsumer;
@@ -40,6 +38,9 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.anim.AnimatedFloat;
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppModule;
+import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.taskbar.TaskbarManager;
import com.android.launcher3.taskbar.bubbles.BubbleBarController;
@@ -54,7 +55,7 @@
import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController;
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController;
import com.android.launcher3.util.LockedUserState;
-import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.SandboxApplication;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
import com.android.quickstep.inputconsumers.BubbleBarInputConsumer;
@@ -75,6 +76,9 @@
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.shared.system.InputMonitorCompat;
+import dagger.BindsInstance;
+import dagger.Component;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -93,8 +97,8 @@
@RunWith(AndroidJUnit4.class)
public class InputConsumerUtilsTest {
- @NonNull private final MainThreadInitializedObject.SandboxContext mContext =
- new MainThreadInitializedObject.SandboxContext(getApplicationContext());
+ @Rule public final SandboxApplication mContext = new SandboxApplication();
+
@NonNull private final InputMonitorCompat mInputMonitorCompat = new InputMonitorCompat("", 0);
private TaskAnimationManager mTaskAnimationManager;
@@ -125,10 +129,12 @@
}
@Before
- public void setupMainThreadInitializedObjects() {
- mContext.putObject(LockedUserState.INSTANCE, mLockedUserState);
- mContext.putObject(RotationTouchHelper.INSTANCE, mock(RotationTouchHelper.class));
- mContext.putObject(RecentsAnimationDeviceState.INSTANCE, mDeviceState);
+ public void setupDaggerGraphOverrides() {
+ mContext.initDaggerComponent(DaggerInputConsumerUtilsTest_TestComponent
+ .builder()
+ .bindLockedState(mLockedUserState)
+ .bindRotationHelper(mock(RotationTouchHelper.class))
+ .bindRecentsState(mDeviceState));
}
@Before
@@ -595,4 +601,18 @@
return bubbleControllers;
}
+
+ @LauncherAppSingleton
+ @Component(modules = {LauncherAppModule.class})
+ interface TestComponent extends LauncherAppComponent {
+ @Component.Builder
+ interface Builder extends LauncherAppComponent.Builder {
+ @BindsInstance Builder bindLockedState(LockedUserState state);
+ @BindsInstance Builder bindRotationHelper(RotationTouchHelper helper);
+ @BindsInstance Builder bindRecentsState(RecentsAnimationDeviceState state);
+
+ @Override
+ TestComponent build();
+ }
+ }
}
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 3d71ff1..c38444c 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -529,9 +529,11 @@
for (int i = 0; i < mDelegatedCellDrawings.size(); i++) {
DelegatedCellDrawing cellDrawing = mDelegatedCellDrawings.get(i);
- cellToPoint(cellDrawing.mDelegateCellX, cellDrawing.mDelegateCellY, mTempLocation);
canvas.save();
- canvas.translate(mTempLocation[0], mTempLocation[1]);
+ if (cellDrawing.mDelegateCellX >= 0 && cellDrawing.mDelegateCellY >= 0) {
+ cellToPoint(cellDrawing.mDelegateCellX, cellDrawing.mDelegateCellY, mTempLocation);
+ canvas.translate(mTempLocation[0], mTempLocation[1]);
+ }
cellDrawing.drawUnderItem(canvas);
canvas.restore();
}
@@ -660,9 +662,11 @@
for (int i = 0; i < mDelegatedCellDrawings.size(); i++) {
DelegatedCellDrawing bg = mDelegatedCellDrawings.get(i);
- cellToPoint(bg.mDelegateCellX, bg.mDelegateCellY, mTempLocation);
canvas.save();
- canvas.translate(mTempLocation[0], mTempLocation[1]);
+ if (bg.mDelegateCellX >= 0 && bg.mDelegateCellY >= 0) {
+ cellToPoint(bg.mDelegateCellX, bg.mDelegateCellY, mTempLocation);
+ canvas.translate(mTempLocation[0], mTempLocation[1]);
+ }
bg.drawOverItem(canvas);
canvas.restore();
}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 7563493..d93c07f 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -659,7 +659,8 @@
// Only fetch badge if the icon is on workspace
if (info.id != ItemInfo.NO_ID && badge == null) {
badge = appState.getIconCache().getShortcutInfoBadge(si)
- .newIcon(context, FLAG_THEMED);
+ .newIcon(context, ThemeManager.INSTANCE.get(context)
+ .isMonoThemeEnabled() ? FLAG_THEMED : 0);
}
}
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
diff --git a/src/com/android/launcher3/dagger/LauncherAppModule.java b/src/com/android/launcher3/dagger/LauncherAppModule.java
index ef136d0..c58a414 100644
--- a/src/com/android/launcher3/dagger/LauncherAppModule.java
+++ b/src/com/android/launcher3/dagger/LauncherAppModule.java
@@ -21,7 +21,9 @@
@Module(includes = {
WindowManagerProxyModule.class,
ApiWrapperModule.class,
- PluginManagerWrapperModule.class
+ PluginManagerWrapperModule.class,
+ StaticObjectModule.class,
+ AppModule.class
})
public class LauncherAppModule {
}
diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
index c3e3992..0b7b20f 100644
--- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
+++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
@@ -27,6 +27,7 @@
import com.android.launcher3.util.DaggerSingletonTracker;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DynamicResource;
+import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.MSDLPlayerWrapper;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PluginManagerWrapper;
@@ -68,6 +69,7 @@
ThemeManager getThemeManager();
DisplayController getDisplayController();
WallpaperColorHints getWallpaperColorHints();
+ LockedUserState getLockedUserState();
/** Builder for LauncherBaseAppComponent. */
interface Builder {
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index be5f8f7..8a1f96d 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.folder;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
+
import android.annotation.SuppressLint;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
@@ -37,6 +39,7 @@
import com.android.launcher3.model.StringCache;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.CollectionInfo;
+import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.Preconditions;
@@ -197,9 +200,18 @@
@Override
public void execute(@NonNull ModelTaskController taskController,
@NonNull BgDataModel dataModel, @NonNull AllAppsList apps) {
- mCollectionInfos = dataModel.collections.clone();
+ mCollectionInfos = getCollectionForSuggestions(dataModel);
mAppInfos = Arrays.asList(apps.copyData());
}
}
+ public static IntSparseArrayMap<CollectionInfo> getCollectionForSuggestions(
+ BgDataModel dataModel) {
+ IntSparseArrayMap<CollectionInfo> result = new IntSparseArrayMap<>();
+ dataModel.itemsIdMap.stream()
+ .filter(item -> item.itemType == ITEM_TYPE_FOLDER)
+ .forEach(item -> result.put(item.id, (FolderInfo) item));
+ return result;
+ }
+
}
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index f0e4fc4..a0b73ae 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -24,10 +24,10 @@
import static com.android.launcher3.BubbleTextView.DISPLAY_WORKSPACE;
import static com.android.launcher3.DeviceProfile.DEFAULT_SCALE;
import static com.android.launcher3.Hotseat.ALPHA_CHANNEL_PREVIEW_RENDERER;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
-import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
-import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks;
+import static com.android.launcher3.model.ModelUtils.currentScreenContentFilter;
import android.app.Fragment;
import android.app.WallpaperColors;
@@ -105,7 +105,9 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.stream.Collectors;
+import java.util.stream.IntStream;
/**
* Utility class for generating the preview of Launcher for a given InvariantDeviceProfile.
@@ -456,54 +458,48 @@
private void populate(BgDataModel dataModel,
Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
- // Separate the items that are on the current screen, and the other remaining items.
- ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
- ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
- ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
- ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
+ IntSet missingHotseatRank = new IntSet();
+ IntStream.range(0, mDp.numShownHotseatIcons).forEach(missingHotseatRank::add);
- IntSet currentScreenIds = IntSet.wrap(mWorkspaceScreens.keySet());
- filterCurrentWorkspaceItems(currentScreenIds, dataModel.workspaceItems,
- currentWorkspaceItems, otherWorkspaceItems);
- filterCurrentWorkspaceItems(currentScreenIds, dataModel.appWidgets, currentAppWidgets,
- otherAppWidgets);
- for (ItemInfo itemInfo : currentWorkspaceItems) {
- switch (itemInfo.itemType) {
- case Favorites.ITEM_TYPE_APPLICATION:
- case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
- inflateAndAddIcon((WorkspaceItemInfo) itemInfo);
- break;
- case Favorites.ITEM_TYPE_FOLDER:
- case Favorites.ITEM_TYPE_APP_PAIR:
- inflateAndAddCollectionIcon((CollectionInfo) itemInfo);
- break;
- default:
- break;
- }
- }
- Map<ComponentKey, AppWidgetProviderInfo> widgetsMap = widgetProviderInfoMap;
- for (ItemInfo itemInfo : currentAppWidgets) {
- switch (itemInfo.itemType) {
- case Favorites.ITEM_TYPE_APPWIDGET:
- case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
- if (widgetsMap == null) {
- widgetsMap = dataModel.widgetsModel.getWidgetsByComponentKey()
- .entrySet()
- .stream()
- .filter(entry -> entry.getValue().widgetInfo != null)
- .collect(Collectors.toMap(
- Map.Entry::getKey,
- entry -> entry.getValue().widgetInfo
- ));
+ Map<ComponentKey, AppWidgetProviderInfo>[] widgetsMap = new Map[] { widgetProviderInfoMap};
+
+ // Separate the items that are on the current screen, and the other remaining items.
+ dataModel.itemsIdMap.stream()
+ .filter(currentScreenContentFilter(IntSet.wrap(mWorkspaceScreens.keySet())))
+ .forEach(itemInfo -> {
+ switch (itemInfo.itemType) {
+ case Favorites.ITEM_TYPE_APPLICATION:
+ case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+ inflateAndAddIcon((WorkspaceItemInfo) itemInfo);
+ break;
+ case Favorites.ITEM_TYPE_FOLDER:
+ case Favorites.ITEM_TYPE_APP_PAIR:
+ inflateAndAddCollectionIcon((CollectionInfo) itemInfo);
+ break;
+ case Favorites.ITEM_TYPE_APPWIDGET:
+ case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
+ if (widgetsMap[0] == null) {
+ widgetsMap[0] = dataModel.widgetsModel.getWidgetsByComponentKey()
+ .entrySet()
+ .stream()
+ .filter(entry -> entry.getValue().widgetInfo != null)
+ .collect(Collectors.toMap(
+ Entry::getKey,
+ entry -> entry.getValue().widgetInfo
+ ));
+ }
+ inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo, widgetsMap[0]);
+ break;
+ default:
+ break;
}
- inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo, widgetsMap);
- break;
- default:
- break;
- }
- }
- IntArray ranks = getMissingHotseatRanks(currentWorkspaceItems,
- mDp.numShownHotseatIcons);
+
+ if (itemInfo.container == CONTAINER_HOTSEAT) {
+ missingHotseatRank.remove(itemInfo.screenId);
+ }
+ });
+
+ IntArray ranks = missingHotseatRank.getArray();
FixedContainerItems hotseatPredictions =
dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION);
List<ItemInfo> predictions = hotseatPredictions == null
diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
index c251114..de74ae8 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -19,8 +19,10 @@
import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
import static com.android.launcher3.Flags.enableWorkspaceInflation;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
import static com.android.launcher3.model.ItemInstallQueue.FLAG_LOADER_RUNNING;
-import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
+import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
+import static com.android.launcher3.model.ModelUtils.currentScreenContentFilter;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -44,11 +46,11 @@
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
+import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.ItemInflater;
import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.LooperIdleLock;
@@ -102,14 +104,12 @@
Trace.beginSection("BaseLauncherBinder#bindWorkspace");
try {
// Save a copy of all the bg-thread collections
- ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
- ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
+ IntSparseArrayMap<ItemInfo> itemsIdMap;
final IntArray orderedScreenIds = new IntArray();
ArrayList<FixedContainerItems> extraItems = new ArrayList<>();
final int workspaceItemCount;
synchronized (mBgDataModel) {
- workspaceItems.addAll(mBgDataModel.workspaceItems);
- appWidgets.addAll(mBgDataModel.appWidgets);
+ itemsIdMap = mBgDataModel.itemsIdMap.clone();
orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens());
mBgDataModel.extraItems.forEach(extraItems::add);
if (incrementBindId) {
@@ -122,7 +122,7 @@
for (Callbacks cb : mCallbacksList) {
new UnifiedWorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId,
- workspaceItems, appWidgets, extraItems, orderedScreenIds)
+ itemsIdMap, extraItems, orderedScreenIds)
.bind(isBindSync, workspaceItemCount);
}
} finally {
@@ -258,8 +258,7 @@
private final BgDataModel mBgDataModel;
private final int mMyBindingId;
- private final ArrayList<ItemInfo> mWorkspaceItems;
- private final ArrayList<LauncherAppWidgetInfo> mAppWidgets;
+ private final IntSparseArrayMap<ItemInfo> mItemIdMap;
private final IntArray mOrderedScreenIds;
private final ArrayList<FixedContainerItems> mExtraItems;
@@ -268,8 +267,7 @@
LauncherAppState app,
BgDataModel bgDataModel,
int myBindingId,
- ArrayList<ItemInfo> workspaceItems,
- ArrayList<LauncherAppWidgetInfo> appWidgets,
+ IntSparseArrayMap<ItemInfo> itemIdMap,
ArrayList<FixedContainerItems> extraItems,
IntArray orderedScreenIds) {
mCallbacks = callbacks;
@@ -277,8 +275,7 @@
mApp = app;
mBgDataModel = bgDataModel;
mMyBindingId = myBindingId;
- mWorkspaceItems = workspaceItems;
- mAppWidgets = appWidgets;
+ mItemIdMap = itemIdMap;
mExtraItems = extraItems;
mOrderedScreenIds = orderedScreenIds;
}
@@ -294,10 +291,15 @@
ArrayList<ItemInfo> currentAppWidgets = new ArrayList<>();
ArrayList<ItemInfo> otherAppWidgets = new ArrayList<>();
- filterCurrentWorkspaceItems(currentScreenIds, mWorkspaceItems, currentWorkspaceItems,
- otherWorkspaceItems);
- filterCurrentWorkspaceItems(currentScreenIds, mAppWidgets, currentAppWidgets,
- otherAppWidgets);
+ Predicate<ItemInfo> currentScreenCheck = currentScreenContentFilter(currentScreenIds);
+ mItemIdMap.forEach(item -> {
+ if (currentScreenCheck.test(item)) {
+ (WIDGET_FILTER.test(item) ? currentAppWidgets : currentWorkspaceItems)
+ .add(item);
+ } else if (item.container == CONTAINER_DESKTOP) {
+ (WIDGET_FILTER.test(item) ? otherAppWidgets : otherWorkspaceItems).add(item);
+ }
+ });
final InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile();
sortWorkspaceItemsSpatially(idp, currentWorkspaceItems);
sortWorkspaceItemsSpatially(idp, otherWorkspaceItems);
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index b9b1e98..a04cbfb 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -20,6 +20,11 @@
import static com.android.launcher3.BuildConfig.QSB_ON_FIRST_SCREEN;
import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
import static com.android.launcher3.shortcuts.ShortcutRequest.PINNED;
@@ -31,7 +36,6 @@
import android.content.pm.ShortcutInfo;
import android.os.UserHandle;
import android.text.TextUtils;
-import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.view.View;
@@ -39,14 +43,11 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.BuildConfig;
import com.android.launcher3.Workspace;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.CollectionInfo;
-import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -93,22 +94,6 @@
public final IntSparseArrayMap<ItemInfo> itemsIdMap = new IntSparseArrayMap<>();
/**
- * List of all the folders and shortcuts directly on the home screen (no widgets
- * or shortcuts within folders).
- */
- public final ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
-
- /**
- * All LauncherAppWidgetInfo created by LauncherModel.
- */
- public final ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
-
- /**
- * Map of id to CollectionInfos of all the folders or app pairs created by LauncherModel
- */
- public final IntSparseArrayMap<CollectionInfo> collections = new IntSparseArrayMap<>();
-
- /**
* Extra container based items
*/
public final IntSparseArrayMap<FixedContainerItems> extraItems = new IntSparseArrayMap<>();
@@ -144,9 +129,6 @@
* Clears all the data
*/
public synchronized void clear() {
- workspaceItems.clear();
- appWidgets.clear();
- collections.clear();
itemsIdMap.clear();
deepShortcutMap.clear();
extraItems.clear();
@@ -158,7 +140,7 @@
public synchronized IntArray collectWorkspaceScreens() {
IntSet screenSet = new IntSet();
for (ItemInfo item: itemsIdMap) {
- if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ if (item.container == CONTAINER_DESKTOP) {
screenSet.add(item.screenId);
}
}
@@ -173,26 +155,14 @@
public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer,
String[] args) {
writer.println(prefix + "Data Model:");
- writer.println(prefix + " ---- workspace items ");
- for (int i = 0; i < workspaceItems.size(); i++) {
- writer.println(prefix + '\t' + workspaceItems.get(i).toString());
- }
- writer.println(prefix + " ---- appwidget items ");
- for (int i = 0; i < appWidgets.size(); i++) {
- writer.println(prefix + '\t' + appWidgets.get(i).toString());
- }
- writer.println(prefix + " ---- collection items ");
- for (int i = 0; i < collections.size(); i++) {
- writer.println(prefix + '\t' + collections.valueAt(i).toString());
+ writer.println(prefix + " ---- items id map ");
+ for (int i = 0; i < itemsIdMap.size(); i++) {
+ writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString());
}
writer.println(prefix + " ---- extra items ");
for (int i = 0; i < extraItems.size(); i++) {
writer.println(prefix + '\t' + extraItems.valueAt(i).toString());
}
- writer.println(prefix + " ---- items id map ");
- for (int i = 0; i < itemsIdMap.size(); i++) {
- writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString());
- }
if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
writer.println(prefix + "shortcut counts ");
@@ -207,94 +177,38 @@
removeItem(context, Arrays.asList(items));
}
- public synchronized void removeItem(Context context, Iterable<? extends ItemInfo> items) {
- ArraySet<UserHandle> updatedDeepShortcuts = new ArraySet<>();
- for (ItemInfo item : items) {
- switch (item.itemType) {
- case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
- case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
- collections.remove(item.id);
- if (FeatureFlags.IS_STUDIO_BUILD) {
- for (ItemInfo info : itemsIdMap) {
- if (info.container == item.id) {
- // We are deleting a collection which still contains items that
- // think they are contained by that collection.
- String msg = "deleting a collection (" + item + ") which still "
- + "contains items (" + info + ")";
- Log.e(TAG, msg);
- }
- }
- }
- workspaceItems.remove(item);
- break;
- case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
- updatedDeepShortcuts.add(item.user);
- // Fall through.
- }
- case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
- workspaceItems.remove(item);
- break;
- case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
- case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
- appWidgets.remove(item);
- break;
- }
- itemsIdMap.remove(item.id);
+ public synchronized void removeItem(Context context, List<? extends ItemInfo> items) {
+ if (BuildConfig.IS_STUDIO_BUILD) {
+ items.stream()
+ .filter(item -> item.itemType == ITEM_TYPE_FOLDER
+ || item.itemType == ITEM_TYPE_APP_PAIR)
+ .forEach(item -> itemsIdMap.stream()
+ .filter(info -> info.container == item.id)
+ // We are deleting a collection which still contains items that
+ // think they are contained by that collection.
+ .forEach(info -> Log.e(TAG,
+ "deleting a collection (" + item + ") which still contains"
+ + " items (" + info + ")")));
}
- updatedDeepShortcuts.forEach(user -> updateShortcutPinnedState(context, user));
+
+ items.forEach(item -> itemsIdMap.remove(item.id));
+ items.stream().map(info -> info.user).distinct().forEach(
+ user -> updateShortcutPinnedState(context, user));
}
public synchronized void addItem(Context context, ItemInfo item, boolean newItem) {
- addItem(context, item, newItem, null);
- }
-
- public synchronized void addItem(
- Context context, ItemInfo item, boolean newItem, @Nullable LoaderMemoryLogger logger) {
- if (logger != null) {
- logger.addLog(
- Log.DEBUG,
- TAG,
- String.format("Adding item to ID map: %s", item.toString()),
- /* stackTrace= */ null);
- }
itemsIdMap.put(item.id, item);
- switch (item.itemType) {
- case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
- collections.put(item.id, (FolderInfo) item);
- workspaceItems.add(item);
- break;
- case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
- collections.put(item.id, (AppPairInfo) item);
- // Fall through here. App pairs are both containers (like folders) and containable
- // items (can be placed in folders). So we need to add app pairs to the folders
- // array (above) but also verify the existence of their container, like regular
- // apps (below).
- case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
- case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
- if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
- item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
- workspaceItems.add(item);
- } else {
- if (newItem) {
- if (!collections.containsKey(item.container)) {
- // Adding an item to a nonexistent collection.
- String msg = "attempted to add item: " + item + " to a nonexistent app"
- + " collection";
- Log.e(TAG, msg);
- }
- } else {
- findOrMakeFolder(item.container).add(item);
- }
- }
- break;
- case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
- case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
- appWidgets.add((LauncherAppWidgetInfo) item);
- break;
- }
- if (newItem && item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+ if (newItem && item.itemType == ITEM_TYPE_DEEP_SHORTCUT) {
updateShortcutPinnedState(context, item.user);
}
+ if (BuildConfig.IS_DEBUG_DEVICE
+ && newItem
+ && item.container != CONTAINER_DESKTOP
+ && item.container != CONTAINER_HOTSEAT
+ && !(itemsIdMap.get(item.container) instanceof CollectionInfo)) {
+ // Adding an item to a nonexistent collection.
+ Log.e(TAG, "attempted to add item: " + item + " to a nonexistent app collection");
+ }
}
/**
@@ -334,7 +248,7 @@
Map<String, Set<String>> modelMap = Stream.concat(
// Model shortcuts
itemStream.build()
- .filter(wi -> wi.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
+ .filter(wi -> wi.itemType == ITEM_TYPE_DEEP_SHORTCUT)
.map(ShortcutKey::fromItemInfo),
// Pending shortcuts
ItemInstallQueue.INSTANCE.get(context).getPendingShortcuts(user))
@@ -375,24 +289,6 @@
}
/**
- * Return an existing FolderInfo object if we have encountered this ID previously,
- * or make a new one.
- */
- public synchronized CollectionInfo findOrMakeFolder(int id) {
- // See if a placeholder was created for us already
- CollectionInfo collectionInfo = collections.get(id);
- if (collectionInfo == null) {
- // No placeholder -- create a new blank folder instance. At this point, we don't know
- // if the desired container is supposed to be a folder or an app pair. In the case that
- // it is an app pair, the blank folder will be replaced by a blank app pair when the app
- // pair is getting processed, in WorkspaceItemProcessor.processFolderOrAppPair().
- collectionInfo = new FolderInfo();
- collections.put(id, collectionInfo);
- }
- return collectionInfo;
- }
-
- /**
* Clear all the deep shortcut counts for the given package, and re-add the new shortcut counts.
*/
public synchronized void updateDeepShortcutCounts(
@@ -424,16 +320,6 @@
}
/**
- * Returns a list containing all workspace items including widgets.
- */
- public synchronized ArrayList<ItemInfo> getAllWorkspaceItems() {
- ArrayList<ItemInfo> items = new ArrayList<>(workspaceItems.size() + appWidgets.size());
- items.addAll(workspaceItems);
- items.addAll(appWidgets);
- return items;
- }
-
- /**
* Calls the provided {@code op} for all workspaceItems in the in-memory model (both persisted
* items and dynamic/predicted items for the provided {@code userHandle}.
* Note the call is not synchronized over the model, that should be handled by the called.
diff --git a/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt b/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt
index aa62c32..6ad52ea 100644
--- a/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt
+++ b/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt
@@ -30,7 +30,6 @@
import com.android.launcher3.model.data.ItemInfo
import com.android.launcher3.model.data.LauncherAppWidgetInfo
import com.android.launcher3.model.data.WorkspaceItemInfo
-import com.android.launcher3.pm.InstallSessionHelper
import com.android.launcher3.util.Executors
import com.android.launcher3.util.PackageManagerHelper
import com.android.launcher3.util.PackageUserKey
@@ -80,21 +79,22 @@
packageManagerHelper: PackageManagerHelper,
firstScreenItems: List<ItemInfo>,
userKeyToSessionMap: Map<PackageUserKey, SessionInfo>,
- allWidgets: List<LauncherAppWidgetInfo>
+ allWidgets: List<ItemInfo>,
): List<FirstScreenBroadcastModel> {
// installers for installing items
- val pendingItemInstallerMap: Map<String, MutableSet<String>> =
+ val pendingItemInstallerMap: Map<String, Set<String>> =
createPendingItemsMap(userKeyToSessionMap)
+
val installingPackages = pendingItemInstallerMap.values.flatten().toSet()
// installers for installed items on first screen
- val installedItemInstallerMap: Map<String, MutableSet<ItemInfo>> =
+ val installedItemInstallerMap: Map<String, List<ItemInfo>> =
createInstalledItemsMap(firstScreenItems, installingPackages, packageManagerHelper)
// installers for widgets on all screens
- val allInstalledWidgetsMap: Map<String, MutableSet<LauncherAppWidgetInfo>> =
- createAllInstalledWidgetsMap(allWidgets, installingPackages, packageManagerHelper)
+ val allInstalledWidgetsMap: Map<String, List<ItemInfo>> =
+ createInstalledItemsMap(allWidgets, installingPackages, packageManagerHelper)
val allInstallers: Set<String> =
pendingItemInstallerMap.keys +
@@ -131,39 +131,39 @@
context,
0 /* requestCode */,
Intent(),
- PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
- )
+ PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE,
+ ),
)
.putStringArrayListExtra(
PENDING_COLLECTION_ITEM_EXTRA,
- ArrayList(model.pendingCollectionItems)
+ ArrayList(model.pendingCollectionItems),
)
.putStringArrayListExtra(
PENDING_WORKSPACE_ITEM_EXTRA,
- ArrayList(model.pendingWorkspaceItems)
+ ArrayList(model.pendingWorkspaceItems),
)
.putStringArrayListExtra(
PENDING_HOTSEAT_ITEM_EXTRA,
- ArrayList(model.pendingHotseatItems)
+ ArrayList(model.pendingHotseatItems),
)
.putStringArrayListExtra(
PENDING_WIDGET_ITEM_EXTRA,
- ArrayList(model.pendingWidgetItems)
+ ArrayList(model.pendingWidgetItems),
)
.putStringArrayListExtra(
INSTALLED_WORKSPACE_ITEMS_EXTRA,
- ArrayList(model.installedWorkspaceItems)
+ ArrayList(model.installedWorkspaceItems),
)
.putStringArrayListExtra(
INSTALLED_HOTSEAT_ITEMS_EXTRA,
- ArrayList(model.installedHotseatItems)
+ ArrayList(model.installedHotseatItems),
)
.putStringArrayListExtra(
ALL_INSTALLED_WIDGETS_ITEM_EXTRA,
ArrayList(
model.firstScreenInstalledWidgets +
model.secondaryScreenInstalledWidgets
- )
+ ),
)
context.sendBroadcast(intent)
}
@@ -172,66 +172,46 @@
/** Maps Installer packages to Set of app packages from install sessions */
private fun createPendingItemsMap(
userKeyToSessionMap: Map<PackageUserKey, SessionInfo>
- ): Map<String, MutableSet<String>> {
+ ): Map<String, Set<String>> {
val myUser = Process.myUserHandle()
- val result = mutableMapOf<String, MutableSet<String>>()
- userKeyToSessionMap.forEach { entry ->
- if (!myUser.equals(InstallSessionHelper.getUserHandle(entry.value))) return@forEach
- val installer = entry.value.installerPackageName
- val appPackage = entry.value.appPackageName
- if (installer.isNullOrEmpty() || appPackage.isNullOrEmpty()) return@forEach
- result.getOrPut(installer) { mutableSetOf() }.add(appPackage)
- }
- return result
- }
-
- /**
- * Maps Installer packages to Set of ItemInfo from first screen. Filter out installing packages.
- */
- private fun createInstalledItemsMap(
- firstScreenItems: List<ItemInfo>,
- installingPackages: Set<String>,
- packageManagerHelper: PackageManagerHelper
- ): Map<String, MutableSet<ItemInfo>> {
- val result = mutableMapOf<String, MutableSet<ItemInfo>>()
- firstScreenItems.forEach { item ->
- val appPackage = getPackageName(item) ?: return@forEach
- if (installingPackages.contains(appPackage)) return@forEach
- val installer = packageManagerHelper.getAppInstallerPackage(appPackage)
- if (installer.isNullOrEmpty()) return@forEach
- result.getOrPut(installer) { mutableSetOf() }.add(item)
- }
- return result
- }
-
- /**
- * Maps Installer packages to Set of AppWidget packages installed on all screens. Filter out
- * installing packages.
- */
- private fun createAllInstalledWidgetsMap(
- allWidgets: List<LauncherAppWidgetInfo>,
- installingPackages: Set<String>,
- packageManagerHelper: PackageManagerHelper
- ): Map<String, MutableSet<LauncherAppWidgetInfo>> {
- val result = mutableMapOf<String, MutableSet<LauncherAppWidgetInfo>>()
- allWidgets
- .sortedBy { widget -> widget.screenId }
- .forEach { widget ->
- val appPackage = getPackageName(widget) ?: return@forEach
- if (installingPackages.contains(appPackage)) return@forEach
- val installer = packageManagerHelper.getAppInstallerPackage(appPackage)
- if (installer.isNullOrEmpty()) return@forEach
- result.getOrPut(installer) { mutableSetOf() }.add(widget)
+ return userKeyToSessionMap.values
+ .filter {
+ it.user == myUser &&
+ !it.installerPackageName.isNullOrEmpty() &&
+ !it.appPackageName.isNullOrEmpty()
}
- return result
+ .groupBy(
+ keySelector = { it.installerPackageName },
+ valueTransform = { it.appPackageName },
+ )
+ .mapValues { it.value.filterNotNull().toSet() } as Map<String, Set<String>>
}
+ /** Maps Installer packages to Set of ItemInfos. Filter out installing packages. */
+ private fun createInstalledItemsMap(
+ allItems: Iterable<ItemInfo>,
+ installingPackages: Set<String>,
+ packageManagerHelper: PackageManagerHelper,
+ ): Map<String, List<ItemInfo>> =
+ allItems
+ .sortedBy { it.screenId }
+ .groupByTo(mutableMapOf()) {
+ getPackageName(it)?.let { pkg ->
+ if (installingPackages.contains(pkg)) {
+ null
+ } else {
+ packageManagerHelper.getAppInstallerPackage(pkg)
+ }
+ }
+ }
+ .apply { remove(null) } as Map<String, List<ItemInfo>>
+
/**
* Add first screen Pending Items from Map to [FirstScreenBroadcastModel] for given installer
*/
private fun FirstScreenBroadcastModel.addPendingItems(
installingItems: Set<String>?,
- firstScreenItems: List<ItemInfo>
+ firstScreenItems: List<ItemInfo>,
) {
if (installingItems == null) return
for (info in firstScreenItems) {
@@ -251,7 +231,7 @@
*/
private fun FirstScreenBroadcastModel.addInstalledItems(
installer: String,
- installedItemInstallerMap: Map<String, Set<ItemInfo>>,
+ installedItemInstallerMap: Map<String, List<ItemInfo>>,
) {
installedItemInstallerMap[installer]?.forEach { info ->
val packageName: String = getPackageName(info) ?: return@forEach
@@ -265,7 +245,7 @@
/** Add Widgets on every screen from Map to [FirstScreenBroadcastModel] for given installer */
private fun FirstScreenBroadcastModel.addAllScreenWidgets(
installer: String,
- allInstalledWidgetsMap: Map<String, Set<LauncherAppWidgetInfo>>
+ allInstalledWidgetsMap: Map<String, List<ItemInfo>>,
) {
allInstalledWidgetsMap[installer]?.forEach { widget ->
val packageName: String = getPackageName(widget) ?: return@forEach
@@ -279,7 +259,7 @@
private fun FirstScreenBroadcastModel.addCollectionItems(
info: ItemInfo,
- installingPackages: Set<String>
+ installingPackages: Set<String>,
) {
if (info !is CollectionInfo) return
pendingCollectionItems.addAll(
@@ -336,7 +316,7 @@
Log.d(
TAG,
"Sending First Screen Broadcast for installer=$installerPackage" +
- ", total packages=${getTotalItemCount()}"
+ ", total packages=${getTotalItemCount()}",
)
pendingCollectionItems.forEach {
Log.d(TAG, "$installerPackage:Pending Collection item:$it")
@@ -361,15 +341,7 @@
}
}
- private fun getPackageName(info: ItemInfo): String? {
- var packageName: String? = null
- if (info is LauncherAppWidgetInfo) {
- info.providerName?.let { packageName = info.providerName.packageName }
- } else if (info.targetComponent != null) {
- packageName = info.targetComponent?.packageName
- }
- return packageName
- }
+ private fun getPackageName(info: ItemInfo): String? = info.targetComponent?.packageName
/**
* Clone the provided list on UI thread. This is used for [FolderInfo.getContents] which is
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 536d4c9..1623881 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -16,6 +16,11 @@
package com.android.launcher3.model;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
@@ -48,6 +53,8 @@
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.CollectionInfo;
+import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.IconRequestInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -84,6 +91,11 @@
private final IntArray mRestoredRows = new IntArray();
private final IntSparseArrayMap<GridOccupancy> mOccupied = new IntSparseArrayMap<>();
+ // CollectionInfo objects, which have not yet been loaded from the DB, but are expected to
+ // found eventually as the loading progresses
+ private final IntSparseArrayMap<CollectionInfo> mPendingCollectionInfo =
+ new IntSparseArrayMap<>();
+
private final int mIconIndex;
public final int mTitleIndex;
@@ -479,8 +491,26 @@
info.cellY = getInt(mCellYIndex);
}
- public void checkAndAddItem(ItemInfo info, BgDataModel dataModel) {
- checkAndAddItem(info, dataModel, null);
+ /**
+ * Return an existing FolderInfo object if we have encountered this ID previously,
+ * or make a new one.
+ */
+ public CollectionInfo findOrMakeFolder(int id, BgDataModel dataModel) {
+ // See if a placeholder was created for us already
+ ItemInfo info = dataModel.itemsIdMap.get(id);
+ if (info instanceof CollectionInfo c) return c;
+
+ CollectionInfo pending = mPendingCollectionInfo.get(id);
+ if (pending != null) return pending;
+
+ // No placeholder -- create a new blank folder instance. At this point, we don't know
+ // if the desired container is supposed to be a folder or an app pair. In the case that
+ // it is an app pair, the blank folder will be replaced by a blank app pair when the app
+ // pair is getting processed, in WorkspaceItemProcessor.processFolderOrAppPair().
+ pending = new FolderInfo();
+ pending.id = id;
+ mPendingCollectionInfo.put(id, pending);
+ return pending;
}
/**
@@ -495,7 +525,21 @@
ShortcutKey.fromItemInfo(info);
}
if (checkItemPlacement(info, dataModel.isFirstPagePinnedItemEnabled)) {
- dataModel.addItem(mContext, info, false, logger);
+ if (logger != null) {
+ logger.addLog(
+ Log.DEBUG,
+ TAG,
+ String.format("Adding item to ID map: %s", info),
+ /* stackTrace= */ null);
+ }
+ dataModel.addItem(mContext, info, false);
+ if ((info.itemType == ITEM_TYPE_APP_PAIR
+ || info.itemType == ITEM_TYPE_DEEP_SHORTCUT
+ || info.itemType == ITEM_TYPE_APPLICATION)
+ && info.container != CONTAINER_DESKTOP
+ && info.container != CONTAINER_HOTSEAT) {
+ findOrMakeFolder(info.container, dataModel).add(info);
+ }
if (mRestoreEventLogger != null) {
mRestoreEventLogger.logSingleFavoritesItemRestored(itemType);
}
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 44b7e8b..fee9696 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -31,7 +31,8 @@
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED;
-import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
+import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
+import static com.android.launcher3.model.ModelUtils.currentScreenContentFilter;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
@@ -82,7 +83,6 @@
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.AppPairInfo;
-import com.android.launcher3.model.data.CollectionInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.IconRequestInfo;
import com.android.launcher3.model.data.ItemInfo;
@@ -210,10 +210,10 @@
final int firstScreen = allScreens.get(0);
IntSet firstScreens = IntSet.wrap(firstScreen);
- ArrayList<ItemInfo> allItems = mBgDataModel.getAllWorkspaceItems();
- ArrayList<ItemInfo> firstScreenItems = new ArrayList<>();
- filterCurrentWorkspaceItems(firstScreens, allItems, firstScreenItems,
- new ArrayList<>() /* otherScreenItems are ignored */);
+ List<ItemInfo> firstScreenItems =
+ mBgDataModel.itemsIdMap.stream()
+ .filter(currentScreenContentFilter(firstScreens))
+ .toList();
final int disableArchivingLauncherBroadcast = Settings.Secure.getInt(
mApp.getContext().getContentResolver(),
"disable_launcher_broadcast_installed_apps",
@@ -227,7 +227,7 @@
mPmHelper,
firstScreenItems,
mInstallingPkgsCached,
- mBgDataModel.appWidgets
+ mBgDataModel.itemsIdMap.stream().filter(WIDGET_FILTER).toList()
);
logASplit("Sending first screen broadcast with additional archiving Extras");
FirstScreenBroadcastHelper.sendBroadcastsForModels(mApp.getContext(), broadcastModels);
@@ -523,14 +523,13 @@
* requests high-res icons for the items that are part of an app pair.
*/
private void processAppPairItems() {
- for (CollectionInfo collection : mBgDataModel.collections) {
- if (!(collection instanceof AppPairInfo appPair)) {
- continue;
- }
-
- appPair.getContents().sort(Folder.ITEM_POS_COMPARATOR);
- appPair.fetchHiResIconsIfNeeded(mIconCache);
- }
+ mBgDataModel.itemsIdMap.stream()
+ .filter(item -> item instanceof AppPairInfo)
+ .forEach(item -> {
+ AppPairInfo appPair = (AppPairInfo) item;
+ appPair.getContents().sort(Folder.ITEM_POS_COMPARATOR);
+ appPair.fetchHiResIconsIfNeeded(mIconCache);
+ });
}
/**
@@ -586,8 +585,8 @@
// Sort the folder items, update ranks, and make sure all preview items are high res.
List<FolderGridOrganizer> verifiers = mApp.getInvariantDeviceProfile().supportedProfiles
.stream().map(FolderGridOrganizer::createFolderGridOrganizer).toList();
- for (CollectionInfo collection : mBgDataModel.collections) {
- if (!(collection instanceof FolderInfo folder)) {
+ for (ItemInfo itemInfo : mBgDataModel.itemsIdMap) {
+ if (!(itemInfo instanceof FolderInfo folder)) {
continue;
}
@@ -657,8 +656,6 @@
IntArray deletedFolderIds = mApp.getModel().getModelDbController().deleteEmptyFolders();
synchronized (mBgDataModel) {
for (int folderId : deletedFolderIds) {
- mBgDataModel.workspaceItems.remove(mBgDataModel.collections.get(folderId));
- mBgDataModel.collections.remove(folderId);
mBgDataModel.itemsIdMap.remove(folderId);
}
}
@@ -676,8 +673,6 @@
synchronized (mBgDataModel) {
for (int id : deleted) {
- mBgDataModel.workspaceItems.remove(mBgDataModel.collections.get(id));
- mBgDataModel.collections.remove(id);
mBgDataModel.itemsIdMap.remove(id);
}
}
@@ -819,18 +814,19 @@
private void loadFolderNames() {
FolderNameProvider provider = FolderNameProvider.newInstance(mApp.getContext(),
- mBgAllAppsList.data, mBgDataModel.collections);
+ mBgAllAppsList.data, FolderNameProvider.getCollectionForSuggestions(mBgDataModel));
synchronized (mBgDataModel) {
- for (int i = 0; i < mBgDataModel.collections.size(); i++) {
- FolderNameInfos suggestionInfos = new FolderNameInfos();
- CollectionInfo info = mBgDataModel.collections.valueAt(i);
- if (info instanceof FolderInfo fi && fi.suggestedFolderNames == null) {
- provider.getSuggestedFolderName(mApp.getContext(), fi.getAppContents(),
- suggestionInfos);
- fi.suggestedFolderNames = suggestionInfos;
- }
- }
+ mBgDataModel.itemsIdMap.stream()
+ .filter(item ->
+ item instanceof FolderInfo fi && fi.suggestedFolderNames == null)
+ .forEach(info -> {
+ FolderInfo fi = (FolderInfo) info;
+ FolderNameInfos suggestionInfos = new FolderNameInfos();
+ provider.getSuggestedFolderName(mApp.getContext(), fi.getAppContents(),
+ suggestionInfos);
+ fi.suggestedFolderNames = suggestionInfos;
+ });
}
}
diff --git a/src/com/android/launcher3/model/ModelUtils.java b/src/com/android/launcher3/model/ModelUtils.java
index 9e72e28..da79982 100644
--- a/src/com/android/launcher3/model/ModelUtils.java
+++ b/src/com/android/launcher3/model/ModelUtils.java
@@ -15,15 +15,15 @@
*/
package com.android.launcher3.model;
-import com.android.launcher3.LauncherSettings;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
+
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-import java.util.stream.IntStream;
+import java.util.function.Predicate;
/**
* Utils class for {@link com.android.launcher3.LauncherModel}.
@@ -31,54 +31,17 @@
public class ModelUtils {
/**
- * Filters the set of items who are directly or indirectly (via another container) on the
- * specified screen.
+ * Returns a filter for items on hotseat or current screens
*/
- public static <T extends ItemInfo> void filterCurrentWorkspaceItems(
- final IntSet currentScreenIds,
- List<? extends T> allWorkspaceItems,
- List<T> currentScreenItems,
- List<T> otherScreenItems) {
- // Purge any null ItemInfos
- allWorkspaceItems.removeIf(Objects::isNull);
- // Order the set of items by their containers first, this allows use to walk through the
- // list sequentially, build up a list of containers that are in the specified screen,
- // as well as all items in those containers.
- IntSet itemsOnScreen = new IntSet();
- Collections.sort(allWorkspaceItems,
- (lhs, rhs) -> Integer.compare(lhs.container, rhs.container));
- for (T info : allWorkspaceItems) {
- if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
- if (currentScreenIds.contains(info.screenId)) {
- currentScreenItems.add(info);
- itemsOnScreen.add(info.id);
- } else {
- otherScreenItems.add(info);
- }
- } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
- currentScreenItems.add(info);
- itemsOnScreen.add(info.id);
- } else {
- if (itemsOnScreen.contains(info.container)) {
- currentScreenItems.add(info);
- itemsOnScreen.add(info.id);
- } else {
- otherScreenItems.add(info);
- }
- }
- }
+ public static Predicate<ItemInfo> currentScreenContentFilter(IntSet currentScreenIds) {
+ return item -> item.container == CONTAINER_HOTSEAT
+ || (item.container == CONTAINER_DESKTOP
+ && currentScreenIds.contains(item.screenId));
}
/**
- * Iterates though current workspace items and returns available hotseat ranks for prediction.
+ * Returns a filter for widget items
*/
- public static IntArray getMissingHotseatRanks(List<ItemInfo> items, int len) {
- IntSet seen = new IntSet();
- items.stream().filter(
- info -> info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)
- .forEach(i -> seen.add(i.screenId));
- IntArray result = new IntArray(len);
- IntStream.range(0, len).filter(i -> !seen.contains(i)).forEach(result::add);
- return result;
- }
+ public static final Predicate<ItemInfo> WIDGET_FILTER = item ->
+ item.itemType == ITEM_TYPE_APPWIDGET || item.itemType == ITEM_TYPE_CUSTOM_APPWIDGET;
}
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index b477cb1..0332775 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -459,37 +459,14 @@
if (item.container != Favorites.CONTAINER_DESKTOP &&
item.container != Favorites.CONTAINER_HOTSEAT) {
// Item is in a collection, make sure this collection exists
- if (!mBgDataModel.collections.containsKey(item.container)) {
+ if (!(mBgDataModel.itemsIdMap.get(item.container) instanceof CollectionInfo)) {
// An items container is being set to a that of an item which is not in
- // the list of Folders.
+ // the list of collections.
String msg = "item: " + item + " container being set to: " +
item.container + ", not in the list of collections";
Log.e(TAG, msg);
}
}
-
- // Items are added/removed from the corresponding FolderInfo elsewhere, such
- // as in Workspace.onDrop. Here, we just add/remove them from the list of items
- // that are on the desktop, as appropriate
- ItemInfo modelItem = mBgDataModel.itemsIdMap.get(itemId);
- if (modelItem != null &&
- (modelItem.container == Favorites.CONTAINER_DESKTOP ||
- modelItem.container == Favorites.CONTAINER_HOTSEAT)) {
- switch (modelItem.itemType) {
- case Favorites.ITEM_TYPE_APPLICATION:
- case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
- case Favorites.ITEM_TYPE_FOLDER:
- case Favorites.ITEM_TYPE_APP_PAIR:
- if (!mBgDataModel.workspaceItems.contains(modelItem)) {
- mBgDataModel.workspaceItems.add(modelItem);
- }
- break;
- default:
- break;
- }
- } else {
- mBgDataModel.workspaceItems.remove(modelItem);
- }
mVerifier.verifyModel();
}
}
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
index d238213..4103937 100644
--- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.model;
+import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
+
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -85,12 +87,16 @@
}
});
- for (LauncherAppWidgetInfo widget : dataModel.appWidgets) {
- if (widget.providerName.getPackageName().equals(mInstallInfo.packageName)) {
- widget.installProgress = mInstallInfo.progress;
- updates.add(widget);
- }
- }
+ dataModel.itemsIdMap.stream()
+ .filter(WIDGET_FILTER)
+ .filter(item -> mInstallInfo.user.equals(item.user))
+ .map(item -> (LauncherAppWidgetInfo) item)
+ .filter(widget -> widget.providerName.getPackageName()
+ .equals(mInstallInfo.packageName))
+ .forEach(widget -> {
+ widget.installProgress = mInstallInfo.progress;
+ updates.add(widget);
+ });
if (!updates.isEmpty()) {
taskController.scheduleCallbackTask(
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index d619965..1153f48 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -18,7 +18,9 @@
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED;
+import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
+import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_RESTORED_ICON;
@@ -347,24 +349,25 @@
}
});
- for (LauncherAppWidgetInfo widgetInfo : dataModel.appWidgets) {
- if (mUser.equals(widgetInfo.user)
- && widgetInfo.hasRestoreFlag(
- LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
- && packageSet.contains(widgetInfo.providerName.getPackageName())) {
- widgetInfo.restoreStatus &=
- ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY
- & ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
+ dataModel.itemsIdMap.stream()
+ .filter(WIDGET_FILTER)
+ .filter(item -> mUser.equals(item.user))
+ .map(item -> (LauncherAppWidgetInfo) item)
+ .filter(widget -> widget.hasRestoreFlag(FLAG_PROVIDER_NOT_READY)
+ && packageSet.contains(widget.providerName.getPackageName()))
+ .forEach(widgetInfo -> {
+ widgetInfo.restoreStatus &=
+ ~FLAG_PROVIDER_NOT_READY
+ & ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
- // adding this flag ensures that launcher shows 'click to setup'
- // if the widget has a config activity. In case there is no config
- // activity, it will be marked as 'restored' during bind.
- widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
+ // adding this flag ensures that launcher shows 'click to setup'
+ // if the widget has a config activity. In case there is no config
+ // activity, it will be marked as 'restored' during bind.
+ widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
- widgets.add(widgetInfo);
- taskController.getModelWriter().updateItemInDatabase(widgetInfo);
- }
- }
+ widgets.add(widgetInfo);
+ taskController.getModelWriter().updateItemInDatabase(widgetInfo);
+ });
}
taskController.bindUpdatedWorkspaceItems(updatedWorkspaceItems);
diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
index dad78dd..de1df2e 100644
--- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
+++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
@@ -404,18 +404,14 @@
* stored in the BgDataModel.
*/
private fun processFolderOrAppPair() {
- var collection = bgDataModel.findOrMakeFolder(c.id)
+ var collection = c.findOrMakeFolder(c.id, bgDataModel)
// If we generated a placeholder Folder before this point, it may need to be replaced with
// an app pair.
if (c.itemType == Favorites.ITEM_TYPE_APP_PAIR && collection is FolderInfo) {
- val folderInfo: FolderInfo = collection
val newAppPair = AppPairInfo()
// Move the placeholder's contents over to the new app pair.
- folderInfo.getContents().forEach(newAppPair::add)
+ collection.getContents().forEach(newAppPair::add)
collection = newAppPair
- // Remove the placeholder and add the app pair into the data model.
- bgDataModel.collections.remove(c.id)
- bgDataModel.collections.put(c.id, collection)
}
c.applyCommonProperties(collection)
@@ -569,7 +565,7 @@
logWidgetInfo(app.invariantDeviceProfile, lapi)
}
}
- c.checkAndAddItem(appWidgetInfo, bgDataModel)
+ c.checkAndAddItem(appWidgetInfo, bgDataModel, memoryLogger)
}
companion object {
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index 772ea7f..7fb0152 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -16,6 +16,8 @@
package com.android.launcher3.model.data;
+import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED;
+
import android.content.Context;
import android.content.Intent;
import android.os.Process;
@@ -23,6 +25,7 @@
import androidx.annotation.Nullable;
import com.android.launcher3.Flags;
+import com.android.launcher3.graphics.ThemeManager;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.BitmapInfo.DrawableCreationFlags;
import com.android.launcher3.icons.FastBitmapDrawable;
@@ -320,6 +323,9 @@
* Returns a FastBitmapDrawable with the icon and context theme applied
*/
public FastBitmapDrawable newIcon(Context context, @DrawableCreationFlags int creationFlags) {
+ if (!ThemeManager.INSTANCE.get(context).isMonoThemeEnabled()) {
+ creationFlags &= ~FLAG_THEMED;
+ }
FastBitmapDrawable drawable = bitmap.newIcon(context, creationFlags);
drawable.setIsDisabled(isDisabled());
return drawable;
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index 836ea4a..864fed0 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -170,6 +170,9 @@
for (NotificationsChangedListener listener : sNotificationsChangedListeners) {
listener.onNotificationPosted(msg.first, msg.second);
}
+ Log.i(TAG, "received notification posted event - " + msg.first);
+ } else {
+ Log.i(TAG, "received notification posted event, but there are no listeners");
}
break;
case MSG_NOTIFICATION_REMOVED:
@@ -178,6 +181,9 @@
for (NotificationsChangedListener listener : sNotificationsChangedListeners) {
listener.onNotificationRemoved(msg.first, msg.second);
}
+ Log.i(TAG, "received notification removed event - " + msg.first);
+ } else {
+ Log.i(TAG, "received notification removed event, but there are no listeners");
}
break;
case MSG_NOTIFICATION_FULL_REFRESH:
@@ -186,6 +192,11 @@
listener.onNotificationFullRefresh(
(List<StatusBarNotification>) message.obj);
}
+ ((List<StatusBarNotification>) message.obj).forEach(sbn -> Log.i(TAG,
+ "Handling notification state refresh for " + sbn.getPackageName() + "#"
+ + sbn.getUserId()));
+ } else {
+ Log.i(TAG, "received notification refresh event, but there are no listeners");
}
break;
}
@@ -205,6 +216,7 @@
@Override
public void onListenerConnected() {
super.onListenerConnected();
+ Log.i(TAG, "onListenerConnected");
sIsConnected = true;
// Register an observer to rebind the notification listener when dots are re-enabled.
@@ -230,6 +242,7 @@
@Override
public void onListenerDisconnected() {
super.onListenerDisconnected();
+ Log.i(TAG, "onListenerDisconnected");
sIsConnected = false;
mSettingsCache.unregister(NOTIFICATION_BADGING_URI, mNotificationSettingsChangedListener);
onNotificationFullRefresh();
diff --git a/src/com/android/launcher3/util/IntSparseArrayMap.java b/src/com/android/launcher3/util/IntSparseArrayMap.java
index 9d5391b..70f74e3 100644
--- a/src/com/android/launcher3/util/IntSparseArrayMap.java
+++ b/src/com/android/launcher3/util/IntSparseArrayMap.java
@@ -19,6 +19,8 @@
import android.util.SparseArray;
import java.util.Iterator;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
/**
* Extension of {@link SparseArray} with some utility methods.
@@ -43,6 +45,10 @@
return new ValueIterator();
}
+ public Stream<E> stream() {
+ return StreamSupport.stream(spliterator(), false);
+ }
+
@Thunk class ValueIterator implements Iterator<E> {
private int mNextIndex = 0;
diff --git a/src/com/android/launcher3/util/LayoutImportExportHelper.kt b/src/com/android/launcher3/util/LayoutImportExportHelper.kt
index 0df9dae..8559f3b 100644
--- a/src/com/android/launcher3/util/LayoutImportExportHelper.kt
+++ b/src/com/android/launcher3/util/LayoutImportExportHelper.kt
@@ -56,7 +56,7 @@
model.enqueueModelUpdateTask { _, dataModel, _ ->
val builder = LauncherLayoutBuilder()
- dataModel.workspaceItems.forEach { info ->
+ dataModel.itemsIdMap.forEach { info ->
val loc =
when (info.container) {
CONTAINER_DESKTOP ->
@@ -67,9 +67,6 @@
}
loc.addItem(context, info)
}
- dataModel.appWidgets.forEach { info ->
- builder.atWorkspace(info.cellX, info.cellY, info.screenId).addItem(context, info)
- }
val layoutXml = builder.build()
callback(layoutXml)
diff --git a/src/com/android/launcher3/util/LockedUserState.kt b/src/com/android/launcher3/util/LockedUserState.kt
index c8d86d4..a6a6ceb 100644
--- a/src/com/android/launcher3/util/LockedUserState.kt
+++ b/src/com/android/launcher3/util/LockedUserState.kt
@@ -20,10 +20,17 @@
import android.os.Process
import android.os.UserManager
import androidx.annotation.VisibleForTesting
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
import com.android.launcher3.util.Executors.MAIN_EXECUTOR
import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
+import javax.inject.Inject
-class LockedUserState(private val mContext: Context) : SafeCloseable {
+@LauncherAppSingleton
+class LockedUserState
+@Inject
+constructor(@ApplicationContext private val context: Context, lifeCycle: DaggerSingletonTracker) {
val isUserUnlockedAtLauncherStartup: Boolean
var isUserUnlocked = false
private set(value) {
@@ -36,7 +43,7 @@
private val mUserUnlockedActions: RunnableList = RunnableList()
@VisibleForTesting
- val mUserUnlockedReceiver =
+ val userUnlockedReceiver =
SimpleBroadcastReceiver(UI_HELPER_EXECUTOR) {
if (Intent.ACTION_USER_UNLOCKED == it.action) {
isUserUnlocked = true
@@ -53,8 +60,8 @@
isUserUnlocked = checkIsUserUnlocked()
isUserUnlockedAtLauncherStartup = isUserUnlocked
if (!isUserUnlocked) {
- mUserUnlockedReceiver.register(
- mContext,
+ userUnlockedReceiver.register(
+ context,
{
// If user is unlocked while registering broadcast receiver, we should update
// [isUserUnlocked], which will call [notifyUserUnlocked] in setter
@@ -62,22 +69,18 @@
MAIN_EXECUTOR.execute { isUserUnlocked = true }
}
},
- Intent.ACTION_USER_UNLOCKED
+ Intent.ACTION_USER_UNLOCKED,
)
}
+ lifeCycle.addCloseable { userUnlockedReceiver.unregisterReceiverSafely(context) }
}
private fun checkIsUserUnlocked() =
- mContext.getSystemService(UserManager::class.java)!!.isUserUnlocked(Process.myUserHandle())
+ context.getSystemService(UserManager::class.java)!!.isUserUnlocked(Process.myUserHandle())
private fun notifyUserUnlocked() {
mUserUnlockedActions.executeAllAndDestroy()
- mUserUnlockedReceiver.unregisterReceiverSafely(mContext)
- }
-
- /** Stops the receiver from listening for ACTION_USER_UNLOCK broadcasts. */
- override fun close() {
- mUserUnlockedReceiver.unregisterReceiverSafely(mContext)
+ userUnlockedReceiver.unregisterReceiverSafely(context)
}
/**
@@ -88,9 +91,7 @@
mUserUnlockedActions.add(action)
}
- /**
- * Removes a previously queued `Runnable` to be run when the user is unlocked.
- */
+ /** Removes a previously queued `Runnable` to be run when the user is unlocked. */
fun removeOnUserUnlockedRunnable(action: Runnable) {
mUserUnlockedActions.remove(action)
}
@@ -98,7 +99,7 @@
companion object {
@VisibleForTesting
@JvmField
- val INSTANCE = MainThreadInitializedObject { LockedUserState(it) }
+ val INSTANCE = DaggerSingletonObject(LauncherAppComponent::getLockedUserState)
@JvmStatic fun get(context: Context): LockedUserState = INSTANCE.get(context)
}
diff --git a/src/com/android/launcher3/util/SettingsCache.java b/src/com/android/launcher3/util/SettingsCache.java
index 8fe6e93..fa183c8 100644
--- a/src/com/android/launcher3/util/SettingsCache.java
+++ b/src/com/android/launcher3/util/SettingsCache.java
@@ -34,11 +34,11 @@
import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.dagger.LauncherBaseAppComponent;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Function;
import javax.inject.Inject;
@@ -57,7 +57,7 @@
* Cache will also be updated if a key queried is missing (even if it has no listeners registered).
*/
@LauncherAppSingleton
-public class SettingsCache extends ContentObserver implements SafeCloseable {
+public class SettingsCache extends ContentObserver {
/** Hidden field Settings.Secure.NOTIFICATION_BADGING */
public static final Uri NOTIFICATION_BADGING_URI =
@@ -79,11 +79,17 @@
private static final String SYSTEM_URI_PREFIX = Settings.System.CONTENT_URI.toString();
private static final String GLOBAL_URI_PREFIX = Settings.Global.CONTENT_URI.toString();
+ private final Function<Uri, CopyOnWriteArrayList<OnChangeListener>> mListenerMapper = uri -> {
+ registerUriAsync(uri);
+ return new CopyOnWriteArrayList<>();
+ };
+
/**
* Caches the last seen value for registered keys.
*/
- private Map<Uri, Boolean> mKeyCache = new ConcurrentHashMap<>();
- private final Map<Uri, CopyOnWriteArrayList<OnChangeListener>> mListenerMap = new HashMap<>();
+ private final Map<Uri, Boolean> mKeyCache = new ConcurrentHashMap<>();
+ private final Map<Uri, CopyOnWriteArrayList<OnChangeListener>> mListenerMap =
+ new ConcurrentHashMap<>();
protected final ContentResolver mResolver;
/**
@@ -96,12 +102,8 @@
SettingsCache(@ApplicationContext Context context, DaggerSingletonTracker tracker) {
super(new Handler(Looper.getMainLooper()));
mResolver = context.getContentResolver();
- tracker.addCloseable(this);
- }
-
- @Override
- public void close() {
- UI_HELPER_EXECUTOR.execute(() -> mResolver.unregisterContentObserver(this));
+ tracker.addCloseable(() ->
+ UI_HELPER_EXECUTOR.execute(() -> mResolver.unregisterContentObserver(this)));
}
@Override
@@ -109,11 +111,12 @@
// We use default of 1, but if we're getting an onChange call, can assume a non-default
// value will exist
boolean newVal = updateValue(uri, 1 /* Effectively Unused */);
- if (!mListenerMap.containsKey(uri)) {
+ List<OnChangeListener> listeners = mListenerMap.get(uri);
+ if (listeners == null) {
return;
}
- for (OnChangeListener listener : mListenerMap.get(uri)) {
+ for (OnChangeListener listener : listeners) {
listener.onSettingsChanged(newVal);
}
}
@@ -138,22 +141,17 @@
}
}
+ private void registerUriAsync(Uri uri) {
+ UI_HELPER_EXECUTOR.execute(() -> mResolver.registerContentObserver(uri, false, this));
+ }
+
/**
* Does not de-dupe if you add same listeners for the same key multiple times.
* Unregister once complete using {@link #unregister(Uri, OnChangeListener)}
*/
@UiThread
public void register(Uri uri, OnChangeListener changeListener) {
- Preconditions.assertUIThread();
- if (mListenerMap.containsKey(uri)) {
- mListenerMap.get(uri).add(changeListener);
- } else {
- CopyOnWriteArrayList<OnChangeListener> l = new CopyOnWriteArrayList<>();
- l.add(changeListener);
- mListenerMap.put(uri, l);
- UI_HELPER_EXECUTOR.execute(
- () -> mResolver.registerContentObserver(uri, false, this));
- }
+ mListenerMap.computeIfAbsent(uri, mListenerMapper).add(changeListener);
}
private boolean updateValue(Uri keyUri, int defaultValue) {
diff --git a/src/com/android/launcher3/views/ArrowTipView.java b/src/com/android/launcher3/views/ArrowTipView.java
index bb4f040..abb0081 100644
--- a/src/com/android/launcher3/views/ArrowTipView.java
+++ b/src/com/android/launcher3/views/ArrowTipView.java
@@ -73,7 +73,7 @@
}
};
- private final ActivityContext mActivityContext;
+ protected final ActivityContext mActivityContext;
private final Handler mHandler = new Handler();
private boolean mIsPointingUp;
private Runnable mOnClosed;
@@ -103,16 +103,26 @@
R.dimen.arrow_toast_arrow_width);
mArrowMinOffset = context.getResources().getDimensionPixelSize(
R.dimen.dynamic_grid_cell_border_spacing);
- TypedArray ta = context.obtainStyledAttributes(R.styleable.ArrowTipView);
+ Context localContext = context;
+ TypedArray ta = localContext.obtainStyledAttributes(R.styleable.ArrowTipView);
// Set style to default to avoid inflation issues with missing attributes.
if (!ta.hasValue(R.styleable.ArrowTipView_arrowTipBackground)
|| !ta.hasValue(R.styleable.ArrowTipView_arrowTipTextColor)) {
- context = new ContextThemeWrapper(context, R.style.ArrowTipStyle);
+ localContext = new ContextThemeWrapper(localContext, R.style.ArrowTipStyle);
}
- mArrowViewPaintColor = ta.getColor(R.styleable.ArrowTipView_arrowTipBackground,
+ mArrowViewPaintColor = applyArrowPaintColor(ta, localContext);
+ init(localContext, layoutId);
+ }
+
+ protected int applyArrowPaintColor(TypedArray typedArray, Context context) {
+ int arrowPaintColor = typedArray.getColor(R.styleable.ArrowTipView_arrowTipBackground,
context.getColor(R.color.arrow_tip_view_bg));
- ta.recycle();
- init(context, layoutId);
+ typedArray.recycle();
+ return arrowPaintColor;
+ }
+
+ protected int getArrowId() {
+ return R.id.arrow;
}
@Override
@@ -154,7 +164,7 @@
inflate(context, layoutId, this);
setOrientation(LinearLayout.VERTICAL);
- mArrowView = findViewById(R.id.arrow);
+ mArrowView = findViewById(getArrowId());
updateArrowTipInView(mIsPointingUp);
setAlpha(0);
@@ -343,6 +353,34 @@
parent.addView(this);
requestLayout();
}
+ return showAtLocation(arrowXCoord, yCoordDownPointingTip, yCoordUpPointingTip,
+ minViewMargin, parentViewWidth, parentViewHeight, shouldAutoClose);
+ }
+
+ /**
+ * Show the ArrowTipView (tooltip) custom aligned. The tooltip is vertically flipped if it
+ * cannot fit on screen in the requested orientation.
+ *
+ * @param arrowXCoord The X coordinate for the arrow on the tooltip. The arrow is usually in the
+ * center of tooltip unless the tooltip goes beyond screen margin.
+ * @param yCoordDownPointingTip The Y coordinate of the pointed tip end of the tooltip when the
+ * tooltip is placed pointing downwards.
+ * @param yCoordUpPointingTip The Y coordinate of the pointed tip end of the tooltip when the
+ * tooltip is placed pointing upwards.
+ * @param minViewMargin The view margin in pixels from the tip end to the y coordinate.
+ * @param parentViewWidth The width in pixels of the parent view.
+ * @param parentViewHeight The height in pixels of the parent view.
+ * @param shouldAutoClose If Tooltip should be auto close.
+ * @return The tool tip view. {@code null} if the tip can not be shown.
+ */
+ protected ArrowTipView showAtLocation(
+ @Px int arrowXCoord,
+ @Px int yCoordDownPointingTip,
+ @Px int yCoordUpPointingTip,
+ @Px int minViewMargin,
+ @Px int parentViewWidth,
+ @Px int parentViewHeight,
+ boolean shouldAutoClose) {
post(() -> {
// Adjust the tooltip horizontally.
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index d850fc6..ab0f9a7 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -577,14 +577,11 @@
public void exitSearchMode() {
if (!mIsInSearchMode) return;
onSearchResults(new ArrayList<>());
- WidgetsRecyclerView searchRecyclerView = mAdapters.get(
- AdapterHolder.SEARCH).mWidgetsRecyclerView;
// Remove all views when exiting the search mode; this prevents animating from stale results
// to new ones the next time we enter search mode. By the time recycler view is hidden,
// layout may not have happened to clear up existing results. So, instead of waiting for it
// to happen, we clear the views here.
- searchRecyclerView.swapAdapter(
- searchRecyclerView.getAdapter(), /*removeAndRecycleExistingViews=*/ true);
+ mAdapters.get(AdapterHolder.SEARCH).reset();
setViewVisibilityBasedOnSearch(/*isInSearchMode=*/ false);
if (mHasWorkProfile) {
mViewPager.snapToPage(AdapterHolder.PRIMARY);
@@ -613,13 +610,12 @@
mNoWidgetsView.setVisibility(GONE);
} else {
mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.setVisibility(GONE);
- mAdapters.get(getCurrentAdapterHolderType()).mWidgetsRecyclerView.setVisibility(
- VISIBLE);
- if (mRecommendedWidgetsCount > 0) {
- // Display recommendations immediately, if present, so that other parts of sticky
- // header (e.g. personal / work tabs) don't flash in interim.
- mWidgetRecommendationsContainer.setVisibility(VISIBLE);
- }
+ AdapterHolder currentAdapterHolder = mAdapters.get(getCurrentAdapterHolderType());
+ // Remove all views when exiting the search mode; this prevents animating / flashing old
+ // list position / state.
+ currentAdapterHolder.reset();
+ currentAdapterHolder.mWidgetsRecyclerView.setVisibility(VISIBLE);
+ post(this::onRecommendedWidgetsBound);
// Visibility of recycler views and headers are handled in methods below.
onWidgetsBound();
}
@@ -1126,6 +1122,21 @@
mWidgetsListItemAnimator = new WidgetsListItemAnimator();
}
+ /**
+ * Swaps the adapter to existing adapter to prevent the recycler view from using stale view
+ * to animate in the new visibility update.
+ *
+ * <p>For instance, when clearing search text and re-entering search with new list shouldn't
+ * use stale results to animate in new results. Alternative is setting list animators to
+ * null, but, we need animations with the default item animator.
+ */
+ private void reset() {
+ mWidgetsRecyclerView.swapAdapter(
+ mWidgetsListAdapter,
+ /*removeAndRecycleExistingViews=*/ true
+ );
+ }
+
private int getEmptySpaceHeight() {
return mStickyHeaderLayout != null ? mStickyHeaderLayout.getHeaderHeight() : 0;
}
diff --git a/src_no_quickstep/com/android/launcher3/dagger/Modules.kt b/src_no_quickstep/com/android/launcher3/dagger/Modules.kt
index dab33a0..c3bf7c5 100644
--- a/src_no_quickstep/com/android/launcher3/dagger/Modules.kt
+++ b/src_no_quickstep/com/android/launcher3/dagger/Modules.kt
@@ -25,3 +25,8 @@
@Module abstract class ApiWrapperModule {}
@Module abstract class PluginManagerWrapperModule {}
+
+@Module object StaticObjectModule {}
+
+// Module containing bindings for the final derivative app
+@Module abstract class AppModule {}
diff --git a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
index 553d08c..15accbd 100644
--- a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
@@ -17,11 +17,14 @@
package com.android.launcher3.folder
import android.R
-import android.graphics.Bitmap
import android.os.Process
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
import com.android.launcher3.graphics.PreloadIconDrawable
import com.android.launcher3.graphics.ThemeManager
import com.android.launcher3.icons.BitmapInfo
@@ -30,13 +33,14 @@
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver
import com.android.launcher3.icons.PlaceHolderIconDrawable
import com.android.launcher3.icons.UserBadgeDrawable
-import com.android.launcher3.icons.mono.MonoThemedBitmap
import com.android.launcher3.model.data.FolderInfo
import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED
import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE
import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.util.ActivityContextWrapper
+import com.android.launcher3.util.AllModulesForTest
import com.android.launcher3.util.Executors
+import com.android.launcher3.util.FakePrefsModule
import com.android.launcher3.util.FlagOp
import com.android.launcher3.util.LauncherLayoutBuilder
import com.android.launcher3.util.LauncherModelHelper
@@ -44,10 +48,19 @@
import com.android.launcher3.util.TestUtil
import com.android.launcher3.util.UserIconInfo
import com.google.common.truth.Truth.assertThat
+import dagger.Component
+import kotlin.annotation.AnnotationRetention.RUNTIME
+import kotlin.annotation.AnnotationTarget.FUNCTION
+import kotlin.annotation.AnnotationTarget.PROPERTY_GETTER
+import kotlin.annotation.AnnotationTarget.PROPERTY_SETTER
import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
+import org.junit.rules.TestRule
+import org.junit.runner.Description
import org.junit.runner.RunWith
+import org.junit.runners.model.Statement
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doReturn
@@ -61,6 +74,8 @@
@RunWith(AndroidJUnit4::class)
class PreviewItemManagerTest {
+ @get:Rule val theseStateRule = ThemeStateRule()
+
private lateinit var previewItemManager: PreviewItemManager
private lateinit var context: SandboxModelContext
private lateinit var folderItems: ArrayList<WorkspaceItemInfo>
@@ -68,15 +83,14 @@
private lateinit var folderIcon: FolderIcon
private lateinit var iconCache: IconCache
- private var defaultThemedIcons = false
-
- private val themeManager: ThemeManager
- get() = ThemeManager.INSTANCE.get(context)
-
@Before
fun setup() {
modelHelper = LauncherModelHelper()
context = modelHelper.sandboxContext
+ context.initDaggerComponent(DaggerPreviewItemManagerTestComponent.builder())
+ theseStateRule.themeState?.let {
+ LauncherPrefs.get(context).putSync(ThemeManager.THEMED_ICONS.to(it))
+ }
folderIcon = FolderIcon(ActivityContextWrapper(context))
val app = spy(LauncherAppState.getInstance(context))
@@ -99,27 +113,16 @@
)
.loadModelSync()
+ folderIcon.mInfo =
+ modelHelper.bgDataModel.itemsIdMap.find { it.itemType == ITEM_TYPE_FOLDER }
+ as FolderInfo
// Use getAppContents() to "cast" contents to WorkspaceItemInfo so we can set bitmaps
- folderItems = modelHelper.bgDataModel.collections.valueAt(0).getAppContents()
- folderIcon.mInfo = modelHelper.bgDataModel.collections.valueAt(0) as FolderInfo
- folderIcon.mInfo.getContents().addAll(folderItems)
-
- // Set first icon to be themed.
- folderItems[0].bitmap.themedBitmap =
- MonoThemedBitmap(
- folderItems[0].bitmap.icon,
- Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888),
- )
+ folderItems = folderIcon.mInfo.getAppContents()
// Set second icon to be non-themed.
folderItems[1].bitmap.themedBitmap = null
// Set third icon to be themed with badge.
- folderItems[2].bitmap.themedBitmap =
- MonoThemedBitmap(
- folderItems[2].bitmap.icon,
- Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888),
- )
folderItems[2].bitmap =
folderItems[2].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
@@ -127,20 +130,17 @@
folderItems[3].bitmap =
folderItems[3].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
folderItems[3].bitmap.themedBitmap = null
-
- defaultThemedIcons = themeManager.isMonoThemeEnabled
}
@After
@Throws(Exception::class)
fun tearDown() {
- themeManager.isMonoThemeEnabled = defaultThemedIcons
modelHelper.destroy()
}
@Test
+ @MonoThemeEnabled(true)
fun checkThemedIconWithThemingOn_iconShouldBeThemed() {
- themeManager.isMonoThemeEnabled = true
val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
previewItemManager.setDrawable(drawingParams, folderItems[0])
@@ -149,8 +149,8 @@
}
@Test
+ @MonoThemeEnabled(false)
fun checkThemedIconWithThemingOff_iconShouldNotBeThemed() {
- themeManager.isMonoThemeEnabled = false
val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
previewItemManager.setDrawable(drawingParams, folderItems[0])
@@ -159,8 +159,8 @@
}
@Test
+ @MonoThemeEnabled(true)
fun checkUnthemedIconWithThemingOn_iconShouldNotBeThemed() {
- themeManager.isMonoThemeEnabled = true
val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
previewItemManager.setDrawable(drawingParams, folderItems[1])
@@ -169,8 +169,8 @@
}
@Test
+ @MonoThemeEnabled(false)
fun checkUnthemedIconWithThemingOff_iconShouldNotBeThemed() {
- themeManager.isMonoThemeEnabled = false
val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
previewItemManager.setDrawable(drawingParams, folderItems[1])
@@ -179,8 +179,8 @@
}
@Test
+ @MonoThemeEnabled(true)
fun checkThemedIconWithBadgeWithThemingOn_iconAndBadgeShouldBeThemed() {
- themeManager.isMonoThemeEnabled = true
val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
previewItemManager.setDrawable(drawingParams, folderItems[2])
@@ -192,8 +192,8 @@
}
@Test
+ @MonoThemeEnabled(true)
fun checkUnthemedIconWithBadgeWithThemingOn_badgeShouldBeThemed() {
- themeManager.isMonoThemeEnabled = true
val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
previewItemManager.setDrawable(drawingParams, folderItems[3])
@@ -205,8 +205,8 @@
}
@Test
+ @MonoThemeEnabled(false)
fun checkUnthemedIconWithBadgeWithThemingOff_iconAndBadgeShouldNotBeThemed() {
- themeManager.isMonoThemeEnabled = false
val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
previewItemManager.setDrawable(drawingParams, folderItems[3])
@@ -278,3 +278,28 @@
private fun profileFlagOp(type: Int) =
UserIconInfo(Process.myUserHandle(), type).applyBitmapInfoFlags(FlagOp.NO_OP)
}
+
+class ThemeStateRule : TestRule {
+
+ var themeState: Boolean? = null
+
+ override fun apply(base: Statement, description: Description): Statement {
+ themeState = description.getAnnotation(MonoThemeEnabled::class.java)?.value
+ return base
+ }
+}
+
+// Annotation for tests that need to be run with quickstep enabled and disabled.
+@Retention(RUNTIME)
+@Target(FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER)
+annotation class MonoThemeEnabled(val value: Boolean = false)
+
+@LauncherAppSingleton
+@Component(modules = [AllModulesForTest::class, FakePrefsModule::class])
+interface PreviewItemManagerTestComponent : LauncherAppComponent {
+
+ @Component.Builder
+ interface Builder : LauncherAppComponent.Builder {
+ override fun build(): PreviewItemManagerTestComponent
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java b/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
index 1e2431f..0ae4d00 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
@@ -16,6 +16,8 @@
package com.android.launcher3.model;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY;
import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
@@ -42,6 +44,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.List;
+
/**
* Tests for layout parser for remote layout
*/
@@ -63,14 +67,23 @@
mModelHelper.destroy();
}
+ private List<ItemInfo> getWorkspaceItems() {
+ return mModelHelper
+ .getBgDataModel()
+ .itemsIdMap
+ .stream()
+ .filter(i -> i.container == CONTAINER_DESKTOP || i.container == CONTAINER_HOTSEAT)
+ .toList();
+ }
+
@Test
public void testCustomProfileLoaded_with_icon_on_hotseat() throws Exception {
writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0)
.putApp(TEST_PACKAGE, TEST_ACTIVITY));
// Verify one item in hotseat
- assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
- ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0);
+ assertEquals(1, getWorkspaceItems().size());
+ ItemInfo info = getWorkspaceItems().get(0);
assertEquals(LauncherSettings.Favorites.CONTAINER_HOTSEAT, info.container);
assertEquals(LauncherSettings.Favorites.ITEM_TYPE_APPLICATION, info.itemType);
}
@@ -84,8 +97,8 @@
.build());
// Verify folder
- assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
- ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0);
+ assertEquals(1, getWorkspaceItems().size());
+ ItemInfo info = getWorkspaceItems().get(0);
assertEquals(LauncherSettings.Favorites.ITEM_TYPE_FOLDER, info.itemType);
assertEquals(3, ((FolderInfo) info).getContents().size());
}
@@ -99,8 +112,8 @@
.build());
// Verify folder
- assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
- ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0);
+ assertEquals(1, getWorkspaceItems().size());
+ ItemInfo info = getWorkspaceItems().get(0);
assertEquals(LauncherSettings.Favorites.ITEM_TYPE_FOLDER, info.itemType);
assertEquals(3, ((FolderInfo) info).getContents().size());
assertEquals("CustomFolder", info.title.toString());
@@ -124,8 +137,8 @@
.putWidget(pendingAppPkg, "PlaceholderWidget", 2, 2));
// Verify widget
- assertEquals(1, mModelHelper.getBgDataModel().appWidgets.size());
- ItemInfo info = mModelHelper.getBgDataModel().appWidgets.get(0);
+ assertEquals(1, getWorkspaceItems().size());
+ ItemInfo info = getWorkspaceItems().get(0);
assertEquals(LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET, info.itemType);
assertEquals(2, info.spanX);
assertEquals(2, info.spanY);
@@ -138,8 +151,8 @@
.putShortcut(TEST_PACKAGE, "shortcut2"));
// Verify one item in hotseat
- assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
- ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0);
+ assertEquals(1, getWorkspaceItems().size());
+ ItemInfo info = getWorkspaceItems().get(0);
assertEquals(LauncherSettings.Favorites.CONTAINER_HOTSEAT, info.container);
assertEquals(LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT, info.itemType);
}
@@ -154,8 +167,8 @@
.build());
// Verify folder
- assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
- FolderInfo info = (FolderInfo) mModelHelper.getBgDataModel().workspaceItems.get(0);
+ assertEquals(1, getWorkspaceItems().size());
+ FolderInfo info = (FolderInfo) getWorkspaceItems().get(0);
assertEquals(3, info.getContents().size());
// Verify last icon
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt
index e8f778f..f357487 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt
@@ -18,8 +18,10 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER
import com.android.launcher3.icons.BitmapInfo
import com.android.launcher3.icons.waitForUpdateHandlerToFinish
+import com.android.launcher3.model.data.FolderInfo
import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.util.Executors
import com.android.launcher3.util.LauncherLayoutBuilder
@@ -149,11 +151,13 @@
// Reload again with correct icon state
app.model.forceReload()
modelHelper.loadModelSync()
- val collections = modelHelper.getBgDataModel().collections
-
- assertThat(collections.size()).isEqualTo(1)
- assertThat(collections.valueAt(0).getAppContents().size).isEqualTo(itemCount)
- return collections.valueAt(0).getAppContents()
+ val collections =
+ modelHelper.bgDataModel.itemsIdMap
+ .filter { it.itemType == ITEM_TYPE_FOLDER }
+ .map { it as FolderInfo }
+ assertThat(collections.size).isEqualTo(1)
+ assertThat(collections[0].getAppContents().size).isEqualTo(itemCount)
+ return collections[0].getAppContents()
}
private fun verifyHighRes(items: ArrayList<WorkspaceItemInfo>, vararg indices: Int) {
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
index d699eee..da87dfc 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
@@ -494,8 +494,7 @@
@Test
fun `When processing Folder then create FolderInfo and mark restored`() {
val actualFolderInfo = FolderInfo()
- mockBgDataModel =
- mock<BgDataModel>().apply { whenever(findOrMakeFolder(1)).thenReturn(actualFolderInfo) }
+ mockBgDataModel = mock<BgDataModel>()
mockCursor =
mock<LoaderCursor>().apply {
user = UserHandle(0)
@@ -509,6 +508,7 @@
whenever(getColumnIndex(Favorites.TITLE)).thenReturn(4)
whenever(getString(4)).thenReturn("title")
whenever(options).thenReturn(5)
+ whenever(findOrMakeFolder(eq(1), any())).thenReturn(actualFolderInfo)
}
val expectedFolderInfo =
FolderInfo().apply {
@@ -600,7 +600,8 @@
// Then
val widgetInfoCaptor = ArgumentCaptor.forClass(LauncherAppWidgetInfo::class.java)
- verify(mockCursor).checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel))
+ verify(mockCursor)
+ .checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel), anyOrNull())
val actualWidgetInfo = widgetInfoCaptor.value
with(actualWidgetInfo) {
assertThat(providerName).isEqualTo(expectedWidgetInfo.providerName)
@@ -655,7 +656,7 @@
itemProcessorUnderTest.processItem()
// Then
- verify(mockCursor).checkAndAddItem(any(), any())
+ verify(mockCursor).checkAndAddItem(any(), any(), anyOrNull())
}
@Test
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/DaggerGraphs.kt b/tests/multivalentTests/src/com/android/launcher3/util/DaggerGraphs.kt
index 68da9ff..b66a9d3 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/DaggerGraphs.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/DaggerGraphs.kt
@@ -19,6 +19,8 @@
import com.android.launcher3.FakeLauncherPrefs
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.dagger.ApiWrapperModule
+import com.android.launcher3.dagger.AppModule
+import com.android.launcher3.dagger.StaticObjectModule
import com.android.launcher3.dagger.WindowManagerProxyModule
import dagger.Binds
import dagger.Module
@@ -31,11 +33,21 @@
}
/** All modules. We also exclude the plugin module from tests */
-@Module(includes = [ApiWrapperModule::class, WindowManagerProxyModule::class])
+@Module(
+ includes =
+ [
+ ApiWrapperModule::class,
+ WindowManagerProxyModule::class,
+ StaticObjectModule::class,
+ AppModule::class,
+ ]
+)
class AllModulesForTest
/** All modules except the WMProxy */
-@Module(includes = [ApiWrapperModule::class]) class AllModulesMinusWMProxy
+@Module(includes = [ApiWrapperModule::class, StaticObjectModule::class, AppModule::class])
+class AllModulesMinusWMProxy
/** All modules except the ApiWrapper */
-@Module(includes = [WindowManagerProxyModule::class]) class AllModulesMinusApiWrapper
+@Module(includes = [WindowManagerProxyModule::class, StaticObjectModule::class, AppModule::class])
+class AllModulesMinusApiWrapper
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/LockedUserStateTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/LockedUserStateTest.kt
index 2711d7a..99f5a5b 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/LockedUserStateTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/LockedUserStateTest.kt
@@ -22,7 +22,10 @@
import android.os.UserManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
import com.google.common.truth.Truth.assertThat
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -38,17 +41,24 @@
private val userManager: UserManager = mock()
private val context: Context = mock()
+ private val lifeCycle: DaggerSingletonTracker = mock()
@Before
fun setup() {
whenever(context.getSystemService(UserManager::class.java)).thenReturn(userManager)
}
+ @After
+ fun tearDown() {
+ UI_HELPER_EXECUTOR.submit {}.get()
+ MAIN_EXECUTOR.submit {}.get()
+ }
+
@Test
fun runOnUserUnlocked_runs_action_immediately_if_already_unlocked() {
whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
val action: Runnable = mock()
- LockedUserState(context).runOnUserUnlocked(action)
+ LockedUserState(context, lifeCycle).runOnUserUnlocked(action)
verify(action).run()
}
@@ -56,23 +66,23 @@
fun runOnUserUnlocked_waits_to_run_action_until_user_is_unlocked() {
whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
val action: Runnable = mock()
- val state = LockedUserState(context)
+ val state = LockedUserState(context, lifeCycle)
state.runOnUserUnlocked(action)
// b/343530737
verifyNoMoreInteractions(action)
- state.mUserUnlockedReceiver.onReceive(context, Intent(Intent.ACTION_USER_UNLOCKED))
+ state.userUnlockedReceiver.onReceive(context, Intent(Intent.ACTION_USER_UNLOCKED))
verify(action).run()
}
@Test
fun isUserUnlocked_returns_true_when_user_is_unlocked() {
whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
- assertThat(LockedUserState(context).isUserUnlocked).isTrue()
+ assertThat(LockedUserState(context, lifeCycle).isUserUnlocked).isTrue()
}
@Test
fun isUserUnlocked_returns_false_when_user_is_locked() {
whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
- assertThat(LockedUserState(context).isUserUnlocked).isFalse()
+ assertThat(LockedUserState(context, lifeCycle).isUserUnlocked).isFalse()
}
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt
index efe7637..0da8891 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt
@@ -48,6 +48,7 @@
class SandboxApplication private constructor(private val base: SandboxApplicationWrapper) :
SandboxModelContext(base), TestRule {
+ @JvmOverloads
constructor(
base: Context = ApplicationProvider.getApplicationContext()
) : this(SandboxApplicationWrapper(base))
diff --git a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
index d2229c4..f04688d 100644
--- a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
@@ -19,6 +19,10 @@
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.LauncherPrefs.Companion.IS_FIRST_LOAD_AFTER_RESTORE
import com.android.launcher3.LauncherPrefs.Companion.RESTORE_DEVICE
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER
import com.android.launcher3.icons.IconCache
import com.android.launcher3.icons.cache.CachingLogic
import com.android.launcher3.icons.cache.IconCacheUpdateHandler
@@ -155,9 +159,24 @@
widgetsFilterDataProvider,
)
.runSyncOnBackgroundThread()
- Truth.assertThat(workspaceItems.size).isAtLeast(25)
- Truth.assertThat(appWidgets.size).isAtLeast(7)
- Truth.assertThat(collections.size()).isAtLeast(8)
+ Truth.assertThat(
+ itemsIdMap
+ .filter {
+ it.container == CONTAINER_DESKTOP || it.container == CONTAINER_HOTSEAT
+ }
+ .size
+ )
+ .isAtLeast(32)
+ Truth.assertThat(itemsIdMap.filter { ModelUtils.WIDGET_FILTER.test(it) }.size)
+ .isAtLeast(7)
+ Truth.assertThat(
+ itemsIdMap
+ .filter {
+ it.itemType == ITEM_TYPE_FOLDER || it.itemType == ITEM_TYPE_APP_PAIR
+ }
+ .size
+ )
+ .isAtLeast(8)
Truth.assertThat(itemsIdMap.size()).isAtLeast(40)
Truth.assertThat(widgetsModel.defaultWidgetsFilter).isNotNull()
}
diff --git a/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt b/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt
index d553f47..8db049c 100644
--- a/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt
+++ b/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt
@@ -58,6 +58,7 @@
import org.mockito.Mockito.RETURNS_DEEP_STUBS
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
@@ -220,7 +221,8 @@
)
.commit()
val widgetInfoCaptor = ArgumentCaptor.forClass(LauncherAppWidgetInfo::class.java)
- verify(mockCursor).checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel))
+ verify(mockCursor)
+ .checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel), anyOrNull())
val actualWidgetInfo = widgetInfoCaptor.value
with(actualWidgetInfo) {
assertThat(providerName).isEqualTo(expectedWidgetInfo.providerName)
@@ -271,7 +273,7 @@
itemProcessorUnderTest.processItem()
// Then
- verify(mockCursor).checkAndAddItem(any(), any())
+ verify(mockCursor).checkAndAddItem(any(), any(), anyOrNull())
}
private fun createWorkspaceItemProcessorUnderTest(