Merge "Remove padding from All Apps and Divider views." into main
diff --git a/quickstep/res/layout/task_desktop.xml b/quickstep/res/layout/task_desktop.xml
index 0472007..5270284 100644
--- a/quickstep/res/layout/task_desktop.xml
+++ b/quickstep/res/layout/task_desktop.xml
@@ -24,10 +24,6 @@
android:focusable="true"
launcher:focusBorderColor="?attr/materialColorOutline"
launcher:hoverBorderColor="?attr/materialColorPrimary">
- <View
- android:id="@+id/background"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
<ViewStub
android:id="@+id/icon"
@@ -38,6 +34,13 @@
<com.android.quickstep.views.DesktopTaskContentView
android:id="@+id/desktop_content"
android:layout_width="match_parent"
- android:layout_height="match_parent" />
+ android:layout_height="match_parent">
+
+ <View
+ android:id="@+id/background"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ </com.android.quickstep.views.DesktopTaskContentView>
</com.android.quickstep.views.DesktopTaskView>
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index 3b7ad3e..de42669 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -299,10 +299,6 @@
return;
}
mQuickSwitchViewController.closeQuickSwitchView(animate);
- if (mOnClosed != null) {
- mOnClosed.run();
- mOnClosed = null;
- }
}
/**
@@ -394,6 +390,13 @@
});
}
+ void onCloseStarted() {
+ if (mOnClosed != null) {
+ mOnClosed.run();
+ mOnClosed = null;
+ }
+ }
+
void onCloseComplete() {
if (Flags.taskbarOverflow() && mOverlayContext != null) {
mOverlayContext.getDragLayer()
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index 985cc26..e623b21 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -198,6 +198,7 @@
// Let currently-running animation finish.
return;
}
+ mControllerCallbacks.onCloseStarted();
if (!animate) {
InteractionJankMonitorWrapper.begin(
mKeyboardQuickSwitchView, Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE);
diff --git a/quickstep/src/com/android/launcher3/taskbar/ManageWindowsTaskbarShortcut.kt b/quickstep/src/com/android/launcher3/taskbar/ManageWindowsTaskbarShortcut.kt
index c0c2a02..860e822 100644
--- a/quickstep/src/com/android/launcher3/taskbar/ManageWindowsTaskbarShortcut.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/ManageWindowsTaskbarShortcut.kt
@@ -46,7 +46,7 @@
class ManageWindowsTaskbarShortcut<T>(
private val target: T,
private val itemInfo: ItemInfo?,
- private val originalView: View?,
+ private val originalView: View,
private val controllers: TaskbarControllers,
) :
SystemShortcut<T>(
@@ -123,7 +123,7 @@
taskbarShortcutAllWindowsView =
TaskbarShortcutManageWindowsView(
- originalView!!,
+ originalView,
controllers.taskbarOverlayController.requestWindow(),
taskList,
onIconClickListener,
@@ -160,7 +160,7 @@
/** Adds the carousel menu to the taskbar overlay drag layer */
override fun addToContainer(menuView: ManageWindowsView) {
- taskbarOverlayContext.dragLayer.post { positionCarouselMenu() }
+ positionCarouselMenu()
controllers.taskbarAutohideSuspendController.updateFlag(
FLAG_AUTOHIDE_SUSPEND_MULTI_INSTANCE_MENU_OPEN,
@@ -184,28 +184,28 @@
* align with the calling app while ensuring it doesn't go beyond the screen edge.
*/
private fun positionCarouselMenu() {
+ val deviceProfile = taskbarActivityContext.deviceProfile
val margin =
context.resources.getDimension(
R.dimen.taskbar_multi_instance_menu_min_padding_from_screen_edge
)
// Calculate the Y position to place the carousel above the taskbar
- val availableHeight = taskbarOverlayContext.dragLayer.height
menuView.rootView.y =
- availableHeight -
+ deviceProfile.availableHeightPx -
menuView.menuHeight -
controllers.taskbarStashController.touchableHeight -
margin
// Calculate the X position to align with the calling app,
// but avoid clashing with the screen edge
- val availableWidth = taskbarOverlayContext.dragLayer.width
- if (Utilities.isRtl(context.resources)) {
- menuView.rootView.translationX = -(availableWidth - menuView.menuWidth) / 2f
- } else {
- val maxX = availableWidth - menuView.menuWidth - margin
- menuView.rootView.translationX = minOf(originalView.x, maxX)
- }
+ menuView.rootView.translationX =
+ if (Utilities.isRtl(context.resources)) {
+ -(deviceProfile.availableWidthPx - menuView.menuWidth) / 2f
+ } else {
+ val maxX = deviceProfile.availableWidthPx - menuView.menuWidth - margin
+ minOf(originalView.x, maxX)
+ }
}
/** Closes the carousel menu and removes it from the taskbar overlay drag layer */
@@ -227,8 +227,8 @@
override fun onControllerInterceptTouchEvent(ev: MotionEvent?): Boolean {
ev?.let {
if (
- ev.action == MotionEvent.ACTION_DOWN &&
- !taskbarOverlayContext.dragLayer.isEventOverView(menuView.rootView, ev)
+ it.action == MotionEvent.ACTION_DOWN &&
+ !taskbarOverlayContext.dragLayer.isEventOverView(menuView.rootView, it)
) {
removeFromContainer()
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index e05178d..f9e7cf0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.taskbar;
+import static android.view.KeyEvent.ACTION_UP;
import static android.view.View.AccessibilityDelegate;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
@@ -72,7 +73,6 @@
import android.graphics.drawable.RotateDrawable;
import android.inputmethodservice.InputMethodService;
import android.os.Handler;
-import android.os.SystemClock;
import android.util.Property;
import android.view.Gravity;
import android.view.KeyEvent;
@@ -864,17 +864,12 @@
TaskbarNavButtonController navButtonController) {
buttonView.setOnTouchListener((v, event) -> {
if (event.getAction() == MotionEvent.ACTION_MOVE) return false;
- long time = SystemClock.uptimeMillis();
- int action = event.getAction();
- KeyEvent keyEvent = new KeyEvent(time, time,
- action == MotionEvent.ACTION_DOWN ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP,
- KeyEvent.KEYCODE_BACK, 0);
- if (event.getAction() == MotionEvent.ACTION_CANCEL) {
- keyEvent.cancel();
- }
- navButtonController.executeBack(keyEvent);
-
- if (action == MotionEvent.ACTION_UP) {
+ int motionEventAction = event.getAction();
+ int keyEventAction = motionEventAction == MotionEvent.ACTION_DOWN
+ ? KeyEvent.ACTION_DOWN : ACTION_UP;
+ boolean isCancelled = event.getAction() == MotionEvent.ACTION_CANCEL;
+ navButtonController.sendBackKeyEvent(keyEventAction, isCancelled);
+ if (motionEventAction == MotionEvent.ACTION_UP) {
buttonView.performClick();
}
return false;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index dce377d..f33666a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -475,7 +475,7 @@
}
mControllers.bubbleControllers.ifPresent(controllers -> {
// Show the bubble bar when on launcher home (hotseat icons visible) or in overview
- boolean onOverview = mLauncherState == LauncherState.OVERVIEW;
+ boolean onOverview = isInLauncher && mLauncherState == LauncherState.OVERVIEW;
boolean hotseatIconsVisible = isInLauncher && mLauncherState.areElementsVisible(
mLauncher, HOTSEAT_ICONS);
BubbleLauncherState state = onOverview
@@ -1009,12 +1009,7 @@
@Override
public void onRecentsAnimationFinished(RecentsAnimationController controller) {
- endGestureStateOverride(!controller.getFinishTargetIsLauncher(),
- controller.getLauncherIsVisibleAtFinish(), false /*canceled*/);
- }
-
- private void endGestureStateOverride(boolean finishedToApp, boolean canceled) {
- endGestureStateOverride(finishedToApp, finishedToApp, canceled);
+ endGestureStateOverride(!controller.getFinishTargetIsLauncher(), false /*canceled*/);
}
/**
@@ -1024,13 +1019,11 @@
*
* @param finishedToApp {@code true} if the recents animation finished to showing an app and
* not workspace or overview
- * @param launcherIsVisible {code true} if launcher is visible at finish
* @param canceled {@code true} if the recents animation was canceled instead of
* finishing
* to completion
*/
- private void endGestureStateOverride(boolean finishedToApp, boolean launcherIsVisible,
- boolean canceled) {
+ private void endGestureStateOverride(boolean finishedToApp, boolean canceled) {
mCallbacks.removeListener(this);
mTaskBarRecentsAnimationListener = null;
((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(null);
@@ -1039,27 +1032,18 @@
mSkipNextRecentsAnimEnd = false;
return;
}
- updateStateForUserFinishedToApp(finishedToApp, launcherIsVisible);
+ updateStateForUserFinishedToApp(finishedToApp);
}
}
/**
- * @see #updateStateForUserFinishedToApp(boolean, boolean)
- */
- private void updateStateForUserFinishedToApp(boolean finishedToApp) {
- updateStateForUserFinishedToApp(finishedToApp, !finishedToApp);
- }
-
- /**
* Updates the visible state immediately to ensure a seamless handoff.
*
* @param finishedToApp True iff user is in an app.
- * @param launcherIsVisible True iff launcher is still visible (ie. transparent app)
*/
- private void updateStateForUserFinishedToApp(boolean finishedToApp,
- boolean launcherIsVisible) {
+ private void updateStateForUserFinishedToApp(boolean finishedToApp) {
// Update the visible state immediately to ensure a seamless handoff
- boolean launcherVisible = !finishedToApp || launcherIsVisible;
+ boolean launcherVisible = !finishedToApp;
updateStateForFlag(FLAG_TRANSITION_TO_VISIBLE, false);
updateStateForFlag(FLAG_VISIBLE, launcherVisible);
applyState();
@@ -1068,7 +1052,7 @@
if (DEBUG) {
Log.d(TAG, "endGestureStateOverride - FLAG_IN_APP: " + finishedToApp);
}
- controller.updateStateForFlag(FLAG_IN_APP, finishedToApp && !launcherIsVisible);
+ controller.updateStateForFlag(FLAG_IN_APP, finishedToApp);
controller.applyState();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 7ab9ef3..d4814d3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -46,6 +46,7 @@
import android.os.Trace;
import android.provider.Settings;
import android.util.Log;
+import android.util.SparseArray;
import android.view.Display;
import android.view.MotionEvent;
import android.view.WindowManager;
@@ -115,13 +116,13 @@
private final Context mContext;
private final @Nullable Context mNavigationBarPanelContext;
private WindowManager mWindowManager;
- private FrameLayout mTaskbarRootLayout;
+ private FrameLayout mDefaultRootLayout;
private boolean mAddedWindow;
- private final TaskbarNavButtonController mNavButtonController;
- private final ComponentCallbacks mComponentCallbacks;
+ private final TaskbarNavButtonController mDefaultNavButtonController;
+ private final ComponentCallbacks mDefaultComponentCallbacks;
private final SimpleBroadcastReceiver mShutdownReceiver =
- new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, i -> destroyExistingTaskbar());
+ new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, i -> destroyAllTaskbars());
// The source for this provider is set when Launcher is available
// We use 'non-destroyable' version here so the original provider won't be destroyed
@@ -129,8 +130,8 @@
// It's destruction/creation will be managed by the activity.
private final ScopedUnfoldTransitionProgressProvider mUnfoldProgressProvider =
new NonDestroyableScopedUnfoldTransitionProgressProvider();
-
- private TaskbarActivityContext mTaskbarActivityContext;
+ /** DisplayId - {@link TaskbarActivityContext} map for Connected Display. */
+ private final SparseArray<TaskbarActivityContext> mTaskbars = new SparseArray<>();
private StatefulActivity mActivity;
private RecentsViewContainer mRecentsViewContainer;
@@ -167,7 +168,9 @@
private final Runnable mActivityOnDestroyCallback = new Runnable() {
@Override
public void run() {
+ int displayId = getDefaultDisplayId();
if (mActivity != null) {
+ displayId = mActivity.getDisplayId();
mActivity.removeOnDeviceProfileChangeListener(
mDebugActivityDeviceProfileChanged);
Log.d(TASKBAR_NOT_DESTROYED_TAG,
@@ -180,8 +183,9 @@
}
mActivity = null;
debugWhyTaskbarNotDestroyed("clearActivity");
- if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.setUIController(TaskbarUIController.DEFAULT);
+ TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
+ if (taskbar != null) {
+ taskbar.setUIController(TaskbarUIController.DEFAULT);
}
mUnfoldProgressProvider.setSourceProvider(null);
}
@@ -236,27 +240,27 @@
mDesktopVisibilityController = desktopVisibilityController;
if (enableTaskbarNoRecreate()) {
mWindowManager = mContext.getSystemService(WindowManager.class);
- mTaskbarRootLayout = new FrameLayout(mContext) {
+ mDefaultRootLayout = new FrameLayout(mContext) {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// The motion events can be outside the view bounds of task bar, and hence
// manually dispatching them to the drag layer here.
- if (mTaskbarActivityContext != null
- && mTaskbarActivityContext.getDragLayer().isAttachedToWindow()) {
- return mTaskbarActivityContext.getDragLayer().dispatchTouchEvent(ev);
+ TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
+ if (taskbar != null && taskbar.getDragLayer().isAttachedToWindow()) {
+ return taskbar.getDragLayer().dispatchTouchEvent(ev);
}
return super.dispatchTouchEvent(ev);
}
};
}
- mNavButtonController = new TaskbarNavButtonController(
+ mDefaultNavButtonController = new TaskbarNavButtonController(
context,
navCallbacks,
SystemUiProxy.INSTANCE.get(mContext),
ContextualEduStatsManager.INSTANCE.get(mContext),
new Handler(),
ContextualSearchInvoker.newInstance(mContext));
- mComponentCallbacks = new ComponentCallbacks() {
+ mDefaultComponentCallbacks = new ComponentCallbacks() {
private Configuration mOldConfig = mContext.getResources().getConfiguration();
@Override
@@ -265,6 +269,7 @@
"onConfigurationChanged: " + newConfig);
debugWhyTaskbarNotDestroyed(
"TaskbarManager#mComponentCallbacks.onConfigurationChanged: " + newConfig);
+ // TODO: adapt this logic to be specific to different displays.
DeviceProfile dp = mUserUnlocked
? LauncherAppState.getIDP(mContext).getDeviceProfile(mContext)
: null;
@@ -281,12 +286,12 @@
debugWhyTaskbarNotDestroyed("ComponentCallbacks#onConfigurationChanged() "
+ "configDiff=" + Configuration.configurationDiffToString(configDiff));
- if (configDiff != 0 || mTaskbarActivityContext == null) {
+ if (configDiff != 0 || getCurrentActivityContext() == null) {
recreateTaskbar();
} else {
// Config change might be handled without re-creating the taskbar
if (dp != null && !isTaskbarEnabled(dp)) {
- destroyExistingTaskbar();
+ destroyDefaultTaskbar();
} else {
if (dp != null && isTaskbarEnabled(dp)) {
if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
@@ -295,10 +300,10 @@
// block above?
recreateTaskbar();
} else {
- mTaskbarActivityContext.updateDeviceProfile(dp);
+ getCurrentActivityContext().updateDeviceProfile(dp);
}
}
- mTaskbarActivityContext.onConfigurationChanged(configDiff);
+ getCurrentActivityContext().onConfigurationChanged(configDiff);
}
}
mOldConfig = new Configuration(newConfig);
@@ -315,7 +320,7 @@
SettingsCache.INSTANCE.get(mContext)
.register(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
Log.d(TASKBAR_NOT_DESTROYED_TAG, "registering component callbacks from constructor.");
- mContext.registerComponentCallbacks(mComponentCallbacks);
+ mContext.registerComponentCallbacks(mDefaultComponentCallbacks);
mShutdownReceiver.register(mContext, Intent.ACTION_SHUTDOWN);
UI_HELPER_EXECUTOR.execute(() -> {
mSharedState.taskbarSystemActionPendingIntent = PendingIntent.getBroadcast(
@@ -331,13 +336,25 @@
recreateTaskbar();
}
- private void destroyExistingTaskbar() {
- debugWhyTaskbarNotDestroyed("destroyExistingTaskbar: " + mTaskbarActivityContext);
- if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.onDestroy();
- if (!ENABLE_TASKBAR_NAVBAR_UNIFICATION || enableTaskbarNoRecreate()) {
- mTaskbarActivityContext = null;
- }
+ private void destroyAllTaskbars() {
+ for (int i = 0; i < mTaskbars.size(); i++) {
+ int displayId = mTaskbars.keyAt(i);
+ destroyTaskbarForDisplay(displayId);
+ }
+ }
+
+ private void destroyDefaultTaskbar() {
+ destroyTaskbarForDisplay(getDefaultDisplayId());
+ }
+
+ private void destroyTaskbarForDisplay(int displayId) {
+ TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
+ debugWhyTaskbarNotDestroyed(
+ "destroyTaskbarForDisplay: " + taskbar + " displayId=" + displayId);
+ if (taskbar != null) {
+ taskbar.onDestroy();
+ // remove all defaults that we store
+ removeTaskbarFromMap(displayId);
}
DeviceProfile dp = mUserUnlocked ?
LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
@@ -350,8 +367,10 @@
* Show Taskbar upon receiving broadcast
*/
private void showTaskbarFromBroadcast(Intent intent) {
- if (ACTION_SHOW_TASKBAR.equals(intent.getAction()) && mTaskbarActivityContext != null) {
- mTaskbarActivityContext.showTaskbarFromBroadcast();
+ // TODO: make this code displayId specific
+ TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
+ if (ACTION_SHOW_TASKBAR.equals(intent.getAction()) && taskbar != null) {
+ taskbar.showTaskbarFromBroadcast();
}
}
@@ -359,12 +378,13 @@
* Toggles All Apps for Taskbar or Launcher depending on the current state.
*/
public void toggleAllApps() {
- if (mTaskbarActivityContext == null || mTaskbarActivityContext.canToggleHomeAllApps()) {
+ TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
+ if (taskbar == null || taskbar.canToggleHomeAllApps()) {
// Home All Apps should be toggled from this class, because the controllers are not
// initialized when Taskbar is disabled (i.e. TaskbarActivityContext is null).
if (mActivity instanceof Launcher l) l.toggleAllAppsSearch();
} else {
- mTaskbarActivityContext.toggleAllAppsSearch();
+ taskbar.toggleAllAppsSearch();
}
}
@@ -375,8 +395,8 @@
* progress.
*/
public AnimatorPlaybackController createLauncherStartFromSuwAnim(int duration) {
- return mTaskbarActivityContext == null
- ? null : mTaskbarActivityContext.createLauncherStartFromSuwAnim(duration);
+ TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
+ return taskbar == null ? null : taskbar.createLauncherStartFromSuwAnim(duration);
}
/**
@@ -386,7 +406,7 @@
mUserUnlocked = true;
DisplayController.INSTANCE.get(mContext).addChangeListener(mRecreationListener);
recreateTaskbar();
- addTaskbarRootViewToWindow();
+ addTaskbarRootViewToWindow(getDefaultDisplayId());
}
/**
@@ -429,8 +449,9 @@
mActivityOnDestroyCallback.run();
}
mRecentsViewContainer = recentsViewContainer;
- if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.setUIController(
+ TaskbarActivityContext taskbar = getCurrentActivityContext();
+ if (taskbar != null) {
+ taskbar.setUIController(
createTaskbarUIControllerForRecentsViewContainer(mRecentsViewContainer));
}
}
@@ -472,11 +493,21 @@
/**
* This method is called multiple times (ex. initial init, then when user unlocks) in which case
- * we fully want to destroy an existing taskbar and create a new one.
+ * we fully want to destroy the existing default display's taskbar and create a new one.
* In other case (folding/unfolding) we don't need to remove and add window.
*/
@VisibleForTesting
public synchronized void recreateTaskbar() {
+ // TODO: make this recreate all taskbars in map.
+ recreateTaskbarForDisplay(getDefaultDisplayId());
+ }
+
+ /**
+ * This method is called multiple times (ex. initial init, then when user unlocks) in which case
+ * we fully want to destroy an existing taskbar for a specified display and create a new one.
+ * In other case (folding/unfolding) we don't need to remove and add window.
+ */
+ private void recreateTaskbarForDisplay(int displayId) {
Trace.beginSection("recreateTaskbar");
try {
DeviceProfile dp = mUserUnlocked ?
@@ -486,7 +517,7 @@
final boolean isLargeScreenTaskbar = dp != null && dp.isTaskbarPresent;
mAllAppsActionManager.setTaskbarPresent(isLargeScreenTaskbar);
- destroyExistingTaskbar();
+ destroyTaskbarForDisplay(displayId);
boolean isTaskbarEnabled = dp != null && isTaskbarEnabled(dp);
debugWhyTaskbarNotDestroyed("recreateTaskbar: isTaskbarEnabled=" + isTaskbarEnabled
@@ -501,29 +532,31 @@
}
}
- if (enableTaskbarNoRecreate() || mTaskbarActivityContext == null) {
- mTaskbarActivityContext = new TaskbarActivityContext(mContext,
- mNavigationBarPanelContext, dp, mNavButtonController,
+ TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
+ if (enableTaskbarNoRecreate() || taskbar == null) {
+ taskbar = new TaskbarActivityContext(mContext,
+ mNavigationBarPanelContext, dp, mDefaultNavButtonController,
mUnfoldProgressProvider, mDesktopVisibilityController);
} else {
- mTaskbarActivityContext.updateDeviceProfile(dp);
+ taskbar.updateDeviceProfile(dp);
}
mSharedState.startTaskbarVariantIsTransient =
- DisplayController.isTransientTaskbar(mTaskbarActivityContext);
+ DisplayController.isTransientTaskbar(taskbar);
mSharedState.allAppsVisible = mSharedState.allAppsVisible && isLargeScreenTaskbar;
- mTaskbarActivityContext.init(mSharedState);
+ taskbar.init(mSharedState);
if (mRecentsViewContainer != null) {
- mTaskbarActivityContext.setUIController(
+ taskbar.setUIController(
createTaskbarUIControllerForRecentsViewContainer(mRecentsViewContainer));
}
if (enableTaskbarNoRecreate()) {
- addTaskbarRootViewToWindow();
- mTaskbarRootLayout.removeAllViews();
- mTaskbarRootLayout.addView(mTaskbarActivityContext.getDragLayer());
- mTaskbarActivityContext.notifyUpdateLayoutParams();
+ addTaskbarRootViewToWindow(displayId);
+ mDefaultRootLayout.removeAllViews();
+ mDefaultRootLayout.addView(taskbar.getDragLayer());
+ taskbar.notifyUpdateLayoutParams();
}
+ addTaskbarToMap(displayId, taskbar);
} finally {
Trace.endSection();
}
@@ -535,14 +568,15 @@
mSharedState.sysuiStateFlags, QuickStepContract::getSystemUiStateString));
}
mSharedState.sysuiStateFlags = systemUiStateFlags;
- if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.updateSysuiStateFlags(systemUiStateFlags, false /* fromInit */);
+ TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
+ if (taskbar != null) {
+ taskbar.updateSysuiStateFlags(systemUiStateFlags, false /* fromInit */);
}
}
public void onLongPressHomeEnabled(boolean assistantLongPressEnabled) {
- if (mNavButtonController != null) {
- mNavButtonController.setAssistantLongPressEnabled(assistantLongPressEnabled);
+ if (mDefaultNavButtonController != null) {
+ mDefaultNavButtonController.setAssistantLongPressEnabled(assistantLongPressEnabled);
}
}
@@ -551,46 +585,53 @@
*/
public void setSetupUIVisible(boolean isVisible) {
mSharedState.setupUIVisible = isVisible;
- if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.setSetupUIVisible(isVisible);
+ TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
+ if (taskbar != null) {
+ taskbar.setSetupUIVisible(isVisible);
}
}
public void setWallpaperVisible(boolean isVisible) {
mSharedState.wallpaperVisible = isVisible;
- if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.setWallpaperVisible(isVisible);
+ TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
+ if (taskbar != null) {
+ taskbar.setWallpaperVisible(isVisible);
}
}
public void checkNavBarModes(int displayId) {
- if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.checkNavBarModes();
+ TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
+ if (taskbar != null) {
+ taskbar.checkNavBarModes();
}
}
public void finishBarAnimations(int displayId) {
- if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.finishBarAnimations();
+ TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
+ if (taskbar != null) {
+ taskbar.finishBarAnimations();
}
}
public void touchAutoDim(int displayId, boolean reset) {
- if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.touchAutoDim(reset);
+ TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
+ if (taskbar != null) {
+ taskbar.touchAutoDim(reset);
}
}
public void transitionTo(int displayId, @BarTransitions.TransitionMode int barMode,
boolean animate) {
- if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.transitionTo(barMode, animate);
+ TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
+ if (taskbar != null) {
+ taskbar.transitionTo(barMode, animate);
}
}
public void appTransitionPending(boolean pending) {
- if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.appTransitionPending(pending);
+ TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
+ if (taskbar != null) {
+ taskbar.appTransitionPending(pending);
}
}
@@ -599,8 +640,9 @@
}
public void onRotationProposal(int rotation, boolean isValid) {
- if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.onRotationProposal(rotation, isValid);
+ TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
+ if (taskbar != null) {
+ taskbar.onRotationProposal(rotation, isValid);
}
}
@@ -608,38 +650,43 @@
mSharedState.disableNavBarDisplayId = displayId;
mSharedState.disableNavBarState1 = state1;
mSharedState.disableNavBarState2 = state2;
- if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.disableNavBarElements(displayId, state1, state2, animate);
+ TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
+ if (taskbar != null) {
+ taskbar.disableNavBarElements(displayId, state1, state2, animate);
}
}
public void onSystemBarAttributesChanged(int displayId, int behavior) {
mSharedState.systemBarAttrsDisplayId = displayId;
mSharedState.systemBarAttrsBehavior = behavior;
- if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.onSystemBarAttributesChanged(displayId, behavior);
+ TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
+ if (taskbar != null) {
+ taskbar.onSystemBarAttributesChanged(displayId, behavior);
}
}
public void onTransitionModeUpdated(int barMode, boolean checkBarModes) {
mSharedState.barMode = barMode;
- if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.onTransitionModeUpdated(barMode, checkBarModes);
+ TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
+ if (taskbar != null) {
+ taskbar.onTransitionModeUpdated(barMode, checkBarModes);
}
}
public void onNavButtonsDarkIntensityChanged(float darkIntensity) {
mSharedState.navButtonsDarkIntensity = darkIntensity;
- if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.onNavButtonsDarkIntensityChanged(darkIntensity);
+ TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
+ if (taskbar != null) {
+ taskbar.onNavButtonsDarkIntensityChanged(darkIntensity);
}
}
public void onNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {
mSharedState.mLumaSamplingDisplayId = displayId;
mSharedState.mIsLumaSamplingEnabled = enable;
- if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.onNavigationBarLumaSamplingEnabled(displayId, enable);
+ TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
+ if (taskbar != null) {
+ taskbar.onNavigationBarLumaSamplingEnabled(displayId, enable);
}
}
@@ -666,7 +713,7 @@
debugWhyTaskbarNotDestroyed("TaskbarManager#destroy()");
removeActivityCallbacksAndListeners();
mTaskbarBroadcastReceiver.unregisterReceiverSafely(mContext);
- destroyExistingTaskbar();
+ destroyAllTaskbars();
removeTaskbarRootViewFromWindow();
if (mUserUnlocked) {
DisplayController.INSTANCE.get(mContext).removeChangeListener(mRecreationListener);
@@ -676,38 +723,64 @@
SettingsCache.INSTANCE.get(mContext)
.unregister(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
Log.d(TASKBAR_NOT_DESTROYED_TAG, "unregistering component callbacks from destroy().");
- mContext.unregisterComponentCallbacks(mComponentCallbacks);
+ mContext.unregisterComponentCallbacks(mDefaultComponentCallbacks);
mShutdownReceiver.unregisterReceiverSafely(mContext);
}
public @Nullable TaskbarActivityContext getCurrentActivityContext() {
- return mTaskbarActivityContext;
+ return getTaskbarForDisplay(mContext.getDisplayId());
}
public void dumpLogs(String prefix, PrintWriter pw) {
pw.println(prefix + "TaskbarManager:");
- if (mTaskbarActivityContext == null) {
+ TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
+ if (taskbar == null) {
pw.println(prefix + "\tTaskbarActivityContext: null");
} else {
- mTaskbarActivityContext.dumpLogs(prefix + "\t", pw);
+ taskbar.dumpLogs(prefix + "\t", pw);
}
}
- private void addTaskbarRootViewToWindow() {
- if (enableTaskbarNoRecreate() && !mAddedWindow && mTaskbarActivityContext != null) {
- mWindowManager.addView(mTaskbarRootLayout,
- mTaskbarActivityContext.getWindowLayoutParams());
+ private void addTaskbarRootViewToWindow(int displayId) {
+ TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
+ if (enableTaskbarNoRecreate() && !mAddedWindow && taskbar != null) {
+ mWindowManager.addView(mDefaultRootLayout, taskbar.getWindowLayoutParams());
mAddedWindow = true;
}
}
private void removeTaskbarRootViewFromWindow() {
if (enableTaskbarNoRecreate() && mAddedWindow) {
- mWindowManager.removeViewImmediate(mTaskbarRootLayout);
+ mWindowManager.removeViewImmediate(mDefaultRootLayout);
mAddedWindow = false;
}
}
+ /**
+ * Returns the {@link TaskbarActivityContext} associated with the given display ID.
+ *
+ * @param displayId The ID of the display to retrieve the taskbar for.
+ * @return The {@link TaskbarActivityContext} for the specified display, or
+ * {@code null} if no taskbar is associated with that display.
+ */
+ private TaskbarActivityContext getTaskbarForDisplay(int displayId) {
+ return mTaskbars.get(displayId);
+ }
+
+ private void addTaskbarToMap(int displayId, TaskbarActivityContext newTaskbar) {
+ if (!mTaskbars.contains(displayId)) {
+ mTaskbars.put(displayId, newTaskbar);
+ }
+ }
+
+ private void removeTaskbarFromMap(int displayId) {
+ mTaskbars.delete(displayId);
+ }
+
+ private int getDefaultDisplayId() {
+ return mContext.getDisplayId();
+ }
+
/** Temp logs for b/254119092. */
public void debugWhyTaskbarNotDestroyed(String debugReason) {
StringJoiner log = new StringJoiner("\n");
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
index c20617d..f905c5f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -208,7 +208,7 @@
private void commitHotseatItemUpdates(
ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks) {
- mContainer.updateHotseatItems(hotseatItemInfos, recentTasks);
+ mContainer.updateItems(hotseatItemInfos, recentTasks);
mControllers.taskbarViewController.updateIconViewsRunningStates();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index d4764c7..4881836 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -16,7 +16,8 @@
package com.android.launcher3.taskbar;
-import static android.view.MotionEvent.ACTION_UP;
+import static android.view.KeyEvent.ACTION_DOWN;
+import static android.view.KeyEvent.ACTION_UP;
import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS;
import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_KEY;
@@ -38,6 +39,7 @@
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
+import android.os.SystemClock;
import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
@@ -78,6 +80,7 @@
private long mLastScreenPinLongPress;
private boolean mScreenPinned;
private boolean mAssistantLongPressEnabled;
+ private int mLastSentBackAction = ACTION_UP;
@Override
public void dumpLogs(String prefix, PrintWriter pw) {
@@ -85,6 +88,8 @@
pw.println(prefix + "\tmLastScreenPinLongPress=" + mLastScreenPinLongPress);
pw.println(prefix + "\tmScreenPinned=" + mScreenPinned);
+ pw.println(prefix + "\tmLastSentBackAction="
+ + KeyEvent.actionToString(mLastSentBackAction));
}
@Retention(RetentionPolicy.SOURCE)
@@ -141,6 +146,11 @@
if (buttonType == BUTTON_SPACE) {
return;
}
+ if (predictiveBackThreeButtonNav() && mLastSentBackAction == ACTION_DOWN) {
+ Log.i(TAG, "Button click ignored while back button is pressed");
+ // prevent interactions with other buttons while back button is pressed
+ return;
+ }
// Provide the same haptic feedback that the system offers for virtual keys.
view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
switch (buttonType) {
@@ -180,6 +190,13 @@
if (buttonType == BUTTON_SPACE) {
return false;
}
+ if (predictiveBackThreeButtonNav() && mLastSentBackAction == ACTION_DOWN
+ && buttonType != BUTTON_BACK && buttonType != BUTTON_RECENTS) {
+ // prevent interactions with other buttons while back button is pressed (except back
+ // and recents button for screen-unpin action).
+ Log.i(TAG, "Button long click ignored while back button is pressed");
+ return false;
+ }
// Provide the same haptic feedback that the system offers for long press.
// The haptic feedback from long pressing on the home button is handled by circle to search.
@@ -327,13 +344,27 @@
mCallbacks.onToggleOverview();
}
- void executeBack(@Nullable KeyEvent keyEvent) {
+ void sendBackKeyEvent(int action, boolean cancelled) {
+ if (action == mLastSentBackAction) {
+ // There must always be an alternating sequence of ACTION_DOWN and ACTION_UP events
+ return;
+ }
+ long time = SystemClock.uptimeMillis();
+ KeyEvent keyEvent = new KeyEvent(time, time, action, KeyEvent.KEYCODE_BACK, 0);
+ if (cancelled) {
+ keyEvent.cancel();
+ }
+ executeBack(keyEvent);
+ }
+
+ private void executeBack(@Nullable KeyEvent keyEvent) {
if (keyEvent == null || (keyEvent.getAction() == ACTION_UP && !keyEvent.isCanceled())) {
logEvent(LAUNCHER_TASKBAR_BACK_BUTTON_TAP);
mContextualEduStatsManager.updateEduStats(/* isTrackpadGesture= */ false,
GestureType.BACK);
}
mSystemUiProxy.onBackEvent(keyEvent);
+ mLastSentBackAction = keyEvent != null ? keyEvent.getAction() : ACTION_UP;
}
private void onImeSwitcherPress() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 8816a6d..b609511 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -20,11 +20,14 @@
import static com.android.launcher3.BubbleTextView.DISPLAY_TASKBAR;
import static com.android.launcher3.Flags.enableCursorHoverStates;
import static com.android.launcher3.Flags.enableRecentsInTaskbar;
+import static com.android.launcher3.Flags.taskbarRecentsLayoutTransition;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
+import static java.util.function.Predicate.not;
+
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
@@ -70,7 +73,9 @@
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import java.util.function.Predicate;
/**
@@ -108,6 +113,8 @@
// Only non-null when device supports having a Taskbar Overflow button.
@Nullable private TaskbarOverflowView mTaskbarOverflowView;
+ private int mNextViewIndex;
+
/**
* Whether the divider is between Hotseat icons and Recents,
* instead of between All Apps button and Hotseat.
@@ -125,6 +132,8 @@
private final int mAllAppsButtonTranslationOffset;
+ private final int mNumStaticViews;
+
public TaskbarView(@NonNull Context context) {
this(context, null);
}
@@ -189,6 +198,8 @@
// TODO: Disable touch events on QSB otherwise it can crash.
mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
+
+ mNumStaticViews = taskbarRecentsLayoutTransition() ? addStaticViews() : 0;
}
/**
@@ -249,6 +260,24 @@
&& (mIdealNumIcons > oldMaxNumIcons || mIdealNumIcons > mMaxNumIcons);
}
+ /**
+ * Pre-adds views that are always children of this view for LayoutTransition support.
+ * <p>
+ * Normally these views are removed and re-added when updating hotseat and recents. This
+ * approach does not behave well with LayoutTransition, so we instead need to add them
+ * initially and avoid removing them during updates.
+ */
+ private int addStaticViews() {
+ int numStaticViews = 1;
+ addView(mAllAppsButtonContainer);
+ if (mActivityContext.getDeviceProfile().isQsbInline) {
+ addView(mQsb, mIsRtl ? 1 : 0);
+ mQsb.setVisibility(View.INVISIBLE);
+ numStaticViews++;
+ }
+ return numStaticViews;
+ }
+
@Override
public void setVisibility(int visibility) {
boolean changed = getVisibility() != visibility;
@@ -362,12 +391,26 @@
view.setTag(null);
}
- /**
- * Inflates/binds the Hotseat views to show in the Taskbar given their ItemInfos.
- */
- protected void updateHotseatItems(ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks) {
- int nextViewIndex = 0;
- int numViewsAnimated = 0;
+ /** Inflates/binds the hotseat items and recent tasks to the view. */
+ protected void updateItems(ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks) {
+ // Filter out unsupported items.
+ hotseatItemInfos = Arrays.stream(hotseatItemInfos)
+ .filter(Objects::nonNull)
+ .toArray(ItemInfo[]::new);
+ // TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
+ recentTasks = recentTasks.stream().filter(not(GroupTask::supportsMultipleTasks)).toList();
+
+ if (taskbarRecentsLayoutTransition()) {
+ updateItemsWithLayoutTransition(hotseatItemInfos, recentTasks);
+ } else {
+ updateItemsWithoutLayoutTransition(hotseatItemInfos, recentTasks);
+ }
+ }
+
+ private void updateItemsWithoutLayoutTransition(
+ ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks) {
+
+ mNextViewIndex = 0;
mAddedDividerForRecents = false;
removeView(mAllAppsButtonContainer);
@@ -380,12 +423,101 @@
}
removeView(mQsb);
- // Add Hotseat icons.
- for (ItemInfo hotseatItemInfo : hotseatItemInfos) {
- if (hotseatItemInfo == null) {
- continue;
- }
+ updateHotseatItems(hotseatItemInfos);
+ if (mTaskbarDividerContainer != null && !recentTasks.isEmpty()) {
+ addView(mTaskbarDividerContainer, mNextViewIndex++);
+ mAddedDividerForRecents = true;
+ }
+
+ updateRecents(recentTasks);
+
+ addView(mAllAppsButtonContainer, mIsRtl ? hotseatItemInfos.length : 0);
+
+ // If there are no recent tasks, add divider after All Apps (unless it's the only view).
+ if (!mAddedDividerForRecents
+ && mTaskbarDividerContainer != null
+ && getChildCount() > 1) {
+ addView(mTaskbarDividerContainer, mIsRtl ? (getChildCount() - 1) : 1);
+ }
+
+ if (mActivityContext.getDeviceProfile().isQsbInline) {
+ addView(mQsb, mIsRtl ? getChildCount() : 0);
+ // Always set QSB to invisible after re-adding.
+ mQsb.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ private void updateItemsWithLayoutTransition(
+ ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks) {
+
+ // Skip static views and potential All Apps divider, if they are on the left.
+ mNextViewIndex = mIsRtl ? 0 : mNumStaticViews;
+ if (getChildAt(mNextViewIndex) == mTaskbarDividerContainer) {
+ mNextViewIndex++;
+ }
+
+ // Update left section.
+ if (mIsRtl) {
+ updateRecents(recentTasks.reversed());
+ } else {
+ updateHotseatItems(hotseatItemInfos);
+ }
+
+ // Now at theoretical position for recent apps divider.
+ updateRecentsDivider(!recentTasks.isEmpty());
+ if (getChildAt(mNextViewIndex) == mTaskbarDividerContainer) {
+ mNextViewIndex++;
+ }
+
+ // Update right section.
+ if (mIsRtl) {
+ updateHotseatItems(hotseatItemInfos);
+ } else {
+ updateRecents(recentTasks);
+ }
+
+ // Recents divider takes priority.
+ if (!mAddedDividerForRecents) {
+ updateAllAppsDivider();
+ }
+ }
+
+ private void updateRecentsDivider(boolean hasRecents) {
+ if (hasRecents && !mAddedDividerForRecents) {
+ mAddedDividerForRecents = true;
+
+ // Remove possible All Apps divider.
+ if (getChildAt(mNumStaticViews) == mTaskbarDividerContainer) {
+ mNextViewIndex--; // All Apps divider on the left. Need to account for removing it.
+ }
+ removeView(mTaskbarDividerContainer);
+
+ addView(mTaskbarDividerContainer, mNextViewIndex);
+ } else if (!hasRecents && mAddedDividerForRecents) {
+ mAddedDividerForRecents = false;
+ removeViewAt(mNextViewIndex);
+ }
+ }
+
+ private void updateAllAppsDivider() {
+ final int allAppsDividerIndex =
+ mIsRtl ? getChildCount() - mNumStaticViews : mNumStaticViews;
+ if (getChildAt(allAppsDividerIndex) == mTaskbarDividerContainer
+ && getChildCount() == mNumStaticViews + 1) {
+ // Only static views with divider so remove divider.
+ removeView(mTaskbarDividerContainer);
+ } else if (getChildAt(allAppsDividerIndex) != mTaskbarDividerContainer
+ && getChildCount() >= mNumStaticViews + 1) {
+ // Static views with at least one app icon so add divider.
+ addView(mTaskbarDividerContainer, allAppsDividerIndex);
+ }
+ }
+
+ private void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
+ int numViewsAnimated = 0;
+
+ for (ItemInfo hotseatItemInfo : hotseatItemInfos) {
// Replace any Hotseat views with the appropriate type if it's not already that type.
final int expectedLayoutResId;
boolean isCollection = false;
@@ -401,8 +533,8 @@
}
View hotseatView = null;
- while (nextViewIndex < getChildCount()) {
- hotseatView = getChildAt(nextViewIndex);
+ while (isNextViewInSection(ItemInfo.class)) {
+ hotseatView = getChildAt(mNextViewIndex);
// see if the view can be reused
if ((hotseatView.getSourceLayoutResId() != expectedLayoutResId)
@@ -443,7 +575,7 @@
}
LayoutParams lp = new LayoutParams(mIconTouchSize, mIconTouchSize);
hotseatView.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
- addView(hotseatView, nextViewIndex, lp);
+ addView(hotseatView, mNextViewIndex, lp);
}
// Apply the Hotseat ItemInfos, or hide the view if there is none for a given index.
@@ -459,14 +591,15 @@
if (enableCursorHoverStates()) {
setHoverListenerForIcon(hotseatView);
}
- nextViewIndex++;
+ mNextViewIndex++;
}
- if (mTaskbarDividerContainer != null && !recentTasks.isEmpty()) {
- addView(mTaskbarDividerContainer, nextViewIndex++);
- mAddedDividerForRecents = true;
+ while (isNextViewInSection(ItemInfo.class)) {
+ removeAndRecycle(getChildAt(mNextViewIndex));
}
+ }
+ private void updateRecents(List<GroupTask> recentTasks) {
// At this point, the all apps button has not been added as a child view, but needs to be
// accounted for when comparing current icon count to max number of icons.
int nonTaskIconsToBeAdded = 1;
@@ -474,19 +607,11 @@
boolean supportsOverflow = Flags.taskbarOverflow();
int overflowSize = 0;
if (supportsOverflow) {
- int numberOfSupportedRecents = 0;
- for (GroupTask task : recentTasks) {
- // TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
- if (!task.supportsMultipleTasks()) {
- ++numberOfSupportedRecents;
- }
- }
-
- mIdealNumIcons = nextViewIndex + numberOfSupportedRecents + nonTaskIconsToBeAdded;
+ mIdealNumIcons = mNextViewIndex + recentTasks.size() + nonTaskIconsToBeAdded;
overflowSize = mIdealNumIcons - mMaxNumIcons;
if (overflowSize > 0 && mTaskbarOverflowView != null) {
- addView(mTaskbarOverflowView, nextViewIndex++);
+ addView(mTaskbarOverflowView, mNextViewIndex++);
} else if (mTaskbarOverflowView != null) {
mTaskbarOverflowView.clearItems();
}
@@ -495,9 +620,10 @@
List<Task> overflownTasks = null;
// An extra item needs to be added to overflow button to account for the space taken up by
// the overflow button.
- final int itemsToAddToOverflow = overflowSize > 0 ? overflowSize + 1 : 0;
+ final int itemsToAddToOverflow =
+ (overflowSize > 0) ? Math.min(overflowSize + 1, recentTasks.size()) : 0;
if (overflowSize > 0) {
- overflownTasks = new ArrayList<Task>(itemsToAddToOverflow);
+ overflownTasks = new ArrayList<>(itemsToAddToOverflow);
}
// Add Recent/Running icons.
@@ -505,10 +631,6 @@
if (mTaskbarOverflowView != null && overflownTasks != null
&& overflownTasks.size() < itemsToAddToOverflow) {
// TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
- if (task.supportsMultipleTasks()) {
- continue;
- }
-
overflownTasks.add(task.task1);
if (overflownTasks.size() == itemsToAddToOverflow) {
mTaskbarOverflowView.setItems(overflownTasks);
@@ -533,8 +655,8 @@
}
View recentIcon = null;
- while (nextViewIndex < getChildCount()) {
- recentIcon = getChildAt(nextViewIndex);
+ while (isNextViewInSection(GroupTask.class)) {
+ recentIcon = getChildAt(mNextViewIndex);
// see if the view can be reused
if ((recentIcon.getSourceLayoutResId() != expectedLayoutResId)
@@ -548,15 +670,11 @@
}
if (recentIcon == null) {
- if (isCollection) {
- // TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
- continue;
- }
-
+ // TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
recentIcon = inflate(expectedLayoutResId);
LayoutParams lp = new LayoutParams(mIconTouchSize, mIconTouchSize);
recentIcon.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
- addView(recentIcon, nextViewIndex, lp);
+ addView(recentIcon, mNextViewIndex, lp);
}
if (recentIcon instanceof BubbleTextView btv) {
@@ -566,29 +684,17 @@
if (enableCursorHoverStates()) {
setHoverListenerForIcon(recentIcon);
}
- nextViewIndex++;
+ mNextViewIndex++;
}
- // Remove remaining views
- while (nextViewIndex < getChildCount()) {
- removeAndRecycle(getChildAt(nextViewIndex));
+ while (isNextViewInSection(GroupTask.class)) {
+ removeAndRecycle(getChildAt(mNextViewIndex));
}
+ }
- addView(mAllAppsButtonContainer, mIsRtl ? hotseatItemInfos.length : 0);
-
- // If there are no recent tasks, add divider after All Apps (unless it's the only view).
- if (!mAddedDividerForRecents
- && mTaskbarDividerContainer != null
- && getChildCount() > 1) {
- addView(mTaskbarDividerContainer, mIsRtl ? (getChildCount() - 1) : 1);
- }
-
-
- if (mActivityContext.getDeviceProfile().isQsbInline) {
- addView(mQsb, mIsRtl ? getChildCount() : 0);
- // Always set QSB to invisible after re-adding.
- mQsb.setVisibility(View.INVISIBLE);
- }
+ private boolean isNextViewInSection(Class<?> tagClass) {
+ return mNextViewIndex < getChildCount()
+ && tagClass.isInstance(getChildAt(mNextViewIndex).getTag());
}
/** Binds the GroupTask to the BubbleTextView to be ready to present to the user. */
@@ -848,6 +954,13 @@
}
/**
+ * The max number of icon views the taskbar can have when taskbar overflow is enabled.
+ */
+ int getMaxNumIconViews() {
+ return mMaxNumIcons;
+ }
+
+ /**
* Returns the all apps button in the taskbar.
*/
public TaskbarAllAppsButtonContainer getAllAppsButtonContainer() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index bb4f07a..bc5f9a3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -351,6 +351,11 @@
OneShotPreDrawListener.add(mTaskbarView, listener);
}
+ @VisibleForTesting
+ int getMaxNumIconViews() {
+ return mTaskbarView.getMaxNumIconViews();
+ }
+
public Rect getIconLayoutVisualBounds() {
return mTaskbarView.getIconLayoutVisualBounds();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index e0814d3..987937e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -115,7 +115,6 @@
private BubbleBarItem mSelectedBubble;
private TaskbarSharedState mSharedState;
- private ImeVisibilityChecker mImeVisibilityChecker;
private BubbleBarViewController mBubbleBarViewController;
private BubbleStashController mBubbleStashController;
private Optional<BubbleStashedHandleViewController> mBubbleStashedHandleViewController;
@@ -126,6 +125,8 @@
// Cache last sent top coordinate to avoid sending duplicate updates to shell
private int mLastSentBubbleBarTop;
+ private boolean mIsImeVisible = false;
+
/**
* Similar to {@link BubbleBarUpdate} but rather than {@link BubbleInfo}s it uses
* {@link BubbleBarBubble}s so that it can be used to update the views.
@@ -192,10 +193,8 @@
/** Initializes controllers. */
public void init(BubbleControllers bubbleControllers,
BubbleBarLocationListener bubbleBarLocationListener,
- ImeVisibilityChecker imeVisibilityChecker,
TaskbarSharedState sharedState) {
mSharedState = sharedState;
- mImeVisibilityChecker = imeVisibilityChecker;
mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
mBubbleStashController = bubbleControllers.bubbleStashController;
mBubbleStashedHandleViewController = bubbleControllers.bubbleStashedHandleViewController;
@@ -234,6 +233,10 @@
boolean sysuiLocked = (flags & MASK_SYSUI_LOCKED) != 0;
mBubbleStashController.setSysuiLocked(sysuiLocked);
+ mIsImeVisible = (flags & SYSUI_STATE_IME_SHOWING) != 0;
+ if (mIsImeVisible) {
+ mBubbleBarViewController.onImeVisible();
+ }
}
//
@@ -309,8 +312,7 @@
// enabling gesture nav. also suppress animation if the bubble bar is hidden for sysui e.g.
// the shade is open, or we're locked.
final boolean suppressAnimation =
- update.initialState || mBubbleBarViewController.isHiddenForSysui()
- || mImeVisibilityChecker.isImeVisible();
+ update.initialState || mBubbleBarViewController.isHiddenForSysui() || mIsImeVisible;
if (update.initialState && mSharedState.hasSavedBubbles()) {
// clear restored state
@@ -572,12 +574,6 @@
mBubbleBarViewController.addBubble(bubble, isExpanding, suppressAnimation);
}
- /** Interface for checking whether the IME is visible. */
- public interface ImeVisibilityChecker {
- /** Whether the IME is visible. */
- boolean isImeVisible();
- }
-
/** Listener of {@link BubbleBarLocation} updates. */
public interface BubbleBarLocationListener {
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index c5c2d69..833be61 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -456,7 +456,7 @@
return;
}
mDragging = dragging;
- setElevation(dragging ? mDragElevation : mBubbleElevation);
+ mController.setIsDragging(dragging);
if (!mDragging) {
// Relayout after dragging to ensure that the dragged bubble is positioned correctly
requestLayout();
@@ -1575,5 +1575,8 @@
/** Requests the controller to update bubble bar location to the given value */
void updateBubbleBarLocation(BubbleBarLocation location,
@BubbleBarLocation.UpdateSource int source);
+
+ /** Notifies the controller that bubble bar is being dragged */
+ void setIsDragging(boolean dragging);
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index d13e0c4..fd08078 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -85,6 +85,7 @@
private final BubbleBarView mBarView;
private int mIconSize;
private int mBubbleBarPadding;
+ private final int mDragElevation;
// Initialized in init.
private BubbleStashController mBubbleStashController;
@@ -152,6 +153,8 @@
mBubbleBarAlpha = new MultiValueAlpha(mBarView, 1 /* num alpha channels */);
mIconSize = activity.getResources().getDimensionPixelSize(
R.dimen.bubblebar_icon_size);
+ mDragElevation = activity.getResources().getDimensionPixelSize(
+ R.dimen.bubblebar_drag_elevation);
mTaskbarTranslationDelta = getBubbleBarTranslationDeltaForTaskbar(activity);
}
@@ -221,6 +224,11 @@
@BubbleBarLocation.UpdateSource int source) {
mBubbleBarController.updateBubbleBarLocation(location, source);
}
+
+ @Override
+ public void setIsDragging(boolean dragging) {
+ mBubbleBarContainer.setElevation(dragging ? mDragElevation : 0);
+ }
});
mBubbleViewController = new BubbleView.Controller() {
@@ -383,6 +391,13 @@
}
}
+ /** Notifies that the IME became visible. */
+ public void onImeVisible() {
+ if (isAnimatingNewBubble()) {
+ mBubbleBarViewAnimator.interruptForIme();
+ }
+ }
+
//
// The below animators are exposed to BubbleStashController so it can manage the stashing
// animation.
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
index d993685..cb592e6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
@@ -89,7 +89,6 @@
);
bubbleBarController.init(this,
bubbleBarLocationListeners,
- taskbarControllers.navbarButtonsViewController::isImeVisible,
taskbarSharedState);
bubbleStashedHandleViewController.ifPresent(
controller -> controller.init(/* bubbleControllers = */ this));
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
index 5184a9f..447dad1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
@@ -473,8 +473,8 @@
ObjectAnimator.ofFloat(bubbleBarView, View.TRANSLATION_Y, ty - bubbleBarBounceDistanceInPx)
.withDuration(BUBBLE_BAR_BOUNCE_ANIMATION_DURATION_MS)
.withEndAction {
- if (animatingBubble?.expand == true) expandBubbleBar()
springBackAnimation.start()
+ if (animatingBubble?.expand == true) expandBubbleBar()
}
.start()
}
@@ -532,6 +532,21 @@
)
}
+ /** Interrupts the animation due to the IME becoming visible. */
+ fun interruptForIme() {
+ cancelFlyout()
+ val hideAnimation = animatingBubble?.hideAnimation ?: return
+ scheduler.cancel(hideAnimation)
+ animatingBubble = null
+ bubbleStashController.getStashedHandlePhysicsAnimator().cancelIfRunning()
+ bubbleBarView.relativePivotY = 1f
+ // stash the bubble bar since the IME is now visible
+ bubbleStashController.onNewBubbleAnimationInterrupted(
+ /* isStashed= */ true,
+ bubbleBarView.translationY,
+ )
+ }
+
fun expandedWhileAnimating() {
val animatingBubble = animatingBubble ?: return
this.animatingBubble = animatingBubble.copy(expand = true)
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
index e62c0d4..22d504f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
@@ -90,7 +90,10 @@
val hasBubbles = bubbleBarViewController.hasBubbles()
bubbleBarViewController.onBubbleBarConfigurationChanged(hasBubbles)
if (!hasBubbles) {
- // if there are no bubbles, there's nothing to show, so just return.
+ // if there are no bubbles, there's no need to update the bubble bar, just keep the
+ // isStashed state up to date so that we can process state changes when bubbles are
+ // created.
+ isStashed = launcherState == BubbleLauncherState.IN_APP
return
}
if (field == BubbleLauncherState.HOME) {
@@ -486,10 +489,9 @@
val isStashed = stash && !isBubblesShowingOnHome && !isBubblesShowingOnOverview
if (this.isStashed != isStashed) {
this.isStashed = isStashed
+
// notify the view controller that the stash state is about to change so that it can
// cancel an ongoing animation if there is one.
- // note that this has to be called before updating mIsStashed with the new value,
- // otherwise interrupting an ongoing animation may update it again with the wrong state
bubbleBarViewController.onStashStateChanging()
animator?.cancel()
animator =
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 4590efe..535ae1c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -418,7 +418,9 @@
mIconRingPaint.setColor(RING_SHADOW_COLOR);
mIconRingPaint.setMaskFilter(mShadowFilter);
int count = canvas.save();
- canvas.scale(mRingScale, mRingScale, canvas.getWidth() / 2f, canvas.getHeight() / 2f);
+ if (Float.compare(1, mRingScale) != 0) {
+ canvas.scale(mRingScale, mRingScale, canvas.getWidth() / 2f, canvas.getHeight() / 2f);
+ }
canvas.drawPath(mRingPath, mIconRingPaint);
mIconRingPaint.setColor(mPlateColor);
mIconRingPaint.setMaskFilter(null);
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 95e7737..5648dad 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -56,9 +56,11 @@
import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_STARTED;
import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+import static com.android.quickstep.TaskViewUtils.extractTargetsAndStates;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.EXPECTING_TASK_APPEARED;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -77,6 +79,7 @@
import android.os.IBinder;
import android.os.SystemClock;
import android.util.Log;
+import android.util.Pair;
import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
@@ -90,6 +93,7 @@
import android.widget.Toast;
import android.window.DesktopModeFlags;
import android.window.PictureInPictureSurfaceTransaction;
+import android.window.WindowAnimationState;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -143,6 +147,7 @@
import com.android.quickstep.views.RecentsViewContainer;
import com.android.quickstep.views.TaskContainer;
import com.android.quickstep.views.TaskView;
+import com.android.systemui.animation.TransitionAnimator;
import com.android.systemui.contextualeducation.GestureType;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -152,10 +157,13 @@
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
+import com.android.wm.shell.Flags;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.shared.startingsurface.SplashScreenExitAnimationUtils;
+import kotlin.Unit;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -165,8 +173,6 @@
import java.util.OptionalInt;
import java.util.function.Consumer;
-import kotlin.Unit;
-
/**
* Handles the navigation gestures when Launcher is the default home activity.
*/
@@ -347,6 +353,9 @@
// Indicates whether the divider is shown, only used when split screen is activated.
private boolean mIsDividerShown = true;
private boolean mStartMovingTasks;
+ // Whether the animation to home should be handed off to another handler once the gesture is
+ // committed.
+ protected boolean mHandOffAnimationToHome = false;
@Nullable
private RemoteAnimationTargets.ReleaseCheck mSwipePipToHomeReleaseCheck = null;
@@ -945,6 +954,10 @@
mSwipePipToHomeReleaseCheck = new RemoteAnimationTargets.ReleaseCheck();
mSwipePipToHomeReleaseCheck.setCanRelease(true);
mRecentsAnimationTargets.addReleaseCheck(mSwipePipToHomeReleaseCheck);
+ if (TransitionAnimator.Companion.longLivedReturnAnimationsEnabled()) {
+ mHandOffAnimationToHome =
+ targets.extras.getBoolean(KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION, false);
+ }
// Only initialize the device profile, if it has not been initialized before, as in some
// configurations targets.homeContentInsets may not be correct.
@@ -1629,6 +1642,10 @@
}
windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
+ if (mHandOffAnimationToHome) {
+ handOffAnimation(velocityPxPerMs);
+ }
+
windowAnim[0].addAnimatorListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
@@ -1711,6 +1728,19 @@
}
}
+ private void handOffAnimation(PointF velocityPxPerMs) {
+ if (!TransitionAnimator.Companion.longLivedReturnAnimationsEnabled()
+ || mRecentsAnimationController == null) {
+ return;
+ }
+
+ Pair<RemoteAnimationTarget[], WindowAnimationState[]> targetsAndStates =
+ extractTargetsAndStates(mRemoteTargetHandles, velocityPxPerMs);
+ mRecentsAnimationController.handOffAnimation(
+ targetsAndStates.first, targetsAndStates.second);
+ ActiveGestureProtoLogProxy.logHandOffAnimation();
+ }
+
private int calculateWindowRotation(RemoteAnimationTarget runningTaskTarget,
RecentsOrientedState orientationState) {
if (runningTaskTarget.rotationChange != 0) {
@@ -2176,8 +2206,9 @@
mSwipePipToHomeAnimator.getFinishTransaction(),
mSwipePipToHomeAnimator.getContentOverlay());
mIsSwipingPipToHome = false;
- } else if (mIsSwipeForSplit) {
+ } else if (mIsSwipeForSplit && !Flags.enablePip2()) {
// Transaction to hide the task to avoid flicker for entering PiP from split-screen.
+ // Note: PiP2 handles entering differently, so skip if enable_pip2=true
PictureInPictureSurfaceTransaction tx =
new PictureInPictureSurfaceTransaction.Builder()
.setAlpha(0f)
diff --git a/quickstep/src/com/android/quickstep/DesktopFullscreenDrawParams.kt b/quickstep/src/com/android/quickstep/DesktopFullscreenDrawParams.kt
new file mode 100644
index 0000000..bafb0b2
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/DesktopFullscreenDrawParams.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep
+
+import android.content.Context
+import com.android.systemui.shared.system.QuickStepContract
+
+// DesktopTaskView thumbnail's corner radius is independent of fullscreenProgress.
+open class DesktopFullscreenDrawParams
+@JvmOverloads
+constructor(context: Context, cornerRadiusProvider: (Context) -> Float = ::computeCornerRadius) :
+ FullscreenDrawParams(context, cornerRadiusProvider, cornerRadiusProvider) {
+ companion object {
+ // computeCornerRadius is used as cornerRadiusProvider, so
+ // QuickStepContract::getWindowCornerRadius can be mocked properly.
+ private fun computeCornerRadius(context: Context): Float =
+ QuickStepContract.getWindowCornerRadius(context)
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/FullscreenDrawParams.kt b/quickstep/src/com/android/quickstep/FullscreenDrawParams.kt
new file mode 100644
index 0000000..a5ba52a
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/FullscreenDrawParams.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep
+
+import android.content.Context
+import com.android.launcher3.R
+import com.android.launcher3.Utilities
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.SafeCloseable
+import com.android.launcher3.views.ActivityContext
+import com.android.quickstep.util.TaskCornerRadius
+import com.android.systemui.shared.system.QuickStepContract
+
+/**
+ * Class for computing corner radius by interpolating between overview and fullscreen corner radius
+ * with fullscreenProgress set in [setProgress].
+ */
+open class FullscreenDrawParams
+@JvmOverloads
+constructor(
+ context: Context,
+ private val taskCornerRadiusProvider: (Context) -> Float = ::computeTaskCornerRadius,
+ private val windowCornerRadiusProvider: (Context) -> Float = ::computeWindowCornerRadius,
+) : SafeCloseable {
+ private var taskCornerRadius = 0f
+ private var windowCornerRadius = 0f
+ var currentCornerRadius = 0f
+
+ init {
+ updateCornerRadius(context)
+ }
+
+ /** Recomputes the start and end corner radius for the given Context. */
+ fun updateCornerRadius(context: Context) {
+ taskCornerRadius = taskCornerRadiusProvider(context)
+ windowCornerRadius = windowCornerRadiusProvider(context)
+ }
+
+ /** Sets the progress in range [0, 1] */
+ fun setProgress(fullscreenProgress: Float, parentScale: Float, taskViewScale: Float) {
+ currentCornerRadius =
+ Utilities.mapRange(fullscreenProgress, taskCornerRadius, windowCornerRadius) /
+ parentScale /
+ taskViewScale
+ }
+
+ override fun close() {}
+
+ companion object {
+ private fun computeTaskCornerRadius(context: Context): Float = TaskCornerRadius.get(context)
+
+ private fun computeWindowCornerRadius(context: Context): Float {
+ val activityContext: ActivityContext? = ActivityContext.lookupContextNoThrow(context)
+ return if (
+ activityContext?.deviceProfile?.isTaskbarPresent == true &&
+ DisplayController.isTransientTaskbar(context)
+ ) {
+ context.resources
+ .getDimensionPixelSize(R.dimen.persistent_taskbar_corner_radius)
+ .toFloat()
+ } else {
+ // The corner radius is fixed to match when Taskbar is persistent mode
+ QuickStepContract.getWindowCornerRadius(context)
+ }
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index 6719ab7..4bd9ffb 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -49,8 +49,8 @@
import android.window.BackEvent;
import android.window.BackMotionEvent;
import android.window.BackProgressAnimator;
+import android.window.IBackAnimationHandoffHandler;
import android.window.IOnBackInvokedCallback;
-
import com.android.app.animation.Interpolators;
import com.android.internal.policy.SystemBarUtils;
import com.android.internal.view.AppearanceRegion;
@@ -225,6 +225,12 @@
public void setTriggerBack(boolean triggerBack) {
// TODO(b/261654570): track touch from the Launcher process.
}
+
+ @Override
+ public void setHandoffHandler(IBackAnimationHandoffHandler unused) {
+ // For now, Launcher handles this internally so it doesn't need to hand off the
+ // animation.
+ }
}
private static class RemoteAnimationRunnerStub extends IRemoteAnimationRunner.Stub {
@@ -384,10 +390,11 @@
// Move the window along the Y axis.
float top = (screenHeight - height) * 0.5f + deltaY;
// Move the window along the X axis.
- float left = event.getSwipeEdge() == BackEvent.EDGE_RIGHT
- ? progress * mWindowScaleMarginX
- : screenWidth - progress * mWindowScaleMarginX - width;
-
+ float left = switch (event.getSwipeEdge()) {
+ case BackEvent.EDGE_RIGHT -> progress * mWindowScaleMarginX;
+ case BackEvent.EDGE_LEFT -> screenWidth - progress * mWindowScaleMarginX - width;
+ default -> (screenWidth - width) / 2;
+ };
mCurrentRect.set(left, top, left + width, top + height);
float cornerRadius = Utilities.mapRange(
progress, mWindowScaleStartCornerRadius, mWindowScaleEndCornerRadius);
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index dacafd4..6087dc2 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -46,7 +46,6 @@
import com.android.launcher3.views.FloatingIconView;
import com.android.launcher3.views.FloatingView;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
-import com.android.quickstep.fallback.window.RecentsWindowManager;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.ScalingWorkspaceRevealAnim;
import com.android.quickstep.util.StaggeredWorkspaceAnim;
@@ -54,6 +53,7 @@
import com.android.quickstep.views.FloatingWidgetView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
+import com.android.systemui.animation.TransitionAnimator;
import com.android.systemui.shared.system.InputConsumerController;
import java.util.Collections;
@@ -108,7 +108,9 @@
mContainer.getRootView().setForceHideBackArrow(true);
- if (!canUseWorkspaceView || appCanEnterPip || mIsSwipeForSplit) {
+ boolean handOffAnimation = TransitionAnimator.Companion.longLivedReturnAnimationsEnabled()
+ && mHandOffAnimationToHome;
+ if (handOffAnimation || !canUseWorkspaceView || appCanEnterPip || mIsSwipeForSplit) {
return new LauncherHomeAnimationFactory() {
@Nullable
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 60fcff8..055aadb 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -21,9 +21,11 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
+import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.WindowManagerGlobal;
import android.window.PictureInPictureSurfaceTransaction;
+import android.window.WindowAnimationState;
import androidx.annotation.UiThread;
@@ -32,6 +34,7 @@
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RunnableList;
import com.android.quickstep.util.ActiveGestureProtoLogProxy;
+import com.android.systemui.animation.TransitionAnimator;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
@@ -53,8 +56,6 @@
private boolean mFinishRequested = false;
// Only valid when mFinishRequested == true.
private boolean mFinishTargetIsLauncher;
- // Only valid when mFinishRequested == true
- private boolean mLauncherIsVisibleAtFinish;
private RunnableList mPendingFinishCallbacks = new RunnableList();
public RecentsAnimationController(RecentsAnimationControllerCompat controller,
@@ -90,6 +91,16 @@
}
@UiThread
+ public void handOffAnimation(RemoteAnimationTarget[] targets, WindowAnimationState[] states) {
+ if (TransitionAnimator.Companion.longLivedReturnAnimationsEnabled()) {
+ UI_HELPER_EXECUTOR.execute(() -> mController.handOffAnimation(targets, states));
+ } else {
+ Log.e(TAG, "Tried to hand off the animation, but the feature is disabled",
+ new Exception());
+ }
+ }
+
+ @UiThread
public void finishAnimationToHome() {
finishController(true /* toRecents */, null, false /* sendUserLeaveHint */);
}
@@ -119,27 +130,13 @@
}
@UiThread
- public void finish(boolean toRecents, boolean launcherIsVisibleAtFinish,
- Runnable onFinishComplete, boolean sendUserLeaveHint) {
- Preconditions.assertUIThread();
- finishController(toRecents, launcherIsVisibleAtFinish, onFinishComplete, sendUserLeaveHint,
- false);
- }
-
- @UiThread
public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) {
- finishController(toRecents, false, callback, sendUserLeaveHint, false /* forceFinish */);
+ finishController(toRecents, callback, sendUserLeaveHint, false /* forceFinish */);
}
@UiThread
public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint,
boolean forceFinish) {
- finishController(toRecents, toRecents, callback, sendUserLeaveHint, forceFinish);
- }
-
- @UiThread
- public void finishController(boolean toRecents, boolean launcherIsVisibleAtFinish,
- Runnable callback, boolean sendUserLeaveHint, boolean forceFinish) {
mPendingFinishCallbacks.add(callback);
if (!forceFinish && mFinishRequested) {
// If finish has already been requested, then add the callback to the pending list.
@@ -151,7 +148,6 @@
// Finish not yet requested
mFinishRequested = true;
mFinishTargetIsLauncher = toRecents;
- mLauncherIsVisibleAtFinish = launcherIsVisibleAtFinish;
mOnFinishedListener.accept(this);
Runnable finishCb = () -> {
mController.finish(toRecents, sendUserLeaveHint, new IResultReceiver.Stub() {
@@ -228,14 +224,6 @@
return mFinishTargetIsLauncher;
}
- /**
- * RecentsAnimationListeners can check this in onRecentsAnimationFinished() to determine whether
- * the animation was finished to launcher vs an app.
- */
- public boolean getLauncherIsVisibleAtFinish() {
- return mLauncherIsVisibleAtFinish;
- }
-
public void dump(String prefix, PrintWriter pw) {
pw.println(prefix + "RecentsAnimationController:");
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
index 06b2972..91d0776 100644
--- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -93,8 +93,7 @@
BaseContainerInterface sizingStrategy, int numHandles, boolean forDesktop) {
RemoteTargetHandle[] handles = new RemoteTargetHandle[numHandles];
for (int i = 0; i < numHandles; i++) {
- TaskViewSimulator tvs = new TaskViewSimulator(context, sizingStrategy);
- tvs.setIsDesktopTask(forDesktop , i);
+ TaskViewSimulator tvs = new TaskViewSimulator(context, sizingStrategy, forDesktop , i);
TransformParams transformParams = new TransformParams();
handles[i] = new RemoteTargetHandle(tvs, transformParams);
}
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 0dbdcb7..fef4c30 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -348,7 +348,7 @@
}
/** Called when the snapshot has updated its full screen drawing parameters. */
- public void setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) {}
+ public void setFullscreenParams(FullscreenDrawParams fullscreenParams) {}
/** Sets visibility for the overlay associated elements. */
public void setVisibility(int visibility) {}
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 07ee479..783c87c 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -47,12 +47,15 @@
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Matrix.ScaleToFit;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.util.Pair;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.View;
import android.window.TransitionInfo;
+import android.window.WindowAnimationState;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -784,4 +787,43 @@
animatorHandler.accept(dockFadeAnimator);
return dockFadeAnimator;
}
+
+ /**
+ * Creates an array of {@link RemoteAnimationTarget}s and a matching array of
+ * {@link WindowAnimationState}s from the provided handles.
+ * Important: the ordering of the two arrays is the same, so the state at each index of the
+ * second applies to the target in the same index of the first.
+ *
+ * @param handles The handles wrapping each target.
+ * @param velocityPxPerMs The current velocity of the target animations.
+ */
+ @NonNull
+ public static Pair<RemoteAnimationTarget[], WindowAnimationState[]> extractTargetsAndStates(
+ @NonNull RemoteTargetHandle[] handles, @NonNull PointF velocityPxPerMs) {
+ RemoteAnimationTarget[] targets = new RemoteAnimationTarget[handles.length];
+ WindowAnimationState[] animationStates = new WindowAnimationState[handles.length];
+ long timestamp = System.currentTimeMillis();
+
+ for (int i = 0; i < handles.length; i++) {
+ targets[i] = handles[i].getTransformParams().getTargetSet().apps[i];
+
+ TaskViewSimulator taskViewSimulator = handles[i].getTaskViewSimulator();
+ RectF startRect = taskViewSimulator.getCurrentRect();
+ float cornerRadius = taskViewSimulator.getCurrentCornerRadius();
+
+ WindowAnimationState state = new WindowAnimationState();
+ state.timestamp = timestamp;
+ state.bounds = new RectF(
+ startRect.left, startRect.top, startRect.right, startRect.bottom);
+ state.topLeftRadius = cornerRadius;
+ state.topRightRadius = cornerRadius;
+ state.bottomRightRadius = cornerRadius;
+ state.bottomLeftRadius = cornerRadius;
+ state.velocityPxPerMs = velocityPxPerMs;
+
+ animationStates[i] = state;
+ }
+
+ return new Pair<>(targets, animationStates);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java
index 7065f37..210065a 100644
--- a/quickstep/src/com/android/quickstep/TopTaskTracker.java
+++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java
@@ -134,6 +134,22 @@
mOrderedTaskList.removeIf(rto -> rto.taskId == taskInfo.taskId);
mOrderedTaskList.addFirst(taskInfo);
+ // Workaround for b/372067617, if the home task is being brought to front, then it will
+ // occlude all other tasks, so mark them as not-visible
+ if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) {
+ // We've moved the task to the front of the list above, so only iterate the tasks after
+ for (int i = 1; i < mOrderedTaskList.size(); i++) {
+ final TaskInfo info = mOrderedTaskList.get(i);
+ if (info.displayId != taskInfo.displayId) {
+ // Only fall through to reset visibility for tasks on the same display as the
+ // home task being brought forward
+ continue;
+ }
+ info.isVisible = false;
+ info.isVisibleRequested = false;
+ }
+ }
+
// Keep the home display's top running task in the first while adding a non-home
// display's task to the list, to avoid showing non-home display's task upon going to
// Recents animation.
diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
index dd11d48..b78e214 100644
--- a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
+++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
@@ -36,9 +36,6 @@
import com.android.quickstep.task.viewmodel.TaskOverlayViewModel
import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
import com.android.quickstep.task.viewmodel.TaskThumbnailViewModelImpl
-import com.android.quickstep.task.viewmodel.TaskViewData
-import com.android.quickstep.task.viewmodel.TaskViewModel
-import com.android.quickstep.views.TaskViewType
import com.android.systemui.shared.recents.model.Task
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
@@ -186,17 +183,11 @@
}
}
RecentsViewData::class.java -> RecentsViewData()
- TaskViewModel::class.java -> TaskViewModel(taskViewData = inject(scopeId, extras))
- TaskViewData::class.java -> {
- val taskViewType = extras["TaskViewType"] as TaskViewType
- TaskViewData(taskViewType)
- }
TaskContainerData::class.java -> TaskContainerData()
TaskThumbnailViewData::class.java -> TaskThumbnailViewData()
TaskThumbnailViewModel::class.java ->
TaskThumbnailViewModelImpl(
recentsViewData = inject(),
- taskViewData = inject(scopeId, extras),
taskContainerData = inject(scopeId),
dispatcherProvider = inject(),
getThumbnailPositionUseCase = inject(),
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
index 87446b0..6ccf372 100644
--- a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
+++ b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
@@ -22,9 +22,6 @@
class RecentsViewData {
val fullscreenProgress = MutableStateFlow(1f)
- // This is typically a View concern but it is used to invalidate rendering in other Views
- val scale = MutableStateFlow(1f)
-
// Whether the current RecentsView state supports task overlays.
// TODO(b/331753115): Derive from RecentsView state flow once migrated to MVVM.
val overlayEnabled = MutableStateFlow(false)
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
index c511005..cfebb81 100644
--- a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
+++ b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
@@ -34,10 +34,6 @@
recentsTasksRepository.setVisibleTasks(visibleTaskIdList.toSet())
}
- fun updateScale(scale: Float) {
- recentsViewData.scale.value = scale
- }
-
fun updateFullscreenProgress(fullscreenProgress: Float) {
recentsViewData.fullscreenProgress.value = fullscreenProgress
}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index a8c8659..0c783d3 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -17,7 +17,6 @@
package com.android.quickstep.task.thumbnail
import android.content.Context
-import android.content.res.Configuration
import android.graphics.Color
import android.graphics.Outline
import android.graphics.Rect
@@ -29,7 +28,6 @@
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isInvisible
import com.android.launcher3.R
-import com.android.launcher3.Utilities
import com.android.launcher3.util.ViewPool
import com.android.quickstep.recents.di.RecentsDependencies
import com.android.quickstep.recents.di.get
@@ -39,9 +37,7 @@
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
-import com.android.quickstep.util.TaskCornerRadius
import com.android.quickstep.views.FixedSizeImageView
-import com.android.systemui.shared.system.QuickStepContract
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -63,18 +59,15 @@
private val splashIcon: FixedSizeImageView by lazy { findViewById(R.id.splash_icon) }
private var uiState: TaskThumbnailUiState = Uninitialized
- private var inheritedScale: Float = 1f
- private val _measuredBounds = Rect()
- private val measuredBounds: Rect
- get() {
- _measuredBounds.set(0, 0, measuredWidth, measuredHeight)
- return _measuredBounds
+ private val bounds = Rect()
+
+ var cornerRadius: Float = 0f
+ set(value) {
+ field = value
+ invalidateOutline()
}
- private var overviewCornerRadius: Float = TaskCornerRadius.get(context)
- private var fullscreenCornerRadius: Float = QuickStepContract.getWindowCornerRadius(context)
-
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
@@ -114,19 +107,12 @@
splashIcon.alpha = splashAlpha
}
.launchIn(viewAttachedScope)
- viewModel.cornerRadiusProgress.onEach { invalidateOutline() }.launchIn(viewAttachedScope)
- viewModel.inheritedScale
- .onEach { viewModelInheritedScale ->
- inheritedScale = viewModelInheritedScale
- invalidateOutline()
- }
- .launchIn(viewAttachedScope)
clipToOutline = true
outlineProvider =
object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
- outline.setRoundRect(measuredBounds, getCurrentCornerRadius())
+ outline.setRoundRect(bounds, cornerRadius)
}
}
}
@@ -157,6 +143,8 @@
if (uiState is SnapshotSplash) {
setImageMatrix()
}
+ bounds.set(0, 0, w, h)
+ invalidateOutline()
}
override fun setScaleX(scaleX: Float) {
@@ -171,14 +159,6 @@
splashIcon.scaleY = 1 / scaleY
}
- override fun onConfigurationChanged(newConfig: Configuration?) {
- super.onConfigurationChanged(newConfig)
-
- overviewCornerRadius = TaskCornerRadius.get(context)
- fullscreenCornerRadius = QuickStepContract.getWindowCornerRadius(context)
- invalidateOutline()
- }
-
private fun resetViews() {
liveTileView.isInvisible = true
thumbnailView.isInvisible = true
@@ -214,13 +194,6 @@
thumbnailView.imageMatrix = viewModel.getThumbnailPositionState(width, height, isLayoutRtl)
}
- private fun getCurrentCornerRadius() =
- Utilities.mapRange(
- viewModel.cornerRadiusProgress.value,
- overviewCornerRadius,
- fullscreenCornerRadius,
- ) / inheritedScale
-
private companion object {
const val TAG = "TaskThumbnailView"
}
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
index f55462a..a048a1d 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
@@ -19,19 +19,9 @@
import android.graphics.Matrix
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
/** ViewModel for representing TaskThumbnails */
interface TaskThumbnailViewModel {
- /**
- * Progress for changes in corner radius. progress: 0 = overview corner radius; 1 = fullscreen
- * corner radius.
- */
- val cornerRadiusProgress: StateFlow<Float>
-
- /** The accumulated View.scale value for parent Views up to and including RecentsView */
- val inheritedScale: Flow<Float>
-
/** Provides the level of dimming that the View should have */
val dimProgress: Flow<Float>
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
index 8b15a82..b6cb984 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
@@ -38,7 +38,6 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
@@ -50,7 +49,6 @@
@OptIn(ExperimentalCoroutinesApi::class)
class TaskThumbnailViewModelImpl(
recentsViewData: RecentsViewData,
- taskViewData: TaskViewData,
taskContainerData: TaskContainerData,
dispatcherProvider: DispatcherProvider,
private val tasksRepository: RecentTasksRepository,
@@ -61,15 +59,6 @@
private val splashProgress = MutableStateFlow(flowOf(0f))
private var taskId: Int = INVALID_TASK_ID
- override val cornerRadiusProgress =
- if (taskViewData.isOutlineFormedByThumbnailView) recentsViewData.fullscreenProgress
- else MutableStateFlow(1f).asStateFlow()
-
- override val inheritedScale =
- combine(recentsViewData.scale, taskViewData.scale) { recentsScale, taskScale ->
- recentsScale * taskScale
- }
-
override val dimProgress: Flow<Float> =
combine(taskContainerData.taskMenuOpenProgress, recentsViewData.tintAmount) {
taskMenuOpenProgress,
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt
deleted file mode 100644
index 7a9ecf2..0000000
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2024 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.task.viewmodel
-
-import com.android.quickstep.views.TaskViewType
-import kotlinx.coroutines.flow.MutableStateFlow
-
-class TaskViewData(taskViewType: TaskViewType) {
- // This is typically a View concern but it is used to invalidate rendering in other Views
- val scale = MutableStateFlow(1f)
-
- // TODO(b/331753115): This property should not be in TaskViewData once TaskView is MVVM.
- /** Whether outline of TaskView is formed by outline thumbnail view(s). */
- val isOutlineFormedByThumbnailView: Boolean = taskViewType != TaskViewType.DESKTOP
-}
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewModel.kt
deleted file mode 100644
index ec75d59..0000000
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewModel.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2024 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.task.viewmodel
-
-import androidx.lifecycle.ViewModel
-
-class TaskViewModel(private val taskViewData: TaskViewData) : ViewModel() {
- fun updateScale(scale: Float) {
- taskViewData.scale.value = scale
- }
-}
diff --git a/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java b/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java
index 083f192..334ff06 100644
--- a/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java
+++ b/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java
@@ -24,7 +24,6 @@
import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
import static com.android.quickstep.util.SystemActionConstants.SYSTEM_ACTION_ID_SEARCH_SCREEN;
-import android.annotation.Nullable;
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.content.Context;
@@ -78,7 +77,6 @@
this::onContextualSearchSettingChanged;
protected final EventLogArray mEventLogArray = new EventLogArray(TAG, MAX_DEBUG_EVENT_SIZE);
- @Nullable private SettingsCache mSettingsCache;
// Cached value whether the ContextualSearch intent filter matched any enabled components.
private boolean mIsContextualSearchIntentAvailable;
private boolean mIsContextualSearchSettingEnabled;
@@ -108,11 +106,10 @@
context, mContextualSearchPackage, Intent.ACTION_PACKAGE_ADDED,
Intent.ACTION_PACKAGE_CHANGED, Intent.ACTION_PACKAGE_REMOVED);
- mSettingsCache = SettingsCache.INSTANCE.get(context);
- mSettingsCache.register(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI,
+ SettingsCache.INSTANCE.get(context).register(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI,
mContextualSearchSettingChangedListener);
onContextualSearchSettingChanged(
- mSettingsCache.getValue(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI));
+ SettingsCache.INSTANCE.get(context).getValue(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI));
SystemUiProxy.INSTANCE.get(mContext).addOnStateChangeListener(mSysUiStateChangeListener);
}
@@ -266,11 +263,8 @@
public void close() {
mContextualSearchPackageReceiver.unregisterReceiverSafely(mContext);
unregisterSearchScreenSystemAction();
-
- if (mSettingsCache != null) {
- mSettingsCache.unregister(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI,
- mContextualSearchSettingChangedListener);
- }
+ SettingsCache.INSTANCE.get(mContext).unregister(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI,
+ mContextualSearchSettingChangedListener);
SystemUiProxy.INSTANCE.get(mContext).removeOnStateChangeListener(mSysUiStateChangeListener);
}
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index a4b8fec..706cfe4 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -50,9 +50,10 @@
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.BaseActivityInterface;
import com.android.quickstep.BaseContainerInterface;
+import com.android.quickstep.DesktopFullscreenDrawParams;
+import com.android.quickstep.FullscreenDrawParams;
import com.android.quickstep.TaskAnimationManager;
import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
-import com.android.quickstep.views.TaskView.FullscreenDrawParams;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.recents.utilities.PreviewPositionHelper;
@@ -116,20 +117,25 @@
private SplitBounds mSplitBounds;
private Boolean mDrawsBelowRecents = null;
private boolean mIsGridTask;
- private boolean mIsDesktopTask;
+ private final boolean mIsDesktopTask;
private boolean mScaleToCarouselTaskSize = false;
private int mTaskRectTranslationX;
private int mTaskRectTranslationY;
private int mDesktopTaskIndex = 0;
- public TaskViewSimulator(Context context, BaseContainerInterface sizeStrategy) {
+ public TaskViewSimulator(Context context, BaseContainerInterface sizeStrategy,
+ boolean isDesktop, int desktopTaskIndex) {
mContext = context;
mSizeStrategy = sizeStrategy;
+ mIsDesktopTask = isDesktop;
+ mDesktopTaskIndex = desktopTaskIndex;
mOrientationState = TraceHelper.allowIpcs("TaskViewSimulator.init",
() -> new RecentsOrientedState(context, sizeStrategy, i -> { }));
mOrientationState.setGestureActive(true);
- mCurrentFullscreenParams = new FullscreenDrawParams(context);
+ mCurrentFullscreenParams = mIsDesktopTask
+ ? new DesktopFullscreenDrawParams(context)
+ : new FullscreenDrawParams(context);
mOrientationStateId = mOrientationState.getStateId();
Resources resources = context.getResources();
mIsRecentsRtl = mOrientationState.getOrientationHandler().getRecentsRtlSetting(resources);
@@ -289,14 +295,6 @@
}
/**
- * Sets whether this task is part of desktop tasks in overview.
- */
- public void setIsDesktopTask(boolean desktop, int index) {
- mIsDesktopTask = desktop;
- mDesktopTaskIndex = index;
- }
-
- /**
* Apply translations on TaskRect's starting location.
*/
public void setTaskRectTranslation(int taskRectTranslationX, int taskRectTranslationY) {
@@ -558,7 +556,7 @@
* TaskView
*/
public float getCurrentCornerRadius() {
- float visibleRadius = mCurrentFullscreenParams.getCurrentDrawnCornerRadius();
+ float visibleRadius = mCurrentFullscreenParams.getCurrentCornerRadius();
mTempPoint[0] = visibleRadius;
mTempPoint[1] = 0;
mInversePositionMatrix.mapVectors(mTempPoint);
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskContentView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskContentView.kt
index 481acac..ef044f4 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskContentView.kt
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskContentView.kt
@@ -23,14 +23,15 @@
import android.view.View
import android.view.ViewOutlineProvider
import android.widget.FrameLayout
-import com.android.quickstep.views.TaskView.FullscreenDrawParams
class DesktopTaskContentView
@JvmOverloads
constructor(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) {
- private val currentFullscreenParams = FullscreenDrawParams(context)
- private val taskCornerRadius: Float
- get() = currentFullscreenParams.cornerRadius
+ var cornerRadius: Float = 0f
+ set(value) {
+ field = value
+ invalidateOutline()
+ }
private val bounds = Rect()
@@ -39,7 +40,7 @@
outlineProvider =
object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
- outline.setRoundRect(bounds, taskCornerRadius)
+ outline.setRoundRect(bounds, cornerRadius)
}
}
}
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
index 5e842aa..576a56e 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -20,13 +20,10 @@
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
-import android.graphics.drawable.ShapeDrawable
-import android.graphics.drawable.shapes.RoundRectShape
import android.util.AttributeSet
import android.util.Log
import android.view.Gravity
import android.view.View
-import android.widget.FrameLayout
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.updateLayoutParams
import com.android.launcher3.Flags.enableRefactorTaskThumbnail
@@ -39,6 +36,8 @@
import com.android.launcher3.util.ViewPool
import com.android.launcher3.util.rects.set
import com.android.quickstep.BaseContainerInterface
+import com.android.quickstep.DesktopFullscreenDrawParams
+import com.android.quickstep.FullscreenDrawParams
import com.android.quickstep.TaskOverlayFactory
import com.android.quickstep.ViewUtils
import com.android.quickstep.task.thumbnail.TaskThumbnailView
@@ -47,14 +46,13 @@
/** TaskView that contains all tasks that are part of the desktop. */
class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
- TaskView(context, attrs, type = TaskViewType.DESKTOP) {
-
- private val snapshotDrawParams =
- object : FullscreenDrawParams(context) {
- // DesktopTaskView thumbnail's corner radius is independent of fullscreenProgress.
- override fun computeTaskCornerRadius(context: Context) =
- computeWindowCornerRadius(context)
- }
+ TaskView(
+ context,
+ attrs,
+ type = TaskViewType.DESKTOP,
+ thumbnailFullscreenParams = DesktopFullscreenDrawParams(context),
+ ) {
+ private val contentViewFullscreenParams = FullscreenDrawParams(context)
private val taskThumbnailViewDeprecatedPool =
if (!enableRefactorTaskThumbnail()) {
@@ -80,28 +78,12 @@
private val tempPointF = PointF()
private val tempRect = Rect()
- private lateinit var backgroundView: View
private lateinit var iconView: TaskViewIcon
- private lateinit var contentView: FrameLayout
+ private lateinit var contentView: DesktopTaskContentView
+ private lateinit var backgroundView: View
override fun onFinishInflate() {
super.onFinishInflate()
- backgroundView =
- findViewById<View>(R.id.background).apply {
- updateLayoutParams<LayoutParams> {
- topMargin = container.deviceProfile.overviewTaskThumbnailTopMarginPx
- }
- background =
- ShapeDrawable(RoundRectShape(FloatArray(8) { taskCornerRadius }, null, null))
- .apply {
- setTint(
- resources.getColor(
- android.R.color.system_neutral2_300,
- context.theme,
- )
- )
- }
- }
iconView =
getOrInflateIconView(R.id.icon).apply {
setIcon(
@@ -115,10 +97,15 @@
setText(resources.getText(R.string.recent_task_desktop))
}
contentView =
- findViewById<FrameLayout>(R.id.desktop_content).apply {
+ findViewById<DesktopTaskContentView>(R.id.desktop_content).apply {
updateLayoutParams<LayoutParams> {
topMargin = container.deviceProfile.overviewTaskThumbnailTopMarginPx
}
+ cornerRadius = contentViewFullscreenParams.currentCornerRadius
+ backgroundView = findViewById(R.id.background)
+ backgroundView.setBackgroundColor(
+ resources.getColor(android.R.color.system_neutral2_300, context.theme)
+ )
}
}
@@ -135,6 +122,7 @@
Log.d(TAG, sb.toString())
}
cancelPendingLoadTasks()
+ val backgroundViewIndex = contentView.indexOfChild(backgroundView)
taskContainers =
tasks.map { task ->
val snapshotView =
@@ -143,7 +131,7 @@
} else {
taskThumbnailViewDeprecatedPool!!.view
}
- contentView.addView(snapshotView, 0)
+ contentView.addView(snapshotView, backgroundViewIndex + 1)
TaskContainer(
this,
@@ -157,8 +145,7 @@
taskOverlayFactory,
)
}
- taskContainers.forEach { it.bind() }
- setOrientationState(orientedState)
+ onBind(orientedState)
}
override fun onRecycle() {
@@ -242,8 +229,12 @@
}
}
- override fun needsUpdate(dataChange: Int, flag: Int) =
- if (flag == FLAG_UPDATE_CORNER_RADIUS) false else super.needsUpdate(dataChange, flag)
+ override fun onTaskListVisibilityChanged(visible: Boolean, changes: Int) {
+ super.onTaskListVisibilityChanged(visible, changes)
+ if (needsUpdate(changes, FLAG_UPDATE_CORNER_RADIUS)) {
+ contentViewFullscreenParams.updateCornerRadius(context)
+ }
+ }
override fun onIconLoaded(taskContainer: TaskContainer) {
// Update contentDescription of snapshotView only, individual task icon is unused.
@@ -258,9 +249,9 @@
override fun getThumbnailBounds(bounds: Rect, relativeToDragLayer: Boolean) {
if (relativeToDragLayer) {
- container.dragLayer.getDescendantRectRelativeToSelf(backgroundView, bounds)
+ container.dragLayer.getDescendantRectRelativeToSelf(contentView, bounds)
} else {
- bounds.set(backgroundView)
+ bounds.set(contentView)
}
}
@@ -306,13 +297,12 @@
backgroundView.alpha = 1 - fullscreenProgress
}
- override fun updateCurrentFullscreenParams() {
- super.updateCurrentFullscreenParams()
- updateFullscreenParams(snapshotDrawParams)
+ override fun updateFullscreenParams() {
+ super.updateFullscreenParams()
+ updateFullscreenParams(contentViewFullscreenParams)
+ contentView.cornerRadius = contentViewFullscreenParams.currentCornerRadius
}
- override fun getThumbnailFullscreenParams() = snapshotDrawParams
-
override fun addChildrenForAccessibility(outChildren: ArrayList<View>) {
super.addChildrenForAccessibility(outChildren)
ViewUtils.addAccessibleChildToList(backgroundView, outChildren)
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
index 92c1e93..0d9583d 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
@@ -104,7 +104,7 @@
R.id.show_windows,
R.id.digital_wellbeing_toast,
STAGE_POSITION_TOP_OR_LEFT,
- taskOverlayFactory
+ taskOverlayFactory,
),
createTaskContainer(
secondaryTask,
@@ -113,14 +113,12 @@
R.id.show_windows_right,
R.id.bottomRight_digital_wellbeing_toast,
STAGE_POSITION_BOTTOM_OR_RIGHT,
- taskOverlayFactory
- )
+ taskOverlayFactory,
+ ),
)
- taskContainers.forEach { it.bind() }
-
this.splitBoundsConfig = splitBoundsConfig
taskContainers.forEach { it.digitalWellBeingToast?.splitBounds = splitBoundsConfig }
- setOrientationState(orientedState)
+ onBind(orientedState)
}
override fun setOrientationState(orientationState: RecentsOrientedState) {
@@ -131,7 +129,7 @@
container.deviceProfile,
it,
layoutParams.width,
- layoutParams.height
+ layoutParams.height,
)
val iconViewMarginStart =
resources.getDimensionPixelSize(
@@ -168,7 +166,7 @@
container.deviceProfile,
splitBoundsConfig,
layoutParams.width,
- layoutParams.height
+ layoutParams.height,
)
pagedOrientationHandler.setSplitIconParams(
taskContainers[0].iconView.asView(),
@@ -181,7 +179,7 @@
isRtl,
container.deviceProfile,
splitBoundsConfig,
- inSplitSelection
+ inSplitSelection,
)
} else {
pagedOrientationHandler.setSplitIconParams(
@@ -195,7 +193,7 @@
isRtl,
container.deviceProfile,
splitBoundsConfig,
- inSplitSelection
+ inSplitSelection,
)
}
}
@@ -216,7 +214,7 @@
InteractionJankMonitorWrapper.begin(
this,
Cuj.CUJ_SPLIT_SCREEN_ENTER,
- "Enter form GroupedTaskView"
+ "Enter form GroupedTaskView",
)
launchTaskInternal(isQuickSwitch = false, launchingExistingTaskView = true) {
endCallback.executeAllAndDestroy()
@@ -230,7 +228,7 @@
override fun launchWithoutAnimation(
isQuickSwitch: Boolean,
- callback: (launched: Boolean) -> Unit
+ callback: (launched: Boolean) -> Unit,
) {
launchTaskInternal(isQuickSwitch, launchingExistingTaskView = false, callback)
}
@@ -244,7 +242,7 @@
private fun launchTaskInternal(
isQuickSwitch: Boolean,
launchingExistingTaskView: Boolean,
- callback: (launched: Boolean) -> Unit
+ callback: (launched: Boolean) -> Unit,
) {
recentsView?.let {
it.splitSelectController.launchExistingSplitPair(
@@ -254,11 +252,11 @@
STAGE_POSITION_TOP_OR_LEFT,
callback,
isQuickSwitch,
- snapPosition
+ snapPosition,
)
Log.d(
TAG,
- "launchTaskInternal - launchExistingSplitPair: ${taskIds.contentToString()}, launchingExistingTaskView: $launchingExistingTaskView"
+ "launchTaskInternal - launchExistingSplitPair: ${taskIds.contentToString()}, launchingExistingTaskView: $launchingExistingTaskView",
)
}
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 6ab3e28..9cc42b4 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -417,9 +417,6 @@
public void setValue(RecentsView view, float scale) {
view.setScaleX(scale);
view.setScaleY(scale);
- if (enableRefactorTaskThumbnail()) {
- view.mRecentsViewModel.updateScale(scale);
- }
view.mLastComputedTaskStartPushOutDistance = null;
view.mLastComputedTaskEndPushOutDistance = null;
view.runActionOnRemoteHandles(new Consumer<RemoteTargetHandle>() {
@@ -692,8 +689,6 @@
protected int mRunningTaskViewId = -1;
private int mTaskViewIdCount;
protected boolean mRunningTaskTileHidden;
- @Nullable
- private Task[] mTmpRunningTasks;
protected int mFocusedTaskViewId = INVALID_TASK_ID;
private boolean mTaskIconScaledDown = false;
@@ -1566,9 +1561,6 @@
updateTaskStackListenerState();
mOrientationState.setRotationWatcherEnabled(enabled);
if (!enabled) {
- // Reset the running task when leaving overview since it can still have a reference to
- // its thumbnail
- mTmpRunningTasks = null;
mSplitBoundsConfig = null;
mTaskOverlayFactory.clearAllActiveState();
}
@@ -2496,13 +2488,6 @@
List<Task> tasksToUpdate = containers.stream()
.map(TaskContainer::getTask)
.collect(Collectors.toCollection(ArrayList::new));
- if (mTmpRunningTasks != null) {
- for (Task t : mTmpRunningTasks) {
- // Skip loading if this is the task that we are animating into
- // TODO(b/280812109) change this equality check to use A.equals(B)
- tasksToUpdate.removeIf(task -> task == t);
- }
- }
if (enableRefactorTaskThumbnail()) {
visibleTaskIds.addAll(
tasksToUpdate.stream().map((task) -> task.key.id).toList());
@@ -2510,6 +2495,7 @@
if (tasksToUpdate.isEmpty()) {
continue;
}
+ int visibilityChanges = 0;
for (Task task : tasksToUpdate) {
if (!mHasVisibleTaskData.get(task.key.id)) {
// Ignore thumbnail update if it's current running task during the gesture
@@ -2518,21 +2504,28 @@
if (taskView == getRunningTaskView() && isGestureActive()) {
changes &= ~TaskView.FLAG_UPDATE_THUMBNAIL;
}
- taskView.onTaskListVisibilityChanged(true /* visible */, changes);
+ visibilityChanges |= changes;
}
mHasVisibleTaskData.put(task.key.id, true);
}
+ if (visibilityChanges != 0) {
+ taskView.onTaskListVisibilityChanged(true /* visible */, visibilityChanges);
+ }
} else {
+ int visibilityChanges = 0;
for (TaskContainer container : containers) {
if (container == null) {
continue;
}
if (mHasVisibleTaskData.get(container.getTask().key.id)) {
- taskView.onTaskListVisibilityChanged(false /* visible */, dataChanges);
+ visibilityChanges = dataChanges;
}
mHasVisibleTaskData.delete(container.getTask().key.id);
}
+ if (visibilityChanges != 0) {
+ taskView.onTaskListVisibilityChanged(false /* visible */, visibilityChanges);
+ }
}
}
if (enableRefactorTaskThumbnail()) {
@@ -2965,23 +2958,18 @@
final TaskView taskView;
if (needDesktopTask) {
taskView = getTaskViewFromPool(TaskViewType.DESKTOP);
- mTmpRunningTasks = Arrays.copyOf(runningTasks, runningTasks.length);
- ((DesktopTaskView) taskView).bind(Arrays.asList(mTmpRunningTasks),
+ ((DesktopTaskView) taskView).bind(Arrays.asList(runningTasks),
mOrientationState, mTaskOverlayFactory);
} else if (needGroupTaskView) {
taskView = getTaskViewFromPool(TaskViewType.GROUPED);
- mTmpRunningTasks = new Task[]{runningTasks[0], runningTasks[1]};
// When we create a placeholder task view mSplitBoundsConfig will be null, but with
// the actual app running we won't need to show the thumbnail until all the tasks
// load later anyways
- ((GroupedTaskView) taskView).bind(mTmpRunningTasks[0], mTmpRunningTasks[1],
+ ((GroupedTaskView) taskView).bind(runningTasks[0], runningTasks[1],
mOrientationState, mTaskOverlayFactory, mSplitBoundsConfig);
} else {
taskView = getTaskViewFromPool(TaskViewType.SINGLE);
- // The temporary running task is only used for the duration between the start of the
- // gesture and the task list is loaded and applied
- mTmpRunningTasks = new Task[]{runningTasks[0]};
- taskView.bind(mTmpRunningTasks[0], mOrientationState, mTaskOverlayFactory);
+ taskView.bind(runningTasks[0], mOrientationState, mTaskOverlayFactory);
}
addView(taskView, 0);
runningTaskViewId = taskView.getTaskViewId();
@@ -5034,7 +5022,7 @@
private void updateTaskViewsSnapshotRadius() {
for (TaskView taskView : getTaskViews()) {
- taskView.updateSnapshotRadius();
+ taskView.updateFullscreenParams();
}
}
@@ -5167,7 +5155,7 @@
if (!enableRefactorTaskThumbnail()) {
taskContainer.getThumbnailViewDeprecated().refreshSplashView();
}
- mSplitHiddenTaskView.updateSnapshotRadius();
+ mSplitHiddenTaskView.updateFullscreenParams();
});
} else if (isInitiatingSplitFromTaskView) {
if (Flags.enableHoverOfChildElementsInTaskview()) {
@@ -5879,22 +5867,15 @@
* Finish recents animation.
*/
public void finishRecentsAnimation(boolean toRecents, @Nullable Runnable onFinishComplete) {
- finishRecentsAnimation(toRecents, false, true /* shouldPip */, onFinishComplete);
+ finishRecentsAnimation(toRecents, true /* shouldPip */, onFinishComplete);
}
/**
- * Finish recents animation.
- */
- public void finishRecentsAnimation(boolean toRecents, boolean shouldPip,
- @Nullable Runnable onFinishComplete) {
- finishRecentsAnimation(toRecents, shouldPip, false, onFinishComplete);
- }
- /**
* NOTE: Whatever value gets passed through to the toRecents param may need to also be set on
* {@link #mRecentsAnimationController#setWillFinishToHome}.
*/
public void finishRecentsAnimation(boolean toRecents, boolean shouldPip,
- boolean allAppTargetsAreTranslucent, @Nullable Runnable onFinishComplete) {
+ @Nullable Runnable onFinishComplete) {
Log.d(TAG, "finishRecentsAnimation - mRecentsAnimationController: "
+ mRecentsAnimationController);
// TODO(b/197232424#comment#10) Move this back into onRecentsAnimationComplete(). Maybe?
@@ -5926,7 +5907,7 @@
tx, null /* overlay */);
}
}
- mRecentsAnimationController.finish(toRecents, allAppTargetsAreTranslucent, () -> {
+ mRecentsAnimationController.finish(toRecents, () -> {
if (onFinishComplete != null) {
onFinishComplete.run();
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
index 5dbc2ef..9f2bb9a 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
@@ -50,9 +50,9 @@
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.SystemUiController.SystemUiControllerFlags;
import com.android.launcher3.util.ViewPool;
+import com.android.quickstep.FullscreenDrawParams;
import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
-import com.android.quickstep.views.TaskView.FullscreenDrawParams;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.recents.utilities.PreviewPositionHelper;
@@ -107,7 +107,7 @@
// Contains the portion of the thumbnail that is clipped when fullscreen progress = 0.
private final Rect mPreviewRect = new Rect();
private final PreviewPositionHelper mPreviewPositionHelper = new PreviewPositionHelper();
- private TaskView.FullscreenDrawParams mFullscreenParams;
+ private FullscreenDrawParams mFullscreenParams;
private ImageView mSplashView;
private Drawable mSplashViewDrawable;
private TaskView mTaskView;
@@ -279,7 +279,7 @@
canvas.save();
// Draw the insets if we're being drawn fullscreen (we do this for quick switch).
drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(),
- mFullscreenParams.getCurrentDrawnCornerRadius());
+ mFullscreenParams.getCurrentCornerRadius());
canvas.restore();
}
@@ -287,7 +287,7 @@
return mPreviewPositionHelper;
}
- public void setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) {
+ public void setFullscreenParams(FullscreenDrawParams fullscreenParams) {
mFullscreenParams = fullscreenParams;
invalidate();
}
@@ -473,7 +473,7 @@
mBitmapShader.setLocalMatrix(mPreviewPositionHelper.getMatrix());
mPaint.setShader(mBitmapShader);
}
- mTaskView.updateCurrentFullscreenParams();
+ mTaskView.updateFullscreenParams();
invalidate();
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index b1cb407..7e489ea 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -59,13 +59,11 @@
import com.android.launcher3.testing.TestLogging
import com.android.launcher3.testing.shared.TestProtocol
import com.android.launcher3.util.CancellableTask
-import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.Executors
import com.android.launcher3.util.MultiPropertyFactory
import com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE
import com.android.launcher3.util.MultiValueAlpha
import com.android.launcher3.util.RunnableList
-import com.android.launcher3.util.SafeCloseable
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption
@@ -74,16 +72,13 @@
import com.android.launcher3.util.TransformingTouchDelegate
import com.android.launcher3.util.ViewPool
import com.android.launcher3.util.rects.set
-import com.android.launcher3.views.ActivityContext
+import com.android.quickstep.FullscreenDrawParams
import com.android.quickstep.RecentsModel
import com.android.quickstep.RemoteAnimationTargets
import com.android.quickstep.TaskOverlayFactory
import com.android.quickstep.TaskViewUtils
import com.android.quickstep.orientation.RecentsPagedOrientationHandler
-import com.android.quickstep.recents.di.RecentsDependencies
-import com.android.quickstep.recents.di.get
import com.android.quickstep.task.thumbnail.TaskThumbnailView
-import com.android.quickstep.task.viewmodel.TaskViewModel
import com.android.quickstep.util.ActiveGestureErrorDetector
import com.android.quickstep.util.ActiveGestureLog
import com.android.quickstep.util.BorderAnimator
@@ -95,7 +90,6 @@
import com.android.systemui.shared.recents.model.Task
import com.android.systemui.shared.recents.model.ThumbnailData
import com.android.systemui.shared.system.ActivityManagerWrapper
-import com.android.systemui.shared.system.QuickStepContract
/** A task in the Recents view. */
open class TaskView
@@ -108,6 +102,7 @@
focusBorderAnimator: BorderAnimator? = null,
hoverBorderAnimator: BorderAnimator? = null,
private val type: TaskViewType = TaskViewType.SINGLE,
+ protected val thumbnailFullscreenParams: FullscreenDrawParams = FullscreenDrawParams(context),
) : FrameLayout(context, attrs), ViewPool.Reusable {
/**
* Used in conjunction with [onTaskListVisibilityChanged], providing more granularity on which
@@ -117,8 +112,6 @@
@IntDef(FLAG_UPDATE_ALL, FLAG_UPDATE_ICON, FLAG_UPDATE_THUMBNAIL, FLAG_UPDATE_CORNER_RADIUS)
annotation class TaskDataChanges
- private lateinit var taskViewModel: TaskViewModel
-
val taskIds: IntArray
/** Returns a copy of integer array containing taskIds of all tasks in the TaskView. */
get() = taskContainers.map { it.task.key.id }.toIntArray()
@@ -142,9 +135,6 @@
this == recentsView?.focusedTaskView ||
(enableLargeDesktopWindowingTile() && type == TaskViewType.DESKTOP)
- val taskCornerRadius: Float
- get() = currentFullscreenParams.cornerRadius
-
val recentsView: RecentsView<*, *>?
get() = parent as? RecentsView<*, *>
@@ -157,15 +147,9 @@
get() = taskContainers[0].task
@get:Deprecated("Use [taskContainers] instead.")
- val firstSnapshotView: View
- /** Returns the first snapshotView of the TaskView. */
- get() = taskContainers[0].snapshotView
-
- @get:Deprecated("Use [taskContainers] instead.")
val firstItemInfo: ItemInfo
get() = taskContainers[0].itemInfo
- private val currentFullscreenParams = FullscreenDrawParams(context)
protected val container: RecentsViewContainer =
RecentsViewContainer.containerFromContext(context)
protected val lastTouchDownPosition = PointF()
@@ -489,17 +473,13 @@
init {
setOnClickListener { _ -> onClick() }
- if (enableRefactorTaskThumbnail()) {
- taskViewModel = RecentsDependencies.get(this, "TaskViewType" to type)
- }
-
val cursorHoverStatesEnabled = enableCursorHoverStates()
setWillNotDraw(!cursorHoverStatesEnabled)
context.obtainStyledAttributes(attrs, R.styleable.TaskView, defStyleAttr, defStyleRes).use {
this.focusBorderAnimator =
focusBorderAnimator
?: createSimpleBorderAnimator(
- currentFullscreenParams.cornerRadius.toInt(),
+ TaskCornerRadius.get(context).toInt(),
context.resources.getDimensionPixelSize(
R.dimen.keyboard_quick_switch_border_width
),
@@ -514,7 +494,7 @@
hoverBorderAnimator
?: if (cursorHoverStatesEnabled)
createSimpleBorderAnimator(
- currentFullscreenParams.cornerRadius.toInt(),
+ TaskCornerRadius.get(context).toInt(),
context.resources.getDimensionPixelSize(
R.dimen.task_hover_border_width
),
@@ -716,7 +696,16 @@
taskOverlayFactory,
)
)
- taskContainers.forEach { it.bind() }
+ onBind(orientedState)
+ }
+
+ open fun onBind(orientedState: RecentsOrientedState) {
+ taskContainers.forEach {
+ it.bind()
+ if (enableRefactorTaskThumbnail()) {
+ it.thumbnailView.cornerRadius = thumbnailFullscreenParams.currentCornerRadius
+ }
+ }
setOrientationState(orientedState)
}
@@ -935,7 +924,7 @@
}
}
if (needsUpdate(changes, FLAG_UPDATE_CORNER_RADIUS)) {
- currentFullscreenParams.updateCornerRadius(context)
+ thumbnailFullscreenParams.updateCornerRadius(context)
}
}
@@ -1514,10 +1503,7 @@
val scale = persistentScale * dismissScale
scaleX = scale
scaleY = scale
- if (enableRefactorTaskThumbnail()) {
- taskViewModel.updateScale(scale)
- }
- updateSnapshotRadius()
+ updateFullscreenParams()
}
protected open fun applyThumbnailSplashAlpha() {
@@ -1560,30 +1546,25 @@
}
focusTransitionFullscreen.value =
FOCUS_TRANSITION_FAST_OUT_INTERPOLATOR.getInterpolation(1 - fullscreenProgress)
- updateSnapshotRadius()
+ updateFullscreenParams()
}
- protected open fun updateSnapshotRadius() {
- updateCurrentFullscreenParams()
+ protected open fun updateFullscreenParams() {
+ updateFullscreenParams(thumbnailFullscreenParams)
taskContainers.forEach {
- if (!enableRefactorTaskThumbnail()) {
- it.thumbnailViewDeprecated.setFullscreenParams(getThumbnailFullscreenParams())
+ if (enableRefactorTaskThumbnail()) {
+ it.thumbnailView.cornerRadius = thumbnailFullscreenParams.currentCornerRadius
+ } else {
+ it.thumbnailViewDeprecated.setFullscreenParams(thumbnailFullscreenParams)
}
- it.overlay.setFullscreenParams(getThumbnailFullscreenParams())
+ it.overlay.setFullscreenParams(thumbnailFullscreenParams)
}
}
- protected open fun updateCurrentFullscreenParams() {
- updateFullscreenParams(currentFullscreenParams)
- }
-
protected fun updateFullscreenParams(fullscreenParams: FullscreenDrawParams) {
recentsView?.let { fullscreenParams.setProgress(fullscreenProgress, it.scaleX, scaleX) }
}
- protected open fun getThumbnailFullscreenParams(): FullscreenDrawParams =
- currentFullscreenParams
-
private fun onModalnessUpdated(modalness: Float) {
taskContainers.forEach {
it.iconView.setModalAlpha(1 - modalness)
@@ -1630,56 +1611,6 @@
private fun getNonGridTrans(endTranslation: Float) =
endTranslation - getGridTrans(endTranslation)
- /** We update and subsequently draw these in [fullscreenProgress]. */
- open class FullscreenDrawParams(context: Context) : SafeCloseable {
- var cornerRadius = 0f
- private var windowCornerRadius = 0f
- var currentDrawnCornerRadius = 0f
-
- init {
- updateCornerRadius(context)
- }
-
- /** Recomputes the start and end corner radius for the given Context. */
- fun updateCornerRadius(context: Context) {
- cornerRadius = computeTaskCornerRadius(context)
- windowCornerRadius = computeWindowCornerRadius(context)
- }
-
- @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
- open fun computeTaskCornerRadius(context: Context): Float {
- return TaskCornerRadius.get(context)
- }
-
- @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
- open fun computeWindowCornerRadius(context: Context): Float {
- val activityContext: ActivityContext? = ActivityContext.lookupContextNoThrow(context)
-
- // The corner radius is fixed to match when Taskbar is persistent mode
- return if (
- activityContext != null &&
- activityContext.deviceProfile?.isTaskbarPresent == true &&
- DisplayController.isTransientTaskbar(context)
- ) {
- context.resources
- .getDimensionPixelSize(R.dimen.persistent_taskbar_corner_radius)
- .toFloat()
- } else {
- QuickStepContract.getWindowCornerRadius(context)
- }
- }
-
- /** Sets the progress in range [0, 1] */
- fun setProgress(fullscreenProgress: Float, parentScale: Float, taskViewScale: Float) {
- currentDrawnCornerRadius =
- Utilities.mapRange(fullscreenProgress, cornerRadius, windowCornerRadius) /
- parentScale /
- taskViewScale
- }
-
- override fun close() {}
- }
-
private fun MotionEvent.isWithinThumbnailBounds(): Boolean {
return thumbnailBounds.contains(x.toInt(), y.toInt())
}
diff --git a/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
index f43a125..0091036 100644
--- a/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
+++ b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
@@ -96,6 +96,12 @@
+ "force finish recents animation complete; clearing state callback.");
}
+ public static void logHandOffAnimation() {
+ ActiveGestureLog.INSTANCE.addLog("AbsSwipeUpHandler.handOffAnimation");
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "AbsSwipeUpHandler.handOffAnimation");
+ }
+
public static void logFinishRecentsAnimationOnTasksAppeared() {
ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimationOnTasksAppeared");
if (!enableActiveGestureProtoLog()) return;
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt
index ff5d8bd..47d2bfc 100644
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt
@@ -22,8 +22,6 @@
import kotlinx.coroutines.flow.MutableStateFlow
class FakeTaskThumbnailViewModel : TaskThumbnailViewModel {
- override val cornerRadiusProgress = MutableStateFlow(0f)
- override val inheritedScale = MutableStateFlow(1f)
override val dimProgress = MutableStateFlow(0f)
override val splashAlpha = MutableStateFlow(0f)
override val uiState = MutableStateFlow<TaskThumbnailUiState>(Uninitialized)
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
index 75769e9..49fe614 100644
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
@@ -66,11 +66,13 @@
val di = RecentsDependencies.initialize(context)
val taskThumbnailView =
LayoutInflater.from(context).inflate(R.layout.task_thumbnail, null, false)
+ as TaskThumbnailView
+ taskThumbnailView.cornerRadius = CORNER_RADIUS
val ttvDiScopeId = di.getScope(taskThumbnailView).scopeId
di.provide(TaskThumbnailViewData::class.java, ttvDiScopeId) { TaskThumbnailViewData() }
di.provide(TaskThumbnailViewModel::class.java, ttvDiScopeId) { taskThumbnailViewModel }
- return taskThumbnailView as TaskThumbnailView
+ return taskThumbnailView
}
companion object {
@@ -82,5 +84,7 @@
isDarkTheme = false,
isLandscape = false,
)
+
+ const val CORNER_RADIUS = 56f
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
index 4b04dba..c682990 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
@@ -15,9 +15,11 @@
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.SCREEN_PIN_LONG_PRESS_THRESHOLD;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
+import static com.android.window.flags.Flags.FLAG_PREDICTIVE_BACK_THREE_BUTTON_NAV;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -28,6 +30,10 @@
import static org.mockito.Mockito.when;
import android.os.Handler;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.Flags;
@@ -43,8 +49,10 @@
import com.android.systemui.contextualeducation.GestureType;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -76,6 +84,9 @@
@Mock
View mockView;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
private int mHomePressCount;
private int mOverviewToggleCount;
private final TaskbarNavButtonCallbacks mCallbacks = new TaskbarNavButtonCallbacks() {
@@ -333,4 +344,46 @@
verify(mockStatsLogger, times(1)).log(LAUNCHER_TASKBAR_BACK_BUTTON_LONGPRESS);
verify(mockStatsLogger, times(0)).log(LAUNCHER_TASKBAR_BACK_BUTTON_TAP);
}
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_PREDICTIVE_BACK_THREE_BUTTON_NAV)
+ public void testPredictiveBackInvoked() {
+ ArgumentCaptor<KeyEvent> keyEventCaptor = ArgumentCaptor.forClass(KeyEvent.class);
+ mNavButtonController.sendBackKeyEvent(KeyEvent.ACTION_DOWN, false);
+ mNavButtonController.sendBackKeyEvent(KeyEvent.ACTION_UP, false);
+ verify(mockSystemUiProxy, times(2)).onBackEvent(keyEventCaptor.capture());
+ verifyKeyEvent(keyEventCaptor.getAllValues().getFirst(), KeyEvent.ACTION_DOWN, false);
+ verifyKeyEvent(keyEventCaptor.getAllValues().getLast(), KeyEvent.ACTION_UP, false);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_PREDICTIVE_BACK_THREE_BUTTON_NAV)
+ public void testPredictiveBackCancelled() {
+ ArgumentCaptor<KeyEvent> keyEventCaptor = ArgumentCaptor.forClass(KeyEvent.class);
+ mNavButtonController.sendBackKeyEvent(KeyEvent.ACTION_DOWN, false);
+ mNavButtonController.sendBackKeyEvent(KeyEvent.ACTION_UP, true);
+ verify(mockSystemUiProxy, times(2)).onBackEvent(keyEventCaptor.capture());
+ verifyKeyEvent(keyEventCaptor.getAllValues().getFirst(), KeyEvent.ACTION_DOWN, false);
+ verifyKeyEvent(keyEventCaptor.getAllValues().getLast(), KeyEvent.ACTION_UP, true);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_PREDICTIVE_BACK_THREE_BUTTON_NAV)
+ public void testButtonsDisabledWhileBackPressed() {
+ mNavButtonController.sendBackKeyEvent(KeyEvent.ACTION_DOWN, false);
+ mNavButtonController.onButtonClick(BUTTON_HOME, mockView);
+ mNavButtonController.onButtonClick(BUTTON_RECENTS, mockView);
+ mNavButtonController.onButtonLongClick(BUTTON_A11Y, mockView);
+ mNavButtonController.onButtonClick(BUTTON_IME_SWITCH, mockView);
+ mNavButtonController.sendBackKeyEvent(KeyEvent.ACTION_UP, false);
+ assertThat(mHomePressCount).isEqualTo(0);
+ verify(mockSystemUiProxy, never()).notifyAccessibilityButtonLongClicked();
+ assertThat(mOverviewToggleCount).isEqualTo(0);
+ verify(mockSystemUiProxy, never()).onImeSwitcherPressed();
+ }
+
+ private void verifyKeyEvent(KeyEvent keyEvent, int action, boolean isCancelled) {
+ assertEquals(isCancelled, keyEvent.isCanceled());
+ assertEquals(action, KeyEvent.ACTION_DOWN, keyEvent.getAction());
+ }
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
new file mode 100644
index 0000000..cc8582c
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2024 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
+
+import android.content.ComponentName
+import android.content.Intent
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import com.android.launcher3.Flags.FLAG_TASKBAR_OVERFLOW
+import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
+import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
+import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController
+import com.android.launcher3.taskbar.rules.MockedRecentsModelTestRule
+import com.android.launcher3.taskbar.rules.TaskbarModeRule
+import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.PINNED
+import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.TRANSIENT
+import com.android.launcher3.taskbar.rules.TaskbarModeRule.TaskbarMode
+import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule
+import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
+import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
+import com.android.launcher3.util.TestUtil.getOnUiThread
+import com.android.quickstep.SystemUiProxy
+import com.android.quickstep.util.DesktopTask
+import com.android.systemui.shared.recents.model.Task
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS
+import com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR
+import com.android.wm.shell.desktopmode.IDesktopTaskListener
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(LauncherMultivalentJUnit::class)
+@EmulatedDevices(["pixelTablet2023"])
+@EnableFlags(
+ FLAG_TASKBAR_OVERFLOW,
+ FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS,
+ FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ FLAG_ENABLE_BUBBLE_BAR,
+)
+class TaskbarOverflowTest {
+ @get:Rule(order = 0) val setFlagsRule = SetFlagsRule()
+
+ @get:Rule(order = 1)
+ val context =
+ TaskbarWindowSandboxContext.create { builder ->
+ builder.bindSystemUiProxy(
+ object : SystemUiProxy(this) {
+ override fun setDesktopTaskListener(listener: IDesktopTaskListener?) {
+ desktopTaskListener = listener
+ }
+ }
+ )
+ }
+
+ @get:Rule(order = 2) val recentsModel = MockedRecentsModelTestRule(context)
+
+ @get:Rule(order = 3) val taskbarModeRule = TaskbarModeRule(context)
+
+ @get:Rule(order = 4) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
+
+ @InjectController lateinit var taskbarViewController: TaskbarViewController
+ @InjectController lateinit var recentAppsController: TaskbarRecentAppsController
+ @InjectController lateinit var bubbleBarViewController: BubbleBarViewController
+ @InjectController lateinit var bubbleStashController: BubbleStashController
+
+ private var desktopTaskListener: IDesktopTaskListener? = null
+
+ @Before
+ fun ensureRunningAppsShowing() {
+ runOnMainSync {
+ if (!recentAppsController.canShowRunningApps) {
+ recentAppsController.onDestroy()
+ recentAppsController.canShowRunningApps = true
+ recentAppsController.init(taskbarUnitTestRule.activityContext.controllers)
+ }
+ recentsModel.resolvePendingTaskRequests()
+ }
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testTaskbarWithMaxNumIcons_pinned() {
+ addRunningAppsAndVerifyOverflowState(0)
+
+ assertThat(taskbarIconsCentered).isTrue()
+ assertThat(taskbarEndMargin).isAtLeast(navButtonEndSpacing)
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testTaskbarWithMaxNumIcons_transient() {
+ addRunningAppsAndVerifyOverflowState(0)
+
+ assertThat(taskbarIconsCentered).isTrue()
+ assertThat(taskbarEndMargin).isAtLeast(navButtonEndSpacing)
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testOverflownTaskbar_pinned() {
+ addRunningAppsAndVerifyOverflowState(5)
+
+ assertThat(taskbarIconsCentered).isTrue()
+ assertThat(taskbarEndMargin).isAtLeast(navButtonEndSpacing)
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testOverflownTaskbar_transient() {
+ addRunningAppsAndVerifyOverflowState(5)
+
+ assertThat(taskbarIconsCentered).isTrue()
+ assertThat(taskbarEndMargin).isAtLeast(navButtonEndSpacing)
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testBubbleBarReducesTaskbarMaxNumIcons_pinned() {
+ var initialMaxNumIconViews = maxNumberOfTaskbarIcons
+ assertThat(initialMaxNumIconViews).isGreaterThan(0)
+
+ runOnMainSync { bubbleBarViewController.setHiddenForBubbles(false) }
+
+ val maxNumIconViews = addRunningAppsAndVerifyOverflowState(2)
+ assertThat(maxNumIconViews).isLessThan(initialMaxNumIconViews)
+
+ assertThat(taskbarIconsCentered).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testBubbleBarReducesTaskbarMaxNumIcons_transient() {
+ var initialMaxNumIconViews = maxNumberOfTaskbarIcons
+ assertThat(initialMaxNumIconViews).isGreaterThan(0)
+
+ runOnMainSync { bubbleBarViewController.setHiddenForBubbles(false) }
+
+ val maxNumIconViews = addRunningAppsAndVerifyOverflowState(2)
+ assertThat(maxNumIconViews).isLessThan(initialMaxNumIconViews)
+
+ assertThat(taskbarIconsCentered).isTrue()
+ assertThat(taskbarEndMargin)
+ .isAtLeast(
+ navButtonEndSpacing +
+ bubbleBarViewController.collapsedWidthWithMaxVisibleBubbles.toInt()
+ )
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testBubbleBarReducesTaskbarMaxNumIcons_transientBubbleInitiallyStashed() {
+ var initialMaxNumIconViews = maxNumberOfTaskbarIcons
+ assertThat(initialMaxNumIconViews).isGreaterThan(0)
+ runOnMainSync {
+ bubbleStashController.stashBubbleBarImmediate()
+ bubbleBarViewController.setHiddenForBubbles(false)
+ }
+
+ val maxNumIconViews = addRunningAppsAndVerifyOverflowState(2)
+ assertThat(maxNumIconViews).isLessThan(initialMaxNumIconViews)
+
+ assertThat(taskbarIconsCentered).isTrue()
+ assertThat(taskbarEndMargin)
+ .isAtLeast(
+ navButtonEndSpacing +
+ bubbleBarViewController.collapsedWidthWithMaxVisibleBubbles.toInt()
+ )
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testStashingBubbleBarMaintainsMaxNumIcons_transient() {
+ runOnMainSync { bubbleBarViewController.setHiddenForBubbles(false) }
+
+ val initialNumIcons = currentNumberOfTaskbarIcons
+ val maxNumIconViews = addRunningAppsAndVerifyOverflowState(2)
+
+ runOnMainSync { bubbleStashController.stashBubbleBarImmediate() }
+ assertThat(maxNumberOfTaskbarIcons).isEqualTo(maxNumIconViews)
+ assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumIconViews)
+ assertThat(taskbarOverflowIconIndex).isEqualTo(initialNumIcons.coerceAtLeast(2))
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testHidingBubbleBarIncreasesMaxNumIcons_pinned() {
+ runOnMainSync { bubbleBarViewController.setHiddenForBubbles(false) }
+
+ val initialNumIcons = currentNumberOfTaskbarIcons
+ val initialMaxNumIconViews = addRunningAppsAndVerifyOverflowState(5)
+
+ runOnMainSync { bubbleBarViewController.setHiddenForBubbles(true) }
+
+ val maxNumIconViews = maxNumberOfTaskbarIcons
+ assertThat(maxNumIconViews).isGreaterThan(initialMaxNumIconViews)
+ assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumIconViews)
+ assertThat(taskbarOverflowIconIndex).isEqualTo(initialNumIcons.coerceAtLeast(2))
+
+ assertThat(taskbarIconsCentered).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testHidingBubbleBarIncreasesMaxNumIcons_transient() {
+ runOnMainSync { bubbleBarViewController.setHiddenForBubbles(false) }
+
+ val initialNumIcons = currentNumberOfTaskbarIcons
+ val initialMaxNumIconViews = addRunningAppsAndVerifyOverflowState(5)
+
+ runOnMainSync { bubbleBarViewController.setHiddenForBubbles(true) }
+
+ val maxNumIconViews = maxNumberOfTaskbarIcons
+ assertThat(maxNumIconViews).isGreaterThan(initialMaxNumIconViews)
+ assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumIconViews)
+ assertThat(taskbarOverflowIconIndex).isEqualTo(initialNumIcons.coerceAtLeast(2))
+
+ assertThat(taskbarIconsCentered).isTrue()
+ }
+
+ private fun createDesktopTask(tasksToAdd: Int) {
+ val tasks =
+ (0..<tasksToAdd).map {
+ Task(Task.TaskKey(it, 0, Intent(), ComponentName("", ""), 0, 2000))
+ }
+ recentsModel.updateRecentTasks(listOf(DesktopTask(tasks)))
+ desktopTaskListener?.onTasksVisibilityChanged(
+ context.virtualDisplay.display.displayId,
+ tasksToAdd,
+ )
+ runOnMainSync { recentsModel.resolvePendingTaskRequests() }
+ }
+
+ private val navButtonEndSpacing: Int
+ get() {
+ return taskbarUnitTestRule.activityContext.resources.getDimensionPixelSize(
+ taskbarUnitTestRule.activityContext.deviceProfile.inv.inlineNavButtonsEndSpacing
+ )
+ }
+
+ private val taskbarOverflowIconIndex: Int
+ get() {
+ return getOnUiThread {
+ taskbarViewController.iconViews.indexOfFirst { it is TaskbarOverflowView }
+ }
+ }
+
+ private val maxNumberOfTaskbarIcons: Int
+ get() = getOnUiThread { taskbarViewController.maxNumIconViews }
+
+ private val currentNumberOfTaskbarIcons: Int
+ get() = getOnUiThread { taskbarViewController.iconViews.size }
+
+ private val taskbarIconsCentered: Boolean
+ get() {
+ return getOnUiThread {
+ val iconLayoutBounds = taskbarViewController.iconLayoutBounds
+ val availableWidth = taskbarUnitTestRule.activityContext.deviceProfile.widthPx
+ iconLayoutBounds.left - (availableWidth - iconLayoutBounds.right) < 2
+ }
+ }
+
+ private val taskbarEndMargin: Int
+ get() {
+ return getOnUiThread {
+ taskbarUnitTestRule.activityContext.deviceProfile.widthPx -
+ taskbarViewController.iconLayoutBounds.right
+ }
+ }
+
+ /**
+ * Adds enough running apps for taskbar to enter overflow of `targetOverflowSize`, and verifies
+ * * max number of icons in the taskbar remains unchanged
+ * * number of icons in the taskbar is at most max number of icons
+ * * whether the taskbar overflow icon is shown, and its position in taskbar.
+ *
+ * Returns max number of icons.
+ */
+ private fun addRunningAppsAndVerifyOverflowState(targetOverflowSize: Int): Int {
+ val maxNumIconViews = maxNumberOfTaskbarIcons
+ assertThat(maxNumIconViews).isGreaterThan(0)
+ // Assume there are at least all apps and divider icon, as they would appear once running
+ // apps are added, even if not present initially.
+ val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2)
+ assertThat(initialIconCount).isLessThan(maxNumIconViews)
+
+ createDesktopTask(maxNumIconViews - initialIconCount + targetOverflowSize)
+
+ assertThat(maxNumberOfTaskbarIcons).isEqualTo(maxNumIconViews)
+ assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumIconViews)
+ assertThat(taskbarOverflowIconIndex)
+ .isEqualTo(if (targetOverflowSize > 0) initialIconCount else -1)
+ return maxNumIconViews
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTest.kt
new file mode 100644
index 0000000..0bb404b
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTest.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2024 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
+
+import android.platform.test.flag.junit.FlagsParameterization
+import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf
+import android.platform.test.flag.junit.SetFlagsRule
+import com.android.launcher3.Flags.FLAG_TASKBAR_RECENTS_LAYOUT_TRANSITION
+import com.android.launcher3.R
+import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
+import com.android.launcher3.taskbar.TaskbarIconType.ALL_APPS
+import com.android.launcher3.taskbar.TaskbarIconType.DIVIDER
+import com.android.launcher3.taskbar.TaskbarIconType.HOTSEAT
+import com.android.launcher3.taskbar.TaskbarIconType.RECENT
+import com.android.launcher3.taskbar.TaskbarViewTestUtil.assertThat
+import com.android.launcher3.taskbar.TaskbarViewTestUtil.createHotseatItems
+import com.android.launcher3.taskbar.TaskbarViewTestUtil.createRecents
+import com.android.launcher3.taskbar.rules.TaskbarDeviceEmulationRule
+import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule
+import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.ForceRtl
+import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
+import com.android.launcher3.util.LauncherMultivalentJUnit.Companion.isRunningInRobolectric
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+@RunWith(ParameterizedAndroidJunit4::class)
+class TaskbarViewTest(deviceName: String, flags: FlagsParameterization) {
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0},{1}")
+ fun getParams(): List<Array<Any>> {
+ val devices =
+ if (isRunningInRobolectric) {
+ listOf("pixelFoldable2023", "pixelTablet2023")
+ } else {
+ listOf("onDevice") // Unused.
+ }
+ val flags = allCombinationsOf(FLAG_TASKBAR_RECENTS_LAYOUT_TRANSITION)
+ return devices.flatMap { d -> flags.map { f -> arrayOf(d, f) } } // Cartesian product.
+ }
+ }
+
+ @get:Rule(order = 0) val setFlagsRule = SetFlagsRule(flags)
+ @get:Rule(order = 1) val context = TaskbarWindowSandboxContext.create()
+ @get:Rule(order = 2) val deviceEmulationRule = TaskbarDeviceEmulationRule(context, deviceName)
+ @get:Rule(order = 3) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
+
+ private lateinit var taskbarView: TaskbarView
+
+ @Before
+ fun obtainView() {
+ taskbarView = taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
+ }
+
+ @Test
+ fun testUpdateItems_noItems_hasOnlyAllApps() {
+ runOnMainSync { taskbarView.updateItems(emptyArray(), emptyList()) }
+ assertThat(taskbarView).hasIconTypes(ALL_APPS)
+ }
+
+ @Test
+ fun testUpdateItems_hotseatItems_hasDividerBetweenAllAppsAndHotseat() {
+ runOnMainSync { taskbarView.updateItems(createHotseatItems(2), emptyList()) }
+ assertThat(taskbarView).hasIconTypes(ALL_APPS, DIVIDER, HOTSEAT, HOTSEAT)
+ }
+
+ @Test
+ @ForceRtl
+ fun testUpdateItems_rtlWithHotseatItems_hasDividerBetweenHotseatAndAllApps() {
+ runOnMainSync { taskbarView.updateItems(createHotseatItems(2), emptyList()) }
+ assertThat(taskbarView).hasIconTypes(HOTSEAT, HOTSEAT, DIVIDER, ALL_APPS)
+ }
+
+ @Test
+ fun testUpdateItems_withNullHotseatItem_filtersNullItem() {
+ runOnMainSync {
+ taskbarView.updateItems(arrayOf(*createHotseatItems(2), null), emptyList())
+ }
+ assertThat(taskbarView).hasIconTypes(ALL_APPS, DIVIDER, HOTSEAT, HOTSEAT)
+ }
+
+ @Test
+ @ForceRtl
+ fun testUpdateItems_rtlWithNullHotseatItem_filtersNullItem() {
+ runOnMainSync {
+ taskbarView.updateItems(arrayOf(*createHotseatItems(2), null), emptyList())
+ }
+ assertThat(taskbarView).hasIconTypes(HOTSEAT, HOTSEAT, DIVIDER, ALL_APPS)
+ }
+
+ @Test
+ fun testUpdateItems_recentsItems_hasDividerBetweenAllAppsAndRecents() {
+ runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(4)) }
+ assertThat(taskbarView).hasIconTypes(ALL_APPS, DIVIDER, *RECENT * 4)
+ }
+
+ @Test
+ fun testUpdateItems_hotseatItemsAndRecents_hasDividerBetweenHotseatAndRecents() {
+ runOnMainSync { taskbarView.updateItems(createHotseatItems(3), createRecents(2)) }
+ assertThat(taskbarView).hasIconTypes(ALL_APPS, *HOTSEAT * 3, DIVIDER, *RECENT * 2)
+ }
+
+ @Test
+ fun testUpdateItems_addHotseatItem_updatesHotseat() {
+ runOnMainSync {
+ taskbarView.updateItems(createHotseatItems(1), createRecents(1))
+ taskbarView.updateItems(createHotseatItems(2), createRecents(1))
+ }
+ assertThat(taskbarView).hasIconTypes(ALL_APPS, *HOTSEAT * 2, DIVIDER, RECENT)
+ }
+
+ @Test
+ fun testUpdateItems_removeHotseatItem_updatesHotseat() {
+ runOnMainSync {
+ taskbarView.updateItems(createHotseatItems(2), createRecents(1))
+ taskbarView.updateItems(createHotseatItems(1), createRecents(1))
+ }
+ assertThat(taskbarView).hasIconTypes(ALL_APPS, HOTSEAT, DIVIDER, RECENT)
+ }
+
+ @Test
+ fun testUpdateItems_addRecentsItem_updatesRecents() {
+ runOnMainSync {
+ taskbarView.updateItems(createHotseatItems(1), createRecents(1))
+ taskbarView.updateItems(createHotseatItems(1), createRecents(2))
+ }
+ assertThat(taskbarView).hasIconTypes(ALL_APPS, HOTSEAT, DIVIDER, *RECENT * 2)
+ }
+
+ @Test
+ fun testUpdateItems_removeRecentsItem_updatesRecents() {
+ runOnMainSync {
+ taskbarView.updateItems(createHotseatItems(1), createRecents(2))
+ taskbarView.updateItems(createHotseatItems(1), createRecents(1))
+ }
+ assertThat(taskbarView).hasIconTypes(ALL_APPS, HOTSEAT, DIVIDER, RECENT)
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt
new file mode 100644
index 0000000..a6bdbb0
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2024 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
+
+import android.content.ComponentName
+import android.content.Intent
+import android.os.Process
+import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.taskbar.TaskbarIconType.ALL_APPS
+import com.android.launcher3.taskbar.TaskbarIconType.DIVIDER
+import com.android.launcher3.taskbar.TaskbarIconType.HOTSEAT
+import com.android.launcher3.taskbar.TaskbarIconType.OVERFLOW
+import com.android.launcher3.taskbar.TaskbarIconType.RECENT
+import com.android.quickstep.util.GroupTask
+import com.android.systemui.shared.recents.model.Task
+import com.android.systemui.shared.recents.model.Task.TaskKey
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Truth.assertAbout
+import com.google.common.truth.Truth.assertThat
+
+/** Common utilities for testing [TaskbarView]. */
+object TaskbarViewTestUtil {
+
+ /** Begins an assertion about a [TaskbarView]. */
+ fun assertThat(view: TaskbarView): TaskbarViewSubject {
+ return assertAbout(::TaskbarViewSubject).that(view)
+ }
+
+ /** Creates an array of fake hotseat items. */
+ fun createHotseatItems(size: Int): Array<ItemInfo> {
+ return Array(size) {
+ WorkspaceItemInfo(
+ AppInfo(TEST_COMPONENT, "Test App $it", Process.myUserHandle(), Intent())
+ )
+ .apply { id = it }
+ }
+ }
+
+ /** Creates a list of fake recent tasks. */
+ fun createRecents(size: Int): List<GroupTask> {
+ return List(size) {
+ GroupTask(
+ Task().apply {
+ key =
+ TaskKey(
+ it,
+ 5,
+ TEST_INTENT,
+ TEST_COMPONENT,
+ Process.myUserHandle().identifier,
+ System.currentTimeMillis(),
+ )
+ }
+ )
+ }
+ }
+}
+
+/** A `Truth` [Subject] with extensions for verifying [TaskbarView]. */
+class TaskbarViewSubject(failureMetadata: FailureMetadata, private val view: TaskbarView) :
+ Subject(failureMetadata, view) {
+
+ /** Verifies that the types of icons match [expectedTypes] in order. */
+ fun hasIconTypes(vararg expectedTypes: TaskbarIconType) {
+ val actualTypes =
+ view.iconViews.map {
+ when (it) {
+ view.allAppsButtonContainer -> ALL_APPS
+ view.taskbarDividerViewContainer -> DIVIDER
+ view.taskbarOverflowView -> OVERFLOW
+ else ->
+ when (it.tag) {
+ is ItemInfo -> HOTSEAT
+ is GroupTask -> RECENT
+ else -> throw IllegalStateException("Unknown type for $it")
+ }
+ }
+ }
+ assertThat(actualTypes).containsExactly(*expectedTypes).inOrder()
+ }
+
+ /** Verifies that recents from [startIndex] have IDs that match [expectedIds] in order. */
+ fun hasRecentsOrder(startIndex: Int, expectedIds: List<Int>) {
+ val actualIds =
+ view.iconViews.slice(startIndex..<expectedIds.size).map {
+ assertThat(it.tag).isInstanceOf(GroupTask::class.java)
+ (it.tag as? GroupTask)?.task1?.key?.id
+ }
+ assertThat(actualIds).containsExactlyElementsIn(expectedIds).inOrder()
+ }
+}
+
+/** Types of icons in the [TaskbarView]. */
+enum class TaskbarIconType {
+ ALL_APPS,
+ DIVIDER,
+ HOTSEAT,
+ RECENT,
+ OVERFLOW;
+
+ operator fun times(size: Int) = Array(size) { this }
+}
+
+private const val TEST_PACKAGE = "com.android.launcher3.taskbar"
+private val TEST_COMPONENT = ComponentName(TEST_PACKAGE, "Activity")
+private val TEST_INTENT = Intent().apply { `package` = TEST_PACKAGE }
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewWithLayoutTransitionTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewWithLayoutTransitionTest.kt
new file mode 100644
index 0000000..15ded8d
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewWithLayoutTransitionTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2024 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
+
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import com.android.launcher3.Flags.FLAG_TASKBAR_RECENTS_LAYOUT_TRANSITION
+import com.android.launcher3.R
+import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
+import com.android.launcher3.taskbar.TaskbarIconType.ALL_APPS
+import com.android.launcher3.taskbar.TaskbarIconType.DIVIDER
+import com.android.launcher3.taskbar.TaskbarIconType.HOTSEAT
+import com.android.launcher3.taskbar.TaskbarIconType.RECENT
+import com.android.launcher3.taskbar.TaskbarViewTestUtil.assertThat
+import com.android.launcher3.taskbar.TaskbarViewTestUtil.createHotseatItems
+import com.android.launcher3.taskbar.TaskbarViewTestUtil.createRecents
+import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule
+import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.ForceRtl
+import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(LauncherMultivalentJUnit::class)
+@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
+@EnableFlags(FLAG_TASKBAR_RECENTS_LAYOUT_TRANSITION)
+class TaskbarViewWithLayoutTransitionTest {
+
+ @get:Rule(order = 0) val setFlagsRule = SetFlagsRule()
+ @get:Rule(order = 1) val context = TaskbarWindowSandboxContext.create()
+ @get:Rule(order = 2) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
+
+ private lateinit var taskbarView: TaskbarView
+
+ @Before
+ fun obtainView() {
+ taskbarView = taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
+ }
+
+ @Test
+ @ForceRtl
+ fun testUpdateItems_rtl_hotseatItems_hasDividerBetweenHotseatAndAllApps() {
+ runOnMainSync { taskbarView.updateItems(createHotseatItems(2), emptyList()) }
+ assertThat(taskbarView).hasIconTypes(*HOTSEAT * 2, DIVIDER, ALL_APPS)
+ }
+
+ @Test
+ @ForceRtl
+ fun testUpdateItems_rtl_recentsItems_hasDividerBetweenRecentsAndAllApps() {
+ runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(4)) }
+ assertThat(taskbarView).hasIconTypes(*RECENT * 4, DIVIDER, ALL_APPS)
+ }
+
+ @Test
+ @ForceRtl
+ fun testUpdateItems_rtl_recentsItems_recentsAreReversed() {
+ runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(4)) }
+ assertThat(taskbarView).hasRecentsOrder(startIndex = 0, expectedIds = listOf(3, 2, 1, 0))
+ }
+
+ @Test
+ @ForceRtl
+ fun testUpdateItems_rtl_hotseatItemsAndRecents_hasDividerBetweenRecentsAndHotseat() {
+ runOnMainSync { taskbarView.updateItems(createHotseatItems(3), createRecents(2)) }
+ assertThat(taskbarView).hasIconTypes(*RECENT * 2, DIVIDER, *HOTSEAT * 3, ALL_APPS)
+ }
+
+ @Test
+ @ForceRtl
+ fun testUpdateItems_rtl_addHotseatItem_updatesHotseat() {
+ runOnMainSync {
+ taskbarView.updateItems(createHotseatItems(1), createRecents(1))
+ taskbarView.updateItems(createHotseatItems(2), createRecents(1))
+ }
+ assertThat(taskbarView).hasIconTypes(RECENT, DIVIDER, *HOTSEAT * 2, ALL_APPS)
+ }
+
+ @Test
+ @ForceRtl
+ fun testUpdateItems_rtl_removeHotseatItem_updatesHotseat() {
+ runOnMainSync {
+ taskbarView.updateItems(createHotseatItems(2), createRecents(1))
+ taskbarView.updateItems(createHotseatItems(1), createRecents(1))
+ }
+ assertThat(taskbarView).hasIconTypes(RECENT, DIVIDER, HOTSEAT, ALL_APPS)
+ }
+
+ @Test
+ @ForceRtl
+ fun testUpdateItems_rtl_addRecentsItem_updatesRecents() {
+ runOnMainSync {
+ taskbarView.updateItems(createHotseatItems(1), createRecents(1))
+ taskbarView.updateItems(createHotseatItems(1), createRecents(2))
+ }
+ assertThat(taskbarView).hasIconTypes(*RECENT * 2, DIVIDER, HOTSEAT, ALL_APPS)
+ }
+
+ @Test
+ @ForceRtl
+ fun testUpdateItems_rtl_removeRecentsItem_updatesRecents() {
+ runOnMainSync {
+ taskbarView.updateItems(createHotseatItems(1), createRecents(2))
+ taskbarView.updateItems(createHotseatItems(1), createRecents(1))
+ }
+ assertThat(taskbarView).hasIconTypes(RECENT, DIVIDER, HOTSEAT, ALL_APPS)
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
index 582ea54..e12876f 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
@@ -48,12 +48,15 @@
import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
import com.android.wm.shell.shared.bubbles.BubbleInfo
import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Semaphore
+import java.util.concurrent.TimeUnit
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.atLeastOnce
+import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -764,10 +767,12 @@
whenever(bubbleStashController.bubbleBarTranslationY)
.thenReturn(BAR_TRANSLATION_Y_FOR_HOTSEAT)
- val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)
-
+ val semaphore = Semaphore(0)
var notifiedExpanded = false
- val onExpanded = Runnable { notifiedExpanded = true }
+ val onExpanded = Runnable {
+ notifiedExpanded = true
+ semaphore.release()
+ }
val animator =
BubbleBarViewAnimator(
bubbleBarView,
@@ -792,7 +797,12 @@
// the lift animation is complete; the spring back animation should start now
InstrumentationRegistry.getInstrumentation().runOnMainSync {}
- barAnimator.assertIsRunning()
+
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ // we should be expanded now
+ assertThat(bubbleBarView.isExpanded).isTrue()
PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
// verify there is no hide animation
@@ -800,7 +810,6 @@
assertThat(animator.isAnimating).isFalse()
assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT)
- assertThat(bubbleBarView.isExpanded).isTrue()
verify(bubbleStashController).showBubbleBarImmediate()
assertThat(notifiedExpanded).isTrue()
}
@@ -1266,6 +1275,50 @@
verify(bubbleStashController).stashBubbleBarImmediate()
}
+ @Test
+ fun interruptForIme() {
+ setUpBubbleBar()
+ setUpBubbleStashController()
+
+ val handle = View(context)
+ val handleAnimator = PhysicsAnimator.getInstance(handle)
+ whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator)
+
+ val animator =
+ BubbleBarViewAnimator(
+ bubbleBarView,
+ bubbleStashController,
+ flyoutController,
+ onExpandedNoOp,
+ animatorScheduler,
+ )
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ animator.animateBubbleInForStashed(bubble, isExpanding = false)
+ }
+
+ // wait for the animation to start
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {}
+ PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(handleAnimator) { true }
+
+ handleAnimator.assertIsRunning()
+ assertThat(animator.isAnimating).isTrue()
+ // verify the hide bubble animation is pending
+ assertThat(animatorScheduler.delayedBlock).isNotNull()
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync { animator.interruptForIme() }
+
+ // verify that the hide animation was canceled
+ assertThat(animatorScheduler.delayedBlock).isNull()
+ assertThat(animator.isAnimating).isFalse()
+ verify(bubbleStashController).onNewBubbleAnimationInterrupted(eq(true), any())
+
+ // PhysicsAnimatorTestUtils posts the cancellation to the main thread so we need to wait
+ // again
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ handleAnimator.assertIsNotRunning()
+ }
+
private fun setUpBubbleBar() {
bubbleBarView = BubbleBarView(context)
InstrumentationRegistry.getInstrumentation().runOnMainSync {
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelTestRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelTestRule.kt
new file mode 100644
index 0000000..ed1443d
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelTestRule.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.rules
+
+import com.android.quickstep.RecentsModel
+import com.android.quickstep.RecentsModel.RecentTasksChangedListener
+import com.android.quickstep.TaskIconCache
+import com.android.quickstep.util.GroupTask
+import java.util.function.Consumer
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+
+class MockedRecentsModelTestRule(private val context: TaskbarWindowSandboxContext) : TestRule {
+
+ private val mockIconCache: TaskIconCache = mock()
+
+ private val mockRecentsModel: RecentsModel = mock {
+ on { iconCache } doReturn mockIconCache
+
+ on { unregisterRecentTasksChangedListener() } doAnswer { recentTasksChangedListener = null }
+
+ on { registerRecentTasksChangedListener(any<RecentTasksChangedListener>()) } doAnswer
+ {
+ recentTasksChangedListener = it.getArgument<RecentTasksChangedListener>(0)
+ }
+
+ on { getTasks(anyOrNull(), anyOrNull()) } doAnswer
+ {
+ val request = it.getArgument<Consumer<List<GroupTask>>?>(0)
+ if (request != null) {
+ taskRequests.add { response -> request.accept(response) }
+ }
+ taskListId
+ }
+
+ on { getTasks(anyOrNull()) } doAnswer
+ {
+ val request = it.getArgument<Consumer<List<GroupTask>>?>(0)
+ if (request != null) {
+ taskRequests.add { response -> request.accept(response) }
+ }
+ taskListId
+ }
+
+ on { isTaskListValid(any()) } doAnswer { taskListId == it.getArgument(0) }
+ }
+
+ private var recentTasks: List<GroupTask> = emptyList()
+ private var taskListId = 0
+ private var recentTasksChangedListener: RecentTasksChangedListener? = null
+ private var taskRequests: MutableList<(List<GroupTask>) -> Unit> = mutableListOf()
+
+ override fun apply(base: Statement?, description: Description?): Statement {
+ return object : Statement() {
+ override fun evaluate() {
+ context.putObject(RecentsModel.INSTANCE, mockRecentsModel)
+ base?.evaluate()
+ }
+ }
+ }
+
+ // NOTE: For the update to take effect, `resolvePendingTaskRequests()` needs to be called, so
+ // calbacks to any pending `RecentsModel.getTasks()` get called with the updated task list.
+ fun updateRecentTasks(tasks: List<GroupTask>) {
+ ++taskListId
+ recentTasks = tasks
+ recentTasksChangedListener?.onRecentTasksChanged()
+ }
+
+ fun resolvePendingTaskRequests() {
+ val requests = mutableListOf<(List<GroupTask>) -> Unit>()
+ requests.addAll(taskRequests)
+ taskRequests.clear()
+
+ requests.forEach { it(recentTasks) }
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
index 6b95f8d..970bdec 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
@@ -17,6 +17,7 @@
package com.android.quickstep;
import static com.android.quickstep.AbsSwipeUpHandler.STATE_HANDLER_INVALIDATED;
+import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertTrue;
@@ -28,6 +29,7 @@
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -40,6 +42,9 @@
import android.graphics.Rect;
import android.os.Bundle;
import android.os.SystemClock;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.ViewTreeObserver;
@@ -58,6 +63,7 @@
import com.android.quickstep.util.ContextInitListener;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.RecentsViewContainer;
+import com.android.systemui.shared.Flags;
import com.android.systemui.shared.system.InputConsumerController;
import org.junit.Before;
@@ -103,14 +109,8 @@
/* startBounds= */ null,
/* taskInfo= */ mRunningTaskInfo,
/* allowEnterPip= */ false);
- protected final RecentsAnimationTargets mRecentsAnimationTargets = new RecentsAnimationTargets(
- new RemoteAnimationTarget[] {mRemoteAnimationTarget},
- new RemoteAnimationTarget[] {mRemoteAnimationTarget},
- new RemoteAnimationTarget[] {mRemoteAnimationTarget},
- /* homeContentInsets= */ new Rect(),
- /* minimizedHomeBounds= */ null,
- new Bundle());
+ protected RecentsAnimationTargets mRecentsAnimationTargets;
protected TaskAnimationManager mTaskAnimationManager;
protected RecentsAnimationDeviceState mRecentsAnimationDeviceState;
@@ -127,6 +127,22 @@
@Rule
public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Before
+ public void setUpAnimationTargets() {
+ Bundle extras = new Bundle();
+ extras.putBoolean(KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION, true);
+ mRecentsAnimationTargets = new RecentsAnimationTargets(
+ new RemoteAnimationTarget[] {mRemoteAnimationTarget},
+ new RemoteAnimationTarget[] {mRemoteAnimationTarget},
+ new RemoteAnimationTarget[] {mRemoteAnimationTarget},
+ /* homeContentInsets= */ new Rect(),
+ /* minimizedHomeBounds= */ null,
+ extras);
+ }
+
@Before
public void setUpRunningTaskInfo() {
mRunningTaskInfo.baseIntent = new Intent(Intent.ACTION_MAIN)
@@ -237,6 +253,30 @@
});
}
+ @EnableFlags({Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED})
+ @Test
+ public void testHomeGesture_handsOffAnimation() {
+ createSwipeUpHandlerForGesture(GestureState.GestureEndTarget.HOME);
+
+ runOnMainSync(() -> {
+ verify(mRecentsAnimationController).handOffAnimation(any(), any());
+ verifyRecentsAnimationFinishedAndCallCallback();
+ });
+ }
+
+ @DisableFlags({Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED})
+ @Test
+ public void testHomeGesture_doesNotHandOffAnimation_withFlagsDisabled() {
+ createSwipeUpHandlerForGesture(GestureState.GestureEndTarget.HOME);
+
+ runOnMainSync(() -> {
+ verify(mRecentsAnimationController, never()).handOffAnimation(any(), any());
+ verifyRecentsAnimationFinishedAndCallCallback();
+ });
+ }
+
@Test
public void testHomeGesture_invalidatesHandlerAfterParallelAnim() {
ValueAnimator parallelAnim = new ValueAnimator();
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/DesktopFullscreenDrawParamsTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/DesktopFullscreenDrawParamsTest.kt
new file mode 100644
index 0000000..e62455f
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/DesktopFullscreenDrawParamsTest.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep
+
+import android.content.Context
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+/** Test for [DesktopFullscreenDrawParams] class. */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DesktopFullscreenDrawParamsTest() {
+ private val params =
+ DesktopFullscreenDrawParams(mock<Context>(), cornerRadiusProvider = { CORNER_RADIUS })
+
+ @Test
+ fun setMiddleProgress_invariantCornerRadiusForDesktop() {
+ params.setProgress(fullscreenProgress = 0f, parentScale = 1f, taskViewScale = 1f)
+ assertThat(params.currentCornerRadius).isEqualTo(CORNER_RADIUS)
+
+ params.setProgress(fullscreenProgress = 0.67f, parentScale = 1f, taskViewScale = 1f)
+ assertThat(params.currentCornerRadius).isEqualTo(CORNER_RADIUS)
+
+ params.setProgress(fullscreenProgress = 1f, parentScale = 1f, taskViewScale = 1f)
+ assertThat(params.currentCornerRadius).isEqualTo(CORNER_RADIUS)
+ }
+
+ companion object {
+ const val CORNER_RADIUS = 32f
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/FullscreenDrawParamsTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
index 5d62a4c..99b81e0 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
@@ -20,21 +20,17 @@
import androidx.test.filters.SmallTest
import com.android.launcher3.FakeInvariantDeviceProfileTest
import com.android.quickstep.util.TaskCornerRadius
-import com.android.quickstep.views.TaskView.FullscreenDrawParams
import com.android.systemui.shared.system.QuickStepContract
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.spy
+import org.mockito.kotlin.mock
-/** Test for FullscreenDrawParams class. */
+/** Test for [FullscreenDrawParams] class. */
@SmallTest
@RunWith(AndroidJUnit4::class)
class FullscreenDrawParamsTest : FakeInvariantDeviceProfileTest() {
-
private lateinit var params: FullscreenDrawParams
@Before
@@ -46,115 +42,108 @@
fun setStartProgress_correctCornerRadiusForTablet() {
initializeVarsForTablet()
- params.setProgress(
- /* fullscreenProgress= */ 0f,
- /* parentScale= */ 1.0f,
- /* taskViewScale= */ 1.0f
- )
+ params.setProgress(fullscreenProgress = 0f, parentScale = 1.0f, taskViewScale = 1.0f)
val expectedRadius = TaskCornerRadius.get(context)
- assertThat(params.currentDrawnCornerRadius).isEqualTo(expectedRadius)
+ assertThat(params.currentCornerRadius).isEqualTo(expectedRadius)
}
@Test
fun setFullProgress_correctCornerRadiusForTablet() {
initializeVarsForTablet()
- params.setProgress(
- /* fullscreenProgress= */ 1.0f,
- /* parentScale= */ 1.0f,
- /* taskViewScale= */ 1.0f
- )
+ params.setProgress(fullscreenProgress = 1.0f, parentScale = 1f, taskViewScale = 1f)
val expectedRadius = QuickStepContract.getWindowCornerRadius(context)
- assertThat(params.currentDrawnCornerRadius).isEqualTo(expectedRadius)
+ assertThat(params.currentCornerRadius).isEqualTo(expectedRadius)
}
@Test
fun setStartProgress_correctCornerRadiusForPhone() {
initializeVarsForPhone()
- params.setProgress(
- /* fullscreenProgress= */ 0f,
- /* parentScale= */ 1.0f,
- /* taskViewScale= */ 1.0f
- )
+ params.setProgress(fullscreenProgress = 0f, parentScale = 1f, taskViewScale = 1f)
val expectedRadius = TaskCornerRadius.get(context)
- assertThat(params.currentDrawnCornerRadius).isEqualTo(expectedRadius)
+ assertThat(params.currentCornerRadius).isEqualTo(expectedRadius)
}
@Test
fun setFullProgress_correctCornerRadiusForPhone() {
initializeVarsForPhone()
- params.setProgress(
- /* fullscreenProgress= */ 1.0f,
- /* parentScale= */ 1.0f,
- /* taskViewScale= */ 1.0f
- )
+ params.setProgress(fullscreenProgress = 1.0f, parentScale = 1f, taskViewScale = 1f)
val expectedRadius = QuickStepContract.getWindowCornerRadius(context)
- assertThat(params.currentDrawnCornerRadius).isEqualTo(expectedRadius)
+ assertThat(params.currentCornerRadius).isEqualTo(expectedRadius)
}
@Test
fun setStartProgress_correctCornerRadiusForMultiDisplay() {
- val display1Context = context
- val display2Context = mock(Context::class.java)
- val spyParams = spy(params)
+ val display1Context = mock<Context>()
+ val display2Context = mock<Context>()
+ val display1TaskRadius = TASK_CORNER_RADIUS + 1
+ val display2TaskRadius = TASK_CORNER_RADIUS + 2
- val display1TaskRadius = TaskCornerRadius.get(display1Context)
- val display1WindowRadius = QuickStepContract.getWindowCornerRadius(display1Context)
- val display2TaskRadius = display1TaskRadius * 2 + 1 // Arbitrarily different.
- val display2WindowRadius = display1WindowRadius * 2 + 1 // Arbitrarily different.
- doReturn(display2TaskRadius).`when`(spyParams).computeTaskCornerRadius(display2Context)
- doReturn(display2WindowRadius).`when`(spyParams).computeWindowCornerRadius(display2Context)
+ val params =
+ FullscreenDrawParams(
+ context,
+ taskCornerRadiusProvider = { context ->
+ when (context) {
+ display1Context -> display1TaskRadius
+ display2Context -> display2TaskRadius
+ else -> TASK_CORNER_RADIUS
+ }
+ },
+ windowCornerRadiusProvider = { 0f },
+ )
- spyParams.updateCornerRadius(display1Context)
- spyParams.setProgress(
- /* fullscreenProgress= */ 0f,
- /* parentScale= */ 1.0f,
- /* taskViewScale= */ 1.0f
- )
- assertThat(spyParams.currentDrawnCornerRadius).isEqualTo(display1TaskRadius)
+ params.setProgress(fullscreenProgress = 0f, parentScale = 1f, taskViewScale = 1f)
+ assertThat(params.currentCornerRadius).isEqualTo(TASK_CORNER_RADIUS)
- spyParams.updateCornerRadius(display2Context)
- spyParams.setProgress(
- /* fullscreenProgress= */ 0f,
- /* parentScale= */ 1.0f,
- /* taskViewScale= */ 1.0f
- )
- assertThat(spyParams.currentDrawnCornerRadius).isEqualTo(display2TaskRadius)
+ params.updateCornerRadius(display1Context)
+ params.setProgress(fullscreenProgress = 0f, parentScale = 1f, taskViewScale = 1f)
+ assertThat(params.currentCornerRadius).isEqualTo(display1TaskRadius)
+
+ params.updateCornerRadius(display2Context)
+ params.setProgress(fullscreenProgress = 0f, parentScale = 1f, taskViewScale = 1f)
+ assertThat(params.currentCornerRadius).isEqualTo(display2TaskRadius)
}
@Test
fun setFullProgress_correctCornerRadiusForMultiDisplay() {
- val display1Context = context
- val display2Context = mock(Context::class.java)
- val spyParams = spy(params)
+ val display1Context = mock<Context>()
+ val display2Context = mock<Context>()
+ val display1WindowRadius = WINDOW_CORNER_RADIUS + 1
+ val display2WindowRadius = WINDOW_CORNER_RADIUS + 2
- val display1TaskRadius = TaskCornerRadius.get(display1Context)
- val display1WindowRadius = QuickStepContract.getWindowCornerRadius(display1Context)
- val display2TaskRadius = display1TaskRadius * 2 + 1 // Arbitrarily different.
- val display2WindowRadius = display1WindowRadius * 2 + 1 // Arbitrarily different.
- doReturn(display2TaskRadius).`when`(spyParams).computeTaskCornerRadius(display2Context)
- doReturn(display2WindowRadius).`when`(spyParams).computeWindowCornerRadius(display2Context)
+ val params =
+ FullscreenDrawParams(
+ context,
+ taskCornerRadiusProvider = { 0f },
+ windowCornerRadiusProvider = { context ->
+ when (context) {
+ display1Context -> display1WindowRadius
+ display2Context -> display2WindowRadius
+ else -> WINDOW_CORNER_RADIUS
+ }
+ },
+ )
- spyParams.updateCornerRadius(display1Context)
- spyParams.setProgress(
- /* fullscreenProgress= */ 1.0f,
- /* parentScale= */ 1.0f,
- /* taskViewScale= */ 1.0f
- )
- assertThat(spyParams.currentDrawnCornerRadius).isEqualTo(display1WindowRadius)
+ params.setProgress(fullscreenProgress = 1f, parentScale = 1f, taskViewScale = 1f)
+ assertThat(params.currentCornerRadius).isEqualTo(WINDOW_CORNER_RADIUS)
- spyParams.updateCornerRadius(display2Context)
- spyParams.setProgress(
- /* fullscreenProgress= */ 1.0f,
- /* parentScale= */ 1.0f,
- /* taskViewScale= */ 1.0f,
- )
- assertThat(spyParams.currentDrawnCornerRadius).isEqualTo(display2WindowRadius)
+ params.updateCornerRadius(display1Context)
+ params.setProgress(fullscreenProgress = 1f, parentScale = 1f, taskViewScale = 1f)
+ assertThat(params.currentCornerRadius).isEqualTo(display1WindowRadius)
+
+ params.updateCornerRadius(display2Context)
+ params.setProgress(fullscreenProgress = 1f, parentScale = 1f, taskViewScale = 1f)
+ assertThat(params.currentCornerRadius).isEqualTo(display2WindowRadius)
+ }
+
+ companion object {
+ const val TASK_CORNER_RADIUS = 56f
+ const val WINDOW_CORNER_RADIUS = 32f
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt
index e3a6adf..a777bd4 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt
@@ -37,8 +37,6 @@
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
import com.android.quickstep.task.viewmodel.TaskContainerData
import com.android.quickstep.task.viewmodel.TaskThumbnailViewModelImpl
-import com.android.quickstep.task.viewmodel.TaskViewData
-import com.android.quickstep.views.TaskViewType
import com.android.systemui.shared.recents.model.Task
import com.android.systemui.shared.recents.model.ThumbnailData
import com.google.common.truth.Truth.assertThat
@@ -57,9 +55,7 @@
private val dispatcher = StandardTestDispatcher()
private val testScope = TestScope(dispatcher)
- private var taskViewType = TaskViewType.SINGLE
private val recentsViewData = RecentsViewData()
- private val taskViewData by lazy { TaskViewData(taskViewType) }
private val taskContainerData = TaskContainerData()
private val dispatcherProvider = TestDispatcherProvider(dispatcher)
private val tasksRepository = FakeTasksRepository()
@@ -69,7 +65,6 @@
private val systemUnderTest by lazy {
TaskThumbnailViewModelImpl(
recentsViewData,
- taskViewData,
taskContainerData,
dispatcherProvider,
tasksRepository,
@@ -124,40 +119,6 @@
}
@Test
- fun setRecentsFullscreenProgress_thenCornerRadiusProgressIsPassedThrough() =
- testScope.runTest {
- recentsViewData.fullscreenProgress.value = 0.5f
-
- assertThat(systemUnderTest.cornerRadiusProgress.first()).isEqualTo(0.5f)
-
- recentsViewData.fullscreenProgress.value = 0.6f
-
- assertThat(systemUnderTest.cornerRadiusProgress.first()).isEqualTo(0.6f)
- }
-
- @Test
- fun setRecentsFullscreenProgress_thenCornerRadiusProgressIsConstantForDesktop() =
- testScope.runTest {
- taskViewType = TaskViewType.DESKTOP
- recentsViewData.fullscreenProgress.value = 0.5f
-
- assertThat(systemUnderTest.cornerRadiusProgress.first()).isEqualTo(1f)
-
- recentsViewData.fullscreenProgress.value = 0.6f
-
- assertThat(systemUnderTest.cornerRadiusProgress.first()).isEqualTo(1f)
- }
-
- @Test
- fun setAncestorScales_thenScaleIsCalculated() =
- testScope.runTest {
- recentsViewData.scale.value = 0.5f
- taskViewData.scale.value = 0.6f
-
- assertThat(systemUnderTest.inheritedScale.first()).isEqualTo(0.3f)
- }
-
- @Test
fun bindRunningTaskThenStoppedTaskWithoutThumbnail_thenStateChangesToBackgroundOnly() =
testScope.runTest {
val runningTaskId = 1
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
index 72cfd92..fa81680 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -202,7 +202,7 @@
mDeviceProfile.updateInsets(mLauncherInsets);
TaskViewSimulator tvs = new TaskViewSimulator(helper.sandboxContext,
- FallbackActivityInterface.INSTANCE);
+ FallbackActivityInterface.INSTANCE, false, 0);
tvs.setDp(mDeviceProfile);
int launcherRotation = info.rotation;
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c280307..f7069a6 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -458,7 +458,7 @@
<string name="widget_resized">Widget resized to width <xliff:g id="number" example="2">%1$s</xliff:g> height <xliff:g id="number" example="1">%2$s</xliff:g></string>
<!-- Accessibility action to show quick actions menu for an icon. [CHAR_LIMIT=30] -->
- <string name="action_deep_shortcut">Shortcuts</string>
+ <string name="action_deep_shortcut">Shortcut Menu</string>
<!-- Accessibility action to dismiss a notification in the shortcuts menu for an icon. [CHAR_LIMIT=30] -->
<string name="action_dismiss_notification">Dismiss</string>
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 74dd971..8981024 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -233,6 +233,7 @@
import com.android.launcher3.util.ItemInflater;
import com.android.launcher3.util.KeyboardShortcutsDelegate;
import com.android.launcher3.util.LockedUserState;
+import com.android.launcher3.util.MSDLPlayerWrapper;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.PendingRequestArgs;
import com.android.launcher3.util.PluginManagerWrapper;
@@ -784,7 +785,7 @@
}
// When the flag oneGridSpecs is on we want to disable ALLOW_ROTATION which is replaced
// by FIXED_LANDSCAPE_MODE, ALLOW_ROTATION will only be used on Tablets afterwards.
- if (!getDeviceProfile().isTablet) {
+ if (getDeviceProfile().isPhone || getDeviceProfile().isTwoPanels) {
LauncherPrefs.get(this).put(LauncherPrefs.ALLOW_ROTATION, false);
}
getRotationHelper().setFixedLandscape(
@@ -2745,6 +2746,7 @@
mModel.dumpState(prefix, fd, writer, args);
mOverlayManager.dump(prefix, writer);
ACTIVITY_TRACKER.dump(prefix, writer);
+ MSDLPlayerWrapper.INSTANCE.get(getApplicationContext()).dump(prefix, writer);
}
/**
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index 7e3e392..fe11ee2 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -150,6 +150,7 @@
mCallback.clearSearchResult();
mInput.reset();
mInput.clearFocus();
+ mInput.hideKeyboard();
mQuery = null;
}
diff --git a/src/com/android/launcher3/model/DatabaseHelper.java b/src/com/android/launcher3/model/DatabaseHelper.java
index 8368256..ed4f492 100644
--- a/src/com/android/launcher3/model/DatabaseHelper.java
+++ b/src/com/android/launcher3/model/DatabaseHelper.java
@@ -56,6 +56,7 @@
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Locale;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.ToLongFunction;
import java.util.stream.Collectors;
@@ -79,8 +80,8 @@
private final Context mContext;
private final ToLongFunction<UserHandle> mUserSerialProvider;
private final Runnable mOnEmptyDbCreateCallback;
+ private final AtomicInteger mMaxItemId = new AtomicInteger(-1);
- private int mMaxItemId = -1;
public boolean mHotseatRestoreTableExists;
/**
@@ -97,21 +98,19 @@
protected void initIds() {
// In the case where neither onCreate nor onUpgrade gets called, we read the maxId from
// the DB here
- if (mMaxItemId == -1) {
- mMaxItemId = initializeMaxItemId(getWritableDatabase());
- }
+ mMaxItemId.compareAndSet(-1, initializeMaxItemId(getWritableDatabase()));
}
@Override
public void onCreate(SQLiteDatabase db) {
if (LOGD) Log.d(TAG, "creating new launcher database");
- mMaxItemId = 1;
+ mMaxItemId.set(1);
addTableToDb(db, getDefaultUserSerial(), false /* optional */);
// Fresh and clean launcher DB.
- mMaxItemId = initializeMaxItemId(db);
+ mMaxItemId.set(initializeMaxItemId(db));
mOnEmptyDbCreateCallback.run();
}
@@ -451,11 +450,10 @@
// after that point
@Override
public int generateNewItemId() {
- if (mMaxItemId < 0) {
+ if (mMaxItemId.get() < 0) {
throw new RuntimeException("Error: max item id was not initialized");
}
- mMaxItemId += 1;
- return mMaxItemId;
+ return mMaxItemId.incrementAndGet();
}
/**
@@ -484,7 +482,7 @@
public void checkId(ContentValues values) {
int id = values.getAsInteger(Favorites._ID);
- mMaxItemId = Math.max(id, mMaxItemId);
+ mMaxItemId.accumulateAndGet(id, Math::max);
}
private int initializeMaxItemId(SQLiteDatabase db) {
@@ -508,7 +506,7 @@
int count = loader.loadLayout(db);
// Ensure that the max ids are initialized
- mMaxItemId = initializeMaxItemId(db);
+ mMaxItemId.set(initializeMaxItemId(db));
return count;
}
diff --git a/src/com/android/launcher3/util/ContextTracker.java b/src/com/android/launcher3/util/ContextTracker.java
index c729b4b..3201bd1 100644
--- a/src/com/android/launcher3/util/ContextTracker.java
+++ b/src/com/android/launcher3/util/ContextTracker.java
@@ -35,7 +35,7 @@
private static final String TAG = "ContextTracker";
private WeakReference<CONTEXT> mCurrentContext = new WeakReference<>(null);
- private CopyOnWriteArrayList<SchedulerCallback<CONTEXT>> mCallbacks =
+ private final CopyOnWriteArrayList<SchedulerCallback<CONTEXT>> mCallbacks =
new CopyOnWriteArrayList<>();
@Nullable
@@ -81,7 +81,7 @@
public boolean handleCreate(CONTEXT context) {
mCurrentContext = new WeakReference<>(context);
- return handleCreate(context, /* alreadyOnHome= */ false);
+ return handleCreate(context, isHomeStarted(context));
}
public boolean handleNewIntent(CONTEXT context) {
diff --git a/src/com/android/launcher3/util/MSDLPlayerWrapper.java b/src/com/android/launcher3/util/MSDLPlayerWrapper.java
index aab7009..eccccc7 100644
--- a/src/com/android/launcher3/util/MSDLPlayerWrapper.java
+++ b/src/com/android/launcher3/util/MSDLPlayerWrapper.java
@@ -30,6 +30,7 @@
import com.google.android.msdl.domain.MSDLPlayer;
import com.google.android.msdl.logging.MSDLEvent;
+import java.io.PrintWriter;
import java.util.List;
import javax.inject.Inject;
@@ -65,4 +66,13 @@
public List<MSDLEvent> getHistory() {
return mMSDLPlayer.getHistory();
}
+
+ /** Print the latest history of MSDL tokens played */
+ public void dump(String prefix, PrintWriter writer) {
+ writer.println(prefix + "MSDLPlayerWrapper history of latest events:");
+ List<MSDLEvent> events = getHistory();
+ for (MSDLEvent event: events) {
+ writer.println(prefix + "\t" + event);
+ }
+ }
}
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index 1ddd453..68e493d 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -183,6 +183,7 @@
</activity>
<activity-alias android:name="Activity2"
android:label="TestActivity2"
+ android:icon="@drawable/test_icon"
android:exported="true"
android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
<intent-filter>
@@ -192,6 +193,7 @@
</activity-alias>
<activity-alias android:name="Activity3"
android:label="TestActivity3"
+ android:icon="@drawable/test_icon"
android:exported="true"
android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
<intent-filter>
@@ -201,6 +203,7 @@
</activity-alias>
<activity-alias android:name="Activity4"
android:label="TestActivity4"
+ android:icon="@drawable/test_icon"
android:exported="true"
android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
<intent-filter>
@@ -210,6 +213,7 @@
</activity-alias>
<activity-alias android:name="Activity5"
android:label="TestActivity5"
+ android:icon="@drawable/test_icon"
android:exported="true"
android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
<intent-filter>
@@ -219,6 +223,7 @@
</activity-alias>
<activity-alias android:name="Activity6"
android:label="TestActivity6"
+ android:icon="@drawable/test_icon"
android:exported="true"
android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
<intent-filter>
@@ -228,6 +233,7 @@
</activity-alias>
<activity-alias android:name="Activity7"
android:label="TestActivity7"
+ android:icon="@drawable/test_icon"
android:exported="true"
android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
<intent-filter>
@@ -237,6 +243,7 @@
</activity-alias>
<activity-alias android:name="Activity8"
android:label="TestActivity8"
+ android:icon="@drawable/test_icon"
android:exported="true"
android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
<intent-filter>
@@ -246,6 +253,7 @@
</activity-alias>
<activity-alias android:name="Activity9" android:exported="true"
android:label="TestActivity9"
+ android:icon="@drawable/test_icon"
android:targetActivity="com.android.launcher3.testcomponent.OtherBaseTestingActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -254,6 +262,7 @@
</activity-alias>
<activity-alias android:name="Activity10" android:exported="true"
android:label="TestActivity10"
+ android:icon="@drawable/test_icon"
android:targetActivity="com.android.launcher3.testcomponent.OtherBaseTestingActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -262,6 +271,7 @@
</activity-alias>
<activity-alias android:name="Activity11" android:exported="true"
android:label="TestActivity11"
+ android:icon="@drawable/test_icon"
android:targetActivity="com.android.launcher3.testcomponent.OtherBaseTestingActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -270,6 +280,7 @@
</activity-alias>
<activity-alias android:name="Activity12" android:exported="true"
android:label="TestActivity12"
+ android:icon="@drawable/test_icon"
android:targetActivity="com.android.launcher3.testcomponent.OtherBaseTestingActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -278,6 +289,7 @@
</activity-alias>
<activity-alias android:name="Activity13" android:exported="true"
android:label="TestActivity13"
+ android:icon="@drawable/test_icon"
android:targetActivity="com.android.launcher3.testcomponent.OtherBaseTestingActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -286,6 +298,7 @@
</activity-alias>
<activity-alias android:name="Activity14" android:exported="true"
android:label="TestActivity14"
+ android:icon="@drawable/test_icon"
android:targetActivity="com.android.launcher3.testcomponent.OtherBaseTestingActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -363,7 +376,7 @@
</activity>
<activity android:name="com.android.launcher3.testcomponent.ImeTestActivity"
android:label="ImeTestActivity"
- android:icon="@drawable/test_theme_icon"
+ android:icon="@drawable/test_icon"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt
index 7cd5da4..e8f778f 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt
@@ -18,6 +18,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.LauncherAppState
+import com.android.launcher3.icons.BitmapInfo
import com.android.launcher3.icons.waitForUpdateHandlerToFinish
import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.util.Executors
@@ -160,6 +161,9 @@
assertWithMessage("Index $index was not highRes")
.that(items[index].bitmap.isNullOrLowRes)
.isFalse()
+ assertWithMessage("Index $index was the default icon")
+ .that(isDefaultIcon(items[index].bitmap))
+ .isFalse()
}
}
@@ -168,9 +172,17 @@
assertWithMessage("Index $index was not lowRes")
.that(items[index].bitmap.isNullOrLowRes)
.isTrue()
+ assertWithMessage("Index $index was the default icon")
+ .that(isDefaultIcon(items[index].bitmap))
+ .isFalse()
}
}
+ private fun isDefaultIcon(bitmap: BitmapInfo) =
+ LauncherAppState.getInstance(modelHelper.sandboxContext)
+ .iconCache
+ .isDefaultIcon(bitmap, modelHelper.sandboxContext.user)
+
/** Recreate DeviceProfiles after changing InvariantDeviceProfile */
private fun recreateSupportedDeviceProfiles() {
LauncherAppState.getIDP(modelHelper.sandboxContext).supportedProfiles =
diff --git a/tests/res/drawable/test_icon.xml b/tests/res/drawable/test_icon.xml
new file mode 100644
index 0000000..72ebfeb
--- /dev/null
+++ b/tests/res/drawable/test_icon.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2024 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.
+-->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@android:color/white"/>
+ <foreground>
+ <color android:color="#FFFF0000" />
+ </foreground>
+ <monochrome>
+ <vector android:width="48dp" android:height="48dp" android:viewportWidth="48.0" android:viewportHeight="48.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M0,24L48,24 48,48, 0,48 Z"/>
+ </vector>
+ </monochrome>
+</adaptive-icon>