Merge "[CD Taskbar] Stop External Taskbar Destruction on Window Close" into main
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 7578bd5..f263f7e 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -345,12 +345,10 @@
<!-- Accessibility label for quick switch tiles that include information about the tile's position in the parent list [CHAR LIMIT=NONE] -->
<string name="quick_switch_task_with_position_in_parent"><xliff:g id="task_description" example="Chrome">%1$s</xliff:g>, item <xliff:g id="index_in_parent" example="1">%2$d</xliff:g> of <xliff:g id="total_tasks" example="5">%3$d</xliff:g></string>
- <!-- Accessibility label for an arrow button within quick switch UI that scrolls the quick switch content left
- TODO(b/397975686): Make these translatable when verified by UX. -->
- <string name="quick_switch_scroll_arrow_left" translatable="false">Scroll left</string>
- <!-- Accessibility label for an arrow button within quick switch UI that scrolls the quick switch content right
- TODO(b/397975686): Make these translatable when verified by UX. -->
- <string name="quick_switch_scroll_arrow_right" translatable="false">Scroll right</string>
+ <!-- Accessibility label for an arrow button within quick switch UI that scrolls the quick switch content left -->
+ <string name="quick_switch_scroll_arrow_left">Scroll left</string>
+ <!-- Accessibility label for an arrow button within quick switch UI that scrolls the quick switch content right -->
+ <string name="quick_switch_scroll_arrow_right">Scroll right</string>
<!-- Strings for bubble bar -->
<!-- Fallback name for a bubble if it does have a title [CHAR_LIMIT=none] -->
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index aae8a56..954272c 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -92,6 +92,7 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
+import android.util.Log;
import android.util.Pair;
import android.util.Size;
import android.view.CrossWindowBlurListeners;
@@ -182,6 +183,7 @@
* Manages the opening and closing app transitions from Launcher
*/
public class QuickstepTransitionManager implements OnDeviceProfileChangeListener {
+ private static final String TAG = "QuickstepTransitionManager";
private static final boolean ENABLE_SHELL_STARTING_SURFACE =
SystemProperties.getBoolean("persist.debug.shell_starting_surface", true);
@@ -1207,7 +1209,7 @@
mLauncher.removeOnDeviceProfileChangeListener(this);
SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener(null);
if (BuildConfig.IS_STUDIO_BUILD && !mRegisteredTaskStackChangeListener.isEmpty()) {
- throw new IllegalStateException("Failed to run onEndCallback created from"
+ Log.e(TAG, "IllegalState: Failed to run onEndCallback created from"
+ " getActivityLaunchOptions()");
}
mRegisteredTaskStackChangeListener.forEach(TaskRestartedDuringLaunchListener::unregister);
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt
index 1438edf..a9e5145 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt
@@ -93,7 +93,7 @@
}
if (trampolineCloseChange != null) {
val trampolineCloseAnimator =
- createTrampolineCloseAnimator(trampolineCloseChange, transaction)
+ createTrampolineCloseAnimator(trampolineCloseChange, transaction, finishCallback)
animatorsList.add(trampolineCloseAnimator)
}
return animatorsList
@@ -112,7 +112,7 @@
private fun getTrampolineCloseChange(info: TransitionInfo): Change? {
if (
info.changes.size < 2 ||
- !DesktopModeFlags.ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX.isTrue
+ !DesktopModeFlags.ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX.isTrue
) {
return null
}
@@ -194,13 +194,22 @@
)
}
- private fun createTrampolineCloseAnimator(change: Change, transaction: Transaction): Animator {
+ private fun createTrampolineCloseAnimator(
+ change: Change,
+ transaction: Transaction,
+ onAnimFinish: (Animator) -> Unit,
+ ): Animator {
return ValueAnimator.ofFloat(1f, 0f).apply {
duration = 100L
interpolator = Interpolators.LINEAR
addUpdateListener { animation ->
transaction.setAlpha(change.leash, animation.animatedValue as Float).apply()
}
+ addListener(
+ onEnd = { animation ->
+ onAnimFinish(animation)
+ }
+ )
}
}
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index 4c24d95..360210b 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -65,7 +65,7 @@
private void onLauncherDraw() {
View view = mLauncher.getDragLayer();
ViewRootImpl viewRootImpl = view.getViewRootImpl();
- setSurface(viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null);
+ setBaseSurface(viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null);
view.post(() -> view.getViewTreeObserver().removeOnDrawListener(mOnDrawListener));
}
@@ -127,7 +127,7 @@
mLauncher.getDragLayer().getViewTreeObserver().addOnDrawListener(mOnDrawListener);
} else {
mLauncher.getDragLayer().getViewTreeObserver().removeOnDrawListener(mOnDrawListener);
- setSurface(null);
+ setBaseSurface(null);
}
}
@@ -189,7 +189,8 @@
writer.println(prefix + "DepthController");
writer.println(prefix + "\tmMaxBlurRadius=" + mMaxBlurRadius);
writer.println(prefix + "\tmCrossWindowBlursEnabled=" + mCrossWindowBlursEnabled);
- writer.println(prefix + "\tmSurface=" + mSurface);
+ writer.println(prefix + "\tmBaseSurface=" + mBaseSurface);
+ writer.println(prefix + "\tmBaseSurfaceOverride=" + mBaseSurfaceOverride);
writer.println(prefix + "\tmStateDepth=" + stateDepth.getValue());
writer.println(prefix + "\tmWidgetDepth=" + widgetDepth.getValue());
writer.println(prefix + "\tmCurrentBlur=" + mCurrentBlur);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index de91c54..e41b2d2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -139,6 +139,7 @@
import com.android.launcher3.taskbar.bubbles.stashing.TransientBubbleStashController;
import com.android.launcher3.taskbar.customization.TaskbarFeatureEvaluator;
import com.android.launcher3.taskbar.customization.TaskbarSpecsEvaluator;
+import com.android.launcher3.taskbar.growth.NudgeController;
import com.android.launcher3.taskbar.navbutton.NearestTouchFrame;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayController;
import com.android.launcher3.testing.TestLogging;
@@ -381,7 +382,8 @@
new TaskbarPinningController(this),
bubbleControllersOptional,
new TaskbarDesktopModeController(this,
- DesktopVisibilityController.INSTANCE.get(this)));
+ DesktopVisibilityController.INSTANCE.get(this)),
+ new NudgeController(this));
mLauncherPrefs = LauncherPrefs.get(this);
onViewCreated();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index a2b6423..9e15a60 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -25,6 +25,7 @@
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.taskbar.allapps.TaskbarAllAppsController;
import com.android.launcher3.taskbar.bubbles.BubbleControllers;
+import com.android.launcher3.taskbar.growth.NudgeController;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayController;
import com.android.systemui.shared.rotation.RotationButtonController;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
@@ -67,6 +68,7 @@
public final TaskbarPinningController taskbarPinningController;
public final Optional<BubbleControllers> bubbleControllers;
public final TaskbarDesktopModeController taskbarDesktopModeController;
+ public final NudgeController nudgeController;
@Nullable private LoggableTaskbarController[] mControllersToLog = null;
@Nullable private BackgroundRendererController[] mBackgroundRendererControllers = null;
@@ -115,7 +117,8 @@
KeyboardQuickSwitchController keyboardQuickSwitchController,
TaskbarPinningController taskbarPinningController,
Optional<BubbleControllers> bubbleControllers,
- TaskbarDesktopModeController taskbarDesktopModeController) {
+ TaskbarDesktopModeController taskbarDesktopModeController,
+ NudgeController nudgeController) {
this.taskbarActivityContext = taskbarActivityContext;
this.taskbarDragController = taskbarDragController;
this.navButtonController = navButtonController;
@@ -143,6 +146,7 @@
this.taskbarPinningController = taskbarPinningController;
this.bubbleControllers = bubbleControllers;
this.taskbarDesktopModeController = taskbarDesktopModeController;
+ this.nudgeController = nudgeController;
}
/**
@@ -179,6 +183,7 @@
keyboardQuickSwitchController.init(this);
taskbarPinningController.init(this, mSharedState);
taskbarDesktopModeController.init(this, mSharedState);
+ nudgeController.init(this);
mControllersToLog = new LoggableTaskbarController[] {
taskbarDragController, navButtonController, navbarButtonsViewController,
@@ -189,6 +194,7 @@
voiceInteractionWindowController, taskbarRecentAppsController,
taskbarTranslationController, taskbarEduTooltipController,
keyboardQuickSwitchController, taskbarPinningController,
+ nudgeController
};
mBackgroundRendererControllers = new BackgroundRendererController[] {
taskbarDragLayerController, taskbarScrimViewController,
@@ -344,7 +350,7 @@
return taskbarActivityContext;
}
- protected interface LoggableTaskbarController {
+ public interface LoggableTaskbarController {
void dumpLogs(String prefix, PrintWriter pw);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 3938c80..b5e271d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -17,6 +17,8 @@
import static android.content.Context.RECEIVER_EXPORTED;
import static android.content.Context.RECEIVER_NOT_EXPORTED;
+import static android.content.pm.PackageManager.FEATURE_PC;
+import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
@@ -87,6 +89,7 @@
import com.android.quickstep.util.ContextualSearchInvoker;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.views.RecentsViewContainer;
+import com.android.server.am.Flags;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.statusbar.phone.BarTransitions;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -167,6 +170,10 @@
private final SparseArray<DeviceProfile> mExternalDeviceProfiles = new SparseArray<>();
private StatefulActivity mActivity;
private RecentsViewContainer mRecentsViewContainer;
+ /** Whether this device is a desktop android device **/
+ private boolean mIsAndroidPC;
+ /** Whether this device supports freeform windows management. Can change dynamically **/
+ private boolean mSupportsFreeformWindowsManagement;
/**
* Cache a copy here so we can initialize state whenever taskbar is recreated, since
@@ -199,6 +206,7 @@
if ((flags & CHANGE_DESKTOP_MODE) != 0) {
debugTaskbarManager("onDisplayInfoChanged: Desktop mode changed",
context.getDisplayId());
+ handleDisplayUpdatesForPerceptibleTasks();
}
if ((flags & CHANGE_TASKBAR_PINNING) != 0) {
debugTaskbarManager("onDisplayInfoChanged: Taskbar pinning changed", displayId);
@@ -234,7 +242,7 @@
recreateTaskbars();
};
- private final PerceptibleTaskListener mTaskStackListener;
+ private PerceptibleTaskListener mTaskStackListener;
private class PerceptibleTaskListener implements TaskStackChangeListener {
private ArraySet<Integer> mPerceptibleTasks = new ArraySet<Integer>();
@@ -288,6 +296,14 @@
public void onTaskRemoved(int taskId) {
mPerceptibleTasks.remove(taskId);
}
+
+ public void unregisterListener() {
+ for (Integer taskId : mPerceptibleTasks) {
+ ActivityManagerWrapper.getInstance().setTaskIsPerceptible(taskId, false);
+ }
+ TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
+ mTaskStackListener);
+ }
}
private final DesktopVisibilityController.TaskbarDesktopModeListener
@@ -347,7 +363,6 @@
}
};
-
private boolean mUserUnlocked = false;
private final SimpleBroadcastReceiver mTaskbarBroadcastReceiver;
@@ -467,7 +482,10 @@
mTaskbarBroadcastReceiver.register(RECEIVER_NOT_EXPORTED, ACTION_SHOW_TASKBAR);
});
- if (ActivityManagerWrapper.usePerceptibleTasks(getPrimaryWindowContext())) {
+ mIsAndroidPC = getPrimaryWindowContext().getPackageManager().hasSystemFeature(FEATURE_PC);
+ mSupportsFreeformWindowsManagement = getFreeformWindowsManagementInfo();
+
+ if (eligibleForPerceptibleTasks()) {
mTaskStackListener = new PerceptibleTaskListener();
TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
} else {
@@ -477,6 +495,30 @@
debugPrimaryTaskbar("TaskbarManager created");
}
+ private void handleDisplayUpdatesForPerceptibleTasks() {
+ // 1. When desktop mode changes, detect eligibility for perceptible tasks.
+ // 2. When no longer eligible for perceptible tasks, turn off and clean up.
+ mSupportsFreeformWindowsManagement = getFreeformWindowsManagementInfo();
+ if (eligibleForPerceptibleTasks()) {
+ if (mTaskStackListener == null) {
+ mTaskStackListener = new PerceptibleTaskListener();
+ TaskStackChangeListeners.getInstance()
+ .registerTaskStackListener(mTaskStackListener);
+ }
+ } else {
+ // not eligible for perceptible tasks, so we should unregister the listener
+ if (mTaskStackListener != null) {
+ mTaskStackListener.unregisterListener();
+ mTaskStackListener = null;
+ }
+ }
+ }
+
+ private boolean getFreeformWindowsManagementInfo() {
+ return getPrimaryWindowContext().getPackageManager().hasSystemFeature(
+ FEATURE_FREEFORM_WINDOW_MANAGEMENT);
+ }
+
private void destroyAllTaskbars() {
debugPrimaryTaskbar("destroyAllTaskbars");
for (int i = 0; i < mTaskbars.size(); i++) {
@@ -997,12 +1039,12 @@
displayId);
removeAndUnregisterComponentCallbacks(displayId);
- debugTaskbarManager("onDisplayRemoved: destroying Taskbar!", displayId);
- destroyTaskbarForDisplay(displayId);
-
debugTaskbarManager("onDisplayRemoved: removing DeviceProfile from map!", displayId);
removeDeviceProfileFromMap(displayId);
+ debugTaskbarManager("onDisplayRemoved: destroying Taskbar!", displayId);
+ destroyTaskbarForDisplay(displayId);
+
debugTaskbarManager("onDisplayRemoved: removing WindowContext from map!", displayId);
removeWindowContextFromMap(displayId);
@@ -1061,17 +1103,26 @@
debugPrimaryTaskbar("destroy: unregistering component callbacks");
removeAndUnregisterComponentCallbacks(getDefaultDisplayId());
mShutdownReceiver.unregisterReceiverSafely();
- if (ActivityManagerWrapper.usePerceptibleTasks(getPrimaryWindowContext())) {
- for (Integer taskId : mTaskStackListener.mPerceptibleTasks) {
- ActivityManagerWrapper.getInstance().setTaskIsPerceptible(taskId, false);
- }
+ if (mTaskStackListener != null) {
+ mTaskStackListener.unregisterListener();
}
- TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
+
debugPrimaryTaskbar("destroy: destroying all taskbars!");
destroyAllTaskbars();
debugPrimaryTaskbar("destroy: finished!");
}
+ private boolean eligibleForPerceptibleTasks() {
+ // Perceptible tasks feature (oom boosting) is eligible for android PC devices, and
+ // other android devices that supports free form windows
+ //
+ // - isAndroidPC is set per device (in this case, desktop devices)
+ // - supportsFreeformWindowsManagement is dynamic, and is to be used for the use-case where
+ // user plugs in their device to external displays
+ return Flags.perceptibleTasks()
+ && (mIsAndroidPC || mSupportsFreeformWindowsManagement);
+ }
+
public @Nullable TaskbarActivityContext getCurrentActivityContext() {
return getTaskbarForDisplay(getDefaultDisplayId());
}
@@ -1177,7 +1228,7 @@
* @return The {@link TaskbarActivityContext} for the specified display, or
* {@code null} if no taskbar is associated with that display.
*/
- private TaskbarActivityContext getTaskbarForDisplay(int displayId) {
+ public TaskbarActivityContext getTaskbarForDisplay(int displayId) {
return mTaskbars.get(displayId);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 4c94432..b2989cd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -368,23 +368,9 @@
if (!(view.getTag() instanceof CollectionInfo)) {
mActivityContext.getViewCache().recycleView(view.getSourceLayoutResId(), view);
}
- if (view instanceof FolderIcon fi) {
- // We should clear FolderInfo's Folder and FolderIcon to avoid memory leak.
- fi.removeListeners();
- }
view.setTag(null);
}
- /** Loop through all {@link FolderIcon} as child views and clear listeners to avoid leak. */
- public void removeFolderIconListeners() {
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- if (getChildAt(i) instanceof FolderIcon fi) {
- fi.removeListeners();
- }
- }
- }
-
/** Inflates/binds the hotseat items and recent tasks to the view. */
protected void updateItems(ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks) {
if (mActivityContext.isDestroyed()) return;
@@ -445,7 +431,6 @@
private void updateItemsWithLayoutTransition(
ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks) {
-
if (mNumStaticViews == 0) {
mNumStaticViews = addStaticViews();
}
@@ -579,6 +564,9 @@
LayoutParams lp = new LayoutParams(mIconTouchSize, mIconTouchSize);
hotseatView.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
addView(hotseatView, mNextViewIndex, lp);
+ } else if (hotseatView instanceof FolderIcon fi) {
+ fi.onItemsChanged(false);
+ fi.getFolder().reapplyItemInfo();
}
// Apply the Hotseat ItemInfos, or hide the view if there is none for a given index.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 605171a..6786aed 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -393,7 +393,6 @@
if (enableTaskbarPinning()) {
mTaskbarView.removeOnLayoutChangeListener(mTaskbarViewLayoutChangeListener);
}
- mTaskbarView.removeFolderIconListeners();
LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks);
mActivity.removeOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutView.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutView.kt
index 75bf937..ac87b5e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutView.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutView.kt
@@ -36,6 +36,8 @@
import androidx.core.animation.ArgbEvaluator
import com.android.launcher3.R
import com.android.launcher3.popup.RoundedArrowDrawable
+import com.android.wm.shell.shared.TypefaceUtils
+import com.android.wm.shell.shared.TypefaceUtils.Companion.setTypeface
import kotlin.math.min
/** The flyout view used to notify the user of a new bubble notification. */
@@ -163,6 +165,9 @@
LayoutInflater.from(context).inflate(R.layout.bubblebar_flyout, this, true)
id = R.id.bubble_bar_flyout_view
+ setTypeface(title, TypefaceUtils.FontFamily.GSF_LABEL_LARGE)
+ setTypeface(message, TypefaceUtils.FontFamily.GSF_BODY_MEDIUM)
+
val ta = context.obtainStyledAttributes(intArrayOf(android.R.attr.dialogCornerRadius))
cornerRadius = ta.getDimensionPixelSize(0, 0).toFloat()
ta.recycle()
diff --git a/quickstep/src/com/android/launcher3/taskbar/growth/NudgeController.kt b/quickstep/src/com/android/launcher3/taskbar/growth/NudgeController.kt
new file mode 100644
index 0000000..04e41bc
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/growth/NudgeController.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar.growth
+
+import android.content.Context
+import com.android.launcher3.Utilities
+import com.android.launcher3.taskbar.TaskbarActivityContext
+import com.android.launcher3.taskbar.TaskbarControllers
+import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.views.ActivityContext
+import java.io.PrintWriter
+
+/** Controls nudge lifecycles. */
+class NudgeController(context: Context) : LoggableTaskbarController {
+
+ protected val activityContext: TaskbarActivityContext = ActivityContext.lookupContext(context)
+
+ private val isNudgeEnabled: Boolean
+ get() {
+ return !Utilities.isRunningInTestHarness() &&
+ !activityContext.isPhoneMode &&
+ !activityContext.isTinyTaskbar
+ }
+
+ private lateinit var controllers: TaskbarControllers
+
+ fun init(controllers: TaskbarControllers) {
+ this.controllers = controllers
+ }
+
+ fun maybeShow(payload: NudgePayload) {
+ if (!isNudgeEnabled || !DisplayController.isTransientTaskbar(activityContext)) {
+ return
+ }
+ // TODO: b/398033012 - create and show nudge view based on the payload.
+ }
+
+ /** Closes the current [nudgeView]. */
+ fun hide() {
+ // TODO: b/398033012 - hide the nudge view.
+ }
+
+ override fun dumpLogs(prefix: String?, pw: PrintWriter?) {
+ pw?.println(prefix + "NudgeController:")
+ pw?.println("$prefix\tisNudgeEnabled=$isNudgeEnabled")
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index cd0a4f3..806b8ab 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -86,6 +86,7 @@
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.Display;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
@@ -226,6 +227,7 @@
public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
SystemShortcut.BubbleActivityStarter {
+ private static final String TAG = "QuickstepLauncher";
private static final boolean TRACE_LAYOUTS =
SystemProperties.getBoolean("persist.debug.trace_layouts", false);
private static final String TRACE_RELAYOUT_CLASS =
@@ -561,20 +563,35 @@
@Override
public void onDestroy() {
+ // wrap non-trivial clean up blocks in try-catch to avoid stopping clean up of rest of
+ // objects
+
if (mAppTransitionManager != null) {
- mAppTransitionManager.onActivityDestroyed();
+ try {
+ mAppTransitionManager.onActivityDestroyed();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to destroy mAppTransitionManager", e);
+ }
}
mAppTransitionManager = null;
mIsPredictiveBackToHomeInProgress = false;
if (mUnfoldTransitionProgressProvider != null) {
- SystemUiProxy.INSTANCE.get(this).setUnfoldAnimationListener(null);
- mUnfoldTransitionProgressProvider.destroy();
+ try {
+ SystemUiProxy.INSTANCE.get(this).setUnfoldAnimationListener(null);
+ mUnfoldTransitionProgressProvider.destroy();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to destroy mUnfoldTransitionProgressProvider", e);
+ }
}
OverviewComponentObserver.INSTANCE.get(this)
.removeOverviewChangeListener(mOverviewChangeListener);
- mTISBindHelper.onDestroy();
+ try {
+ mTISBindHelper.onDestroy();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to destroy mTISBindHelper", e);
+ }
if (mLauncherUnfoldAnimationController != null) {
mLauncherUnfoldAnimationController.onDestroy();
@@ -584,15 +601,22 @@
mSplitSelectStateController.onDestroy();
}
- RecentsView recentsView = getOverviewPanel();
- if (recentsView != null) {
- recentsView.destroy();
+ try {
+ RecentsView recentsView = getOverviewPanel();
+ if (recentsView != null) {
+ recentsView.destroy();
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to destroy RecentsView", e);
}
- super.onDestroy();
- mHotseatPredictionController.destroy();
- if (mViewCapture != null) mViewCapture.close();
- removeBackAnimationCallback(mSplitSelectStateController.getSplitBackHandler());
+ try {
+ super.onDestroy();
+ } finally { // trivial close operations in finally.
+ mHotseatPredictionController.destroy();
+ if (mViewCapture != null) mViewCapture.close();
+ removeBackAnimationCallback(mSplitSelectStateController.getSplitBackHandler());
+ }
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 963504f..8529dcf 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -17,10 +17,12 @@
import static com.android.app.animation.Interpolators.DECELERATE_2;
import static com.android.launcher3.Flags.enableDesktopExplodedView;
+import static com.android.launcher3.Flags.enableOverviewBackgroundWallpaperBlur;
import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
import android.content.Context;
+import android.graphics.Color;
import android.graphics.Rect;
import android.os.SystemProperties;
@@ -158,7 +160,8 @@
@Override
public int getWorkspaceScrimColor(Launcher launcher) {
- return Themes.getAttrColor(launcher, R.attr.overviewScrimColor);
+ return enableOverviewBackgroundWallpaperBlur() ? Color.TRANSPARENT
+ : Themes.getAttrColor(launcher, R.attr.overviewScrimColor);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java
index 081ed9d..e5cbc66 100644
--- a/quickstep/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/src/com/android/quickstep/InputConsumer.java
@@ -63,13 +63,25 @@
"TYPE_BUBBLE_BAR", // 16
};
- InputConsumer NO_OP = () -> TYPE_NO_OP;
+ InputConsumer DEFAULT_NO_OP = createNoOpInputConsumer(Display.DEFAULT_DISPLAY);
+
+ static InputConsumer createNoOpInputConsumer(int displayId) {
+ return new InputConsumer() {
+ @Override
+ public int getType() {
+ return TYPE_NO_OP;
+ }
+
+ @Override
+ public int getDisplayId() {
+ return displayId;
+ }
+ };
+ }
int getType();
- default int getDisplayId() {
- return Display.DEFAULT_DISPLAY;
- }
+ int getDisplayId();
/**
* Returns true if the user has crossed the threshold for it to be an explicit action.
diff --git a/quickstep/src/com/android/quickstep/InputConsumerUtils.kt b/quickstep/src/com/android/quickstep/InputConsumerUtils.kt
index cd3ac12..c8ca534 100644
--- a/quickstep/src/com/android/quickstep/InputConsumerUtils.kt
+++ b/quickstep/src/com/android/quickstep/InputConsumerUtils.kt
@@ -57,7 +57,7 @@
@JvmStatic
fun <S : BaseState<S>, T> newConsumer(
context: Context,
- resetGestureInputConsumer: ResetGestureInputConsumer?,
+ userUnlocked: Boolean,
overviewComponentObserver: OverviewComponentObserver,
deviceState: RecentsAnimationDeviceState,
previousGestureState: GestureState,
@@ -122,7 +122,8 @@
// camera).
createDeviceLockedInputConsumer(
context,
- resetGestureInputConsumer,
+ userUnlocked,
+ taskbarManager,
deviceState,
gestureState,
taskAnimationManager,
@@ -131,7 +132,10 @@
)
} else {
getDefaultInputConsumer(
- resetGestureInputConsumer,
+ gestureState.displayId,
+ userUnlocked,
+ taskAnimationManager,
+ taskbarManager,
reasonString.append("%scannot start system gesture", SUBSTRING_PREFIX),
)
}
@@ -153,7 +157,8 @@
base =
newBaseConsumer<S, T>(
context,
- resetGestureInputConsumer,
+ userUnlocked,
+ taskbarManager,
overviewComponentObserver,
deviceState,
previousGestureState,
@@ -172,7 +177,14 @@
"cannot start system gesture and recents " +
"animation was not running, trying to use default input consumer"
)
- base = getDefaultInputConsumer(resetGestureInputConsumer, reasonString)
+ base =
+ getDefaultInputConsumer(
+ gestureState.displayId,
+ userUnlocked,
+ taskAnimationManager,
+ taskbarManager,
+ reasonString,
+ )
}
if (deviceState.isGesturalNavMode || gestureState.isTrackpadGesture) {
handleOrientationSetup(base)
@@ -237,7 +249,14 @@
SUBSTRING_PREFIX,
)
// Bubbles can handle home gesture itself.
- base = getDefaultInputConsumer(resetGestureInputConsumer, reasonString)
+ base =
+ getDefaultInputConsumer(
+ gestureState.displayId,
+ userUnlocked,
+ taskAnimationManager,
+ taskbarManager,
+ reasonString,
+ )
}
}
@@ -279,7 +298,14 @@
SUBSTRING_PREFIX,
)
// Bubbles can handle home gesture itself.
- base = getDefaultInputConsumer(resetGestureInputConsumer, reasonString)
+ base =
+ getDefaultInputConsumer(
+ gestureState.displayId,
+ userUnlocked,
+ taskAnimationManager,
+ taskbarManager,
+ reasonString,
+ )
}
}
@@ -374,7 +400,14 @@
"%sscreen pinning is active, trying to use default input consumer",
SUBSTRING_PREFIX,
)
- base = getDefaultInputConsumer(resetGestureInputConsumer, reasonString)
+ base =
+ getDefaultInputConsumer(
+ gestureState.displayId,
+ userUnlocked,
+ taskAnimationManager,
+ taskbarManager,
+ reasonString,
+ )
}
if (deviceState.canTriggerOneHandedAction(event)) {
@@ -411,7 +444,7 @@
context,
deviceState,
inputMonitorCompat,
- InputConsumer.NO_OP,
+ InputConsumer.createNoOpInputConsumer(gestureState.displayId),
gestureState,
motionEvent,
CompoundString.NO_OP,
@@ -450,7 +483,8 @@
@JvmStatic
fun <S : BaseState<S>, T> newBaseConsumer(
context: Context,
- resetGestureInputConsumer: ResetGestureInputConsumer?,
+ userUnlocked: Boolean,
+ taskbarManager: TaskbarManager,
overviewComponentObserver: OverviewComponentObserver,
deviceState: RecentsAnimationDeviceState,
previousGestureState: GestureState,
@@ -467,7 +501,8 @@
// This handles apps showing over the lockscreen (e.g. camera)
return createDeviceLockedInputConsumer(
context,
- resetGestureInputConsumer,
+ userUnlocked,
+ taskbarManager,
deviceState,
gestureState,
taskAnimationManager,
@@ -521,7 +556,9 @@
return if (gestureState.getContainerInterface<S, T>().isInLiveTileMode()) {
createOverviewInputConsumer<S, T>(
- resetGestureInputConsumer,
+ userUnlocked,
+ taskAnimationManager,
+ taskbarManager,
deviceState,
inputMonitorCompat,
previousGestureState,
@@ -534,7 +571,10 @@
)
} else if (runningTask == null) {
getDefaultInputConsumer(
- resetGestureInputConsumer,
+ gestureState.displayId,
+ userUnlocked,
+ taskAnimationManager,
+ taskbarManager,
reasonString.append("%srunning task == null", SUBSTRING_PREFIX),
)
} else if (
@@ -543,7 +583,9 @@
forceOverviewInputConsumer
) {
createOverviewInputConsumer<S, T>(
- resetGestureInputConsumer,
+ userUnlocked,
+ taskAnimationManager,
+ taskbarManager,
deviceState,
inputMonitorCompat,
previousGestureState,
@@ -565,7 +607,10 @@
)
} else if (deviceState.isGestureBlockedTask(runningTask) || launcherChildActivityResumed) {
getDefaultInputConsumer(
- resetGestureInputConsumer,
+ gestureState.displayId,
+ userUnlocked,
+ taskAnimationManager,
+ taskbarManager,
reasonString.append(
if (launcherChildActivityResumed)
"%sis launcher child-task, trying to use default input consumer"
@@ -592,7 +637,8 @@
private fun createDeviceLockedInputConsumer(
context: Context,
- resetGestureInputConsumer: ResetGestureInputConsumer?,
+ userUnlocked: Boolean,
+ taskbarManager: TaskbarManager,
deviceState: RecentsAnimationDeviceState,
gestureState: GestureState,
taskAnimationManager: TaskAnimationManager,
@@ -617,7 +663,10 @@
)
} else {
getDefaultInputConsumer(
- resetGestureInputConsumer,
+ gestureState.displayId,
+ userUnlocked,
+ taskAnimationManager,
+ taskbarManager,
reasonString.append(
if (deviceState.isFullyGesturalNavMode || gestureState.isTrackpadGesture)
"%srunning task == null, trying to use default input consumer"
@@ -631,7 +680,9 @@
}
private fun <S : BaseState<S>, T> createOverviewInputConsumer(
- resetGestureInputConsumer: ResetGestureInputConsumer?,
+ userUnlocked: Boolean,
+ taskAnimationManager: TaskAnimationManager,
+ taskbarManager: TaskbarManager,
deviceState: RecentsAnimationDeviceState,
inputMonitorCompat: InputMonitorCompat,
previousGestureState: GestureState,
@@ -642,7 +693,10 @@
val container: T =
gestureState.getContainerInterface<S, T>().getCreatedContainer()
?: return getDefaultInputConsumer(
- resetGestureInputConsumer,
+ gestureState.displayId,
+ userUnlocked,
+ taskAnimationManager,
+ taskbarManager,
reasonString.append(
"%sactivity == null, trying to use default input consumer",
SUBSTRING_PREFIX,
@@ -694,24 +748,34 @@
}
/** Returns the [ResetGestureInputConsumer] if user is unlocked, else NO_OP. */
- private fun getDefaultInputConsumer(
- resetGestureInputConsumer: ResetGestureInputConsumer?,
+ @JvmStatic
+ fun getDefaultInputConsumer(
+ displayId: Int,
+ userUnlocked: Boolean,
+ taskAnimationManager: TaskAnimationManager?,
+ taskbarManager: TaskbarManager?,
reasonString: CompoundString,
): InputConsumer {
- return if (resetGestureInputConsumer != null) {
+ return if (userUnlocked && taskAnimationManager != null && taskbarManager != null) {
reasonString.append(
- "%smResetGestureInputConsumer initialized, using ResetGestureInputConsumer",
+ "%sResetGestureInputConsumer available, using ResetGestureInputConsumer",
SUBSTRING_PREFIX,
)
- resetGestureInputConsumer
+ ResetGestureInputConsumer(displayId, taskAnimationManager) {
+ taskbarManager.getTaskbarForDisplay(displayId)
+ }
} else {
reasonString.append(
- "%smResetGestureInputConsumer not initialized, using no-op input consumer",
+ "%s${
+ if (userUnlocked) "user is locked"
+ else if (taskAnimationManager == null) "taskAnimationManager is null"
+ else "taskbarManager is null"
+ }, using no-op input consumer",
SUBSTRING_PREFIX,
)
- // mResetGestureInputConsumer isn't initialized until onUserUnlocked(), so reset to
+ // ResetGestureInputConsumer isn't available until onUserUnlocked(), so reset to
// NO_OP until then (we never want these to be null).
- InputConsumer.NO_OP
+ InputConsumer.createNoOpInputConsumer(displayId)
}
}
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index cc5b2da..dab78c5 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -511,13 +511,14 @@
// When the multiple desktop feature is disabled, there can only be up to a single desk
// on each display, The desk ID doesn't matter and should not be used.
return MapsKt.map(perDisplayTasks,
- it -> new DesktopTask(DesktopVisibilityController.INACTIVE_DESK_ID,
+ it -> new DesktopTask(DesktopVisibilityController.INACTIVE_DESK_ID, it.getKey(),
it.getValue()));
} else {
final int deskId = recentTaskInfo.getDeskId();
+ final int displayId = recentTaskInfo.getDeskDisplayId();
List<Task> tasks = CollectionsKt.map(recentTaskInfo.getTaskInfoList(),
it -> createTask(it, minimizedTaskIds));
- return List.of(new DesktopTask(deskId, tasks));
+ return List.of(new DesktopTask(deskId, displayId, tasks));
}
}
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 855ff98..d161d45 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -433,9 +433,7 @@
out.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationStart(Animator animation) {
- for (RemoteTargetHandle remoteTargetHandle : remoteTargetHandles) {
- remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(false);
- }
+ recentsView.setDrawBelowRecents(false, remoteTargetHandles);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 741ae7d..2bc5626 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -35,6 +35,7 @@
import static com.android.quickstep.GestureState.DEFAULT_STATE;
import static com.android.quickstep.GestureState.TrackpadGestureType.getTrackpadGestureType;
import static com.android.quickstep.InputConsumer.TYPE_CURSOR_HOVER;
+import static com.android.quickstep.InputConsumer.createNoOpInputConsumer;
import static com.android.quickstep.InputConsumerUtils.newConsumer;
import static com.android.quickstep.InputConsumerUtils.tryCreateAssistantInputConsumer;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
@@ -98,7 +99,6 @@
import com.android.quickstep.fallback.window.RecentsWindowSwipeHandler;
import com.android.quickstep.inputconsumers.BubbleBarInputConsumer;
import com.android.quickstep.inputconsumers.OneHandedModeInputConsumer;
-import com.android.quickstep.inputconsumers.ResetGestureInputConsumer;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.ActiveGestureLog.CompoundString;
import com.android.quickstep.util.ActiveGestureProtoLogProxy;
@@ -559,10 +559,10 @@
private RecentsAnimationDeviceState mDeviceState;
private TaskAnimationManager mTaskAnimationManager;
- private @NonNull InputConsumer mUncheckedConsumer = InputConsumer.NO_OP;
- private @NonNull InputConsumer mConsumer = InputConsumer.NO_OP;
+ private @NonNull InputConsumer mUncheckedConsumer = InputConsumer.DEFAULT_NO_OP;
+ private @NonNull InputConsumer mConsumer = InputConsumer.DEFAULT_NO_OP;
private Choreographer mMainChoreographer;
- private @Nullable ResetGestureInputConsumer mResetGestureInputConsumer;
+ private boolean mUserUnlocked = false;
private GestureState mGestureState = DEFAULT_STATE;
private InputMonitorDisplayModel mInputMonitorDisplayModel;
@@ -692,8 +692,7 @@
mOverviewCommandHelper = new OverviewCommandHelper(this,
mOverviewComponentObserver, mTaskAnimationManager, mRecentsDisplayModel,
SystemUiProxy.INSTANCE.get(this).getFocusState(), mTaskbarManager);
- mResetGestureInputConsumer = new ResetGestureInputConsumer(
- mTaskAnimationManager, mTaskbarManager::getCurrentActivityContext);
+ mUserUnlocked = true;
mInputConsumer.registerInputConsumer();
for (int displayId : mDeviceState.getDisplaysWithSysUIState()) {
onSystemUiFlagsChanged(mDeviceState.getSystemUiStateFlags(displayId), displayId);
@@ -917,7 +916,7 @@
} else {
reasonString.append(" but event cannot trigger Assistant, "
+ "consuming gesture as no-op");
- mUncheckedConsumer = InputConsumer.NO_OP;
+ mUncheckedConsumer = createNoOpInputConsumer(displayId);
}
} else if ((!isOneHandedModeActive && isInSwipeUpTouchRegion)
|| isHoverActionWithoutConsumer || isOnBubbles) {
@@ -934,7 +933,7 @@
mGestureState = newGestureState;
mConsumer = newConsumer(
this,
- mResetGestureInputConsumer,
+ mUserUnlocked,
mOverviewComponentObserver,
mDeviceState,
prevGestureState,
@@ -968,19 +967,22 @@
+ "consuming gesture for one-handed action");
// Consume gesture event for triggering one handed feature.
mUncheckedConsumer = new OneHandedModeInputConsumer(
- this, displayId, mDeviceState, InputConsumer.NO_OP, inputMonitorCompat);
+ this,
+ displayId,
+ mDeviceState,
+ InputConsumer.createNoOpInputConsumer(displayId), inputMonitorCompat);
} else {
- mUncheckedConsumer = InputConsumer.NO_OP;
+ mUncheckedConsumer = InputConsumer.createNoOpInputConsumer(displayId);
}
} else {
// Other events
- if (mUncheckedConsumer != InputConsumer.NO_OP) {
+ if (mUncheckedConsumer.getType() != InputConsumer.TYPE_NO_OP) {
// Only transform the event if we are handling it in a proper consumer
mRotationTouchHelper.setOrientationTransformIfNeeded(event);
}
}
- if (mUncheckedConsumer != InputConsumer.NO_OP) {
+ if (mUncheckedConsumer.getType() != InputConsumer.TYPE_NO_OP) {
switch (action) {
case ACTION_DOWN:
ActiveGestureProtoLogProxy.logOnInputEventActionDown(displayId, reasonString);
@@ -1100,7 +1102,12 @@
}
private void reset(int displayId) {
- mConsumer = mUncheckedConsumer = getDefaultInputConsumer();
+ mConsumer = mUncheckedConsumer = InputConsumerUtils.getDefaultInputConsumer(
+ displayId,
+ mUserUnlocked,
+ mTaskAnimationManager,
+ mTaskbarManager,
+ CompoundString.NO_OP);
mGestureState = DEFAULT_STATE;
// By default, use batching of the input events, but check receiver before using in the rare
// case that the monitor was disposed before the swipe settled
@@ -1110,29 +1117,6 @@
}
}
- private @NonNull InputConsumer getDefaultInputConsumer() {
- return getDefaultInputConsumer(CompoundString.NO_OP);
- }
-
- /**
- * Returns the {@link ResetGestureInputConsumer} if user is unlocked, else NO_OP.
- */
- private @NonNull InputConsumer getDefaultInputConsumer(@NonNull CompoundString reasonString) {
- if (mResetGestureInputConsumer != null) {
- reasonString.append(
- "%smResetGestureInputConsumer initialized, using ResetGestureInputConsumer",
- SUBSTRING_PREFIX);
- return mResetGestureInputConsumer;
- } else {
- reasonString.append(
- "%smResetGestureInputConsumer not initialized, using no-op input consumer",
- SUBSTRING_PREFIX);
- // mResetGestureInputConsumer isn't initialized until onUserUnlocked(), so reset to
- // NO_OP until then (we never want these to be null).
- return InputConsumer.NO_OP;
- }
- }
-
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (!LockedUserState.get(this).isUserUnlocked()) {
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 8ec97ed..29b6626 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -292,8 +292,7 @@
// disabling this so app icons aren't drawn on top of recent tasks.
if (isOverlayEnabled && !RecentsWindowFlags.Companion.getEnableOverviewInWindow()) {
- runActionOnRemoteHandles(remoteTargetHandle ->
- remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true));
+ mBlurUtils.setDrawLiveTileBelowRecents(true);
}
}
@@ -303,6 +302,7 @@
if (enabled) {
RecentsState state = mContainer.getStateManager().getState();
setDisallowScrollToClearAll(!state.hasClearAllButton());
+ setDisallowScrollToAddDesk(!state.hasAddDeskButton());
}
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
deleted file mode 100644
index 349f4d2..0000000
--- a/quickstep/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2019 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.inputconsumers;
-
-import android.view.MotionEvent;
-
-import com.android.launcher3.taskbar.TaskbarActivityContext;
-import com.android.quickstep.InputConsumer;
-import com.android.quickstep.TaskAnimationManager;
-
-import java.util.function.Supplier;
-
-/**
- * A NO_OP input consumer which also resets any pending gesture
- */
-public class ResetGestureInputConsumer implements InputConsumer {
-
- private final TaskAnimationManager mTaskAnimationManager;
- private final Supplier<TaskbarActivityContext> mActivityContextSupplier;
-
- public ResetGestureInputConsumer(
- TaskAnimationManager taskAnimationManager,
- Supplier<TaskbarActivityContext> activityContextSupplier) {
- mTaskAnimationManager = taskAnimationManager;
- mActivityContextSupplier = activityContextSupplier;
- }
-
- @Override
- public int getType() {
- return TYPE_RESET_GESTURE;
- }
-
- @Override
- public void onMotionEvent(MotionEvent ev) {
- if (ev.getAction() == MotionEvent.ACTION_DOWN
- && mTaskAnimationManager.isRecentsAnimationRunning()) {
- TaskbarActivityContext tac = mActivityContextSupplier.get();
- mTaskAnimationManager.finishRunningRecentsAnimation(
- /* toHome= */ tac != null && !tac.isInApp());
- }
- }
-}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.kt b/quickstep/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.kt
new file mode 100644
index 0000000..96e7943
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 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.inputconsumers
+
+import android.view.MotionEvent
+import com.android.launcher3.taskbar.TaskbarActivityContext
+import com.android.quickstep.InputConsumer
+import com.android.quickstep.TaskAnimationManager
+import java.util.function.Supplier
+
+/** A NO_OP input consumer which also resets any pending gesture */
+class ResetGestureInputConsumer(
+ private val displayId: Int,
+ private val taskAnimationManager: TaskAnimationManager,
+ private val activityContextSupplier: Supplier<TaskbarActivityContext?>,
+) : InputConsumer {
+ override fun getType() = InputConsumer.TYPE_RESET_GESTURE
+
+ override fun getDisplayId() = displayId
+
+ override fun onMotionEvent(ev: MotionEvent) {
+ if (
+ ev.action == MotionEvent.ACTION_DOWN && taskAnimationManager.isRecentsAnimationRunning
+ ) {
+ val tac = activityContextSupplier.get()
+ taskAnimationManager.finishRunningRecentsAnimation(
+ /* toHome= */ tac != null && !tac.isInApp
+ )
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/BaseDepthController.java b/quickstep/src/com/android/quickstep/util/BaseDepthController.java
index f956a7c..89f1e33 100644
--- a/quickstep/src/com/android/quickstep/util/BaseDepthController.java
+++ b/quickstep/src/com/android/quickstep/util/BaseDepthController.java
@@ -15,6 +15,7 @@
*/
package com.android.quickstep.util;
+import static com.android.launcher3.Flags.enableOverviewBackgroundWallpaperBlur;
import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import android.app.WallpaperManager;
@@ -24,6 +25,8 @@
import android.view.AttachedSurfaceControl;
import android.view.SurfaceControl;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.Flags;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
@@ -75,11 +78,14 @@
/**
* Ratio from 0 to 1, where 0 is fully zoomed out, and 1 is zoomed in.
+ *
* @see android.service.wallpaper.WallpaperService.Engine#onZoomChanged(float)
*/
private float mDepth;
- protected SurfaceControl mSurface;
+ protected SurfaceControl mBaseSurface;
+
+ protected SurfaceControl mBaseSurfaceOverride;
// Hints that there is potentially content behind Launcher and that we shouldn't optimize by
// marking the launcher surface as opaque. Only used in certain Launcher states.
@@ -100,6 +106,8 @@
protected boolean mWaitingOnSurfaceValidity;
+ private SurfaceControl mBlurSurface = null;
+
public BaseDepthController(Launcher activity) {
mLauncher = activity;
if (Flags.allAppsBlur()) {
@@ -114,6 +122,13 @@
new MultiPropertyFactory<>(this, DEPTH, DEPTH_INDEX_COUNT, Float::max);
stateDepth = depthProperty.get(DEPTH_INDEX_STATE_TRANSITION);
widgetDepth = depthProperty.get(DEPTH_INDEX_WIDGET);
+ if (enableOverviewBackgroundWallpaperBlur()) {
+ mBlurSurface = new SurfaceControl.Builder()
+ .setName("Overview Blur")
+ .setHidden(false)
+ .build();
+ }
+
}
protected void setCrossWindowBlursEnabled(boolean isEnabled) {
@@ -151,11 +166,11 @@
if (!BlurUtils.supportsBlursOnWindows()) {
return;
}
- if (mSurface == null) {
+ if (mBaseSurface == null) {
Log.d(TAG, "mSurface is null and mCurrentBlur is: " + mCurrentBlur);
return;
}
- if (!mSurface.isValid()) {
+ if (!mBaseSurface.isValid()) {
Log.d(TAG, "mSurface is not valid");
mWaitingOnSurfaceValidity = true;
onInvalidSurface();
@@ -174,10 +189,21 @@
mCurrentBlur = !mCrossWindowBlursEnabled || hasOpaqueBg || mPauseBlurs
? 0 : (int) (blurAmount * mMaxBlurRadius);
- SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()
- .setBackgroundBlurRadius(mSurface, mCurrentBlur)
- .setOpaque(mSurface, isSurfaceOpaque);
-
+ SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+ if (enableOverviewBackgroundWallpaperBlur() && mBlurSurface != null) {
+ // Reparent to launcher for full screen blur.
+ transaction.setBackgroundBlurRadius(mBlurSurface, mCurrentBlur)
+ .reparent(mBlurSurface, mBaseSurface);
+ // Set mBlurSurface to be 1 layer behind mBaseSurface or mBaseSurfaceOverride.
+ if (mBaseSurfaceOverride != null && mBaseSurfaceOverride.isValid()) {
+ transaction.setRelativeLayer(mBlurSurface, mBaseSurfaceOverride, -1);
+ } else {
+ transaction.setRelativeLayer(mBlurSurface, mBaseSurface, -1);
+ }
+ } else {
+ transaction.setBackgroundBlurRadius(mBaseSurface, mCurrentBlur);
+ }
+ transaction.setOpaque(mBaseSurface, isSurfaceOpaque);
// Set early wake-up flags when we know we're executing an expensive operation, this way
// SurfaceFlinger will adjust its internal offsets to avoid jank.
boolean wantsEarlyWakeUp = depth > 0 && depth < 1;
@@ -209,13 +235,26 @@
}
/**
+ * Sets the lowest surface that should not be blurred.
+ * <p>
+ * Blur is applied to below {@link #mBaseSurfaceOverride}. When set to {@code null}, blur is
+ * applied
+ * to below {@link #mBaseSurface}.
+ * </p>
+ */
+ public void setBaseSurfaceOverride(@Nullable SurfaceControl baseSurfaceOverride) {
+ this.mBaseSurfaceOverride = baseSurfaceOverride;
+ applyDepthAndBlur();
+ }
+
+ /**
* Sets the specified app target surface to apply the blur to.
*/
- protected void setSurface(SurfaceControl surface) {
- if (mSurface != surface || mWaitingOnSurfaceValidity) {
- mSurface = surface;
+ protected void setBaseSurface(SurfaceControl baseSurface) {
+ if (mBaseSurface != baseSurface || mWaitingOnSurfaceValidity) {
+ mBaseSurface = baseSurface;
Log.d(TAG, "setSurface:\n\tmWaitingOnSurfaceValidity: " + mWaitingOnSurfaceValidity
- + "\n\tmSurface: " + mSurface);
+ + "\n\tmBaseSurface: " + mBaseSurface);
applyDepthAndBlur();
}
}
diff --git a/quickstep/src/com/android/quickstep/util/DesksUtils.kt b/quickstep/src/com/android/quickstep/util/DesksUtils.kt
index ecddc3f..a10f771 100644
--- a/quickstep/src/com/android/quickstep/util/DesksUtils.kt
+++ b/quickstep/src/com/android/quickstep/util/DesksUtils.kt
@@ -19,6 +19,7 @@
import android.app.TaskInfo
import android.content.ComponentName
import android.content.res.Resources
+import android.util.Log
import android.window.DesktopExperienceFlags
import com.android.systemui.shared.recents.model.Task
@@ -38,8 +39,10 @@
task.key.component?.let(::isDesktopWallpaperComponent) == true
@JvmStatic
- fun isDesktopWallpaperTask(taskInfo: TaskInfo) =
- taskInfo.baseIntent.component?.let(::isDesktopWallpaperComponent) == true
+ fun isDesktopWallpaperTask(taskInfo: TaskInfo): Boolean {
+ Log.d("b/403118101", "isDesktopWallpaperTask: $taskInfo")
+ return taskInfo.baseIntent.component?.let(::isDesktopWallpaperComponent) == true
+ }
@JvmStatic
fun isDesktopWallpaperComponent(component: ComponentName) =
diff --git a/quickstep/src/com/android/quickstep/util/DesktopTask.kt b/quickstep/src/com/android/quickstep/util/DesktopTask.kt
index fbe3bc6..7c27293 100644
--- a/quickstep/src/com/android/quickstep/util/DesktopTask.kt
+++ b/quickstep/src/com/android/quickstep/util/DesktopTask.kt
@@ -17,22 +17,27 @@
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
- * tasks list. Note that desktops can be empty with no tasks in them. The [deskId] makes sense only
- * when the multiple desks feature is enabled.
+ * tasks list. Note that desktops can be empty with no tasks in them. The [deskId], [displayId]
+ * makes sense only when the multiple desks feature is enabled.
*/
-class DesktopTask(val deskId: Int, tasks: List<Task>) : GroupTask(tasks, TaskViewType.DESKTOP) {
+class DesktopTask(val deskId: Int, val displayId: Int, tasks: List<Task>) :
+ GroupTask(tasks, TaskViewType.DESKTOP) {
- override fun copy() = DesktopTask(deskId, tasks)
+ override fun copy() = DesktopTask(deskId, displayId, tasks)
- override fun toString() = "type=$taskViewType deskId=$deskId tasks=$tasks"
+ override fun toString() = "type=$taskViewType deskId=$deskId displayId=$displayId tasks=$tasks"
override fun equals(o: Any?): Boolean {
if (this === o) return true
if (o !is DesktopTask) return false
if (deskId != o.deskId) return false
+ if (displayId != o.displayId) return false
return super.equals(o)
}
+
+ override fun hashCode() = Objects.hash(super.hashCode(), deskId, displayId)
}
diff --git a/quickstep/src/com/android/quickstep/util/GroupTask.kt b/quickstep/src/com/android/quickstep/util/GroupTask.kt
index add8821..2b754e2 100644
--- a/quickstep/src/com/android/quickstep/util/GroupTask.kt
+++ b/quickstep/src/com/android/quickstep/util/GroupTask.kt
@@ -86,6 +86,8 @@
return TaskItemInfo(task.task.key.id, wii)
}
}
+
+ override fun hashCode() = super.hashCode()
}
/**
diff --git a/quickstep/src/com/android/quickstep/util/InputProxyHandlerFactory.java b/quickstep/src/com/android/quickstep/util/InputProxyHandlerFactory.java
index 843619d..9aded89 100644
--- a/quickstep/src/com/android/quickstep/util/InputProxyHandlerFactory.java
+++ b/quickstep/src/com/android/quickstep/util/InputProxyHandlerFactory.java
@@ -35,8 +35,8 @@
private final GestureState mGestureState;
@UiThread
- public InputProxyHandlerFactory(BaseContainerInterface activityInterface,
- GestureState gestureState) {
+ public InputProxyHandlerFactory(
+ BaseContainerInterface activityInterface, GestureState gestureState) {
mContainerInterface = activityInterface;
mGestureState = gestureState;
}
@@ -47,7 +47,8 @@
@Override
public InputConsumer get() {
RecentsViewContainer container = mContainerInterface.getCreatedContainer();
- return container == null ? InputConsumer.NO_OP
+ return container == null
+ ? InputConsumer.createNoOpInputConsumer(mGestureState.getDisplayId())
: new OverviewInputConsumer(mGestureState, container, null, true);
}
}
diff --git a/quickstep/src/com/android/quickstep/views/BlurUtils.kt b/quickstep/src/com/android/quickstep/views/BlurUtils.kt
new file mode 100644
index 0000000..d6b2a05
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/BlurUtils.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.views
+
+import com.android.launcher3.Flags.enableOverviewBackgroundWallpaperBlur
+import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle
+
+/** Applies blur either behind launcher surface or live tile app. */
+class BlurUtils(private val recentsView: RecentsView<*, *>) {
+
+ fun setDrawLiveTileBelowRecents(drawBelowRecents: Boolean) {
+ val liveTileRemoteTargetHandles =
+ if (
+ recentsView.remoteTargetHandles != null &&
+ recentsView.recentsAnimationController != null
+ )
+ recentsView.remoteTargetHandles
+ else null
+ setDrawBelowRecents(drawBelowRecents, liveTileRemoteTargetHandles)
+ }
+
+ /**
+ * Set surface in [remoteTargetHandles] to be above or below Recents layer, and update the base
+ * layer to apply blur to in BaseDepthController.
+ */
+ fun setDrawBelowRecents(
+ drawBelowRecents: Boolean,
+ remoteTargetHandles: Array<RemoteTargetHandle>? = null,
+ ) {
+ remoteTargetHandles?.forEach { it.taskViewSimulator.setDrawsBelowRecents(drawBelowRecents) }
+ if (enableOverviewBackgroundWallpaperBlur()) {
+ recentsView.depthController?.setBaseSurfaceOverride(
+ // Blurs behind launcher layer.
+ if (!drawBelowRecents || remoteTargetHandles == null) {
+ null
+ } else {
+ // Blurs behind live tile. blur will be applied behind window
+ // which farthest from user in case of desktop and split apps.
+ remoteTargetHandles
+ .maxByOrNull { it.transformParams.targetSet.firstAppTarget.leash.layerId }
+ ?.transformParams
+ ?.targetSet
+ ?.firstAppTarget
+ ?.leash
+ }
+ )
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
index 27657b4..8876633 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -25,6 +25,7 @@
import android.util.AttributeSet
import android.util.Log
import android.util.Size
+import android.view.Display.INVALID_DISPLAY
import android.view.Gravity
import android.view.View
import android.view.ViewStub
@@ -59,6 +60,7 @@
import com.android.quickstep.task.thumbnail.TaskThumbnailView
import com.android.quickstep.util.DesktopTask
import com.android.quickstep.util.RecentsOrientedState
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.enableMultipleDesktops
import kotlin.math.roundToInt
/** TaskView that contains all tasks that are part of the desktop. */
@@ -69,7 +71,10 @@
type = TaskViewType.DESKTOP,
thumbnailFullscreenParams = DesktopFullscreenDrawParams(context),
) {
- var deskId = DesktopVisibilityController.INACTIVE_DESK_ID
+ val deskId
+ get() = desktopTask?.deskId ?: DesktopVisibilityController.INACTIVE_DESK_ID
+
+ private var desktopTask: DesktopTask? = null
private val contentViewFullscreenParams = FullscreenDrawParams(context)
@@ -112,6 +117,14 @@
positionTaskWindows()
}
+ override val displayId: Int
+ get() =
+ if (enableMultipleDesktops(context)) {
+ desktopTask?.displayId ?: INVALID_DISPLAY
+ } else {
+ super.displayId
+ }
+
private fun getRemoteTargetHandle(taskId: Int): RemoteTargetHandle? =
remoteTargetHandles?.firstOrNull {
it.transformParams.targetSet.firstAppTargetTaskId == taskId
@@ -284,7 +297,7 @@
orientedState: RecentsOrientedState,
taskOverlayFactory: TaskOverlayFactory,
) {
- deskId = desktopTask.deskId
+ this.desktopTask = desktopTask
// TODO(b/370495260): Minimized tasks should not be filtered with desktop exploded view
// support.
// Minimized tasks should not be shown in Overview.
@@ -336,7 +349,7 @@
override fun onRecycle() {
super.onRecycle()
- deskId = DesktopVisibilityController.INACTIVE_DESK_ID
+ desktopTask = null
explodeProgress = 0.0f
viewModel = null
visibility = VISIBLE
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 0f1c294..74de2ac 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -19,6 +19,7 @@
import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY;
import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON;
+import static com.android.launcher3.LauncherState.ADD_DESK_BUTTON;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
@@ -184,10 +185,8 @@
if (finalState.isRecentsViewVisible && finalState != OVERVIEW_MODAL_TASK) {
setTaskBorderEnabled(true);
}
-
if (isOverlayEnabled) {
- runActionOnRemoteHandles(remoteTargetHandle ->
- remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true));
+ mBlurUtils.setDrawLiveTileBelowRecents(true);
}
}
@@ -198,7 +197,10 @@
LauncherState state = getStateManager().getState();
boolean hasClearAllButton = (state.getVisibleElements(mContainer)
& CLEAR_ALL_BUTTON) != 0;
+ boolean hasAddDeskButton = (state.getVisibleElements(mContainer)
+ & ADD_DESK_BUTTON) != 0;
setDisallowScrollToClearAll(!hasClearAllButton);
+ setDisallowScrollToAddDesk(!hasAddDeskButton);
}
}
@@ -276,7 +278,7 @@
GestureState.GestureEndTarget endTarget = mCurrentGestureEndTarget;
if (endTarget == GestureState.GestureEndTarget.LAST_TASK
&& desktopVisibilityController.isInDesktopModeAndNotInOverview(
- mContainer.getDisplayId())) {
+ mContainer.getDisplayId())) {
// Recents gesture was cancelled and we are returning to the previous task.
// After super class has handled clean up, show desktop apps on top again
showDesktopApps = true;
diff --git a/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt
index 4ce18f5..d39b528 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt
@@ -96,63 +96,86 @@
onEndRunnable()
}
if (!isDismissing) {
- addNeighboringSpringAnimationsForDismissCancel(
+ addNeighborSettlingSpringAnimations(
draggedTaskView,
draggedTaskViewSpringAnimation,
+ driverProgressThreshold = 0f,
+ isSpringDirectionVertical = true,
)
}
return draggedTaskViewSpringAnimation
}
- private fun addNeighboringSpringAnimationsForDismissCancel(
+ private fun addNeighborSettlingSpringAnimations(
draggedTaskView: TaskView,
- draggedTaskViewSpringAnimation: SpringAnimation,
+ springAnimationDriver: SpringAnimation,
+ tasksToExclude: List<TaskView> = emptyList(),
+ driverProgressThreshold: Float,
+ isSpringDirectionVertical: Boolean,
) {
// Empty spring animation exists for conditional start, and to drive neighboring springs.
val neighborsToSettle =
SpringAnimation(FloatValueHolder()).setSpring(createExpressiveDismissSpringForce())
- var lastPosition = 0f
- var startSettling = false
- draggedTaskViewSpringAnimation.addUpdateListener { _, value, velocity ->
- // Start the settling animation the first time the dragged task passes the origin (from
- // negative displacement to positive displacement). We do not check for an exact value
- // to compare to, as the update listener does not necessarily hit every value (e.g. a
- // value of zero). Do not check again once it has started settling, as a spring can
- // bounce past the origin multiple times depending on the stiffness and damping ratio.
- if (startSettling) return@addUpdateListener
- if (lastPosition < 0 && value >= 0) {
- startSettling = true
- }
- lastPosition = value
- if (startSettling) {
- neighborsToSettle.setStartVelocity(velocity).animateToFinalPosition(0f)
- playDismissSettlingHaptic(velocity)
- }
- }
+ addThresholdSpringAnimationTrigger(
+ springAnimationDriver,
+ progressThreshold = driverProgressThreshold,
+ neighborsToSettle,
+ )
// Add tasks before dragged index, fanning out from the dragged task.
// The order they are added matters, as each spring drives the next.
var previousNeighbor = neighborsToSettle
- getTasksOffsetPairAdjacentToDraggedTask(draggedTaskView, towardsStart = true).forEach {
- (taskView, offset) ->
- previousNeighbor =
- createNeighboringTaskViewSpringAnimation(
- taskView,
- offset * ADDITIONAL_DISMISS_DAMPING_RATIO,
- previousNeighbor,
- )
- }
+ getTasksOffsetPairAdjacentToDraggedTask(draggedTaskView, towardsStart = true)
+ .filter { (taskView, _) -> !tasksToExclude.contains(taskView) }
+ .forEach { (taskView, offset) ->
+ previousNeighbor =
+ createNeighboringTaskViewSpringAnimation(
+ taskView,
+ offset * ADDITIONAL_DISMISS_DAMPING_RATIO,
+ previousNeighbor,
+ isSpringDirectionVertical,
+ )
+ }
// Add tasks after dragged index, fanning out from the dragged task.
// The order they are added matters, as each spring drives the next.
previousNeighbor = neighborsToSettle
- getTasksOffsetPairAdjacentToDraggedTask(draggedTaskView, towardsStart = false).forEach {
- (taskView, offset) ->
- previousNeighbor =
- createNeighboringTaskViewSpringAnimation(
- taskView,
- offset * ADDITIONAL_DISMISS_DAMPING_RATIO,
- previousNeighbor,
- )
+ getTasksOffsetPairAdjacentToDraggedTask(draggedTaskView, towardsStart = false)
+ .filter { (taskView, _) -> !tasksToExclude.contains(taskView) }
+ .forEach { (taskView, offset) ->
+ previousNeighbor =
+ createNeighboringTaskViewSpringAnimation(
+ taskView,
+ offset * ADDITIONAL_DISMISS_DAMPING_RATIO,
+ previousNeighbor,
+ isSpringDirectionVertical,
+ )
+ }
+ }
+
+ /** As spring passes threshold for the first time, run conditional spring with velocity. */
+ private fun addThresholdSpringAnimationTrigger(
+ springAnimationDriver: SpringAnimation,
+ progressThreshold: Float,
+ conditionalSpring: SpringAnimation,
+ ) {
+ var lastPosition = 0f
+ var startSettling = false
+ springAnimationDriver.addUpdateListener { _, value, velocity ->
+ // We do not compare to the threshold directly, as the update listener
+ // does not necessarily hit every value. Do not check again once it has started
+ // settling, as a spring can bounce past the end value multiple times.
+ if (startSettling) return@addUpdateListener
+ if (
+ lastPosition < progressThreshold && value >= progressThreshold ||
+ lastPosition > progressThreshold && value <= progressThreshold
+ ) {
+ startSettling = true
+ }
+ lastPosition = value
+ if (startSettling) {
+ conditionalSpring.setStartVelocity(velocity).animateToFinalPosition(0f)
+ playDismissSettlingHaptic(velocity)
+ }
}
}
@@ -208,21 +231,25 @@
taskView: TaskView,
dampingOffsetRatio: Float,
previousNeighborSpringAnimation: SpringAnimation,
+ springingDirectionVertical: Boolean,
): SpringAnimation {
+ val springProperty =
+ if (springingDirectionVertical) taskView.secondaryDismissTranslationProperty
+ else taskView.primaryDismissTranslationProperty
val neighboringTaskViewSpringAnimation =
- SpringAnimation(
- taskView,
- FloatPropertyCompat.createFloatPropertyCompat(
- taskView.secondaryDismissTranslationProperty
- ),
- )
+ SpringAnimation(taskView, FloatPropertyCompat.createFloatPropertyCompat(springProperty))
.setSpring(createExpressiveDismissSpringForce(dampingOffsetRatio))
// Update live tile on spring animation.
if (taskView.isRunningTask && recentsView.enableDrawingLiveTile) {
neighboringTaskViewSpringAnimation.addUpdateListener { _, _, _ ->
recentsView.runActionOnRemoteHandles { remoteTargetHandle ->
- remoteTargetHandle.taskViewSimulator.taskSecondaryTranslation.value =
- taskView.secondaryDismissTranslationProperty.get(taskView)
+ val taskTranslation =
+ if (springingDirectionVertical) {
+ remoteTargetHandle.taskViewSimulator.taskSecondaryTranslation
+ } else {
+ remoteTargetHandle.taskViewSimulator.taskPrimaryTranslation
+ }
+ taskTranslation.value = springProperty.get(taskView)
}
recentsView.redrawLiveTile()
}
@@ -271,7 +298,7 @@
.playToken(
MSDLToken.CANCEL,
InteractionProperties.DynamicVibrationScale(
- boundToRange(velocity / maxDismissSettlingVelocity, 0f, 1f),
+ boundToRange(abs(velocity) / maxDismissSettlingVelocity, 0f, 1f),
VibrationAttributes.Builder()
.setUsage(VibrationAttributes.USAGE_TOUCH)
.setFlags(VibrationAttributes.FLAG_PIPELINED_EFFECT)
@@ -319,32 +346,39 @@
.setSpring(createExpressiveGridReflowSpringForce(finalPosition = dismissedTaskGap))
val towardsStart = if (recentsView.isRtl) dismissedTaskGap < 0 else dismissedTaskGap > 0
+ var tasksToReflow: List<TaskView>
// Build the chains of Spring Animations
when {
!recentsView.showAsGrid() -> {
- buildDismissReflowSpringAnimationChain(
+ tasksToReflow =
getTasksToReflow(
recentsView.mUtils.taskViews.toList(),
dismissedTaskView,
towardsStart,
- ),
+ )
+ buildDismissReflowSpringAnimationChain(
+ tasksToReflow,
dismissedTaskGap,
previousSpring = springAnimationDriver,
)
}
dismissedTaskView.isLargeTile -> {
+ tasksToReflow =
+ getTasksToReflow(
+ recentsView.mUtils.getLargeTaskViews(),
+ dismissedTaskView,
+ towardsStart,
+ )
val lastSpringAnimation =
buildDismissReflowSpringAnimationChain(
- getTasksToReflow(
- recentsView.mUtils.getLargeTaskViews(),
- dismissedTaskView,
- towardsStart,
- ),
+ tasksToReflow,
dismissedTaskGap,
previousSpring = springAnimationDriver,
)
// Add all top and bottom grid tasks when animating towards the end of the grid.
if (!towardsStart) {
+ tasksToReflow += recentsView.mUtils.getTopRowTaskViews()
+ tasksToReflow += recentsView.mUtils.getBottomRowTaskViews()
buildDismissReflowSpringAnimationChain(
recentsView.mUtils.getTopRowTaskViews(),
dismissedTaskGap,
@@ -358,29 +392,43 @@
}
}
recentsView.isOnGridBottomRow(dismissedTaskView) -> {
- buildDismissReflowSpringAnimationChain(
+ tasksToReflow =
getTasksToReflow(
recentsView.mUtils.getBottomRowTaskViews(),
dismissedTaskView,
towardsStart,
- ),
+ )
+ buildDismissReflowSpringAnimationChain(
+ tasksToReflow,
dismissedTaskGap,
previousSpring = springAnimationDriver,
)
}
else -> {
- buildDismissReflowSpringAnimationChain(
+ tasksToReflow =
getTasksToReflow(
recentsView.mUtils.getTopRowTaskViews(),
dismissedTaskView,
towardsStart,
- ),
+ )
+ buildDismissReflowSpringAnimationChain(
+ tasksToReflow,
dismissedTaskGap,
previousSpring = springAnimationDriver,
)
}
}
+ if (tasksToReflow.isNotEmpty()) {
+ addNeighborSettlingSpringAnimations(
+ dismissedTaskView,
+ springAnimationDriver,
+ tasksToExclude = tasksToReflow,
+ driverProgressThreshold = dismissedTaskGap,
+ isSpringDirectionVertical = false,
+ )
+ }
+
// Start animations and remove the dismissed task at the end, dismiss immediately if no
// neighboring tasks exist.
val runGridEndAnimationAndRelayout = {
@@ -429,8 +477,8 @@
else -> 1f
} * (if (recentsView.isRtl) 1f else -1f)
- return (dismissedTaskView.layoutParams.width + recentsView.pageSpacing) *
- dismissHorizontalFactor
+ return (recentsView.pagedOrientationHandler.getPrimarySize(dismissedTaskView) +
+ recentsView.pageSpacing) * dismissHorizontalFactor
}
private fun getTasksToReflow(
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 6067550..c1df0fb 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -42,6 +42,7 @@
import static com.android.launcher3.Flags.enableDesktopTaskAlphaAnimation;
import static com.android.launcher3.Flags.enableGridOnlyOverview;
import static com.android.launcher3.Flags.enableLargeDesktopWindowingTile;
+import static com.android.launcher3.Flags.enableOverviewBackgroundWallpaperBlur;
import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
import static com.android.launcher3.Flags.enableSeparateExternalDisplayTasks;
import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
@@ -142,7 +143,6 @@
import androidx.core.graphics.ColorUtils;
import androidx.dynamicanimation.animation.SpringAnimation;
-import com.android.app.tracing.TraceUtilsKt;
import com.android.internal.jank.Cuj;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseActivity.MultiWindowModeChangedListener;
@@ -589,6 +589,8 @@
private final TaskOverlayFactory mTaskOverlayFactory;
protected boolean mDisallowScrollToClearAll;
+ // True if it is not allowed to scroll to [AddDesktopButton].
+ protected boolean mDisallowScrollToAddDesk;
private boolean mOverlayEnabled;
protected boolean mFreezeViewVisibility;
private boolean mOverviewGridEnabled;
@@ -868,6 +870,9 @@
private final Matrix mTmpMatrix = new Matrix();
private int mTaskViewCount = 0;
+
+ protected final BlurUtils mBlurUtils = new BlurUtils(this);
+
@Nullable
public TaskView getFirstTaskView() {
return mUtils.getFirstTaskView();
@@ -2747,9 +2752,7 @@
}
setEnableDrawingLiveTile(false);
}
- runActionOnRemoteHandles(remoteTargetHandle ->
- remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(false));
-
+ mBlurUtils.setDrawLiveTileBelowRecents(false);
// These are relatively expensive and don't need to be done this frame (RecentsView isn't
// visible anyway), so defer by a frame to get off the critical path, e.g. app to home.
post(this::onReset);
@@ -3121,7 +3124,8 @@
// TODO: b/401582344 - Implement a way to exclude the `DesktopWallpaperActivity`.
desktopTaskView.bind(
- new DesktopTask(activeDeskId, Arrays.asList(runningTasks)),
+ new DesktopTask(activeDeskId, mContainer.getDisplayId(),
+ Arrays.asList(runningTasks)),
mOrientationState, mTaskOverlayFactory);
return desktopTaskView;
}
@@ -5819,12 +5823,10 @@
// above RecentsView to avoid wallpaper blur from being applied to it.
if (!taskView.isRunningTask()) {
runActionOnRemoteHandles(
- remoteTargetHandle -> {
- remoteTargetHandle.getTaskViewSimulator().setPivotOverride(
- mTempPointF);
- remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(
- false);
- });
+ remoteTargetHandle ->
+ remoteTargetHandle.getTaskViewSimulator()
+ .setPivotOverride(mTempPointF));
+ mBlurUtils.setDrawLiveTileBelowRecents(false);
}
}
@@ -5959,8 +5961,7 @@
mPendingAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
- runActionOnRemoteHandles(remoteTargetHandle ->
- remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(false));
+ mBlurUtils.setDrawLiveTileBelowRecents(false);
}
});
mPendingAnimation.addEndListener(isSuccess -> {
@@ -5998,8 +5999,7 @@
// If launch animation didn't complete i.e. user dragged live tile down and then
// back up and returned to Overview, then we need to ensure we reset the
// view to draw below recents so that it can't be interacted with.
- runActionOnRemoteHandles(remoteTargetHandle ->
- remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true));
+ mBlurUtils.setDrawLiveTileBelowRecents(true);
redrawLiveTile();
}
return Unit.INSTANCE;
@@ -6078,6 +6078,7 @@
});
}
+ @Nullable
public RemoteTargetHandle[] getRemoteTargetHandles() {
return mRemoteTargetHandles;
}
@@ -6231,6 +6232,9 @@
mRecentsAnimationController = null;
mSplitSelectStateController.setRecentsAnimationRunning(false);
executeSideTaskLaunchCallback();
+ if (enableOverviewBackgroundWallpaperBlur()) {
+ mBlurUtils.setDrawLiveTileBelowRecents(false);
+ }
}
public void setDisallowScrollToClearAll(boolean disallowScrollToClearAll) {
@@ -6239,6 +6243,17 @@
updateMinAndMaxScrollX();
}
}
+ /**
+ * Update the value of [mDisallowScrollToAddDesk]
+ */
+ public void setDisallowScrollToAddDesk(boolean disallowScrollToAddDesk) {
+ if (mDisallowScrollToAddDesk != disallowScrollToAddDesk) {
+ mDisallowScrollToAddDesk = disallowScrollToAddDesk;
+ updateMinAndMaxScrollX();
+ }
+ }
+
+
/**
* Updates page scroll synchronously after measure and layout child views.
@@ -6384,7 +6399,20 @@
if (addDesktopButtonIndex >= 0 && addDesktopButtonIndex < outPageScrolls.length) {
int firstViewIndex = getFirstViewIndex();
if (firstViewIndex >= 0 && firstViewIndex < outPageScrolls.length) {
- outPageScrolls[addDesktopButtonIndex] = outPageScrolls[firstViewIndex];
+ // If we can scroll to [AddDesktopButton], make its page scroll equal to
+ // the first [TaskView]. Otherwise, make its page scroll out of range of
+ // [minScroll, maxScroll].
+ if (!mDisallowScrollToAddDesk) {
+ outPageScrolls[addDesktopButtonIndex] = outPageScrolls[firstViewIndex];
+ } else {
+ outPageScrolls[addDesktopButtonIndex] =
+ outPageScrolls[firstViewIndex] + (mIsRtl ? 1 : -1);
+ }
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "getPageScrolls - addDesktopButtonScroll: "
+ + outPageScrolls[addDesktopButtonIndex]);
}
}
if (DEBUG) {
@@ -7026,7 +7054,7 @@
// `AddNewDesktopButton`.
DesktopTaskView desktopTaskView =
(DesktopTaskView) getTaskViewFromPool(TaskViewType.DESKTOP);
- desktopTaskView.bind(new DesktopTask(deskId, new ArrayList<>()),
+ desktopTaskView.bind(new DesktopTask(deskId, displayId, new ArrayList<>()),
mOrientationState, mTaskOverlayFactory);
Objects.requireNonNull(mAddDesktopButton);
@@ -7186,4 +7214,15 @@
public interface TaskLaunchListener {
void onTaskLaunched();
}
+
+ /**
+ * Sets whether the remote animation targets should draw below the recents view.
+ *
+ * @param drawBelowRecents whether the surface should draw below Recents.
+ * @param remoteTargetHandles collection of remoteTargetHandles in Recents.
+ */
+ public void setDrawBelowRecents(boolean drawBelowRecents,
+ RemoteTargetHandle[] remoteTargetHandles) {
+ mBlurUtils.setDrawBelowRecents(drawBelowRecents, remoteTargetHandles);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 8d95b13..0a373b5 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -77,6 +77,7 @@
import com.android.quickstep.FullscreenDrawParams
import com.android.quickstep.RecentsModel
import com.android.quickstep.RemoteAnimationTargets
+import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle
import com.android.quickstep.TaskOverlayFactory
import com.android.quickstep.TaskViewUtils
import com.android.quickstep.orientation.RecentsPagedOrientationHandler
@@ -146,7 +147,7 @@
val isRunningTask: Boolean
get() = this === recentsView?.runningTaskView
- val displayId: Int
+ open val displayId: Int
get() = taskContainers.firstOrNull()?.task.displayId
val isExternalDisplay: Boolean
@@ -1210,15 +1211,14 @@
/** Launch of the current task (both live and inactive tasks) with an animation. */
fun launchWithAnimation(): RunnableList? {
return if (isRunningTask && recentsView?.remoteTargetHandles != null) {
- launchAsLiveTile()
+ launchAsLiveTile(recentsView?.remoteTargetHandles!!)
} else {
launchAsStaticTile()
}
}
- private fun launchAsLiveTile(): RunnableList? {
+ private fun launchAsLiveTile(remoteTargetHandles: Array<RemoteTargetHandle>): RunnableList? {
val recentsView = recentsView ?: return null
- val remoteTargetHandles = recentsView.remoteTargetHandles
if (!isClickableAsLiveTile) {
Log.e(
TAG,
@@ -1228,21 +1228,27 @@
}
isClickableAsLiveTile = false
val targets =
- if (remoteTargetHandles.size == 1) {
- remoteTargetHandles[0].transformParams.targetSet
+ if (remoteTargetHandles.isNotEmpty()) {
+ if (remoteTargetHandles.size == 1) {
+ remoteTargetHandles[0].transformParams.targetSet
+ } else {
+ val apps =
+ remoteTargetHandles.flatMap {
+ it.transformParams.targetSet.apps.asIterable()
+ }
+ val wallpapers =
+ remoteTargetHandles.flatMap {
+ it.transformParams.targetSet.wallpapers.asIterable()
+ }
+ RemoteAnimationTargets(
+ apps.toTypedArray(),
+ wallpapers.toTypedArray(),
+ remoteTargetHandles[0].transformParams.targetSet.nonApps,
+ remoteTargetHandles[0].transformParams.targetSet.targetMode,
+ )
+ }
} else {
- val apps =
- remoteTargetHandles.flatMap { it.transformParams.targetSet.apps.asIterable() }
- val wallpapers =
- remoteTargetHandles.flatMap {
- it.transformParams.targetSet.wallpapers.asIterable()
- }
- RemoteAnimationTargets(
- apps.toTypedArray(),
- wallpapers.toTypedArray(),
- remoteTargetHandles[0].transformParams.targetSet.nonApps,
- remoteTargetHandles[0].transformParams.targetSet.targetMode,
- )
+ null
}
if (targets == null) {
// If the recents animation is cancelled somehow between the parent if block and
@@ -1484,10 +1490,11 @@
}
private fun closeTaskMenu(): Boolean {
- val floatingView: AbstractFloatingView? = AbstractFloatingView.getTopOpenViewWithType(
- container,
- AbstractFloatingView.TYPE_TASK_MENU
- )
+ val floatingView: AbstractFloatingView? =
+ AbstractFloatingView.getTopOpenViewWithType(
+ container,
+ AbstractFloatingView.TYPE_TASK_MENU,
+ )
if (floatingView?.isOpen == true) {
floatingView.close(true)
return true
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt
index 26f1197..52d288a 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt
@@ -17,6 +17,7 @@
import com.android.launcher3.taskbar.allapps.TaskbarAllAppsController
import com.android.launcher3.taskbar.bubbles.BubbleControllers
+import com.android.launcher3.taskbar.growth.NudgeController
import com.android.launcher3.taskbar.overlay.TaskbarOverlayController
import com.android.systemui.shared.rotation.RotationButtonController
import java.util.Optional
@@ -58,6 +59,7 @@
@Mock lateinit var taskbarPinningController: TaskbarPinningController
@Mock lateinit var optionalBubbleControllers: Optional<BubbleControllers>
@Mock lateinit var taskbarDesktopModeController: TaskbarDesktopModeController
+ @Mock lateinit var nudgeController: NudgeController
lateinit var taskbarControllers: TaskbarControllers
@@ -100,6 +102,7 @@
taskbarPinningController,
optionalBubbleControllers,
taskbarDesktopModeController,
+ nudgeController,
)
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
index 3761044..c589415 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
@@ -23,6 +23,7 @@
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
+import android.view.Display.DEFAULT_DISPLAY
import androidx.test.core.app.ApplicationProvider
import com.android.launcher3.Flags.FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR
import com.android.launcher3.Flags.FLAG_TASKBAR_OVERFLOW
@@ -446,7 +447,7 @@
)
})
- recentsModel.updateRecentTasks(listOf(DesktopTask(deskId = 0, tasks)))
+ recentsModel.updateRecentTasks(listOf(DesktopTask(deskId = 0, DEFAULT_DISPLAY, tasks)))
for (task in 1..tasks.size) {
desktopTaskListener?.onTasksVisibilityChanged(
context.virtualDisplay.display.displayId,
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 8376bc1..8758d7c 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
@@ -25,6 +25,7 @@
import android.os.Process
import android.os.UserHandle
import android.platform.test.annotations.EnableFlags
+import android.view.Display.DEFAULT_DISPLAY
import androidx.test.annotation.UiThreadTest
import com.android.internal.R
import com.android.launcher3.BubbleTextView.RunningAppState
@@ -877,7 +878,7 @@
val allTasks =
ArrayList<GroupTask>().apply {
if (!runningTasks.isEmpty()) {
- add(DesktopTask(deskId = 0, ArrayList(runningTasks)))
+ add(DesktopTask(deskId = 0, DEFAULT_DISPLAY, ArrayList(runningTasks)))
}
addAll(recentTasks)
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
index 9722e9d..1e4315a 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
@@ -52,6 +52,7 @@
import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.util.DaggerSingletonTracker;
import com.android.launcher3.util.LooperExecutor;
+import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.views.TaskViewType;
import com.android.systemui.shared.recents.model.Task;
@@ -148,6 +149,66 @@
}
@Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ public void loadTasksInBackground_freeformTask_multiDesksInMultiDisplays() throws Exception {
+ List<TaskInfo> tasksInDefaultDesk1 = Arrays.asList(
+ createRecentTaskInfo(/* taskId = */ 1, DEFAULT_DISPLAY),
+ createRecentTaskInfo(/* taskId = */ 4, DEFAULT_DISPLAY));
+ List<TaskInfo> tasksInDefaultDesk2 = Arrays.asList(
+ createRecentTaskInfo(/* taskId = */ 2, DEFAULT_DISPLAY),
+ createRecentTaskInfo(/* taskId = */ 3, DEFAULT_DISPLAY));
+ List<TaskInfo> tasksInExtend = Arrays.asList(
+ createRecentTaskInfo(/* taskId = */ 5, /* displayId = */ 1),
+ createRecentTaskInfo(/* taskId = */ 6, /* displayId = */ 1));
+ GroupedTaskInfo recentTaskInfosOfDesk1 = GroupedTaskInfo.forDeskTasks(/* deskId = */1,
+ DEFAULT_DISPLAY, tasksInDefaultDesk1, /* minimizedTaskIds = */
+ Collections.emptySet());
+ GroupedTaskInfo recentTaskInfosOfDesk2 = GroupedTaskInfo.forDeskTasks(/* deskId = */2,
+ DEFAULT_DISPLAY, tasksInDefaultDesk2, /* minimizedTaskIds = */
+ Collections.emptySet());
+ GroupedTaskInfo recentTaskInfosOfDesk3 = GroupedTaskInfo.forDeskTasks(/* deskId = */3,
+ /* displayId = */ 1, tasksInExtend, /* minimizedTaskIds = */
+ Collections.emptySet());
+ when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt())).thenReturn(
+ new ArrayList<>(Arrays.asList(recentTaskInfosOfDesk1, recentTaskInfosOfDesk2,
+ recentTaskInfosOfDesk3)));
+
+ List<GroupTask> taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, -1,
+ false);
+
+ assertThat(taskList).hasSize(3);
+ assertThat(taskList.get(2).taskViewType).isEqualTo(TaskViewType.DESKTOP);
+ List<Task> actualFreeformTasksInDesk1 = taskList.get(2).getTasks();
+ assertThat(actualFreeformTasksInDesk1).hasSize(2);
+ assertThat(actualFreeformTasksInDesk1.get(0).key.id).isEqualTo(1);
+ assertThat(actualFreeformTasksInDesk1.get(0).isMinimized).isFalse();
+ assertThat(actualFreeformTasksInDesk1.get(1).key.id).isEqualTo(4);
+ assertThat(actualFreeformTasksInDesk1.get(1).isMinimized).isFalse();
+ assertThat(((DesktopTask) taskList.get(2)).getDeskId()).isEqualTo(1);
+ assertThat(((DesktopTask) taskList.get(2)).getDisplayId()).isEqualTo(DEFAULT_DISPLAY);
+
+ assertThat(taskList.get(1).taskViewType).isEqualTo(TaskViewType.DESKTOP);
+ List<Task> actualFreeformTasksInDesk2 = taskList.get(1).getTasks();
+ assertThat(actualFreeformTasksInDesk2).hasSize(2);
+ assertThat(actualFreeformTasksInDesk2.get(0).key.id).isEqualTo(2);
+ assertThat(actualFreeformTasksInDesk2.get(0).isMinimized).isFalse();
+ assertThat(actualFreeformTasksInDesk2.get(1).key.id).isEqualTo(3);
+ assertThat(actualFreeformTasksInDesk2.get(1).isMinimized).isFalse();
+ assertThat(((DesktopTask) taskList.get(1)).getDeskId()).isEqualTo(2);
+ assertThat(((DesktopTask) taskList.get(1)).getDisplayId()).isEqualTo(DEFAULT_DISPLAY);
+
+ assertThat(taskList.get(0).taskViewType).isEqualTo(TaskViewType.DESKTOP);
+ List<Task> actualFreeformTasksInDesk3 = taskList.get(0).getTasks();
+ assertThat(actualFreeformTasksInDesk3).hasSize(2);
+ assertThat(actualFreeformTasksInDesk3.get(0).key.id).isEqualTo(5);
+ assertThat(actualFreeformTasksInDesk3.get(0).isMinimized).isFalse();
+ assertThat(actualFreeformTasksInDesk3.get(1).key.id).isEqualTo(6);
+ assertThat(actualFreeformTasksInDesk3.get(1).isMinimized).isFalse();
+ assertThat(((DesktopTask) taskList.get(0)).getDeskId()).isEqualTo(3);
+ assertThat(((DesktopTask) taskList.get(0)).getDisplayId()).isEqualTo(1);
+ }
+
+ @Test
public void loadTasksInBackground_moreThanKeys_hasValidTaskDescription() throws Exception {
String taskDescription = "Wheeee!";
RecentTaskInfo task1 = new RecentTaskInfo();
@@ -175,7 +236,8 @@
}
@Test
- @DisableFlags(FLAG_ENABLE_SEPARATE_EXTERNAL_DISPLAY_TASKS)
+ @DisableFlags({FLAG_ENABLE_SEPARATE_EXTERNAL_DISPLAY_TASKS,
+ FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND})
public void loadTasksInBackground_freeformTask_createsDesktopTask() throws Exception {
List<TaskInfo> tasks = Arrays.asList(
createRecentTaskInfo(1 /* taskId */, DEFAULT_DISPLAY),
@@ -183,7 +245,8 @@
createRecentTaskInfo(5 /* taskId */, 1 /* displayId */),
createRecentTaskInfo(6 /* taskId */, 1 /* displayId */));
GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forDeskTasks(
- 0 /* deskId */, tasks, Collections.emptySet() /* minimizedTaskIds */);
+ 0 /* deskId */, DEFAULT_DISPLAY, tasks,
+ Collections.emptySet() /* minimizedTaskIds */);
when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
.thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
@@ -214,7 +277,8 @@
createRecentTaskInfo(5 /* taskId */, 1 /* displayId */),
createRecentTaskInfo(6 /* taskId */, 1 /* displayId */));
GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forDeskTasks(
- 0 /* deskId */, tasks, Collections.emptySet() /* minimizedTaskIds */);
+ 0 /* deskId */, DEFAULT_DISPLAY, tasks,
+ Collections.emptySet() /* minimizedTaskIds */);
when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
.thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
@@ -248,7 +312,7 @@
Set<Integer> minimizedTaskIds =
Arrays.stream(new Integer[]{1, 4, 5}).collect(Collectors.toSet());
GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forDeskTasks(
- 0 /* deskId */, tasks, minimizedTaskIds);
+ 0 /* deskId */, DEFAULT_DISPLAY, tasks, minimizedTaskIds);
when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
.thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
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 6790567..e22892c 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
@@ -21,6 +21,7 @@
import android.graphics.Bitmap
import android.graphics.Rect
import android.graphics.drawable.Drawable
+import android.view.Display.DEFAULT_DISPLAY
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.launcher3.util.TestDispatcherProvider
@@ -64,7 +65,7 @@
/* snapPosition = */ SNAP_TO_2_50_50,
),
),
- DesktopTask(deskId = 0, tasks.subList(3, 6)),
+ DesktopTask(deskId = 0, DEFAULT_DISPLAY, tasks.subList(3, 6)),
)
private val recentsModel = FakeRecentTasksDataSource()
private val taskThumbnailDataSource = FakeTaskThumbnailDataSource()
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/DesktopTaskTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/DesktopTaskTest.kt
index 6fbf482..15da4d4 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/DesktopTaskTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/DesktopTaskTest.kt
@@ -18,6 +18,7 @@
import android.content.ComponentName
import android.content.Intent
+import android.view.Display.DEFAULT_DISPLAY
import com.android.launcher3.util.LauncherMultivalentJUnit
import com.android.systemui.shared.recents.model.Task
import com.google.common.truth.Truth.assertThat
@@ -29,42 +30,42 @@
@Test
fun testDesktopTask_sameInstance_isEqual() {
- val task = DesktopTask(deskId = 0, createTasks(1))
+ val task = DesktopTask(deskId = 0, DEFAULT_DISPLAY, createTasks(1))
assertThat(task).isEqualTo(task)
}
@Test
fun testDesktopTask_identicalConstructor_isEqual() {
- val task1 = DesktopTask(deskId = 0, createTasks(1))
- val task2 = DesktopTask(deskId = 0, createTasks(1))
+ val task1 = DesktopTask(deskId = 0, DEFAULT_DISPLAY, createTasks(1))
+ val task2 = DesktopTask(deskId = 0, DEFAULT_DISPLAY, createTasks(1))
assertThat(task1).isEqualTo(task2)
}
@Test
fun testDesktopTask_copy_isEqual() {
- val task1 = DesktopTask(deskId = 0, createTasks(1))
+ val task1 = DesktopTask(deskId = 0, DEFAULT_DISPLAY, createTasks(1))
val task2 = task1.copy()
assertThat(task1).isEqualTo(task2)
}
@Test
fun testDesktopTask_differentDeskIds_isNotEqual() {
- val task1 = DesktopTask(deskId = 0, createTasks(1))
- val task2 = DesktopTask(deskId = 1, createTasks(1))
+ val task1 = DesktopTask(deskId = 0, DEFAULT_DISPLAY, createTasks(1))
+ val task2 = DesktopTask(deskId = 1, DEFAULT_DISPLAY, createTasks(1))
assertThat(task1).isNotEqualTo(task2)
}
@Test
fun testDesktopTask_differentTaskIds_isNotEqual() {
- val task1 = DesktopTask(deskId = 0, createTasks(1))
- val task2 = DesktopTask(deskId = 0, createTasks(2))
+ val task1 = DesktopTask(deskId = 0, DEFAULT_DISPLAY, createTasks(1))
+ val task2 = DesktopTask(deskId = 0, DEFAULT_DISPLAY, createTasks(2))
assertThat(task1).isNotEqualTo(task2)
}
@Test
fun testDesktopTask_differentLength_isNotEqual() {
- val task1 = DesktopTask(deskId = 0, createTasks(1))
- val task2 = DesktopTask(deskId = 0, createTasks(1, 2))
+ val task1 = DesktopTask(deskId = 0, DEFAULT_DISPLAY, createTasks(1))
+ val task2 = DesktopTask(deskId = 0, DEFAULT_DISPLAY, createTasks(1, 2))
assertThat(task1).isNotEqualTo(task2)
}
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 67fc62f..9f49171 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
@@ -19,6 +19,7 @@
import android.content.ComponentName
import android.content.Intent
import android.graphics.Rect
+import android.view.Display.DEFAULT_DISPLAY
import com.android.launcher3.util.LauncherMultivalentJUnit
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.systemui.shared.recents.model.Task
@@ -98,7 +99,7 @@
@Test
fun testGroupTask_differentType_isNotEqual() {
val task1 = SingleTask(createTask(1))
- val task2 = DesktopTask(deskId = 0, listOf(createTask(1)))
+ val task2 = DesktopTask(deskId = 0, DEFAULT_DISPLAY, listOf(createTask(1)))
assertThat(task1).isNotEqualTo(task2)
}
diff --git a/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java b/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
index ef6f55e..3db4398 100644
--- a/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
@@ -27,7 +27,6 @@
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.os.Looper;
import android.view.Choreographer;
import android.view.Display;
@@ -100,12 +99,13 @@
@Rule public final SandboxApplication mContext = new SandboxApplication();
+ private final int mDisplayId = Display.DEFAULT_DISPLAY;
@NonNull private final InputMonitorCompat mInputMonitorCompat =
- new InputMonitorCompat("", Display.DEFAULT_DISPLAY);
+ new InputMonitorCompat("", mDisplayId);
private TaskAnimationManager mTaskAnimationManager;
private InputChannelCompat.InputEventReceiver mInputEventReceiver;
- @Nullable private ResetGestureInputConsumer mResetGestureInputConsumer;
+ private boolean mUserUnlocked = true;
@NonNull private Function<GestureState, AnimatedFloat> mSwipeUpProxyProvider = (state) -> null;
@NonNull @Mock private TaskbarActivityContext mTaskbarActivityContext;
@@ -164,12 +164,6 @@
}
@Before
- public void setUpResetGestureInputConsumer() {
- mResetGestureInputConsumer = new ResetGestureInputConsumer(
- mTaskAnimationManager, mTaskbarManager::getCurrentActivityContext);
- }
-
- @Before
public void setupLockedUserState() {
when(mLockedUserState.isUserUnlocked()).thenReturn(true);
}
@@ -314,8 +308,6 @@
@Test
public void testNewBaseConsumer_containsOtherActivityInputConsumer() {
- // OtherActivityInputConsumer needs to be initialized on the main thread because of
- // MotionPauseDetector.mForcePauseTimeout
assertCorrectInputConsumer(
this::createBaseInputConsumer,
OtherActivityInputConsumer.class,
@@ -486,7 +478,7 @@
MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
InputConsumer inputConsumer = newConsumer(
mContext,
- mResetGestureInputConsumer,
+ mUserUnlocked,
mOverviewComponentObserver,
mDeviceState,
mPreviousGestureState,
@@ -510,7 +502,8 @@
MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
InputConsumer inputConsumer = newBaseConsumer(
mContext,
- mResetGestureInputConsumer,
+ mUserUnlocked,
+ mTaskbarManager,
mOverviewComponentObserver,
mDeviceState,
mPreviousGestureState,
@@ -535,12 +528,15 @@
ResetGestureInputConsumer.class,
InputConsumer.TYPE_RESET_GESTURE);
- mResetGestureInputConsumer = null;
+ mUserUnlocked = false;
- runOnMainSync(() -> assertThat(inputConsumerProvider.get()).isEqualTo(InputConsumer.NO_OP));
+ assertCorrectInputConsumer(
+ inputConsumerProvider,
+ InputConsumer.class,
+ InputConsumer.TYPE_NO_OP);
}
- private static void assertCorrectInputConsumer(
+ private void assertCorrectInputConsumer(
@NonNull Provider<InputConsumer> inputConsumerProvider,
@NonNull Class<? extends InputConsumer> expectedOutputConsumer,
int expectedType) {
@@ -551,11 +547,13 @@
expectedType);
}
- private static void assertCorrectInputConsumer(
+ private void assertCorrectInputConsumer(
@NonNull Provider<InputConsumer> inputConsumerProvider,
@NonNull Class<? extends InputConsumer> expectedOutputConsumer,
@NonNull Class<? extends InputConsumer> expectedActiveConsumer,
int expectedType) {
+ when(mCurrentGestureState.getDisplayId()).thenReturn(mDisplayId);
+
runOnMainSync(() -> {
InputConsumer inputConsumer = inputConsumerProvider.get();
@@ -563,7 +561,14 @@
assertThat(inputConsumer.getActiveConsumerInHierarchy())
.isInstanceOf(expectedActiveConsumer);
assertThat(inputConsumer.getType()).isEqualTo(expectedType);
+ assertThat(inputConsumer.getDisplayId()).isEqualTo(mDisplayId);
});
+ int expectedDisplayId = mDisplayId + 1;
+
+ when(mCurrentGestureState.getDisplayId()).thenReturn(expectedDisplayId);
+
+ runOnMainSync(() -> assertThat(inputConsumerProvider.get().getDisplayId())
+ .isEqualTo(expectedDisplayId));
}
private static void runOnMainSync(@NonNull Runnable runnable) {
diff --git a/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchAnimatorHelperTest.kt b/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchAnimatorHelperTest.kt
index 47108e0..daa77d2 100644
--- a/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchAnimatorHelperTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchAnimatorHelperTest.kt
@@ -32,7 +32,9 @@
import android.view.SurfaceControl
import android.view.WindowManager
import android.window.TransitionInfo
+import android.window.TransitionInfo.Change
import androidx.core.util.Supplier
+import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
import com.android.app.animation.Interpolators
import com.android.internal.jank.Cuj
import com.android.launcher3.desktop.DesktopAppLaunchAnimatorHelper
@@ -45,6 +47,7 @@
import org.junit.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
class DesktopAppLaunchAnimatorHelperTest {
@@ -70,6 +73,10 @@
whenever(transactionSupplier.get()).thenReturn(transaction)
whenever(transaction.setCrop(any(), any())).thenReturn(transaction)
whenever(transaction.setCornerRadius(any(), any())).thenReturn(transaction)
+ whenever(transaction.setScale(any(), any(), any())).thenReturn(transaction)
+ whenever(transaction.setPosition(any(), any(), any())).thenReturn(transaction)
+ whenever(transaction.setAlpha(any(), any())).thenReturn(transaction)
+ whenever(transaction.setFrameTimeline(any())).thenReturn(transaction)
whenever(context.resources).thenReturn(resources)
whenever(resources.displayMetrics).thenReturn(DisplayMetrics())
@@ -77,14 +84,8 @@
}
@Test
- fun launchTransition_returnsLaunchAnimator() {
- val openChange =
- TransitionInfo.Change(mock(), mock()).apply {
- mode = WindowManager.TRANSIT_OPEN
- taskInfo = TASK_INFO_FREEFORM
- }
- val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0)
- transitionInfo.addChange(openChange)
+ fun launchTransition_returnsLaunchAnimator() = runOnUiThread {
+ val transitionInfo = createTransitionInfo(listOf(OPEN_CHANGE))
val actual = helper.createAnimators(transitionInfo, finishCallback = {})
@@ -93,14 +94,27 @@
}
@Test
- fun noLaunchTransition_returnsEmptyAnimatorsList() {
+ fun launchTransition_callsAnimationEndListener() = runOnUiThread {
+ val finishCallback = mock<Function1<Animator, Unit>>()
+ val transitionInfo = createTransitionInfo(listOf(OPEN_CHANGE))
+
+ val animators = helper.createAnimators(transitionInfo, finishCallback = finishCallback)
+
+ animators.forEach { animator ->
+ animator.start()
+ animator.end()
+ verify(finishCallback).invoke(animator)
+ }
+ }
+
+ @Test
+ fun noLaunchTransition_returnsEmptyAnimatorsList() = runOnUiThread {
val pipChange =
TransitionInfo.Change(mock(), mock()).apply {
mode = WindowManager.TRANSIT_PIP
taskInfo = TASK_INFO_FREEFORM
}
- val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0)
- transitionInfo.addChange(pipChange)
+ val transitionInfo = createTransitionInfo(listOf(pipChange))
val actual = helper.createAnimators(transitionInfo, finishCallback = {})
@@ -108,20 +122,8 @@
}
@Test
- fun minimizeTransition_returnsLaunchAndMinimizeAnimator() {
- val openChange =
- TransitionInfo.Change(mock(), mock()).apply {
- mode = WindowManager.TRANSIT_OPEN
- taskInfo = TASK_INFO_FREEFORM
- }
- val minimizeChange =
- TransitionInfo.Change(mock(), mock()).apply {
- mode = WindowManager.TRANSIT_TO_BACK
- taskInfo = TASK_INFO_FREEFORM
- }
- val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0)
- transitionInfo.addChange(openChange)
- transitionInfo.addChange(minimizeChange)
+ fun minimizeTransition_returnsLaunchAndMinimizeAnimator() = runOnUiThread {
+ val transitionInfo = createTransitionInfo(listOf(OPEN_CHANGE, MINIMIZE_CHANGE))
val actual = helper.createAnimators(transitionInfo, finishCallback = {})
@@ -131,21 +133,23 @@
}
@Test
+ fun minimizeTransition_callsAnimationEndListener() = runOnUiThread {
+ val finishCallback = mock<Function1<Animator, Unit>>()
+ val transitionInfo = createTransitionInfo(listOf(OPEN_CHANGE, MINIMIZE_CHANGE))
+
+ val animators = helper.createAnimators(transitionInfo, finishCallback = finishCallback)
+
+ animators.forEach { animator ->
+ animator.start()
+ animator.end()
+ verify(finishCallback).invoke(animator)
+ }
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX)
- fun trampolineTransition_flagEnabled_returnsLaunchAndCloseAnimator() {
- val openChange =
- TransitionInfo.Change(mock(), mock()).apply {
- mode = WindowManager.TRANSIT_OPEN
- taskInfo = TASK_INFO_FREEFORM
- }
- val closeChange =
- TransitionInfo.Change(mock(), mock()).apply {
- mode = WindowManager.TRANSIT_CLOSE
- taskInfo = TASK_INFO_FREEFORM
- }
- val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0)
- transitionInfo.addChange(openChange)
- transitionInfo.addChange(closeChange)
+ fun trampolineTransition_flagEnabled_returnsLaunchAndCloseAnimator() = runOnUiThread {
+ val transitionInfo = createTransitionInfo(listOf(OPEN_CHANGE, CLOSE_CHANGE))
val actual = helper.createAnimators(transitionInfo, finishCallback = {})
@@ -155,21 +159,24 @@
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX)
+ fun trampolineTransition_flagEnabled_callsAnimationEndListener() = runOnUiThread {
+ val finishCallback = mock<Function1<Animator, Unit>>()
+ val transitionInfo = createTransitionInfo(listOf(OPEN_CHANGE, CLOSE_CHANGE))
+
+ val animators = helper.createAnimators(transitionInfo, finishCallback = finishCallback)
+
+ animators.forEach { animator ->
+ animator.start()
+ animator.end()
+ verify(finishCallback).invoke(animator)
+ }
+ }
+
+ @Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX)
- fun trampolineTransition_flagDisabled_returnsLaunchAnimator() {
- val openChange =
- TransitionInfo.Change(mock(), mock()).apply {
- mode = WindowManager.TRANSIT_OPEN
- taskInfo = TASK_INFO_FREEFORM
- }
- val closeChange =
- TransitionInfo.Change(mock(), mock()).apply {
- mode = WindowManager.TRANSIT_CLOSE
- taskInfo = TASK_INFO_FREEFORM
- }
- val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0)
- transitionInfo.addChange(openChange)
- transitionInfo.addChange(closeChange)
+ fun trampolineTransition_flagDisabled_returnsLaunchAnimator() = runOnUiThread {
+ val transitionInfo = createTransitionInfo(listOf(OPEN_CHANGE, CLOSE_CHANGE))
val actual = helper.createAnimators(transitionInfo, finishCallback = {})
@@ -179,26 +186,9 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX)
- fun trampolineTransition_flagEnabled_hitDesktopWindowLimit_returnsLaunchMinimizeCloseAnimator() {
- val openChange =
- TransitionInfo.Change(mock(), mock()).apply {
- mode = WindowManager.TRANSIT_OPEN
- taskInfo = TASK_INFO_FREEFORM
- }
- val minimizeChange =
- TransitionInfo.Change(mock(), mock()).apply {
- mode = WindowManager.TRANSIT_TO_BACK
- taskInfo = TASK_INFO_FREEFORM
- }
- val closeChange =
- TransitionInfo.Change(mock(), mock()).apply {
- mode = WindowManager.TRANSIT_CLOSE
- taskInfo = TASK_INFO_FREEFORM
- }
- val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0)
- transitionInfo.addChange(openChange)
- transitionInfo.addChange(minimizeChange)
- transitionInfo.addChange(closeChange)
+ fun trampolineTransition_flagEnabled_hitDesktopWindowLimit_returnsLaunchMinimizeCloseAnimator() = runOnUiThread {
+ val transitionInfo = createTransitionInfo(
+ listOf(OPEN_CHANGE, MINIMIZE_CHANGE, CLOSE_CHANGE))
val actual = helper.createAnimators(transitionInfo, finishCallback = {})
@@ -210,26 +200,9 @@
@Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX)
- fun trampolineTransition_flagDisabled_hitDesktopWindowLimit_returnsLaunchMinimizeAnimator() {
- val openChange =
- TransitionInfo.Change(mock(), mock()).apply {
- mode = WindowManager.TRANSIT_OPEN
- taskInfo = TASK_INFO_FREEFORM
- }
- val minimizeChange =
- TransitionInfo.Change(mock(), mock()).apply {
- mode = WindowManager.TRANSIT_TO_BACK
- taskInfo = TASK_INFO_FREEFORM
- }
- val closeChange =
- TransitionInfo.Change(mock(), mock()).apply {
- mode = WindowManager.TRANSIT_CLOSE
- taskInfo = TASK_INFO_FREEFORM
- }
- val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0)
- transitionInfo.addChange(openChange)
- transitionInfo.addChange(minimizeChange)
- transitionInfo.addChange(closeChange)
+ fun trampolineTransition_flagDisabled_hitDesktopWindowLimit_returnsLaunchMinimizeAnimator() = runOnUiThread {
+ val transitionInfo = createTransitionInfo(
+ listOf(OPEN_CHANGE, MINIMIZE_CHANGE, CLOSE_CHANGE))
val actual = helper.createAnimators(transitionInfo, finishCallback = {})
@@ -280,6 +253,12 @@
assertThat(animator.duration).isEqualTo(100)
}
+ private fun createTransitionInfo(changes: List<Change>): TransitionInfo {
+ val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0)
+ changes.forEach { transitionInfo.addChange(it) }
+ return transitionInfo
+ }
+
private companion object {
val TASK_INFO_FREEFORM =
ActivityManager.RunningTaskInfo().apply {
@@ -290,5 +269,23 @@
configuration.windowConfiguration.windowingMode =
WindowConfiguration.WINDOWING_MODE_FREEFORM
}
+
+ val OPEN_CHANGE =
+ TransitionInfo.Change(mock(), mock()).apply {
+ mode = WindowManager.TRANSIT_OPEN
+ taskInfo = TASK_INFO_FREEFORM
+ }
+
+ val CLOSE_CHANGE =
+ TransitionInfo.Change(mock(), mock()).apply {
+ mode = WindowManager.TRANSIT_CLOSE
+ taskInfo = TASK_INFO_FREEFORM
+ }
+
+ val MINIMIZE_CHANGE =
+ TransitionInfo.Change(mock(), mock()).apply {
+ mode = WindowManager.TRANSIT_TO_BACK
+ taskInfo = TASK_INFO_FREEFORM
+ }
}
}
diff --git a/res/values/config.xml b/res/values/config.xml
index d65580c..3b48c9e 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -224,4 +224,7 @@
of the same name in SystemUI. -->
<string-array name="config_appsSupportMultiInstancesSplit">
</string-array>
+
+ <!-- Used to differentiate between desktop and non-desktop devices. -->
+ <bool name="desktop_form_factor">false</bool>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 3c1a9c6..a626097 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -549,4 +549,5 @@
<string name="ps_add_button_label">Install</string>
<!-- Content description for install app icon -->
<string name="ps_add_button_content_description">Install apps to Private Space</string>
+ <string name="ps_app_content_description">Add files and more to Private Space</string>
</resources>
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 289f175..5c9392d 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1472,8 +1472,7 @@
// Adding a shortcut to a Folder.
FolderIcon folderIcon = findFolderIcon(container);
if (folderIcon != null) {
- FolderInfo folderInfo = (FolderInfo) folderIcon.getTag();
- folderInfo.add(info, args.rank, false);
+ folderIcon.getFolder().addFolderContent(info, args.rank, false);
} else {
Log.e(TAG, "Could not find folder with id " + container + " to add shortcut.");
}
@@ -1792,7 +1791,6 @@
SettingsCache.INSTANCE.get(this).unregister(TOUCHPAD_NATURAL_SCROLLING,
mNaturalScrollingChangedListener);
ScreenOnTracker.INSTANCE.get(this).removeListener(mScreenOnListener);
- mWorkspace.removeFolderListeners();
PluginManagerWrapper.INSTANCE.get(this).removePluginListener(this);
mModel.removeCallbacks(this);
@@ -2053,9 +2051,10 @@
@Nullable final String reason) {
if (itemInfo instanceof WorkspaceItemInfo) {
View collectionIcon = mWorkspace.getViewByItemId(itemInfo.container);
- if (collectionIcon instanceof FolderIcon) {
+ if (collectionIcon instanceof FolderIcon folderIcon) {
// Remove the shortcut from the folder before removing it from launcher
- ((FolderInfo) collectionIcon.getTag()).remove((WorkspaceItemInfo) itemInfo, true);
+ Folder folder = folderIcon.getFolder();
+ folder.removeFolderContent(true, itemInfo);
} else if (collectionIcon instanceof AppPairIcon appPairIcon) {
removeItem(appPairIcon, appPairIcon.getInfo(), deleteFromDb,
"removing app pair because one of its member apps was removed");
@@ -2066,9 +2065,6 @@
getModelWriter().deleteItemFromDatabase(itemInfo, reason);
}
} else if (itemInfo instanceof CollectionInfo ci) {
- if (v instanceof FolderIcon) {
- ((FolderIcon) v).removeListeners();
- }
mWorkspace.removeWorkspaceItem(v);
if (deleteFromDb) {
getModelWriter().deleteCollectionAndContentsFromDatabase(ci);
diff --git a/src/com/android/launcher3/LauncherModel.kt b/src/com/android/launcher3/LauncherModel.kt
index 02d70ae..557ad67 100644
--- a/src/com/android/launcher3/LauncherModel.kt
+++ b/src/com/android/launcher3/LauncherModel.kt
@@ -86,12 +86,11 @@
private val loaderFactory: LoaderTaskFactory,
private val binderFactory: BaseLauncherBinderFactory,
private val spaceFinderFactory: Provider<WorkspaceItemSpaceFinder>,
+ val modelDbController: ModelDbController,
) {
private val mCallbacksList = ArrayList<BgDataModel.Callbacks>(1)
- val modelDbController = ModelDbController(context)
-
private val mLock = Any()
private var mLoaderTask: LoaderTask? = null
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 03ecf14..acb2b48 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -26,13 +26,13 @@
import android.content.ContentValues;
import android.content.pm.PackageManager;
import android.database.Cursor;
-import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Process;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.model.ModelDbController;
@@ -74,24 +74,20 @@
@Override
public String getType(Uri uri) {
- SqlArguments args = new SqlArguments(uri, null, null);
- if (TextUtils.isEmpty(args.where)) {
- return "vnd.android.cursor.dir/" + args.table;
+ if (TextUtils.isEmpty(parseUri(uri, null, null).first)) {
+ return "vnd.android.cursor.dir/" + Favorites.TABLE_NAME;
} else {
- return "vnd.android.cursor.item/" + args.table;
+ return "vnd.android.cursor.item/" + Favorites.TABLE_NAME;
}
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
- SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
- SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
- qb.setTables(args.table);
-
+ Pair<String, String[]> args = parseUri(uri, selection, selectionArgs);
Cursor[] result = new Cursor[1];
executeControllerTask(controller -> {
- result[0] = controller.query(args.table, projection, args.where, args.args, sortOrder);
+ result[0] = controller.query(projection, args.first, args.second, sortOrder);
return 0;
});
return result[0];
@@ -108,7 +104,7 @@
// attempt allocate and bind the widget.
Integer itemType = values.getAsInteger(Favorites.ITEM_TYPE);
if (itemType != null
- && itemType.intValue() == Favorites.ITEM_TYPE_APPWIDGET
+ && itemType == Favorites.ITEM_TYPE_APPWIDGET
&& !values.containsKey(Favorites.APPWIDGET_ID)) {
ComponentName cn = ComponentName.unflattenFromString(
@@ -135,8 +131,7 @@
}
}
- SqlArguments args = new SqlArguments(uri);
- return controller.insert(args.table, values);
+ return controller.insert(values);
});
return rowId < 0 ? null : ContentUris.withAppendedId(uri, rowId);
@@ -144,14 +139,14 @@
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
- SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
- return executeControllerTask(c -> c.delete(args.table, args.where, args.args));
+ Pair<String, String[]> args = parseUri(uri, selection, selectionArgs);
+ return executeControllerTask(c -> c.delete(args.first, args.second));
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
- return executeControllerTask(c -> c.update(args.table, values, args.where, args.args));
+ Pair<String, String[]> args = parseUri(uri, selection, selectionArgs);
+ return executeControllerTask(c -> c.update(values, args.first, args.second));
}
@Override
@@ -209,35 +204,24 @@
}
}
- static class SqlArguments {
- public final String table;
- public final String where;
- public final String[] args;
-
- SqlArguments(Uri url, String where, String[] args) {
- if (url.getPathSegments().size() == 1) {
- this.table = url.getPathSegments().get(0);
- this.where = where;
- this.args = args;
- } else if (url.getPathSegments().size() != 2) {
- throw new IllegalArgumentException("Invalid URI: " + url);
- } else if (!TextUtils.isEmpty(where)) {
- throw new UnsupportedOperationException("WHERE clause not supported: " + url);
- } else {
- this.table = url.getPathSegments().get(0);
- this.where = "_id=" + ContentUris.parseId(url);
- this.args = null;
+ /**
+ * Parses the uri and returns the where and arg clause.
+ *
+ * Note: This should be called on the binder thread (before posting on any executor) so that
+ * any parsing error gets propagated to the caller.
+ */
+ private static Pair<String, String[]> parseUri(Uri url, String where, String[] args) {
+ switch (url.getPathSegments().size()) {
+ case 1 -> {
+ return Pair.create(where, args);
}
- }
-
- SqlArguments(Uri url) {
- if (url.getPathSegments().size() == 1) {
- table = url.getPathSegments().get(0);
- where = null;
- args = null;
- } else {
- throw new IllegalArgumentException("Invalid URI: " + url);
+ case 2 -> {
+ if (!TextUtils.isEmpty(where)) {
+ throw new UnsupportedOperationException("WHERE clause not supported: " + url);
+ }
+ return Pair.create("_id=" + ContentUris.parseId(url), null);
}
+ default -> throw new IllegalArgumentException("Invalid URI: " + url);
}
}
}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 6ed183a..59f84ab 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -137,7 +137,6 @@
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
-import java.util.stream.Collectors;
/**
* The workspace is a wide area with a wallpaper and a finite number of pages.
@@ -659,7 +658,6 @@
}
// Remove the pages and clear the screen models
- removeFolderListeners();
removeAllViews();
mScreenOrder.clear();
mWorkspaceScreens.clear();
@@ -1914,7 +1912,7 @@
boolean aboveShortcut = Folder.willAccept(dropOverView.getTag())
&& ((ItemInfo) dropOverView.getTag()).container != CONTAINER_HOTSEAT_PREDICTION;
- boolean willBecomeShortcut = Folder.willAcceptItemType(info.itemType);
+ boolean willBecomeShortcut = FolderInfo.willAcceptItemType(info.itemType);
return (aboveShortcut && willBecomeShortcut);
}
@@ -1994,8 +1992,8 @@
fi.performCreateAnimation(destInfo, v, sourceInfo, d, folderLocation, scale);
} else {
fi.prepareCreateAnimation(v);
- fi.addItem(destInfo);
- fi.addItem(sourceInfo);
+ fi.getFolder().addFolderContent(destInfo);
+ fi.getFolder().addFolderContent(sourceInfo);
}
return true;
}
@@ -3209,21 +3207,6 @@
});
}
- /**
- * Removes all folder listeners
- */
- public void removeFolderListeners() {
- mapOverItems(new ItemOperator() {
- @Override
- public boolean evaluate(ItemInfo info, View view) {
- if (view instanceof FolderIcon) {
- ((FolderIcon) view).removeListeners();
- }
- return false;
- }
- });
- }
-
public boolean isDropEnabled() {
return true;
}
@@ -3349,15 +3332,15 @@
if (child instanceof DropTarget) {
mDragController.removeDropTarget((DropTarget) child);
}
- } else if (child instanceof FolderIcon) {
+ } else if (child instanceof FolderIcon folderIcon) {
FolderInfo folderInfo = (FolderInfo) info;
- List<ItemInfo> matches = folderInfo.getContents().stream()
+ ItemInfo[] matches = folderInfo.getContents().stream()
.filter(matcher)
- .collect(Collectors.toList());
- if (!matches.isEmpty()) {
- folderInfo.removeAll(matches, false);
- if (((FolderIcon) child).getFolder().isOpen()) {
- ((FolderIcon) child).getFolder().close(false /* animate */);
+ .toArray(ItemInfo[]::new);
+ if (matches.length > 0) {
+ folderIcon.getFolder().removeFolderContent(false, matches);
+ if (folderIcon.getFolder().isOpen()) {
+ folderIcon.getFolder().close(false /* animate */);
}
}
} else if (info instanceof AppPairInfo api) {
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index cd91f8e..df34ccf 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -519,7 +519,7 @@
Folder folder = Folder.getOpen(mContext);
folder.close(true);
WorkspaceItemInfo info = (WorkspaceItemInfo) item;
- folder.getInfo().remove(info, false);
+ folder.removeFolderContent(false, info);
final int[] coordinates = new int[2];
final int screenId = findSpaceOnWorkspace(item, coordinates);
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 870c891..f223eaa 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -436,6 +436,8 @@
if (currentItem.itemInfo != null && Objects.equals(
currentItem.itemInfo.getTargetPackage(), PRIVATE_SPACE_PACKAGE)) {
currentItem.itemInfo.bitmap.creationFlags |= FLAG_NO_BADGE;
+ currentItem.itemInfo.contentDescription =
+ mPrivateProviderManager.getPsAppContentDesc();
privateSpaceAppIndex = i;
}
}
diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java
index 609edd2..1bc1b17 100644
--- a/src/com/android/launcher3/allapps/PrivateProfileManager.java
+++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java
@@ -142,6 +142,7 @@
private PrivateSpaceSettingsButton mPrivateSpaceSettingsButton;
@Nullable
private ConstraintLayout mFloatingMaskView;
+ private final String mPrivateSpaceAppContentDesc;
private final String mLockedStateContentDesc;
private final String mUnLockedStateContentDesc;
@@ -157,6 +158,8 @@
UI_HELPER_EXECUTOR.post(() -> initializeInBackgroundThread(appContext));
mPsHeaderHeight = mAllApps.getContext().getResources().getDimensionPixelSize(
R.dimen.ps_header_height);
+ mPrivateSpaceAppContentDesc = mAllApps.getContext()
+ .getString(R.string.ps_app_content_description);
mLockedStateContentDesc = mAllApps.getContext()
.getString(R.string.ps_container_lock_button_content_description);
mUnLockedStateContentDesc = mAllApps.getContext()
@@ -929,6 +932,10 @@
return mPsHeaderHeight;
}
+ String getPsAppContentDesc() {
+ return mPrivateSpaceAppContentDesc;
+ }
+
boolean isPrivateSpaceItem(BaseAllAppsAdapter.AdapterItem item) {
return getItemInfoMatcher().test(item.itemInfo) || item.decorationInfo != null
|| (item.itemInfo instanceof PrivateSpaceInstallAppButtonInfo);
diff --git a/src/com/android/launcher3/dot/FolderDotInfo.java b/src/com/android/launcher3/dot/FolderDotInfo.java
index 54800a0..cb91db7 100644
--- a/src/com/android/launcher3/dot/FolderDotInfo.java
+++ b/src/com/android/launcher3/dot/FolderDotInfo.java
@@ -30,6 +30,10 @@
private int mNumNotifications;
+ public void reset() {
+ mNumNotifications = 0;
+ }
+
public void addDotInfo(DotInfo dotToAdd) {
if (dotToAdd == null) {
return;
@@ -39,15 +43,6 @@
mNumNotifications, MIN_COUNT, DotInfo.MAX_COUNT);
}
- public void subtractDotInfo(DotInfo dotToSubtract) {
- if (dotToSubtract == null) {
- return;
- }
- mNumNotifications -= dotToSubtract.getNotificationKeys().size();
- mNumNotifications = Utilities.boundToRange(
- mNumNotifications, MIN_COUNT, DotInfo.MAX_COUNT);
- }
-
@Override
public int getNotificationCount() {
return mNumNotifications;
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 0ae9519..967af05 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -19,9 +19,6 @@
import static android.text.TextUtils.isEmpty;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
-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.LauncherState.EDIT_MODE;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
@@ -29,6 +26,7 @@
import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED;
+import static com.android.launcher3.model.data.FolderInfo.willAcceptItemType;
import static com.android.launcher3.testing.shared.TestProtocol.FOLDER_OPENED_MESSAGE;
import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
@@ -95,7 +93,6 @@
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.model.data.FolderInfo;
-import com.android.launcher3.model.data.FolderInfo.FolderListener;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemFactory;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -111,6 +108,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
@@ -122,7 +120,7 @@
* Represents a set of icons chosen by the user or generated by the system.
*/
public class Folder extends AbstractFloatingView implements ClipPathView, DragSource,
- View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener,
+ View.OnLongClickListener, DropTarget, TextView.OnEditorActionListener,
View.OnFocusChangeListener, DragListener, ExtendedEditText.OnBackKeyListener,
LauncherBindableItemsContainer {
private static final String TAG = "Launcher.Folder";
@@ -179,15 +177,6 @@
return o instanceof ItemInfo info && willAcceptItemType(info.itemType);
}
- /**
- * Checks if {@code itemType} is a type that can be placed in folders.
- */
- public static boolean willAcceptItemType(int itemType) {
- return itemType == ITEM_TYPE_APPLICATION
- || itemType == ITEM_TYPE_DEEP_SHORTCUT
- || itemType == ITEM_TYPE_APP_PAIR;
- }
-
private Alarm mReorderAlarm = new Alarm(Looper.getMainLooper());
private Alarm mOnExitAlarm = new Alarm(Looper.getMainLooper());
private Alarm mOnScrollHintAlarm = new Alarm(Looper.getMainLooper());
@@ -243,7 +232,10 @@
private boolean mIsExternalDrag;
private boolean mIsDragInProgress = false;
private boolean mDeleteFolderOnDropCompleted = false;
+
private boolean mSuppressFolderDeletion = false;
+ private boolean mSuppressContentUpdate = false;
+
private boolean mItemAddedBackToSelfViaIcon = false;
private boolean mIsEditingName = false;
@@ -385,9 +377,8 @@
// We do not want to get events for the item being removed, as they will get handled
// when the drop completes
- try (SuppressInfoChanges s = new SuppressInfoChanges()) {
- mInfo.remove(dragObject.dragInfo, true);
- }
+ executeWithContentUpdateSuppressed(() -> removeFolderContent(true, dragObject.dragInfo));
+
mIsDragInProgress = true;
mItemAddedBackToSelfViaIcon = false;
}
@@ -532,16 +523,7 @@
lp.customPosition = true;
setLayoutParams(lp);
}
- mItemsInvalidated = true;
- mInfo.addListener(this);
-
- if (!isEmpty(mInfo.title)) {
- mFolderName.setText(mInfo.title);
- mFolderName.setHint(null);
- } else {
- mFolderName.setText("");
- mFolderName.setHint(R.string.folder_hint_text);
- }
+ reapplyItemInfo();
// In case any children didn't come across during loading, clean up the folder accordingly
mFolderIcon.post(() -> {
if (getItemCount() <= 1) {
@@ -550,6 +532,17 @@
});
}
+ public void reapplyItemInfo() {
+ mItemsInvalidated = true;
+
+ if (!isEmpty(mInfo.title)) {
+ mFolderName.setText(mInfo.title);
+ mFolderName.setHint(null);
+ } else {
+ mFolderName.setText("");
+ mFolderName.setHint(R.string.folder_hint_text);
+ }
+ }
/**
* Show suggested folder title in FolderEditText if the first suggestion is non-empty, push
@@ -680,7 +673,6 @@
if (!shouldAnimateOpen(items)) {
return;
}
-
Folder openFolder = getOpen(mActivityContext);
closeOpenFolder(openFolder);
@@ -954,9 +946,7 @@
@Override
public boolean acceptDrop(DragObject d) {
- final ItemInfo item = d.dragInfo;
- final int itemType = item.itemType;
- return Folder.willAcceptItemType(itemType);
+ return willAcceptItemType(d.dragInfo.itemType);
}
public void onDragEnter(DragObject d) {
@@ -1121,9 +1111,8 @@
mContent.arrangeChildren(views);
mItemsInvalidated = true;
- try (SuppressInfoChanges s = new SuppressInfoChanges()) {
- mFolderIcon.onDrop(d, true /* itemReturnedOnFailedDrop */);
- }
+ executeWithContentUpdateSuppressed(
+ () -> mFolderIcon.onDrop(d, true /* itemReturnedOnFailedDrop */));
}
}
@@ -1417,9 +1406,7 @@
rearrangeChildren();
// Temporarily suppress the listener, as we did all the work already here.
- try (SuppressInfoChanges s = new SuppressInfoChanges()) {
- mInfo.add(si, mEmptyCellRank, false);
- }
+ executeWithContentUpdateSuppressed(() -> addFolderContent(si, mEmptyCellRank, false));
// We only need to update the locations if it doesn't get handled in
// #onDropCompleted.
@@ -1465,37 +1452,66 @@
}
}
- @Override
- public void onAdd(ItemInfo item, int rank) {
- FolderGridOrganizer verifier = createFolderGridOrganizer(
- mActivityContext.getDeviceProfile()).setFolderInfo(mInfo);
- verifier.updateRankAndPos(item, rank);
- mLauncherDelegate.getModelWriter().addOrMoveItemInDatabase(item, mInfo.id, 0, item.cellX,
- item.cellY);
- updateItemLocationsInDatabaseBatch(false);
-
- if (mContent.areViewsBound()) {
- mContent.createAndAddViewForRank(item, rank);
- }
- mItemsInvalidated = true;
+ /** Add an app or shortcut */
+ public void addFolderContent(ItemInfo item) {
+ addFolderContent(item, mInfo.getContents().size(), true);
}
- @Override
- public void onRemove(List<ItemInfo> items) {
- mItemsInvalidated = true;
- items.stream().map(this::getViewForInfo).forEach(mContent::removeItem);
- if (mState == STATE_ANIMATING) {
- mRearrangeOnClose = true;
- } else {
- rearrangeChildren();
+ /** Add an app or shortcut for a specified rank */
+ public void addFolderContent(ItemInfo item, int rank, boolean animate) {
+ if (!willAcceptItemType(item.itemType)) {
+ throw new RuntimeException("tried to add an illegal type into a folder");
}
- if (getItemCount() <= 1) {
- if (mIsOpen) {
- close(true);
- } else {
- replaceFolderWithFinalItem();
+
+ rank = Utilities.boundToRange(rank, 0, mInfo.getContents().size());
+ mInfo.getContents().add(rank, item);
+
+ if (!mSuppressContentUpdate) {
+ FolderGridOrganizer verifier = createFolderGridOrganizer(
+ mActivityContext.getDeviceProfile()).setFolderInfo(mInfo);
+ verifier.updateRankAndPos(item, rank);
+ mLauncherDelegate.getModelWriter().addOrMoveItemInDatabase(item, mInfo.id, 0,
+ item.cellX,
+ item.cellY);
+ updateItemLocationsInDatabaseBatch(false);
+
+ if (mContent.areViewsBound()) {
+ mContent.createAndAddViewForRank(item, rank);
}
+ mItemsInvalidated = true;
+ updateTextViewFocus();
}
+
+ mLauncherDelegate.getModelWriter().notifyItemModified(mInfo);
+ mFolderIcon.onItemsChanged(animate);
+ }
+
+ /** Remove all matching app or shortcut. Does not change the DB. */
+ public void removeFolderContent(boolean animate, ItemInfo... items) {
+ List<ItemInfo> itemArray = Arrays.asList(items);
+ if (mInfo.getContents().removeAll(itemArray)) {
+ mLauncherDelegate.getModelWriter().notifyItemModified(mInfo);
+ }
+
+ if (!mSuppressContentUpdate) {
+ mItemsInvalidated = true;
+ itemArray.forEach(item -> mContent.removeItem(getViewForInfo(item)));
+ if (mState == STATE_ANIMATING) {
+ mRearrangeOnClose = true;
+ } else {
+ rearrangeChildren();
+ }
+ if (getItemCount() <= 1) {
+ if (mIsOpen) {
+ close(true);
+ } else {
+ replaceFolderWithFinalItem();
+ }
+ }
+ updateTextViewFocus();
+ }
+
+ mFolderIcon.onItemsChanged(animate);
}
@VisibleForTesting
@@ -1503,16 +1519,6 @@
return mContent.iterateOverItems((info, view) -> info == item);
}
- @Override
- public void onItemsChanged(boolean animate) {
- updateTextViewFocus();
- }
-
- @Override
- public void onTitleChanged(CharSequence title) {
- mFolderName.setText(title);
- }
-
/**
* Utility methods to iterate over items of the view
*/
@@ -1666,18 +1672,14 @@
}
};
- /**
- * Temporary resource held while we don't want to handle info changes
- */
- private class SuppressInfoChanges implements AutoCloseable {
-
- SuppressInfoChanges() {
- mInfo.removeListener(Folder.this);
- }
-
- @Override
- public void close() {
- mInfo.addListener(Folder.this);
+ /** Executes the task while suppressing the content update for the folder */
+ private void executeWithContentUpdateSuppressed(Runnable task) {
+ if (mSuppressContentUpdate) {
+ task.run();
+ } else {
+ mSuppressContentUpdate = true;
+ task.run();
+ mSuppressContentUpdate = false;
updateTextViewFocus();
}
}
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 0ed8787..1cd9bcc 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -23,6 +23,7 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_AUTO_LABELED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_PRIMARY;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_SUGGESTIONS;
+import static com.android.launcher3.model.data.FolderInfo.willAcceptItemType;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -73,7 +74,6 @@
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.FolderInfo;
-import com.android.launcher3.model.data.FolderInfo.FolderListener;
import com.android.launcher3.model.data.FolderInfo.LabelState;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemFactory;
@@ -92,7 +92,7 @@
/**
* An icon that can appear on in the workspace representing an {@link Folder}.
*/
-public class FolderIcon extends FrameLayout implements FolderListener, FloatingIconViewCompanion,
+public class FolderIcon extends FrameLayout implements FloatingIconViewCompanion,
DraggableView, Reorderable {
private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this);
@@ -127,7 +127,7 @@
private boolean mForceHideDot;
@ViewDebug.ExportedProperty(category = "launcher", deepExport = true)
- private FolderDotInfo mDotInfo = new FolderDotInfo();
+ private final FolderDotInfo mDotInfo = new FolderDotInfo();
private DotRenderer mDotRenderer;
@ViewDebug.ExportedProperty(category = "launcher", deepExport = true)
private DotRenderer.DrawParams mDotParams;
@@ -178,7 +178,6 @@
folder.bind(folderInfo);
icon.setFolder(folder);
- folderInfo.addListener(icon);
return icon;
}
@@ -217,13 +216,7 @@
icon.mDotRenderer = grid.mDotRendererWorkSpace;
icon.setContentDescription(icon.getAccessiblityTitle(folderInfo.title));
-
- // Keep the notification dot up to date with the sum of all the content's dots.
- FolderDotInfo folderDotInfo = new FolderDotInfo();
- for (ItemInfo si : folderInfo.getContents()) {
- folderDotInfo.addDotInfo(activity.getDotInfoForItem(si));
- }
- icon.setDotInfo(folderDotInfo);
+ icon.updateDotInfo();
icon.setAccessibilityDelegate(activity.getAccessibilityDelegate());
@@ -264,22 +257,13 @@
}
private boolean willAcceptItem(ItemInfo item) {
- final int itemType = item.itemType;
- return (Folder.willAcceptItemType(itemType) && item != mInfo && !mFolder.isOpen());
+ return (willAcceptItemType(item.itemType) && item != mInfo && !mFolder.isOpen());
}
public boolean acceptDrop(ItemInfo dragInfo) {
return !mFolder.isDestroyed() && willAcceptItem(dragInfo);
}
- public void addItem(ItemInfo item) {
- mInfo.add(item, true);
- }
-
- public void removeItem(ItemInfo item, boolean animate) {
- mInfo.remove(item, animate);
- }
-
public void onDragEnter(ItemInfo dragInfo) {
if (mFolder.isDestroyed() || !willAcceptItem(dragInfo)) return;
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) getLayoutParams();
@@ -308,9 +292,8 @@
public void performCreateAnimation(final ItemInfo destInfo, final View destView,
final ItemInfo srcInfo, final DragObject d, Rect dstRect,
float scaleRelativeToDragLayer) {
- final DragView srcView = d.dragView;
prepareCreateAnimation(destView);
- addItem(destInfo);
+ getFolder().addFolderContent(destInfo);
// This will animate the first item from it's position as an icon into its
// position as the first item in the preview
mPreviewItemManager.createFirstItemAnimation(false /* reverse */, null)
@@ -364,7 +347,7 @@
boolean itemAdded = false;
if (itemReturnedOnFailedDrop || index >= MAX_NUM_ITEMS_IN_PREVIEW) {
List<ItemInfo> oldPreviewItems = new ArrayList<>(mCurrentPreviewItems);
- mInfo.add(item, index, false);
+ getFolder().addFolderContent(item, index, false);
mCurrentPreviewItems.clear();
mCurrentPreviewItems.addAll(getPreviewItemsOnPage(0));
@@ -380,12 +363,12 @@
mPreviewItemManager.onDrop(oldPreviewItems, mCurrentPreviewItems, item);
itemAdded = true;
} else {
- removeItem(item, false);
+ getFolder().removeFolderContent(false, item);
}
}
if (!itemAdded) {
- mInfo.add(item, index, true);
+ getFolder().addFolderContent(item, index, true);
}
int[] center = new int[2];
@@ -431,7 +414,7 @@
}, DROP_IN_ANIMATION_DURATION);
});
} else {
- addItem(item);
+ getFolder().addFolderContent(item);
}
}
@@ -500,9 +483,23 @@
);
}
- public void setDotInfo(FolderDotInfo dotInfo) {
- updateDotScale(mDotInfo.hasDot(), dotInfo.hasDot());
- mDotInfo = dotInfo;
+ /** Keep the notification dot up to date with the sum of all the content's dots. */
+ public void updateDotInfo() {
+ boolean hadDot = mDotInfo.hasDot();
+ mDotInfo.reset();
+ for (ItemInfo si : mInfo.getContents()) {
+ mDotInfo.addDotInfo(mActivity.getDotInfoForItem(si));
+ }
+ boolean isDotted = mDotInfo.hasDot();
+ float newDotScale = isDotted ? 1f : 0f;
+ // Animate when a dot is first added or when it is removed.
+ if ((hadDot ^ isDotted) && isShown()) {
+ animateDotScale(newDotScale);
+ } else {
+ cancelDotScaleAnim();
+ mDotScale = newDotScale;
+ invalidate();
+ }
}
public ClippedFolderIconLayoutRule getLayoutRule() {
@@ -523,22 +520,6 @@
}
}
- /**
- * Sets mDotScale to 1 or 0, animating if wasDotted or isDotted is false
- * (the dot is being added or removed).
- */
- private void updateDotScale(boolean wasDotted, boolean isDotted) {
- float newDotScale = isDotted ? 1f : 0f;
- // Animate when a dot is first added or when it is removed.
- if ((wasDotted ^ isDotted) && isShown()) {
- animateDotScale(newDotScale);
- } else {
- cancelDotScaleAnim();
- mDotScale = newDotScale;
- invalidate();
- }
- }
-
private void cancelDotScaleAnim() {
if (mDotScaleAnim != null) {
mDotScaleAnim.cancel();
@@ -682,13 +663,6 @@
return mPreviewItemManager.verifyDrawable(who) || super.verifyDrawable(who);
}
- @Override
- public void onItemsChanged(boolean animate) {
- updatePreviewItems(animate);
- invalidate();
- requestLayout();
- }
-
private void updatePreviewItems(boolean animate) {
mPreviewItemManager.updatePreviewItems(animate);
mCurrentPreviewItems.clear();
@@ -702,31 +676,15 @@
mPreviewItemManager.updatePreviewItems(itemCheck);
}
- @Override
- public void onAdd(ItemInfo item, int rank) {
+ public void onItemsChanged(boolean animate) {
updatePreviewItems(false);
- boolean wasDotted = mDotInfo.hasDot();
- mDotInfo.addDotInfo(mActivity.getDotInfoForItem(item));
- boolean isDotted = mDotInfo.hasDot();
- updateDotScale(wasDotted, isDotted);
+ updateDotInfo();
setContentDescription(getAccessiblityTitle(mInfo.title));
+ updatePreviewItems(animate);
invalidate();
requestLayout();
}
- @Override
- public void onRemove(List<ItemInfo> items) {
- updatePreviewItems(false);
- boolean wasDotted = mDotInfo.hasDot();
- items.stream().map(mActivity::getDotInfoForItem).forEach(mDotInfo::subtractDotInfo);
- boolean isDotted = mDotInfo.hasDot();
- updateDotScale(wasDotted, isDotted);
- setContentDescription(getAccessiblityTitle(mInfo.title));
- invalidate();
- requestLayout();
- }
-
- @Override
public void onTitleChanged(CharSequence title) {
mFolderName.setText(title);
setContentDescription(getAccessiblityTitle(title));
@@ -762,11 +720,6 @@
mLongPressHelper.cancelLongPress();
}
- public void removeListeners() {
- mInfo.removeListener(this);
- mInfo.removeListener(mFolder);
- }
-
private boolean isInHotseat() {
return mInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT;
}
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index 5a9b9c2..457d12e 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -21,7 +21,6 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.launcher3.LauncherPrefs.GRID_NAME;
-import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.graphics.ThemeManager.PREF_ICON_SHAPE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -179,7 +178,7 @@
ModelDbController mainController =
LauncherAppState.getInstance(mContext).getModel().getModelDbController();
- try (Cursor c = mainController.query(TABLE_NAME,
+ try (Cursor c = mainController.query(
new String[] {
LauncherSettings.Favorites.APPWIDGET_ID,
LauncherSettings.Favorites.SPANX,
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 77d0d76..efe6157 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -21,7 +21,6 @@
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;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
@@ -466,7 +465,7 @@
public boolean commitDeleted() {
if (mItemsToRemove.size() > 0) {
// Remove dead items
- mModel.getModelDbController().delete(TABLE_NAME,
+ mModel.getModelDbController().delete(
Utilities.createDbSelectionQuery(Favorites._ID, mItemsToRemove), null);
return true;
}
@@ -492,7 +491,7 @@
// Update restored items that no longer require special handling
ContentValues values = new ContentValues();
values.put(Favorites.RESTORED, 0);
- mModel.getModelDbController().update(TABLE_NAME, values,
+ mModel.getModelDbController().update(values,
Utilities.createDbSelectionQuery(Favorites._ID, mRestoredRows), null);
}
if (mRestoreEventLogger != null) {
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 73af6a2..78e5d89 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -23,7 +23,6 @@
import static com.android.launcher3.LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE;
import static com.android.launcher3.LauncherPrefs.SHOULD_SHOW_SMARTSPACE;
import static com.android.launcher3.LauncherSettings.Favorites.DESKTOP_ICON_FLAG;
-import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.icons.CacheableShortcutInfo.convertShortcutsToCacheableShortcuts;
import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
@@ -485,7 +484,7 @@
mShortcutKeyToPinnedShortcuts = new HashMap<>();
final LoaderCursor c = mLoaderCursorFactory.createLoaderCursor(
- dbController.query(TABLE_NAME, null, selection, null, null),
+ dbController.query(null, selection, null, null),
mUserManagerState,
mIsRestoreFromBackup ? restoreEventLogger : null);
final Bundle extras = c.getExtras();
diff --git a/src/com/android/launcher3/model/ModelDbController.java b/src/com/android/launcher3/model/ModelDbController.java
index feae632..64b9c1c 100644
--- a/src/com/android/launcher3/model/ModelDbController.java
+++ b/src/com/android/launcher3/model/ModelDbController.java
@@ -73,6 +73,8 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.backuprestore.LauncherRestoreEventLogger;
import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.provider.LauncherDbUtils;
@@ -91,10 +93,13 @@
import java.util.List;
import java.util.stream.Collectors;
+import javax.inject.Inject;
+
/**
* Utility class which maintains an instance of Launcher database and provides utility methods
* around it.
*/
+@LauncherAppSingleton
public class ModelDbController {
private static final String TAG = "ModelDbController";
@@ -105,17 +110,25 @@
protected DatabaseHelper mOpenHelper;
private final Context mContext;
+ private final InvariantDeviceProfile mIdp;
+ private final LauncherPrefs mPrefs;
+ private final UserCache mUserCache;
- public ModelDbController(Context context) {
+ @Inject
+ ModelDbController(
+ @ApplicationContext Context context,
+ InvariantDeviceProfile idp,
+ LauncherPrefs prefs,
+ UserCache userCache) {
mContext = context;
+ mIdp = idp;
+ mPrefs = prefs;
+ mUserCache = userCache;
}
private void printDBs(String prefix) {
try {
- File directory = new File(
- mContext.getDatabasePath(InvariantDeviceProfile.INSTANCE.get(mContext).dbFile)
- .getParent()
- );
+ File directory = new File(mContext.getDatabasePath(mIdp.dbFile).getParent());
if (directory.exists()) {
for (File file : directory.listFiles()) {
Log.d("b/353505773", prefix + "Database file: " + file.getName());
@@ -130,9 +143,9 @@
private synchronized void createDbIfNotExists() {
if (mOpenHelper == null) {
- String dbFile = LauncherPrefs.get(mContext).get(DB_FILE);
+ String dbFile = mPrefs.get(DB_FILE);
if (dbFile.isEmpty()) {
- dbFile = InvariantDeviceProfile.INSTANCE.get(mContext).dbFile;
+ dbFile = mIdp.dbFile;
}
mOpenHelper = createDatabaseHelper(false /* forMigration */, dbFile);
printDBs("before: ");
@@ -144,7 +157,7 @@
protected DatabaseHelper createDatabaseHelper(boolean forMigration, String dbFile) {
// Set the flag for empty DB
Runnable onEmptyDbCreateCallback = forMigration ? () -> { }
- : () -> LauncherPrefs.get(mContext).putSync(getEmptyDbCreatedKey(dbFile).to(true));
+ : () -> mPrefs.putSync(getEmptyDbCreatedKey(dbFile).to(true));
DatabaseHelper databaseHelper = new DatabaseHelper(mContext, dbFile,
this::getSerialNumberForUser, onEmptyDbCreateCallback);
@@ -169,12 +182,12 @@
* Refer {@link SQLiteDatabase#query}
*/
@WorkerThread
- public Cursor query(String table, String[] projection, String selection,
+ public Cursor query(String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
createDbIfNotExists();
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
Cursor result = db.query(
- table, projection, selection, selectionArgs, null, null, sortOrder);
+ TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
final Bundle extra = new Bundle();
extra.putString(EXTRA_DB_NAME, mOpenHelper.getDatabaseName());
@@ -186,12 +199,12 @@
* Refer {@link SQLiteDatabase#insert(String, String, ContentValues)}
*/
@WorkerThread
- public int insert(String table, ContentValues initialValues) {
+ public int insert(ContentValues initialValues) {
createDbIfNotExists();
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
addModifiedTime(initialValues);
- int rowId = mOpenHelper.dbInsertAndCheck(db, table, initialValues);
+ int rowId = mOpenHelper.dbInsertAndCheck(db, TABLE_NAME, initialValues);
if (rowId >= 0) {
onAddOrDeleteOp(db);
}
@@ -202,11 +215,11 @@
* Refer {@link SQLiteDatabase#delete(String, String, String[])}
*/
@WorkerThread
- public int delete(String table, String selection, String[] selectionArgs) {
+ public int delete(String selection, String[] selectionArgs) {
createDbIfNotExists();
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- int count = db.delete(table, selection, selectionArgs);
+ int count = db.delete(TABLE_NAME, selection, selectionArgs);
if (count > 0) {
onAddOrDeleteOp(db);
}
@@ -217,14 +230,12 @@
* Refer {@link SQLiteDatabase#update(String, ContentValues, String, String[])}
*/
@WorkerThread
- public int update(String table, ContentValues values,
- String selection, String[] selectionArgs) {
+ public int update(ContentValues values, String selection, String[] selectionArgs) {
createDbIfNotExists();
addModifiedTime(values);
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- int count = db.update(table, values, selection, selectionArgs);
- return count;
+ return db.update(TABLE_NAME, values, selection, selectionArgs);
}
/**
@@ -261,7 +272,7 @@
public void createEmptyDB() {
createDbIfNotExists();
mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
- LauncherPrefs.get(mContext).putSync(getEmptyDbCreatedKey().to(true));
+ mPrefs.putSync(getEmptyDbCreatedKey().to(true));
}
/**
@@ -292,7 +303,6 @@
mOpenHelper.getReadableDatabase(), Favorites.HYBRID_HOTSEAT_BACKUP_TABLE);
}
-
/**
* Resets the launcher DB if we should reset it.
*/
@@ -302,11 +312,10 @@
}
FileLog.d(TAG, "resetLauncherDb: Migration failed: resetting launcher database");
createEmptyDB();
- LauncherPrefs.get(mContext).putSync(
- getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()).to(true));
+ mPrefs.putSync(getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()).to(true));
// Write the grid state to avoid another migration
- new DeviceGridState(LauncherAppState.getIDP(mContext)).writeToPrefs(mContext);
+ new DeviceGridState(mIdp).writeToPrefs(mContext);
}
/**
@@ -326,7 +335,7 @@
}
private boolean isThereExistingDb() {
- if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey())) {
+ if (mPrefs.get(getEmptyDbCreatedKey())) {
// If we already have a new DB, ignore migration
FileLog.d(TAG, "isThereExistingDb: new DB already created, skipping migration");
return true;
@@ -335,8 +344,7 @@
}
private boolean isGridMigrationNecessary() {
- InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
- if (GridSizeMigrationDBController.needsToMigrate(mContext, idp)) {
+ if (GridSizeMigrationDBController.needsToMigrate(mContext, mIdp)) {
return true;
}
FileLog.d(TAG, "isGridMigrationNecessary: no grid migration needed");
@@ -344,8 +352,7 @@
}
private boolean isCurrentDbSameAsTarget() {
- InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
- String targetDbName = new DeviceGridState(idp).getDbFile();
+ String targetDbName = new DeviceGridState(mIdp).getDbFile();
if (TextUtils.equals(targetDbName, mOpenHelper.getDatabaseName())) {
FileLog.e(TAG, "isCurrentDbSameAsTarget: target db is same as current"
+ " current db: " + mOpenHelper.getDatabaseName()
@@ -367,7 +374,6 @@
return;
}
- InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
DatabaseHelper oldHelper = mOpenHelper;
// We save the existing db's before creating the destination db helper so we know what logic
@@ -376,12 +382,12 @@
.filter(dbName -> mContext.getDatabasePath(dbName).exists())
.collect(Collectors.toList());
- mOpenHelper = createDatabaseHelper(true, new DeviceGridState(idp).getDbFile());
+ mOpenHelper = createDatabaseHelper(true, new DeviceGridState(mIdp).getDbFile());
try {
// This is the current grid we have, given by the mContext
DeviceGridState srcDeviceState = new DeviceGridState(mContext);
// This is the state we want to migrate to that is given by the idp
- DeviceGridState destDeviceState = new DeviceGridState(idp);
+ DeviceGridState destDeviceState = new DeviceGridState(mIdp);
boolean isDestNewDb = !existingDBs.contains(destDeviceState.getDbFile());
GridSizeMigrationLogic gridSizeMigrationLogic = new GridSizeMigrationLogic();
@@ -404,10 +410,10 @@
ModelDelegate modelDelegate) {
if (!migrateGridIfNeeded(modelDelegate)) {
if (restoreEventLogger != null) {
- if (LauncherPrefs.get(mContext).get(NO_DB_FILES_RESTORED)) {
+ if (mPrefs.get(NO_DB_FILES_RESTORED)) {
restoreEventLogger.logLauncherItemsRestoreFailed(DATA_TYPE_DB_FILE, 1,
RestoreError.DATABASE_FILE_NOT_RESTORED);
- LauncherPrefs.get(mContext).put(NO_DB_FILES_RESTORED, false);
+ mPrefs.put(NO_DB_FILES_RESTORED, false);
FileLog.d(TAG, "There is no data to migrate: resetting launcher database");
} else {
restoreEventLogger.logLauncherItemsRestored(DATA_TYPE_DB_FILE, 1);
@@ -416,11 +422,10 @@
}
FileLog.d(TAG, "tryMigrateDB: Migration failed: resetting launcher database");
createEmptyDB();
- LauncherPrefs.get(mContext).putSync(
- getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()).to(true));
+ mPrefs.putSync(getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()).to(true));
// Write the grid state to avoid another migration
- new DeviceGridState(LauncherAppState.getIDP(mContext)).writeToPrefs(mContext);
+ new DeviceGridState(mIdp).writeToPrefs(mContext);
} else if (restoreEventLogger != null) {
restoreEventLogger.logLauncherItemsRestored(DATA_TYPE_DB_FILE, 1);
}
@@ -434,17 +439,16 @@
*/
private boolean migrateGridIfNeeded(ModelDelegate modelDelegate) {
createDbIfNotExists();
- if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey())) {
+ if (mPrefs.get(getEmptyDbCreatedKey())) {
// If we have already create a new DB, ignore migration
FileLog.d(TAG, "migrateGridIfNeeded: new DB already created, skipping migration");
return false;
}
- InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
- if (!GridSizeMigrationDBController.needsToMigrate(mContext, idp)) {
+ if (!GridSizeMigrationDBController.needsToMigrate(mContext, mIdp)) {
FileLog.d(TAG, "migrateGridIfNeeded: no grid migration needed");
return true;
}
- String targetDbName = new DeviceGridState(idp).getDbFile();
+ String targetDbName = new DeviceGridState(mIdp).getDbFile();
if (TextUtils.equals(targetDbName, mOpenHelper.getDatabaseName())) {
FileLog.e(TAG, "migrateGridIfNeeded: target db is same as current"
+ " current db: " + mOpenHelper.getDatabaseName()
@@ -462,7 +466,7 @@
// This is the current grid we have, given by the mContext
DeviceGridState srcDeviceState = new DeviceGridState(mContext);
// This is the state we want to migrate to that is given by the idp
- DeviceGridState destDeviceState = new DeviceGridState(idp);
+ DeviceGridState destDeviceState = new DeviceGridState(mIdp);
boolean isDestNewDb = !existingDBs.contains(destDeviceState.getDbFile());
return GridSizeMigrationDBController.migrateGridIfNeeded(mContext, srcDeviceState,
destDeviceState, mOpenHelper, oldHelper.getWritableDatabase(), isDestNewDb,
@@ -611,7 +615,7 @@
}
private void clearFlagEmptyDbCreated() {
- LauncherPrefs.get(mContext).removeSync(getEmptyDbCreatedKey());
+ mPrefs.removeSync(getEmptyDbCreatedKey());
}
/**
@@ -625,7 +629,7 @@
public synchronized void loadDefaultFavoritesIfNecessary() {
createDbIfNotExists();
- if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey())) {
+ if (mPrefs.get(getEmptyDbCreatedKey())) {
Log.d(TAG, "loading default workspace");
LauncherWidgetHolder widgetHolder = mOpenHelper.newLauncherWidgetHolder();
@@ -737,10 +741,9 @@
}
private DefaultLayoutParser getDefaultLayoutParser(LauncherWidgetHolder widgetHolder) {
- InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
- int defaultLayout = idp.demoModeLayoutId != 0
+ int defaultLayout = mIdp.demoModeLayoutId != 0
&& mContext.getSystemService(UserManager.class).isDemoUser()
- ? idp.demoModeLayoutId : idp.defaultLayoutId;
+ ? mIdp.demoModeLayoutId : mIdp.defaultLayoutId;
return new DefaultLayoutParser(mContext, widgetHolder,
mOpenHelper, mContext.getResources(), defaultLayout);
@@ -766,6 +769,6 @@
* Returns the serial number for the provided user
*/
public long getSerialNumberForUser(UserHandle user) {
- return UserCache.INSTANCE.get(mContext).getSerialNumberForUser(user);
+ return mUserCache.getSerialNumberForUser(user);
}
}
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index 0332775..2650e03 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -16,7 +16,6 @@
package com.android.launcher3.model;
-import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.provider.LauncherDbUtils.itemIdMatch;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -229,7 +228,7 @@
}).executeOnModelThread();
}
- private void notifyItemModified(ItemInfo item) {
+ public void notifyItemModified(ItemInfo item) {
notifyOtherCallbacks(c -> c.bindItemsModified(Collections.singletonList(item)));
}
@@ -253,7 +252,7 @@
item.onAddToDatabase(writer);
writer.put(Favorites._ID, item.id);
- mModel.getModelDbController().insert(Favorites.TABLE_NAME, writer.getValues(mContext));
+ mModel.getModelDbController().insert(writer.getValues(mContext));
synchronized (mBgDataModel) {
checkItemInfoLocked(item.id, item, stackTrace);
mBgDataModel.addItem(mContext, item, true);
@@ -292,7 +291,7 @@
notifyDelete(items);
enqueueDeleteRunnable(newModelTask(() -> {
for (ItemInfo item : items) {
- mModel.getModelDbController().delete(TABLE_NAME, itemIdMatch(item.id), null);
+ mModel.getModelDbController().delete(itemIdMatch(item.id), null);
mBgDataModel.removeItem(mContext, item);
verifier.verifyModel();
}
@@ -307,12 +306,12 @@
notifyDelete(Collections.singleton(info));
enqueueDeleteRunnable(newModelTask(() -> {
- mModel.getModelDbController().delete(Favorites.TABLE_NAME,
+ mModel.getModelDbController().delete(
Favorites.CONTAINER + "=" + info.id, null);
mBgDataModel.removeItem(mContext, info.getContents());
info.getContents().clear();
- mModel.getModelDbController().delete(Favorites.TABLE_NAME,
+ mModel.getModelDbController().delete(
Favorites._ID + "=" + info.id, null);
mBgDataModel.removeItem(mContext, info);
verifier.verifyModel();
@@ -411,7 +410,7 @@
@Override
public void runImpl() {
mModel.getModelDbController().update(
- TABLE_NAME, mWriter.get().getValues(mContext), itemIdMatch(mItemId), null);
+ mWriter.get().getValues(mContext), itemIdMatch(mItemId), null);
updateItemArrays(mItem, mItemId);
}
}
@@ -433,7 +432,7 @@
ItemInfo item = mItems.get(i);
final int itemId = item.id;
mModel.getModelDbController().update(
- TABLE_NAME, mValues.get(i), itemIdMatch(itemId), null);
+ mValues.get(i), itemIdMatch(itemId), null);
updateItemArrays(item, itemId);
}
t.commit();
diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java
index 9656ac1..4c792a7 100644
--- a/src/com/android/launcher3/model/data/FolderInfo.java
+++ b/src/com/android/launcher3/model/data/FolderInfo.java
@@ -20,6 +20,9 @@
import static androidx.core.util.Preconditions.checkNotNull;
+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.logger.LauncherAtom.Attribute.EMPTY_LABEL;
import static com.android.launcher3.logger.LauncherAtom.Attribute.MANUAL_LABEL;
import static com.android.launcher3.logger.LauncherAtom.Attribute.SUGGESTED_LABEL;
@@ -30,8 +33,6 @@
import androidx.annotation.Nullable;
import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderNameInfos;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logger.LauncherAtom.Attribute;
@@ -42,8 +43,6 @@
import com.android.launcher3.util.ContentWriter;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
import java.util.OptionalInt;
import java.util.stream.IntStream;
@@ -52,18 +51,6 @@
*/
public class FolderInfo extends CollectionInfo {
- public static final int NO_FLAGS = 0x00000000;
-
- /**
- * The folder is locked in sorted mode
- */
- public static final int FLAG_ITEMS_SORTED = 0x00000001;
-
- /**
- * It is a work folder
- */
- public static final int FLAG_WORK_FOLDER = 0x00000002;
-
/**
* The multi-page animation has run for this folder
*/
@@ -95,8 +82,6 @@
}
}
- public static final String EXTRA_FOLDER_SUGGESTIONS = "suggest";
-
public int options;
public FolderNameInfos suggestedFolderNames;
@@ -106,61 +91,16 @@
*/
private final ArrayList<ItemInfo> contents = new ArrayList<>();
- private ArrayList<FolderListener> mListeners = new ArrayList<>();
-
public FolderInfo() {
itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
}
- /** Adds a app or shortcut to the contents ArrayList without animation. */
@Override
public void add(@NonNull ItemInfo item) {
- add(item, false /* animate */);
- }
-
- /**
- * Add an app or shortcut
- *
- * @param item
- */
- public void add(ItemInfo item, boolean animate) {
- add(item, getContents().size(), animate);
- }
-
- /**
- * Add an app or shortcut for a specified rank.
- */
- public void add(ItemInfo item, int rank, boolean animate) {
- if (!Folder.willAccept(item)) {
+ if (!willAcceptItemType(item.itemType)) {
throw new RuntimeException("tried to add an illegal type into a folder");
}
-
- rank = Utilities.boundToRange(rank, 0, getContents().size());
- getContents().add(rank, item);
- for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).onAdd(item, rank);
- }
- itemsChanged(animate);
- }
-
- /**
- * Remove an app or shortcut. Does not change the DB.
- *
- * @param item
- */
- public void remove(ItemInfo item, boolean animate) {
- removeAll(Collections.singletonList(item), animate);
- }
-
- /**
- * Remove all matching app or shortcut. Does not change the DB.
- */
- public void removeAll(List<ItemInfo> items, boolean animate) {
- contents.removeAll(items);
- for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).onRemove(items);
- }
- itemsChanged(animate);
+ getContents().add(item);
}
/**
@@ -197,28 +137,6 @@
writer.put(LauncherSettings.Favorites.OPTIONS, options);
}
- public void addListener(FolderListener listener) {
- mListeners.add(listener);
- }
-
- public void removeListener(FolderListener listener) {
- mListeners.remove(listener);
- }
-
- public void itemsChanged(boolean animate) {
- for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).onItemsChanged(animate);
- }
- }
-
- public interface FolderListener {
- void onAdd(ItemInfo item, int rank);
- void onRemove(List<ItemInfo> item);
- void onItemsChanged(boolean animate);
- void onTitleChanged(CharSequence title);
-
- }
-
public boolean hasOption(int optionFlag) {
return (options & optionFlag) != 0;
}
@@ -261,7 +179,6 @@
.build();
}
- @Override
public void setTitle(@Nullable CharSequence title, ModelWriter modelWriter) {
// Updating label from null to empty is considered as false touch.
// Retaining null title(ie., UNLABELED state) allows auto-labeling when new items added.
@@ -289,10 +206,6 @@
if (modelWriter != null) {
modelWriter.updateItemInDatabase(this);
}
-
- for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).onTitleChanged(title);
- }
}
/**
@@ -401,4 +314,13 @@
}
return LauncherAtom.ToState.TO_STATE_UNSPECIFIED;
}
+
+ /**
+ * Checks if {@code itemType} is a type that can be placed in folders.
+ */
+ public static boolean willAcceptItemType(int itemType) {
+ return itemType == ITEM_TYPE_APPLICATION
+ || itemType == ITEM_TYPE_DEEP_SHORTCUT
+ || itemType == ITEM_TYPE_APP_PAIR;
+ }
}
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index 588e759..ad7696c 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -61,7 +61,6 @@
import com.android.launcher3.logger.LauncherAtom.TaskSwitcherContainer;
import com.android.launcher3.logger.LauncherAtom.WallpapersContainer;
import com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers;
-import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.ContentWriter;
@@ -536,14 +535,6 @@
}
/**
- * Sets the title of the item and writes to DB model if needed.
- */
- public void setTitle(@Nullable final CharSequence title,
- @Nullable final ModelWriter modelWriter) {
- this.title = title;
- }
-
- /**
* Returns a string ID that is stable for a user session, but may not be persisted
*/
@Nullable
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index 2112638..384f876 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -79,6 +79,7 @@
private static final int SMALL_HEIGHT_MULTIPLIER = 4;
private static final int LARGE_WIDTH_MULTIPLIER = 5;
private static final int SMALL_WIDTH_MULTIPLIER = 3;
+ private static final float ARROW_TOUCH_BOX_FACTOR = 5f;
private static final int PAGE_INDICATOR_ALPHA = 255;
private static final int DOT_ALPHA = 128;
@@ -130,10 +131,10 @@
private final float mGapWidth;
private final float mCircleGap;
private final boolean mIsRtl;
- private final VectorDrawable mArrowEnd;
- private final VectorDrawable mArrowStart;
- private final Rect mArrowEndBounds = new Rect();
- private final Rect mArrowStartBounds = new Rect();
+ private final VectorDrawable mArrowRight;
+ private final VectorDrawable mArrowLeft;
+ private final Rect mArrowRightBounds = new Rect();
+ private final Rect mArrowLeftBounds = new Rect();
private int mNumPages;
private int mActivePage;
@@ -185,8 +186,8 @@
: DOT_GAP_FACTOR * mDotRadius;
setOutlineProvider(new MyOutlineProver());
mIsRtl = Utilities.isRtl(getResources());
- mArrowEnd = (VectorDrawable) getResources().getDrawable(R.drawable.ic_chevron_end);
- mArrowStart = (VectorDrawable) getResources().getDrawable(R.drawable.ic_chevron_start);
+ mArrowRight = (VectorDrawable) getResources().getDrawable(R.drawable.ic_chevron_end);
+ mArrowLeft = (VectorDrawable) getResources().getDrawable(R.drawable.ic_chevron_start);
}
@Override
@@ -473,18 +474,51 @@
float y = getHeight() / 2;
if (mEntryAnimationRadiusFactors != null) {
- // During entry animation, only draw the circles
- // TODO(b/394355070): Verify Folder Entry Animation works correctly - visual updates
+ if (enableLauncherVisualRefresh()) {
+ x -= mDotRadius;
+ if (mIsRtl) {
+ x = getWidth() - x;
+ circleGap = -circleGap;
+ }
+ sTempRect.top = y - mDotRadius;
+ sTempRect.bottom = y + mDotRadius;
- if (mIsRtl) {
- x = getWidth() - x;
- circleGap = -circleGap;
- }
- for (int i = 0; i < mEntryAnimationRadiusFactors.length; i++) {
- mPaginationPaint.setAlpha(i == mActivePage ? PAGE_INDICATOR_ALPHA : DOT_ALPHA);
- canvas.drawCircle(x, y, mDotRadius * mEntryAnimationRadiusFactors[i],
- mPaginationPaint);
- x += circleGap;
+ for (int i = 0; i < mEntryAnimationRadiusFactors.length; i++) {
+ if (i == mActivePage) {
+ if (mIsRtl) {
+ sTempRect.left = x - (mDotRadius * 3);
+ sTempRect.right = x + mDotRadius;
+ x += circleGap - (mDotRadius * 2);
+ } else {
+ sTempRect.left = x - mDotRadius;
+ sTempRect.right = x + (mDotRadius * 3);
+ x += circleGap + (mDotRadius * 2);
+ }
+ scale(sTempRect, mEntryAnimationRadiusFactors[i]);
+ float scaledRadius = mDotRadius * mEntryAnimationRadiusFactors[i];
+ mPaginationPaint.setAlpha(PAGE_INDICATOR_ALPHA);
+ canvas.drawRoundRect(sTempRect, scaledRadius, scaledRadius,
+ mPaginationPaint);
+ } else {
+ mPaginationPaint.setAlpha(DOT_ALPHA);
+ canvas.drawCircle(x, y, mDotRadius * mEntryAnimationRadiusFactors[i],
+ mPaginationPaint);
+ x += circleGap;
+ }
+ }
+ } else {
+ // During entry animation, only draw the circles
+
+ if (mIsRtl) {
+ x = getWidth() - x;
+ circleGap = -circleGap;
+ }
+ for (int i = 0; i < mEntryAnimationRadiusFactors.length; i++) {
+ mPaginationPaint.setAlpha(i == mActivePage ? PAGE_INDICATOR_ALPHA : DOT_ALPHA);
+ canvas.drawCircle(x, y, mDotRadius * mEntryAnimationRadiusFactors[i],
+ mPaginationPaint);
+ x += circleGap;
+ }
}
} else {
// Save the current alpha value, so we can reset to it again after drawing the dots
@@ -514,14 +548,14 @@
if (mOnArrowClickListener != null && boundedPosition >= 1) {
// Here we draw the Left Arrow
- mArrowStart.setAlpha(alpha);
+ mArrowLeft.setAlpha(alpha);
int size = (int) (mGapWidth * 4);
- mArrowStartBounds.left = (int) (sTempRect.left - mGapWidth - size);
- mArrowStartBounds.top = (int) (y - size / 2);
- mArrowStartBounds.right = (int) (sTempRect.left - mGapWidth);
- mArrowStartBounds.bottom = (int) (y + size / 2);
- mArrowStart.setBounds(mArrowStartBounds);
- mArrowStart.draw(canvas);
+ mArrowLeftBounds.left = (int) (sTempRect.left - mGapWidth - size);
+ mArrowLeftBounds.top = (int) (y - size / 2);
+ mArrowLeftBounds.right = (int) (sTempRect.left - mGapWidth);
+ mArrowLeftBounds.bottom = (int) (y + size / 2);
+ mArrowLeft.setBounds(mArrowLeftBounds);
+ mArrowLeft.draw(canvas);
}
// Here we draw the dots, one at a time from the left-most dot to the right-most dot
@@ -575,14 +609,14 @@
if (mOnArrowClickListener != null && boundedPosition <= mNumPages - 2) {
// Here we draw the Right Arrow
- mArrowEnd.setAlpha(alpha);
+ mArrowRight.setAlpha(alpha);
int size = (int) (mGapWidth * 4);
- mArrowEndBounds.left = (int) sTempRect.left;
- mArrowEndBounds.top = (int) (y - size / 2);
- mArrowEndBounds.right = (int) (int) (sTempRect.left + size);
- mArrowEndBounds.bottom = (int) (y + size / 2);
- mArrowEnd.setBounds(mArrowEndBounds);
- mArrowEnd.draw(canvas);
+ mArrowRightBounds.left = (int) sTempRect.left;
+ mArrowRightBounds.top = (int) (y - size / 2);
+ mArrowRightBounds.right = (int) (int) (sTempRect.left + size);
+ mArrowRightBounds.bottom = (int) (y + size / 2);
+ mArrowRight.setBounds(mArrowRightBounds);
+ mArrowRight.draw(canvas);
}
} else {
// Here we draw the dots
@@ -606,9 +640,11 @@
public boolean onTouchEvent(MotionEvent ev) {
if (mOnArrowClickListener == null) {
// No - Op. Don't care about touch events
- } else if (withinExpandedBounds(mArrowStartBounds, ev)) {
+ } else if ((mIsRtl && withinExpandedBounds(mArrowRightBounds, ev))
+ || (!mIsRtl && withinExpandedBounds(mArrowLeftBounds, ev))) {
mOnArrowClickListener.accept(Direction.START);
- } else if (withinExpandedBounds(mArrowEndBounds, ev)) {
+ } else if ((mIsRtl && withinExpandedBounds(mArrowLeftBounds, ev))
+ || (!mIsRtl && withinExpandedBounds(mArrowRightBounds, ev))) {
mOnArrowClickListener.accept(Direction.END);
}
return super.onTouchEvent(ev);
@@ -616,18 +652,20 @@
// For larger Touch box
private boolean withinExpandedBounds(Rect rect, MotionEvent ev) {
- Rect scaledRect = new Rect();
- scaledRect.set(rect);
+ RectF scaledRect = new RectF(rect);
+ scale(scaledRect, ARROW_TOUCH_BOX_FACTOR);
+ return scaledRect.contains(ev.getX(), ev.getY());
+ }
- float verticalAdjustment = (scaledRect.bottom - scaledRect.top) * 2;
- scaledRect.top -= verticalAdjustment;
- scaledRect.bottom += verticalAdjustment;
+ private static void scale(RectF rect, float factor) {
+ float horizontalAdjustment = rect.width() * (factor - 1) / 2;
+ float verticalAdjustment = rect.height() * (factor - 1) / 2;
- float horizontalAdjustment = (scaledRect.right - scaledRect.left) * 2;
- scaledRect.left -= horizontalAdjustment;
- scaledRect.right += horizontalAdjustment;
+ rect.top -= verticalAdjustment;
+ rect.bottom += verticalAdjustment;
- return scaledRect.contains((int) ev.getX(), (int) ev.getY());
+ rect.left -= horizontalAdjustment;
+ rect.right += horizontalAdjustment;
}
private RectF getActiveRect() {
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index aad1400..39f68bf 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -16,9 +16,12 @@
package com.android.launcher3.popup;
+import static android.multiuser.Flags.enableMovingContentIntoPrivateSpace;
+
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS;
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.Utilities.squaredTouchSlop;
+import static com.android.launcher3.allapps.AlphabeticalAppsList.PRIVATE_SPACE_PACKAGE;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE;
import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -65,6 +68,7 @@
import com.android.launcher3.views.BaseDragLayer;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -207,7 +211,10 @@
container = (PopupContainerWithArrow) launcher.getLayoutInflater().inflate(
R.layout.popup_container, launcher.getDragLayer(), false);
container.configureForLauncher(launcher, item);
- container.populateAndShowRows(icon, deepShortcutCount, systemShortcuts);
+ boolean shouldHideSystemShortcuts = enableMovingContentIntoPrivateSpace()
+ && Objects.equals(item.getTargetPackage(), PRIVATE_SPACE_PACKAGE);
+ container.populateAndShowRows(icon, deepShortcutCount,
+ shouldHideSystemShortcuts ? Collections.emptyList() : systemShortcuts);
launcher.refreshAndBindWidgetsForPackageUser(PackageUserKey.fromItemInfo(item));
container.requestFocus();
return container;
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index 5c1a755..9511032 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -26,7 +26,6 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.allapps.ActivityAllAppsContainerView;
import com.android.launcher3.dot.DotInfo;
-import com.android.launcher3.dot.FolderDotInfo;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.model.data.FolderInfo;
@@ -76,11 +75,7 @@
((BubbleTextView) v).applyDotState(info, true /* animate */);
} else if (v instanceof FolderIcon icon
&& info instanceof FolderInfo fi && fi.anyMatch(matcher)) {
- FolderDotInfo folderDotInfo = new FolderDotInfo();
- for (ItemInfo si : fi.getContents()) {
- folderDotInfo.addDotInfo(getDotInfoForItem(si));
- }
- icon.setDotInfo(folderDotInfo);
+ icon.updateDotInfo();
}
// process all the shortcuts
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 23941bb..f6ee26b 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -80,6 +80,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
@@ -206,7 +207,8 @@
LauncherRestoreEventLogger restoreEventLogger =
LauncherRestoreEventLogger.Companion.newInstance(context);
task.sanitizeDB(context, controller, db, backupManager, restoreEventLogger);
- task.restoreAppWidgetIdsIfExists(context, controller, restoreEventLogger);
+ task.restoreAppWidgetIdsIfExists(context, controller, restoreEventLogger,
+ () -> new AppWidgetHost(context, APPWIDGET_HOST_ID));
t.commit();
return true;
} catch (Exception e) {
@@ -438,14 +440,13 @@
@WorkerThread
@VisibleForTesting
void restoreAppWidgetIdsIfExists(Context context, ModelDbController controller,
- LauncherRestoreEventLogger restoreEventLogger) {
+ LauncherRestoreEventLogger restoreEventLogger, Supplier<AppWidgetHost> hostSupplier) {
LauncherPrefs lp = LauncherPrefs.get(context);
if (lp.has(APP_WIDGET_IDS, OLD_APP_WIDGET_IDS)) {
- AppWidgetHost host = new AppWidgetHost(context, APPWIDGET_HOST_ID);
restoreAppWidgetIds(context, controller, restoreEventLogger,
IntArray.fromConcatString(lp.get(OLD_APP_WIDGET_IDS)).toArray(),
IntArray.fromConcatString(lp.get(APP_WIDGET_IDS)).toArray(),
- host);
+ hostSupplier.get());
} else {
FileLog.d(TAG, "Did not receive new app widget id map during Launcher restore");
}
diff --git a/src/com/android/launcher3/util/ContentWriter.java b/src/com/android/launcher3/util/ContentWriter.java
index 9910dc2..c2c1fee 100644
--- a/src/com/android/launcher3/util/ContentWriter.java
+++ b/src/com/android/launcher3/util/ContentWriter.java
@@ -23,7 +23,6 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.GraphicsUtils;
import com.android.launcher3.model.ModelDbController;
@@ -107,7 +106,7 @@
public int commit() {
if (mCommitParams != null) {
return mCommitParams.mDbController.update(
- Favorites.TABLE_NAME, getValues(mContext),
+ getValues(mContext),
mCommitParams.mWhere, mCommitParams.mSelectionArgs);
}
return 0;
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
index 47110fa..f772339 100644
--- a/tests/multivalentTests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
@@ -15,7 +15,6 @@
*/
package com.android.launcher3.celllayout;
-import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.TestUtil.runOnExecutorSync;
@@ -89,7 +88,7 @@
item.onAddToDatabase(writer);
writer.put(LauncherSettings.Favorites._ID, i);
- controller.insert(TABLE_NAME, writer.getValues(mContext));
+ controller.insert(writer.getValues(mContext));
}
for (int i = 0; i < containerItems.size(); i++) {
@@ -97,7 +96,7 @@
ItemInfo item = containerItems.get(i);
item.onAddToDatabase(writer);
writer.put(LauncherSettings.Favorites._ID, count + i);
- controller.insert(TABLE_NAME, writer.getValues(mContext));
+ controller.insert(writer.getValues(mContext));
}
transaction.commit();
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.kt
index 2e556e8..3405cae 100644
--- a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.kt
@@ -44,7 +44,7 @@
private fun fillWithWidgets(
widgetRect: WidgetRect,
transaction: FavoriteItemsTransaction,
- screenId: Int
+ screenId: Int,
): FavoriteItemsTransaction {
val initX = widgetRect.cellX
val initY = widgetRect.cellY
@@ -76,7 +76,7 @@
appComponentName =
ComponentName(
InstrumentationRegistry.getInstrumentation().context.packageName,
- TEST_ACTIVITY_PACKAGE_PREFIX + testAppName
+ TEST_ACTIVITY_PACKAGE_PREFIX + testAppName,
)
}
@@ -92,7 +92,7 @@
private fun addCorrespondingWidgetRect(
widgetRect: WidgetRect,
transaction: FavoriteItemsTransaction,
- screenId: Int
+ screenId: Int,
) {
if (widgetRect.type == 'x') {
fillWithWidgets(widgetRect, transaction, screenId)
@@ -105,7 +105,7 @@
fun buildFromBoard(
board: CellLayoutBoard,
transaction: FavoriteItemsTransaction,
- screenId: Int
+ screenId: Int,
): FavoriteItemsTransaction {
board.widgets.forEach { addCorrespondingWidgetRect(it, transaction, screenId) }
board.icons.forEach { transaction.addItem { createIconInCell(it, screenId) } }
@@ -130,7 +130,7 @@
WidgetUtils.createWidgetInfo(
TestViewHelpers.findWidgetProvider(false),
ApplicationProvider.getApplicationContext(),
- true
+ true,
)
.apply {
cellX = widgetRect.cellX
@@ -154,7 +154,7 @@
minSpanY = 1
setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, null)
for (i in 0 until folderPoint.numberIconsInside) {
- add(getDefaultWorkspaceItem(paramScreenId), false)
+ add(getDefaultWorkspaceItem(paramScreenId))
}
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/folder/FolderTest.kt b/tests/multivalentTests/src/com/android/launcher3/folder/FolderTest.kt
index bcf5f0d..e825995 100644
--- a/tests/multivalentTests/src/com/android/launcher3/folder/FolderTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/folder/FolderTest.kt
@@ -298,12 +298,13 @@
val dragObject = DragObject(context)
dragObject.dragInfo = Mockito.mock(ItemInfo::class.java)
folder.mContent = Mockito.mock(FolderPagedView::class.java)
+ folder.mFolderIcon = Mockito.mock(FolderIcon::class.java)
dragObject.dragSource = folder
folder.onDragStart(dragObject, DragOptions())
verify(folder.mContent, times(1)).removeItem(folder.currentDragView)
- verify(folder.mInfo, times(1)).remove(dragObject.dragInfo, true)
+ verify(folder, times(1)).removeFolderContent(true, dragObject.dragInfo)
assertTrue(folder.itemsInvalidated)
assertTrue(folder.isDragInProgress)
assertFalse(folder.itemAddedBackToSelfViaIcon)
@@ -827,19 +828,19 @@
@Test
fun `onRemove should call removeItem with the correct views`() {
+ val folderInfo =
+ workspaceBuilder.createFolderInCell(FolderPoint(Point(1, 0), TWO_ICON_FOLDER_TYPE), 0)
+ val items = folderInfo.getContents().toTypedArray()
+ folder.mInfo = folderInfo
+ folder.mFolderIcon = Mockito.mock(FolderIcon::class.java)
folder.mContent = Mockito.mock(FolderPagedView::class.java)
- val items =
- arrayListOf<ItemInfo>(
- Mockito.mock(ItemInfo::class.java),
- Mockito.mock(ItemInfo::class.java),
- )
val view1 = Mockito.mock(View::class.java)
val view2 = Mockito.mock(View::class.java)
doReturn(view1).whenever(folder).getViewForInfo(items[0])
doReturn(view2).whenever(folder).getViewForInfo(items[1])
doReturn(2).whenever(folder).itemCount
- folder.onRemove(items)
+ folder.removeFolderContent(false, *items)
verify(folder.mContent, times(1)).removeItem(view1)
verify(folder.mContent, times(1)).removeItem(view2)
@@ -847,20 +848,20 @@
@Test
fun `onRemove should set mRearrangeOnClose to true and not call rearrangeChildren if animating`() {
+ val folderInfo =
+ workspaceBuilder.createFolderInCell(FolderPoint(Point(1, 0), TWO_ICON_FOLDER_TYPE), 0)
+ val items = folderInfo.getContents().toTypedArray()
+ folder.mInfo = folderInfo
+ folder.mFolderIcon = Mockito.mock(FolderIcon::class.java)
folder.mContent = Mockito.mock(FolderPagedView::class.java)
folder.state = STATE_ANIMATING
- val items =
- arrayListOf<ItemInfo>(
- Mockito.mock(ItemInfo::class.java),
- Mockito.mock(ItemInfo::class.java),
- )
val view1 = Mockito.mock(View::class.java)
val view2 = Mockito.mock(View::class.java)
doReturn(view1).whenever(folder).getViewForInfo(items[0])
doReturn(view2).whenever(folder).getViewForInfo(items[1])
doReturn(2).whenever(folder).itemCount
- folder.onRemove(items)
+ folder.removeFolderContent(true, *items)
assertTrue(folder.rearrangeOnClose)
verify(folder, times(0)).rearrangeChildren()
@@ -868,21 +869,21 @@
@Test
fun `onRemove should set not change mRearrangeOnClose and not call rearrangeChildren if not animating`() {
+ val folderInfo =
+ workspaceBuilder.createFolderInCell(FolderPoint(Point(1, 0), TWO_ICON_FOLDER_TYPE), 0)
+ val items = folderInfo.getContents().toTypedArray()
+ folder.mInfo = folderInfo
+ folder.mFolderIcon = Mockito.mock(FolderIcon::class.java)
folder.mContent = Mockito.mock(FolderPagedView::class.java)
folder.state = STATE_CLOSED
folder.rearrangeOnClose = false
- val items =
- arrayListOf<ItemInfo>(
- Mockito.mock(ItemInfo::class.java),
- Mockito.mock(ItemInfo::class.java),
- )
val view1 = Mockito.mock(View::class.java)
val view2 = Mockito.mock(View::class.java)
doReturn(view1).whenever(folder).getViewForInfo(items[0])
doReturn(view2).whenever(folder).getViewForInfo(items[1])
doReturn(2).whenever(folder).itemCount
- folder.onRemove(items)
+ folder.removeFolderContent(false, *items)
assertFalse(folder.rearrangeOnClose)
verify(folder, times(1)).rearrangeChildren()
@@ -890,12 +891,12 @@
@Test
fun `onRemove should call close if mIsOpen is true and item count is less than or equal to one`() {
+ val folderInfo =
+ workspaceBuilder.createFolderInCell(FolderPoint(Point(1, 0), TWO_ICON_FOLDER_TYPE), 0)
+ val items = folderInfo.getContents().toTypedArray()
+ folder.mInfo = folderInfo
+ folder.mFolderIcon = Mockito.mock(FolderIcon::class.java)
folder.mContent = Mockito.mock(FolderPagedView::class.java)
- val items =
- arrayListOf<ItemInfo>(
- Mockito.mock(ItemInfo::class.java),
- Mockito.mock(ItemInfo::class.java),
- )
val view1 = Mockito.mock(View::class.java)
val view2 = Mockito.mock(View::class.java)
doReturn(view1).whenever(folder).getViewForInfo(items[0])
@@ -904,19 +905,19 @@
folder.setIsOpen(true)
doNothing().`when`(folder).close(true)
- folder.onRemove(items)
+ folder.removeFolderContent(false, *items)
verify(folder, times(1)).close(true)
}
@Test
fun `onRemove should call replaceFolderWithFinalItem if mIsOpen is false and item count is less than or equal to one`() {
+ val folderInfo =
+ workspaceBuilder.createFolderInCell(FolderPoint(Point(1, 0), TWO_ICON_FOLDER_TYPE), 0)
+ val items = folderInfo.getContents().toTypedArray()
+ folder.mInfo = folderInfo
+ folder.mFolderIcon = Mockito.mock(FolderIcon::class.java)
folder.mContent = Mockito.mock(FolderPagedView::class.java)
- val items =
- arrayListOf<ItemInfo>(
- Mockito.mock(ItemInfo::class.java),
- Mockito.mock(ItemInfo::class.java),
- )
val view1 = Mockito.mock(View::class.java)
val view2 = Mockito.mock(View::class.java)
doReturn(view1).whenever(folder).getViewForInfo(items[0])
@@ -924,7 +925,7 @@
doReturn(1).whenever(folder).itemCount
folder.setIsOpen(false)
- folder.onRemove(items)
+ folder.removeFolderContent(false, *items)
verify(folder, times(1)).replaceFolderWithFinalItem()
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/DatabaseHelperTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/DatabaseHelperTest.kt
index 09752b8..89e676f 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/DatabaseHelperTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/DatabaseHelperTest.kt
@@ -11,6 +11,7 @@
import com.android.launcher3.LauncherSettings.Favorites.addTableToDb
import com.android.launcher3.pm.UserCache
import com.android.launcher3.provider.LauncherDbUtils
+import com.android.launcher3.util.ModelTestExtensions
import java.util.function.ToLongFunction
import org.junit.After
import org.junit.Assert.assertEquals
@@ -33,7 +34,7 @@
@Before
fun setUp() {
- db = FactitiousDbController(context, INSERTION_SQL).inMemoryDb
+ db = ModelTestExtensions.createInMemoryDb(INSERTION_SQL)
}
@After
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/FactitiousDbController.kt b/tests/multivalentTests/src/com/android/launcher3/model/FactitiousDbController.kt
deleted file mode 100644
index 711e1d2..0000000
--- a/tests/multivalentTests/src/com/android/launcher3/model/FactitiousDbController.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-package com.android.launcher3.model
-
-import android.content.Context
-import android.database.Cursor
-import android.database.sqlite.SQLiteDatabase
-import androidx.test.platform.app.InstrumentationRegistry
-import java.io.BufferedReader
-import java.io.InputStreamReader
-
-private val All_COLUMNS =
- arrayOf(
- "_id",
- "title",
- "intent",
- "container",
- "screen",
- "cellX",
- "cellY",
- "spanX",
- "spanY",
- "itemType",
- "appWidgetId",
- "icon",
- "appWidgetProvider",
- "modified",
- "restored",
- "profileId",
- "rank",
- "options",
- "appWidgetSource"
- )
-
-class FactitiousDbController(context: Context, insertFile: String) : ModelDbController(context) {
-
- val inMemoryDb: SQLiteDatabase by lazy {
- SQLiteDatabase.createInMemory(SQLiteDatabase.OpenParams.Builder().build()).also { db ->
- BufferedReader(
- InputStreamReader(
- InstrumentationRegistry.getInstrumentation().context.assets.open(insertFile)
- )
- )
- .lines()
- .forEach { sqlStatement -> db.execSQL(sqlStatement) }
- }
- }
-
- override fun query(
- table: String,
- projection: Array<out String>?,
- selection: String?,
- selectionArgs: Array<out String>?,
- sortOrder: String?
- ): Cursor {
- return inMemoryDb.query(table, All_COLUMNS, selection, selectionArgs, null, null, sortOrder)
- }
-
- override fun loadDefaultFavoritesIfNecessary() {
- // No-Op
- }
-}
diff --git a/tests/multivalentTests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/tests/multivalentTests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
index c30b730..0f4940e 100644
--- a/tests/multivalentTests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
@@ -24,8 +24,6 @@
import static com.android.launcher3.LauncherPrefs.RESTORE_DEVICE;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
-import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
-import static com.android.launcher3.widget.LauncherWidgetHolder.APPWIDGET_HOST_ID;
import static com.google.common.truth.Truth.assertThat;
@@ -50,7 +48,6 @@
import android.database.sqlite.SQLiteDatabase;
import android.os.UserHandle;
import android.os.UserManager;
-import android.util.LongSparseArray;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -60,9 +57,17 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.backuprestore.LauncherRestoreEventLogger;
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.model.ModelDbController;
+import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.AllModulesForTest;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.LauncherModelHelper;
+import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext;
+
+import dagger.BindsInstance;
+import dagger.Component;
import org.junit.After;
import org.junit.Before;
@@ -85,7 +90,9 @@
private final UserHandle mWorkUser = UserHandle.getUserHandleForUid(PER_USER_RANGE);
private LauncherModelHelper mModelHelper;
- private Context mContext;
+ private SandboxModelContext mContext;
+ private UserCache mUserCacheSpy;
+
private RestoreDbTask mTask;
private ModelDbController mMockController;
private SQLiteDatabase mMockDb;
@@ -94,10 +101,17 @@
private LauncherRestoreEventLogger mMockRestoreEventLogger;
private SQLiteDatabase mDb;
+ private AppWidgetHost mWidgetHost;
+
@Before
public void setup() {
mModelHelper = new LauncherModelHelper();
mContext = mModelHelper.sandboxContext;
+ mUserCacheSpy = spy(UserCache.getInstance(getInstrumentation().getTargetContext()));
+
+ mContext.initDaggerComponent(
+ DaggerRestoreDbTaskTest_TestComponent.builder().bindUserCache(mUserCacheSpy));
+
mTask = new RestoreDbTask();
mMockController = Mockito.mock(ModelDbController.class);
mMockDb = mock(SQLiteDatabase.class);
@@ -106,24 +120,34 @@
mMockRestoreEventLogger = mock(LauncherRestoreEventLogger.class);
}
+ private synchronized AppWidgetHost getWidgetHostLazy() {
+ if (mWidgetHost == null) {
+ mWidgetHost = new AppWidgetHost(mContext, 1012);
+ }
+ return mWidgetHost;
+ }
+
@After
public void teardown() {
if (mDb != null) {
mDb.close();
}
+ if (mWidgetHost != null) {
+ mWidgetHost.deleteHost();
+ }
mModelHelper.destroy();
LauncherPrefs.get(mContext).removeSync(RESTORE_DEVICE);
}
@Test
public void testGetProfileId() throws Exception {
- mDb = new MyModelDbController(23).getDb();
+ mDb = getModelDbController(23).getDb();
assertEquals(23, new RestoreDbTask().getDefaultProfileId(mDb));
}
@Test
public void testMigrateProfileId() throws Exception {
- mDb = new MyModelDbController(42).getDb();
+ mDb = getModelDbController(42).getDb();
// Add some mock data
for (int i = 0; i < 5; i++) {
ContentValues values = new ContentValues();
@@ -143,7 +167,7 @@
@Test
public void testChangeDefaultColumn() throws Exception {
- mDb = new MyModelDbController(42).getDb();
+ mDb = getModelDbController(42).getDb();
// Add some mock data
for (int i = 0; i < 5; i++) {
ContentValues values = new ContentValues();
@@ -173,12 +197,12 @@
long workProfileId = myProfileId + 2;
long workProfileId_old = myProfileId + 3;
- MyModelDbController controller = new MyModelDbController(myProfileId);
+ ModelDbController controller = getModelDbController(myProfileId);
mDb = controller.getDb();
BackupManager bm = spy(new BackupManager(mContext));
doReturn(myUserHandle()).when(bm).getUserForAncestralSerialNumber(eq(myProfileId_old));
doReturn(mWorkUser).when(bm).getUserForAncestralSerialNumber(eq(workProfileId_old));
- controller.users.put(workProfileId, mWorkUser);
+ doReturn(workProfileId).when(mUserCacheSpy).getSerialNumberForUser(mWorkUser);
addIconsBulk(controller, 10, 1, myProfileId_old);
addIconsBulk(controller, 6, 2, workProfileId_old);
@@ -202,7 +226,7 @@
long myProfileId_old = myProfileId + 1;
long workProfileId_old = myProfileId + 3;
- MyModelDbController controller = new MyModelDbController(myProfileId);
+ ModelDbController controller = getModelDbController(myProfileId);
mDb = controller.getDb();
BackupManager bm = spy(new BackupManager(mContext));
doReturn(myUserHandle()).when(bm).getUserForAncestralSerialNumber(eq(myProfileId_old));
@@ -226,7 +250,8 @@
@Test
public void givenLauncherPrefsHasNoIds_whenRestoreAppWidgetIdsIfExists_thenIdsAreRemoved() {
// When
- mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger);
+ mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger,
+ this::getWidgetHostLazy);
// Then
assertThat(mPrefs.has(OLD_APP_WIDGET_IDS, APP_WIDGET_IDS)).isFalse();
}
@@ -234,7 +259,7 @@
@Test
public void givenNoPendingRestore_WhenRestoreAppWidgetIds_ThenRemoveNewWidgetIds() {
// Given
- AppWidgetHost expectedHost = new AppWidgetHost(mContext, APPWIDGET_HOST_ID);
+ AppWidgetHost expectedHost = getWidgetHostLazy();
int[] expectedOldIds = generateOldWidgetIds(expectedHost);
int[] expectedNewIds = generateNewWidgetIds(expectedHost, expectedOldIds);
when(mMockController.getDb()).thenReturn(mMockDb);
@@ -242,7 +267,8 @@
// When
setRestoredAppWidgetIds(mContext, expectedOldIds, expectedNewIds);
- mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger);
+ mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger,
+ this::getWidgetHostLazy);
// Then
assertThat(expectedHost.getAppWidgetIds()).isEqualTo(expectedOldIds);
@@ -254,7 +280,7 @@
@Test
public void givenRestoreWithNonExistingWidgets_WhenRestoreAppWidgetIds_ThenRemoveNewIds() {
// Given
- AppWidgetHost expectedHost = new AppWidgetHost(mContext, APPWIDGET_HOST_ID);
+ AppWidgetHost expectedHost = getWidgetHostLazy();
int[] expectedOldIds = generateOldWidgetIds(expectedHost);
int[] expectedNewIds = generateNewWidgetIds(expectedHost, expectedOldIds);
when(mMockController.getDb()).thenReturn(mMockDb);
@@ -265,18 +291,19 @@
// When
setRestoredAppWidgetIds(mContext, expectedOldIds, expectedNewIds);
- mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger);
+ mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger,
+ this::getWidgetHostLazy);
// Then
assertThat(expectedHost.getAppWidgetIds()).isEqualTo(expectedOldIds);
assertThat(mPrefs.has(OLD_APP_WIDGET_IDS, APP_WIDGET_IDS)).isFalse();
- verify(mMockController, times(expectedOldIds.length)).update(any(), any(), any(), any());
+ verify(mMockController, times(expectedOldIds.length)).update(any(), any(), any());
}
@Test
public void givenRestore_WhenRestoreAppWidgetIds_ThenAddNewIds() {
// Given
- AppWidgetHost expectedHost = new AppWidgetHost(mContext, APPWIDGET_HOST_ID);
+ AppWidgetHost expectedHost = getWidgetHostLazy();
int[] expectedOldIds = generateOldWidgetIds(expectedHost);
int[] expectedNewIds = generateNewWidgetIds(expectedHost, expectedOldIds);
int[] allExpectedIds = IntStream.concat(
@@ -294,15 +321,16 @@
// When
setRestoredAppWidgetIds(mContext, expectedOldIds, expectedNewIds);
- mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger);
+ mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger,
+ this::getWidgetHostLazy);
// Then
assertThat(expectedHost.getAppWidgetIds()).isEqualTo(allExpectedIds);
assertThat(mPrefs.has(OLD_APP_WIDGET_IDS, APP_WIDGET_IDS)).isFalse();
- verify(mMockController, times(expectedOldIds.length)).update(any(), any(), any(), any());
+ verify(mMockController, times(expectedOldIds.length)).update(any(), any(), any());
}
- private void addIconsBulk(MyModelDbController controller,
+ private void addIconsBulk(ModelDbController controller,
int count, int screen, long profileId) {
int columns = LauncherAppState.getIDP(mContext).numColumns;
String packageName = getInstrumentation().getContext().getPackageName();
@@ -320,7 +348,7 @@
values.put(LauncherSettings.Favorites.INTENT,
new Intent(Intent.ACTION_MAIN).setPackage(packageName).toUri(0));
- controller.insert(TABLE_NAME, values);
+ controller.insert(values);
}
}
@@ -346,7 +374,7 @@
}
private void runRemoveScreenIdGapsTest(int[] screenIds, int[] expectedScreenIds) {
- mDb = new MyModelDbController(42).getDb();
+ mDb = getModelDbController(42).getDb();
// Add some mock data
for (int i = 0; i < screenIds.length; i++) {
ContentValues values = new ContentValues();
@@ -397,25 +425,29 @@
.map(id -> host.allocateAppWidgetId()).toArray();
}
- private class MyModelDbController extends ModelDbController {
-
- public final LongSparseArray<UserHandle> users = new LongSparseArray<>();
-
- MyModelDbController(long profileId) {
- super(mContext);
- users.put(profileId, myUserHandle());
- }
-
- @Override
- public long getSerialNumberForUser(UserHandle user) {
- int index = users.indexOfValue(user);
- return index >= 0 ? users.keyAt(index) : -1;
- }
- }
-
private void setRestoredAppWidgetIds(Context context, int[] oldIds, int[] newIds) {
LauncherPrefs.get(context).putSync(
OLD_APP_WIDGET_IDS.to(IntArray.wrap(oldIds).toConcatString()),
APP_WIDGET_IDS.to(IntArray.wrap(newIds).toConcatString()));
}
+
+ private ModelDbController getModelDbController(long profileId) {
+ doReturn(profileId).when(mUserCacheSpy).getSerialNumberForUser(myUserHandle());
+ return ((TestComponent) mContext.getAppComponent()).getDbController();
+ }
+
+ @LauncherAppSingleton
+ @Component(modules = AllModulesForTest.class)
+ public interface TestComponent extends LauncherAppComponent {
+
+ ModelDbController getDbController();
+
+ @Component.Builder
+ interface Builder extends LauncherAppComponent.Builder {
+
+ @BindsInstance Builder bindUserCache(UserCache userCache);
+
+ TestComponent build();
+ }
+ }
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt b/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt
index ceefb0d..524acff 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt
@@ -1,10 +1,11 @@
package com.android.launcher3.util
import android.content.ContentValues
+import android.database.sqlite.SQLiteDatabase
import android.os.Process
+import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.Flags
import com.android.launcher3.LauncherModel
-import com.android.launcher3.LauncherSettings.Favorites
import com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_ID
import com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_PROVIDER
import com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_SOURCE
@@ -24,6 +25,8 @@
import com.android.launcher3.LauncherSettings.Favorites._ID
import com.android.launcher3.model.BgDataModel
import com.android.launcher3.model.ModelDbController
+import java.io.BufferedReader
+import java.io.InputStreamReader
object ModelTestExtensions {
/** Clears and reloads Launcher db to cleanup the workspace */
@@ -68,7 +71,6 @@
spanY: Int = 1,
id: Int = 0,
profileId: Int = Process.myUserHandle().identifier,
- tableName: String = Favorites.TABLE_NAME,
appWidgetId: Int = -1,
appWidgetSource: Int = -1,
appWidgetProvider: String? = null,
@@ -97,9 +99,21 @@
values[APPWIDGET_PROVIDER] = appWidgetProvider
}
// Migrate any previous data so that the DB state is correct
- controller.insert(tableName, values)
+ controller.insert(values)
transaction.commit()
}
}
}
+
+ /** Creates an in-memory sqlite DB and initializes with the data in [insertFile] */
+ fun createInMemoryDb(insertFile: String): SQLiteDatabase =
+ SQLiteDatabase.createInMemory(SQLiteDatabase.OpenParams.Builder().build()).also { db ->
+ BufferedReader(
+ InputStreamReader(
+ InstrumentationRegistry.getInstrumentation().context.assets.open(insertFile)
+ )
+ )
+ .lines()
+ .forEach { sqlStatement -> db.execSQL(sqlStatement) }
+ }
}
diff --git a/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt b/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
index 38fad6b..34d9d40 100644
--- a/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
+++ b/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
@@ -23,8 +23,8 @@
import androidx.test.filters.MediumTest
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.launcher3.Flags
+import com.android.launcher3.LauncherAppState
import com.android.launcher3.LauncherPrefs
-import com.android.launcher3.model.ModelDbController
import com.android.launcher3.model.ModelDelegate
import com.android.launcher3.provider.RestoreDbTask
import com.android.launcher3.util.Executors.MODEL_EXECUTOR
@@ -45,11 +45,8 @@
@MediumTest
class BackupAndRestoreDBSelectionTest {
- @JvmField @Rule var backAndRestoreRule = BackAndRestoreRule()
-
- @JvmField
- @Rule
- val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
+ @get:Rule var backAndRestoreRule = BackAndRestoreRule()
+ @get:Rule val setFlagsRule = SetFlagsRule()
val modelDelegate = mock<ModelDelegate>()
@@ -70,7 +67,8 @@
@Test
fun oldDatabasesNotPresentAfterRestore() {
- val dbController = ModelDbController(getInstrumentation().targetContext)
+ val dbController =
+ LauncherAppState.getInstance(getInstrumentation().targetContext).model.modelDbController
if (Flags.gridMigrationRefactor()) {
dbController.attemptMigrateDb(null, modelDelegate)
} else {
diff --git a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
index 7f9b7a0..560d306 100644
--- a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
@@ -5,6 +5,7 @@
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.LauncherActivityInfo
+import android.database.sqlite.SQLiteDatabase
import android.os.Process
import android.os.UserHandle
import android.platform.test.annotations.DisableFlags
@@ -25,6 +26,7 @@
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.LauncherSettings.Favorites.TABLE_NAME
import com.android.launcher3.dagger.LauncherAppComponent
import com.android.launcher3.dagger.LauncherAppSingleton
import com.android.launcher3.icons.IconCache
@@ -41,6 +43,7 @@
import com.android.launcher3.util.Executors.MODEL_EXECUTOR
import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
import com.android.launcher3.util.LooperIdleLock
+import com.android.launcher3.util.ModelTestExtensions
import com.android.launcher3.util.TestUtil
import com.android.launcher3.util.UserIconInfo
import com.google.common.truth.Truth.assertThat
@@ -63,6 +66,7 @@
import org.mockito.Spy
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
@@ -94,6 +98,7 @@
@Mock private lateinit var launcherModel: LauncherModel
@Mock private lateinit var iconCache: IconCache
@Mock private lateinit var userCache: UserCache
+ @Mock private lateinit var modelDbController: ModelDbController
@Mock private lateinit var launcherBinder: BaseLauncherBinder
@Mock private lateinit var transaction: LoaderTransaction
@@ -110,6 +115,10 @@
private val bgDataModel: BgDataModel
get() = testComponent.getDataModel()
+ private val inMemoryDb: SQLiteDatabase by lazy {
+ ModelTestExtensions.createInMemoryDb(INSERTION_STATEMENT_FILE)
+ }
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
@@ -123,8 +132,23 @@
.getAppWidgetInfo(any())
`when`(launcherModel.beginLoader(any())).thenReturn(transaction)
- `when`(launcherModel.modelDbController)
- .thenReturn(FactitiousDbController(context, INSERTION_STATEMENT_FILE))
+
+ `when`(launcherModel.modelDbController).thenReturn(modelDbController)
+ doAnswer {}.whenever(modelDbController).loadDefaultFavoritesIfNecessary()
+ doAnswer { i ->
+ inMemoryDb.query(
+ TABLE_NAME,
+ i.getArgument(0),
+ i.getArgument(1),
+ i.getArgument(2),
+ null,
+ null,
+ i.getArgument(3),
+ )
+ }
+ .whenever(modelDbController)
+ .query(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())
+
`when`(launcherModel.modelDelegate).thenReturn(modelDelegate)
`when`(launcherBinder.newIdleLock(any())).thenReturn(idleLock)
`when`(idleLock.awaitLocked(1000)).thenReturn(false)
@@ -149,6 +173,7 @@
fun tearDown() {
LauncherPrefs.get(context).removeSync(RESTORE_DEVICE)
LauncherPrefs.get(context).putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(false))
+ inMemoryDb.close()
context.onDestroy()
mockitoSession.finishMocking()
}
diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index 8846d65..67ee1ac 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -15,7 +15,6 @@
*/
package com.android.launcher3.ui.widget;
-import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_ID_NOT_VALID;
import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
@@ -318,7 +317,7 @@
try {
return MODEL_EXECUTOR.submit(() ->
mModel.getModelDbController().query(
- TABLE_NAME, null, itemIdMatch(0), null, null)).get();
+ null, itemIdMatch(0), null, null)).get();
} catch (Exception e) {
throw new RuntimeException(e);
}