Merge "Use the Coreographer's frame time for a more reliable timestamp." into main
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index e624be7..7d193aa 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -19,6 +19,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.role.RoleManager.ROLE_HOME;
import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
@@ -74,6 +75,7 @@
import android.animation.ValueAnimator;
import android.app.ActivityOptions;
import android.app.WindowConfiguration;
+import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
@@ -1194,7 +1196,9 @@
.registerRemoteTransition(mLauncherOpenTransition, homeCheck);
if (mBackAnimationController != null) {
mBackAnimationController.registerComponentCallbacks();
- mBackAnimationController.registerBackCallbacks(mHandler);
+ if (isHomeRoleHeld()) {
+ mBackAnimationController.registerBackCallbacks(mHandler);
+ }
}
}
@@ -1207,6 +1211,22 @@
.unregisterContentObserver(mAnimationRemovalObserver));
}
+ /**
+ * Called when the overview-target changes. Updates the back callback registration state.
+ */
+ public void onOverviewTargetChange() {
+ if (isHomeRoleHeld()) {
+ mBackAnimationController.registerBackCallbacks(mHandler);
+ } else {
+ mBackAnimationController.unregisterBackCallbacks();
+ }
+ }
+
+ private boolean isHomeRoleHeld() {
+ RoleManager roleManager = mLauncher.getSystemService(RoleManager.class);
+ return roleManager == null || roleManager.isRoleHeld(ROLE_HOME);
+ }
+
private void unregisterRemoteAnimations() {
if (SEPARATE_RECENTS_ACTIVITY.get()) {
return;
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..0a53bd3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/ManageWindowsTaskbarShortcut.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/ManageWindowsTaskbarShortcut.kt
@@ -34,7 +34,7 @@
import com.android.quickstep.SystemUiProxy
import com.android.quickstep.util.GroupTask
import com.android.systemui.shared.recents.model.ThumbnailData
-import com.android.wm.shell.shared.desktopmode.ManageWindowsViewContainer
+import com.android.wm.shell.shared.multiinstance.ManageWindowsViewContainer
import java.util.Collections
import java.util.function.Predicate
@@ -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 f9e7cf0..f346e19 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -66,6 +66,7 @@
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.Region.Op;
import android.graphics.drawable.Drawable;
@@ -75,6 +76,7 @@
import android.os.Handler;
import android.util.Property;
import android.view.Gravity;
+import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
@@ -862,15 +864,21 @@
private void setBackButtonTouchListener(View buttonView,
TaskbarNavButtonController navButtonController) {
+ final RectF rect = new RectF();
buttonView.setOnTouchListener((v, event) -> {
- if (event.getAction() == MotionEvent.ACTION_MOVE) return false;
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ rect.set(0, 0, v.getWidth(), v.getHeight());
+ }
+ boolean isCancelled = event.getAction() == MotionEvent.ACTION_CANCEL
+ || !rect.contains(event.getX(), event.getY());
+ if (event.getAction() == MotionEvent.ACTION_MOVE && !isCancelled) return false;
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) {
+ if (motionEventAction == MotionEvent.ACTION_UP && !isCancelled) {
buttonView.performClick();
+ buttonView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
}
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..4498fea 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,12 @@
private final Context mContext;
private final @Nullable Context mNavigationBarPanelContext;
private WindowManager mWindowManager;
- private FrameLayout mTaskbarRootLayout;
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 +129,10 @@
// 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<>();
+ /** DisplayId - {@link FrameLayout} map for Connected Display. */
+ private final SparseArray<FrameLayout> mRootLayouts = new SparseArray<>();
private StatefulActivity mActivity;
private RecentsViewContainer mRecentsViewContainer;
@@ -167,7 +169,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 +184,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 +241,28 @@
mDesktopVisibilityController = desktopVisibilityController;
if (enableTaskbarNoRecreate()) {
mWindowManager = mContext.getSystemService(WindowManager.class);
- mTaskbarRootLayout = new FrameLayout(mContext) {
+ FrameLayout taskbarRootLayout = 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);
}
};
+ addTaskbarRootLayoutToMap(getDefaultDisplayId(), taskbarRootLayout);
}
- 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 +271,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 +288,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 +302,10 @@
// block above?
recreateTaskbar();
} else {
- mTaskbarActivityContext.updateDeviceProfile(dp);
+ getCurrentActivityContext().updateDeviceProfile(dp);
}
}
- mTaskbarActivityContext.onConfigurationChanged(configDiff);
+ getCurrentActivityContext().onConfigurationChanged(configDiff);
}
}
mOldConfig = new Configuration(newConfig);
@@ -315,7 +322,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,18 +338,31 @@
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);
+ removeTaskbarRootViewFromWindow(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;
if (dp == null || !isTaskbarEnabled(dp)) {
- removeTaskbarRootViewFromWindow();
+ removeTaskbarRootViewFromWindow(displayId);
}
}
@@ -350,8 +370,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 +381,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 +398,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 +409,7 @@
mUserUnlocked = true;
DisplayController.INSTANCE.get(mContext).addChangeListener(mRecreationListener);
recreateTaskbar();
- addTaskbarRootViewToWindow();
+ addTaskbarRootViewToWindow(getDefaultDisplayId());
}
/**
@@ -429,8 +452,9 @@
mActivityOnDestroyCallback.run();
}
mRecentsViewContainer = recentsViewContainer;
- if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.setUIController(
+ TaskbarActivityContext taskbar = getCurrentActivityContext();
+ if (taskbar != null) {
+ taskbar.setUIController(
createTaskbarUIControllerForRecentsViewContainer(mRecentsViewContainer));
}
}
@@ -472,11 +496,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 +520,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 +535,32 @@
}
}
- 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);
+ FrameLayout taskbarRootLayout = getTaskbarRootLayoutForDisplay(displayId);
+ taskbarRootLayout.removeAllViews();
+ taskbarRootLayout.addView(taskbar.getDragLayer());
+ taskbar.notifyUpdateLayoutParams();
}
+ addTaskbarToMap(displayId, taskbar);
} finally {
Trace.endSection();
}
@@ -535,14 +572,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 +589,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 +644,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 +654,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,8 +717,7 @@
debugWhyTaskbarNotDestroyed("TaskbarManager#destroy()");
removeActivityCallbacksAndListeners();
mTaskbarBroadcastReceiver.unregisterReceiverSafely(mContext);
- destroyExistingTaskbar();
- removeTaskbarRootViewFromWindow();
+ destroyAllTaskbars();
if (mUserUnlocked) {
DisplayController.INSTANCE.get(mContext).removeChangeListener(mRecreationListener);
}
@@ -676,38 +726,112 @@
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(getTaskbarRootLayoutForDisplay(displayId),
+ taskbar.getWindowLayoutParams());
mAddedWindow = true;
}
}
- private void removeTaskbarRootViewFromWindow() {
- if (enableTaskbarNoRecreate() && mAddedWindow) {
- mWindowManager.removeViewImmediate(mTaskbarRootLayout);
+ private void removeTaskbarRootViewFromWindow(int displayId) {
+ FrameLayout rootLayout = getTaskbarRootLayoutForDisplay(displayId);
+ if (enableTaskbarNoRecreate() && mAddedWindow && rootLayout != null) {
+ mWindowManager.removeViewImmediate(rootLayout);
mAddedWindow = false;
+ removeTaskbarRootLayoutFromMap(displayId);
}
}
+ /**
+ * 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);
+ }
+
+ /**
+ * Adds the {@link TaskbarActivityContext} associated with the given display ID to taskbar
+ * map if there is not already a taskbar mapped to that displayId.
+ *
+ * @param displayId The ID of the display to retrieve the taskbar for.
+ * @param newTaskbar The new {@link TaskbarActivityContext} to add to the map.
+ */
+ private void addTaskbarToMap(int displayId, TaskbarActivityContext newTaskbar) {
+ if (!mTaskbars.contains(displayId)) {
+ mTaskbars.put(displayId, newTaskbar);
+ }
+ }
+
+ /**
+ * Removes the taskbar associated with the given display ID from the taskbar map.
+ *
+ * @param displayId The ID of the display for which to remove the taskbar.
+ */
+ private void removeTaskbarFromMap(int displayId) {
+ mTaskbars.delete(displayId);
+ }
+
+ /**
+ * Retrieves the root layout of the taskbar for the specified display.
+ *
+ * @param displayId The ID of the display for which to retrieve the taskbar root layout.
+ * @return The taskbar root layout {@link FrameLayout} for a given display or {@code null}.
+ */
+ private FrameLayout getTaskbarRootLayoutForDisplay(int displayId) {
+ return mRootLayouts.get(displayId);
+ }
+
+ /**
+ * Adds the taskbar root layout {@link FrameLayout} to taskbar map, mapped to display ID.
+ *
+ * @param displayId The ID of the display to associate with the taskbar root layout.
+ * @param rootLayout The taskbar root layout {@link FrameLayout} to add to the map.
+ */
+ private void addTaskbarRootLayoutToMap(int displayId, FrameLayout rootLayout) {
+ if (!mRootLayouts.contains(displayId)) {
+ mRootLayouts.put(displayId, rootLayout);
+ }
+ }
+
+ /**
+ * Removes taskbar root layout {@link FrameLayout} for given display ID from the taskbar map.
+ *
+ * @param displayId The ID of the display for which to remove the taskbar root layout.
+ */
+ private void removeTaskbarRootLayoutFromMap(int displayId) {
+ if (mRootLayouts.contains(displayId)) {
+ mRootLayouts.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/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 3c5d71e..f2355b8 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,9 @@
// TODO: Disable touch events on QSB otherwise it can crash.
mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
+
+ mNumStaticViews = taskbarRecentsLayoutTransition() && !mActivityContext.isPhoneMode()
+ ? addStaticViews() : 0;
}
/**
@@ -249,6 +261,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 +392,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 +424,105 @@
}
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() {
+ // Index where All Apps divider would be if it is already in Taskbar.
+ final int expectedAllAppsDividerIndex =
+ mIsRtl ? getChildCount() - mNumStaticViews - 1 : mNumStaticViews;
+ if (getChildAt(expectedAllAppsDividerIndex) == mTaskbarDividerContainer
+ && getChildCount() == mNumStaticViews + 1) {
+ // Only static views with divider so remove divider.
+ removeView(mTaskbarDividerContainer);
+ } else if (getChildAt(expectedAllAppsDividerIndex) != mTaskbarDividerContainer
+ && getChildCount() >= mNumStaticViews + 1) {
+ // Static views with at least one app icon so add divider. For RTL, add it after the
+ // icon that is at the expected index.
+ addView(
+ mTaskbarDividerContainer,
+ mIsRtl ? expectedAllAppsDividerIndex + 1 : expectedAllAppsDividerIndex);
+ }
+ }
+
+ 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 +538,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 +580,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 +596,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 +612,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 +625,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 +636,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 +660,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 +675,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 +689,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. */
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/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 1f76bd1..fd08078 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -391,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/taskbar/customization/TaskbarAllAppsButtonContainer.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarAllAppsButtonContainer.kt
index 7e3b362..e552b24 100644
--- a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarAllAppsButtonContainer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarAllAppsButtonContainer.kt
@@ -26,7 +26,6 @@
import android.view.ViewConfiguration
import androidx.annotation.DimenRes
import androidx.annotation.DrawableRes
-import androidx.core.view.setPadding
import com.android.launcher3.R
import com.android.launcher3.Utilities.dpToPx
import com.android.launcher3.config.FeatureFlags.enableTaskbarPinning
@@ -68,7 +67,6 @@
)
backgroundTintList = ColorStateList.valueOf(TRANSPARENT)
setIconDrawable(drawable)
- setPadding(dpToPx(activityContext.taskbarSpecsEvaluator.taskbarIconPadding.toFloat()))
setForegroundTint(activityContext.getColor(R.color.all_apps_button_color))
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarDividerContainer.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarDividerContainer.kt
index 344f163..df61d8a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarDividerContainer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarDividerContainer.kt
@@ -21,7 +21,6 @@
import android.content.res.ColorStateList
import android.graphics.Color.TRANSPARENT
import android.util.AttributeSet
-import androidx.core.view.setPadding
import com.android.launcher3.R
import com.android.launcher3.Utilities.dpToPx
import com.android.launcher3.taskbar.TaskbarActivityContext
@@ -51,7 +50,6 @@
backgroundTintList = ColorStateList.valueOf(TRANSPARENT)
val drawable = resources.getDrawable(R.drawable.taskbar_divider_button)
setIconDrawable(drawable)
- setPadding(dpToPx(activityContext.taskbarSpecsEvaluator.taskbarIconPadding.toFloat()))
}
@SuppressLint("ClickableViewAccessibility")
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/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 1baba74..cc51adc 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -264,6 +264,8 @@
private boolean mIsOverlayVisible;
+ private final Runnable mOverviewTargetChangeRunnable = this::onOverviewTargetChanged;
+
public static QuickstepLauncher getLauncher(Context context) {
return fromContext(context);
}
@@ -550,6 +552,10 @@
mUnfoldTransitionProgressProvider.destroy();
}
+ TISBinder binder = mTISBindHelper.getBinder();
+ if (binder != null) {
+ binder.unregisterOverviewTargetChangeListener(mOverviewTargetChangeRunnable);
+ }
mTISBindHelper.onDestroy();
if (mLauncherUnfoldAnimationController != null) {
@@ -1025,12 +1031,20 @@
}
}
+ private void onOverviewTargetChanged() {
+ QuickstepTransitionManager transitionManager = getAppTransitionManager();
+ if (transitionManager != null) {
+ transitionManager.onOverviewTargetChange();
+ }
+ }
+
private void onTISConnected(TISBinder binder) {
TaskbarManager taskbarManager = mTISBindHelper.getTaskbarManager();
if (taskbarManager != null) {
taskbarManager.setActivity(this);
}
mTISBindHelper.setPredictiveBackToHomeInProgress(mIsPredictiveBackToHomeInProgress);
+ binder.registerOverviewTargetChangeListener(mOverviewTargetChangeRunnable);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 27790ce..a58bb9b 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -159,6 +159,7 @@
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;
@@ -785,7 +786,7 @@
&& recentsAttachedToAppWindow) {
// Only move running task if RecentsView has never been attached before, to avoid
// TaskView jumping to new position as we move the tasks.
- mRecentsView.moveRunningTaskToFront();
+ mRecentsView.moveRunningTaskToExpectedPosition();
}
mAnimationFactory.setRecentsAttachedToAppWindow(
recentsAttachedToAppWindow, animate, updateRunningTaskAlpha);
@@ -2224,8 +2225,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/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index 7abcfb8..9b56fd4 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -64,8 +64,6 @@
import com.android.launcher3.util.DisplayController;
import com.android.quickstep.fallback.FallbackRecentsView;
import com.android.quickstep.fallback.RecentsState;
-import com.android.quickstep.fallback.window.RecentsWindowManager;
-import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
import com.android.quickstep.util.TransformParams;
@@ -172,16 +170,14 @@
}
@Override
- protected boolean handleTaskAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTarget,
- @NonNull ActiveGestureLog.CompoundString failureReason) {
- if (mActiveAnimationFactory != null
- && mActiveAnimationFactory.handleHomeTaskAppeared(appearedTaskTarget)) {
+ public void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets) {
+ if (mActiveAnimationFactory != null && mActiveAnimationFactory.handleHomeTaskAppeared(
+ appearedTaskTargets)) {
mActiveAnimationFactory = null;
- failureReason.append("(FallbackSwipeHandler) should be handled as home task appeared");
- return false;
+ return;
}
- return super.handleTaskAppeared(appearedTaskTarget, failureReason);
+ super.onTasksAppeared(appearedTaskTargets);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index f3ed491..4bd9ffb 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -390,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/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index a03c0f8..ef103c4 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -37,6 +37,7 @@
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.NavigationMode;
import com.android.launcher3.util.window.CachedDisplayInfo;
+import com.android.quickstep.util.ActiveGestureProtoLogProxy;
import com.android.systemui.shared.Flags;
import java.io.PrintWriter;
@@ -111,13 +112,13 @@
mNavBarGesturalHeight);
}
- private void refreshTouchRegion(Info info, Resources newRes) {
+ private void refreshTouchRegion(Info info, Resources newRes, String reason) {
// Swipe touch regions are independent of nav mode, so we have to clear them explicitly
// here to avoid, for ex, a nav region for 2-button rotation 0 being used for 3-button mode
// It tries to cache and reuse swipe regions whenever possible based only on rotation
mResources = newRes;
mSwipeTouchRegions.clear();
- resetSwipeRegions(info);
+ resetSwipeRegions(info, reason);
}
void setNavigationMode(NavigationMode newMode, Info info, Resources newRes) {
@@ -128,7 +129,7 @@
return;
}
this.mMode = newMode;
- refreshTouchRegion(info, newRes);
+ refreshTouchRegion(info, newRes, "setNavigationMode");
}
void setGesturalHeight(int newGesturalHeight, Info info, Resources newRes) {
@@ -136,7 +137,7 @@
return;
}
mNavBarGesturalHeight = newGesturalHeight;
- refreshTouchRegion(info, newRes);
+ refreshTouchRegion(info, newRes, "setGesturalHeight");
}
/**
@@ -147,14 +148,14 @@
*
* @see #enableMultipleRegions(boolean, Info)
*/
- void createOrAddTouchRegion(Info info) {
+ void createOrAddTouchRegion(Info info, String reason) {
mCachedDisplayInfo = new CachedDisplayInfo(info.currentSize, info.rotation);
if (mQuickStepStartingRotation > QUICKSTEP_ROTATION_UNINITIALIZED
&& mCachedDisplayInfo.rotation == mQuickStepStartingRotation) {
// User already was swiping and the current screen is same rotation as the starting one
// Remove active nav bars in other rotations except for the one we started out in
- resetSwipeRegions(info);
+ resetSwipeRegions(info, reason);
return;
}
OrientationRectF region = mSwipeTouchRegions.get(mCachedDisplayInfo);
@@ -163,9 +164,9 @@
}
if (mEnableMultipleRegions) {
- mSwipeTouchRegions.put(mCachedDisplayInfo, createRegionForDisplay(info));
+ mSwipeTouchRegions.put(mCachedDisplayInfo, createRegionForDisplay(info, reason));
} else {
- resetSwipeRegions(info);
+ resetSwipeRegions(info, reason);
}
}
@@ -184,7 +185,7 @@
mActiveTouchRotation = 0;
mQuickStepStartingRotation = QUICKSTEP_ROTATION_UNINITIALIZED;
}
- resetSwipeRegions(info);
+ resetSwipeRegions(info, "enableMultipleRegions");
}
/**
@@ -198,7 +199,7 @@
*/
void setSingleActiveRegion(Info displayInfo) {
mActiveTouchRotation = displayInfo.rotation;
- resetSwipeRegions(displayInfo);
+ resetSwipeRegions(displayInfo, "setSingleActiveRegion");
}
/**
@@ -207,19 +208,21 @@
* To be called whenever we want to stop tracking more than one swipe region.
* Ok to call multiple times.
*/
- private void resetSwipeRegions(Info region) {
+ private void resetSwipeRegions(Info region, String reason) {
if (enableLog()) {
- Log.d(TAG, "clearing all regions except rotation: " + mCachedDisplayInfo.rotation);
+ Log.d(TAG, "clearing all regions except rotation: " + mCachedDisplayInfo.rotation
+ + " reason=" + reason);
}
mCachedDisplayInfo = new CachedDisplayInfo(region.currentSize, region.rotation);
OrientationRectF regionToKeep = mSwipeTouchRegions.get(mCachedDisplayInfo);
if (regionToKeep == null) {
- regionToKeep = createRegionForDisplay(region);
+ regionToKeep = createRegionForDisplay(region, reason);
}
mSwipeTouchRegions.clear();
mSwipeTouchRegions.put(mCachedDisplayInfo, regionToKeep);
updateAssistantRegions(regionToKeep);
+ updateOneHandedRegions(regionToKeep);
}
private void resetSwipeRegions() {
@@ -228,15 +231,17 @@
if (regionToKeep != null) {
mSwipeTouchRegions.put(mCachedDisplayInfo, regionToKeep);
updateAssistantRegions(regionToKeep);
+ updateOneHandedRegions(regionToKeep);
}
}
- private OrientationRectF createRegionForDisplay(Info display) {
+ private OrientationRectF createRegionForDisplay(Info display, String reason) {
if (enableLog()) {
Log.d(TAG, "creating rotation region for: " + mCachedDisplayInfo.rotation
+ " with mode: " + mMode + " displayRotation: " + display.rotation +
" displaySize: " + display.currentSize +
- " navBarHeight: " + mNavBarGesturalHeight);
+ " navBarHeight: " + mNavBarGesturalHeight +
+ " reason: " + reason);
}
Point size = display.currentSize;
@@ -264,9 +269,10 @@
orientationRectF.top = orientationRectF.bottom - touchHeight;
}
}
- // One handed gestural only active on portrait mode
- mOneHandedModeRegion.set(0, orientationRectF.bottom - mNavBarLargerGesturalHeight,
- size.x, size.y);
+ updateOneHandedRegions(orientationRectF);
+ ActiveGestureProtoLogProxy.logCreateTouchRegionForDisplay(rotation, size, orientationRectF,
+ mOneHandedModeRegion, mNavBarGesturalHeight, mNavBarLargerGesturalHeight,
+ reason);
return orientationRectF;
}
@@ -286,6 +292,12 @@
mAssistantRightRegion.left = orientationRectF.right - assistantWidth;
}
+ private void updateOneHandedRegions(OrientationRectF orientationRectF) {
+ // One handed gestural only active on portrait mode
+ mOneHandedModeRegion.set(0, orientationRectF.bottom - mNavBarLargerGesturalHeight,
+ orientationRectF.right, orientationRectF.bottom);
+ }
+
boolean touchInAssistantRegion(MotionEvent ev) {
return mAssistantLeftRegion.contains(ev.getX(), ev.getY())
|| mAssistantRightRegion.contains(ev.getX(), ev.getY());
@@ -411,9 +423,11 @@
OrientationRectF rectF = mSwipeTouchRegions.get(key);
regions.append(rectF).append(" ");
}
- pw.println(regions.toString());
+ pw.println(regions);
pw.println(" mNavBarGesturalHeight=" + mNavBarGesturalHeight);
pw.println(" mNavBarLargerGesturalHeight=" + mNavBarLargerGesturalHeight);
+ pw.println(" mAssistantLeftRegion=" + mAssistantLeftRegion);
+ pw.println(" mAssistantRightRegion=" + mAssistantRightRegion);
pw.println(" mOneHandedModeRegion=" + mOneHandedModeRegion);
}
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 145773d..055aadb 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -56,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,
@@ -132,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.
@@ -164,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() {
@@ -241,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/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
index 79abc0f..909cc35 100644
--- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -236,7 +236,8 @@
return;
}
- mOrientationTouchTransformer.createOrAddTouchRegion(mDisplayController.getInfo());
+ mOrientationTouchTransformer.createOrAddTouchRegion(mDisplayController.getInfo(),
+ "RTH.updateGestureTouchRegions");
}
/**
@@ -273,7 +274,8 @@
if (hasGestures(mMode)) {
updateGestureTouchRegions();
- mOrientationTouchTransformer.createOrAddTouchRegion(info);
+ mOrientationTouchTransformer.createOrAddTouchRegion(info,
+ "RTH.onDisplayInfoChanged");
mCurrentAppRotation = mDisplayRotation;
/* Update nav bars on the following:
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/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index d38eaf3..8edb16f 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -137,6 +137,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
+import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -163,7 +164,7 @@
private final WeakReference<TouchInteractionService> mTis;
- @Nullable private Runnable mOnOverviewTargetChangeListener = null;
+ private final Set<Runnable> mOnOverviewTargetChangeListeners = new HashSet<>();
private TISBinder(TouchInteractionService tis) {
mTis = new WeakReference<>(tis);
@@ -512,15 +513,19 @@
tis -> tis.mDeviceState.setGestureBlockingTaskId(taskId));
}
- /** Sets a listener to be run on Overview Target updates. */
- public void setOverviewTargetChangeListener(@Nullable Runnable listener) {
- mOnOverviewTargetChangeListener = listener;
+ /** Registers a listener to be run on Overview Target updates. */
+ public void registerOverviewTargetChangeListener(@NonNull Runnable listener) {
+ mOnOverviewTargetChangeListeners.add(listener);
+ }
+
+ /** Unregisters an OverviewTargetChange listener. */
+ public void unregisterOverviewTargetChangeListener(@NonNull Runnable listener) {
+ mOnOverviewTargetChangeListeners.remove(listener);
}
protected void onOverviewTargetChange() {
- if (mOnOverviewTargetChangeListener != null) {
- mOnOverviewTargetChangeListener.run();
- mOnOverviewTargetChangeListener = null;
+ for (Runnable listener : mOnOverviewTargetChangeListeners) {
+ listener.run();
}
}
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index f7f3157..3eba9c0 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -121,6 +121,8 @@
private TextView mHintView;
+ private final Runnable mOverviewTargetChangeRunnable = this::onOverviewTargetChanged;
+
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -285,7 +287,7 @@
private void onTISConnected(TISBinder binder) {
setSetupUIVisible(isResumed());
binder.setSwipeUpProxy(isResumed() ? this::createSwipeUpProxy : null);
- binder.setOverviewTargetChangeListener(binder::preloadOverviewForSUWAllSet);
+ binder.registerOverviewTargetChangeListener(mOverviewTargetChangeRunnable);
binder.preloadOverviewForSUWAllSet();
TaskbarManager taskbarManager = binder.getTaskbarManager();
if (taskbarManager != null) {
@@ -293,6 +295,14 @@
}
}
+ private void onOverviewTargetChanged() {
+ TISBinder binder = mTISBindHelper.getBinder();
+ if (binder != null) {
+ binder.preloadOverviewForSUWAllSet();
+ binder.unregisterOverviewTargetChangeListener(mOverviewTargetChangeRunnable);
+ }
+ }
+
@Override
protected void onPause() {
super.onPause();
@@ -309,7 +319,7 @@
if (binder != null) {
setSetupUIVisible(false);
binder.setSwipeUpProxy(null);
- binder.setOverviewTargetChangeListener(null);
+ binder.unregisterOverviewTargetChangeListener(mOverviewTargetChangeRunnable);
}
}
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/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index aac97bb..743fa40 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -689,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;
@@ -1563,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();
}
@@ -1773,26 +1768,17 @@
}
/**
- * Moves the running task to the front of the carousel in tablets, to minimize animation
- * required to move the running task in grid.
+ * Moves the running task to the expected position in the carousel. In tablets, this minimize
+ * animation required to move the running task into focused task position.
*/
- public void moveRunningTaskToFront() {
- if (!mContainer.getDeviceProfile().isTablet) {
- return;
- }
-
+ public void moveRunningTaskToExpectedPosition() {
TaskView runningTaskView = getRunningTaskView();
- if (runningTaskView == null) {
+ if (runningTaskView == null || mCurrentPage != indexOfChild(runningTaskView)) {
return;
}
- if (indexOfChild(runningTaskView) != mCurrentPage) {
- return;
- }
-
- int frontIndex = enableLargeDesktopWindowingTile() ? getDesktopTaskViewCount() : 0;
-
- if (mCurrentPage <= frontIndex) {
+ int runningTaskExpectedIndex = getRunningTaskExpectedIndex(runningTaskView);
+ if (mCurrentPage == runningTaskExpectedIndex) {
return;
}
@@ -1805,12 +1791,31 @@
mMovingTaskView = null;
runningTaskView.resetPersistentViewTransforms();
- addView(runningTaskView, frontIndex);
- setCurrentPage(frontIndex);
+ addView(runningTaskView, runningTaskExpectedIndex);
+ setCurrentPage(runningTaskExpectedIndex);
updateTaskSize();
}
+ private int getRunningTaskExpectedIndex(TaskView runningTaskView) {
+ if (mContainer.getDeviceProfile().isTablet) {
+ if (runningTaskView instanceof DesktopTaskView) {
+ return 0; // Desktop running task is always in front.
+ } else if (enableLargeDesktopWindowingTile()) {
+ return getDesktopTaskViewCount(); // Other running task is behind desktop tasks.
+ } else {
+ return 0;
+ }
+ } else {
+ int currentIndex = indexOfChild(runningTaskView);
+ if (currentIndex != -1) {
+ return currentIndex; // Keep the position if running task already in layout.
+ } else {
+ return 0; // New running task are added to the front to begin with.
+ }
+ }
+ }
+
@Override
protected void onScrollerAnimationAborted() {
ActiveGestureProtoLogProxy.logOnScrollerAnimationAborted();
@@ -2493,13 +2498,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());
@@ -2507,6 +2505,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
@@ -2515,21 +2514,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()) {
@@ -2962,25 +2968,20 @@
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);
+ addView(taskView, getRunningTaskExpectedIndex(taskView));
runningTaskViewId = taskView.getTaskViewId();
if (wasEmpty) {
addView(mClearAllButton);
@@ -5698,43 +5699,6 @@
updateCurrentTaskActionsVisibility();
loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
updateEnabledOverlays();
-
- if (enableRefactorTaskThumbnail()) {
- int screenStart = 0;
- int screenEnd = 0;
- int centerPageIndex = 0;
- if (showAsGrid()) {
- screenStart = getPagedOrientationHandler().getPrimaryScroll(this);
- int pageOrientedSize = getPagedOrientationHandler().getMeasuredSize(this);
- screenEnd = screenStart + pageOrientedSize;
- } else {
- centerPageIndex = getPageNearestToCenterOfScreen();
- }
-
- Set<Integer> fullyVisibleTaskIds = new HashSet<>();
-
- // Update the task data for the in/visible children
- for (int i = 0; i < getTaskViewCount(); i++) {
- TaskView taskView = requireTaskViewAt(i);
- List<TaskContainer> containers = taskView.getTaskContainers();
- if (containers.isEmpty()) {
- continue;
- }
- boolean isFullyVisible;
- if (showAsGrid()) {
- isFullyVisible = isTaskViewFullyWithinBounds(taskView, screenStart,
- screenEnd);
- } else {
- isFullyVisible = i == centerPageIndex;
- }
- if (isFullyVisible) {
- List<Integer> taskIds = containers.stream().map(
- taskContainer -> taskContainer.getTask().key.id).toList();
- fullyVisibleTaskIds.addAll(taskIds);
- }
- }
- mRecentsViewModel.updateTasksFullyVisible(fullyVisibleTaskIds);
- }
}
@Override
@@ -5876,22 +5840,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?
@@ -5923,7 +5880,7 @@
tx, null /* overlay */);
}
}
- mRecentsAnimationController.finish(toRecents, allAppTargetsAreTranslucent, () -> {
+ mRecentsAnimationController.finish(toRecents, () -> {
if (onFinishComplete != null) {
onFinishComplete.run();
}
@@ -6300,17 +6257,27 @@
}
private void updateEnabledOverlays() {
- TaskView focusedTaskView = getFocusedTaskView();
- for (TaskView taskView : getTaskViews()) {
- if (taskView == focusedTaskView) {
- continue;
+ if (enableRefactorTaskThumbnail()) {
+ Set<Integer> fullyVisibleTaskIds = new HashSet<>();
+ for (TaskView taskView : getTaskViews()) {
+ if (isTaskViewFullyVisible(taskView)) {
+ fullyVisibleTaskIds.addAll(taskView.getTaskIdSet());
+ }
}
- taskView.setOverlayEnabled(mOverlayEnabled && isTaskViewFullyVisible(taskView));
- }
- // Focus task overlay should be enabled and refreshed at last
- if (focusedTaskView != null) {
- focusedTaskView.setOverlayEnabled(
- mOverlayEnabled && isTaskViewFullyVisible(focusedTaskView));
+ mRecentsViewModel.updateTasksFullyVisible(fullyVisibleTaskIds);
+ } else {
+ TaskView focusedTaskView = getFocusedTaskView();
+ for (TaskView taskView : getTaskViews()) {
+ if (taskView == focusedTaskView) {
+ continue;
+ }
+ taskView.setOverlayEnabled(mOverlayEnabled && isTaskViewFullyVisible(taskView));
+ }
+ // Focus task overlay should be enabled and refreshed at last
+ if (focusedTaskView != null) {
+ focusedTaskView.setOverlayEnabled(
+ mOverlayEnabled && isTaskViewFullyVisible(focusedTaskView));
+ }
}
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 819ab05..7e489ea 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -147,11 +147,6 @@
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
diff --git a/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
index 0091036..f25f6f4 100644
--- a/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
+++ b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
@@ -38,6 +38,8 @@
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.START_RECENTS_ANIMATION;
import static com.android.quickstep.util.QuickstepProtoLogGroup.ACTIVE_GESTURE_LOG;
+import android.graphics.Point;
+import android.graphics.RectF;
import android.view.MotionEvent;
import androidx.annotation.NonNull;
@@ -505,4 +507,16 @@
taskId,
packageName);
}
+
+ public static void logCreateTouchRegionForDisplay(int displayRotation,
+ @NonNull Point displaySize, @NonNull RectF swipeRegion, @NonNull RectF ohmRegion,
+ int gesturalHeight, int largerGesturalHeight, @NonNull String reason) {
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG,
+ "OrientationTouchTransformer.createRegionForDisplay: "
+ + "dispRot=%d, dispSize=%s, swipeRegion=%s, ohmRegion=%s, "
+ + "gesturalHeight=%d, largerGesturalHeight=%d, reason=%s",
+ displayRotation, displaySize.flattenToString(), swipeRegion.toShortString(),
+ ohmRegion.toShortString(), gesturalHeight, largerGesturalHeight, reason);
+ }
}
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 6ebae49..c682990 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
@@ -353,7 +353,7 @@
mNavButtonController.sendBackKeyEvent(KeyEvent.ACTION_UP, false);
verify(mockSystemUiProxy, times(2)).onBackEvent(keyEventCaptor.capture());
verifyKeyEvent(keyEventCaptor.getAllValues().getFirst(), KeyEvent.ACTION_DOWN, false);
- verifyKeyEvent(keyEventCaptor.getAllValues().getFirst(), KeyEvent.ACTION_UP, false);
+ verifyKeyEvent(keyEventCaptor.getAllValues().getLast(), KeyEvent.ACTION_UP, false);
}
@Test
@@ -364,7 +364,7 @@
mNavButtonController.sendBackKeyEvent(KeyEvent.ACTION_UP, true);
verify(mockSystemUiProxy, times(2)).onBackEvent(keyEventCaptor.capture());
verifyKeyEvent(keyEventCaptor.getAllValues().getFirst(), KeyEvent.ACTION_DOWN, false);
- verifyKeyEvent(keyEventCaptor.getAllValues().getFirst(), KeyEvent.ACTION_UP, true);
+ verifyKeyEvent(keyEventCaptor.getAllValues().getLast(), KeyEvent.ACTION_UP, true);
}
@Test
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..78d8e5d
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewWithLayoutTransitionTest.kt
@@ -0,0 +1,134 @@
+/*
+ * 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_addHotseatItemWithoutRecents_updatesHotseat() {
+ runOnMainSync {
+ taskbarView.updateItems(createHotseatItems(1), emptyList())
+ taskbarView.updateItems(createHotseatItems(2), emptyList())
+ }
+ assertThat(taskbarView).hasIconTypes(*HOTSEAT * 2, DIVIDER, ALL_APPS)
+ }
+
+ @Test
+ @ForceRtl
+ fun testUpdateItems_rtl_addHotseatItemWithRecents_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/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
index f5d082d..ff0ad53 100644
--- a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
+++ b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
@@ -90,7 +90,7 @@
float landscapeRegionY =
generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_90) + 1;
- mTouchTransformer.createOrAddTouchRegion(mInfo);
+ mTouchTransformer.createOrAddTouchRegion(mInfo, "test");
tapAndAssertTrue(100, portraitRegionY,
event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
tapAndAssertFalse(100, landscapeRegionY,
@@ -102,7 +102,8 @@
// Override region
mTouchTransformer
- .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90));
+ .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90),
+ "test");
tapAndAssertFalse(100, portraitRegionY,
event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
tapAndAssertTrue(100, landscapeRegionY,
@@ -113,7 +114,7 @@
event -> mTouchTransformer.touchInAssistantRegion(event));
// Override region again
- mTouchTransformer.createOrAddTouchRegion(mInfo);
+ mTouchTransformer.createOrAddTouchRegion(mInfo, "test");
tapAndAssertTrue(100, portraitRegionY,
event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
tapAndAssertFalse(100, landscapeRegionY,
@@ -132,7 +133,8 @@
generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_90) + 1;
mTouchTransformer
- .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90));
+ .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90),
+ "test");
tapAndAssertFalse(100, portraitRegionY,
event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
tapAndAssertTrue(100, landscapeRegionY,
@@ -144,7 +146,7 @@
// We have to add 0 rotation second so that gets set as the current rotation, otherwise
// matrix transform will fail (tests only work in Portrait at the moment)
mTouchTransformer.enableMultipleRegions(true, mInfo);
- mTouchTransformer.createOrAddTouchRegion(mInfo);
+ mTouchTransformer.createOrAddTouchRegion(mInfo, "test");
tapAndAssertTrue(100, portraitRegionY,
event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
@@ -165,8 +167,9 @@
mTouchTransformer.enableMultipleRegions(true, mInfo);
mTouchTransformer
- .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90));
- mTouchTransformer.createOrAddTouchRegion(mInfo);
+ .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90),
+ "test");
+ mTouchTransformer.createOrAddTouchRegion(mInfo, "test");
tapAndAssertTrue(0, portraitRegionY,
event -> mTouchTransformer.touchInAssistantRegion(event));
tapAndAssertFalse(0, landscapeRegionY,
@@ -181,9 +184,10 @@
generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_90) + 1;
mTouchTransformer.enableMultipleRegions(true, mInfo);
- mTouchTransformer.createOrAddTouchRegion(mInfo);
+ mTouchTransformer.createOrAddTouchRegion(mInfo, "test");
mTouchTransformer
- .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90));
+ .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90),
+ "test");
mTouchTransformer.enableMultipleRegions(false, mInfo);
tapAndAssertTrue(0, portraitRegionY,
event -> mTouchTransformer.touchInAssistantRegion(event));
@@ -213,14 +217,14 @@
@Test
public void applyTransform_taskNotFrozen_notInRegion() {
- mTouchTransformer.createOrAddTouchRegion(mInfo);
+ mTouchTransformer.createOrAddTouchRegion(mInfo, "test");
tapAndAssertFalse(100, 100,
event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
}
@Test
public void applyTransform_taskFrozen_noRotate_outOfRegion() {
- mTouchTransformer.createOrAddTouchRegion(mInfo);
+ mTouchTransformer.createOrAddTouchRegion(mInfo, "test");
mTouchTransformer.enableMultipleRegions(true, mInfo);
tapAndAssertFalse(100, 100,
event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
@@ -228,7 +232,7 @@
@Test
public void applyTransform_taskFrozen_noRotate_inRegion() {
- mTouchTransformer.createOrAddTouchRegion(mInfo);
+ mTouchTransformer.createOrAddTouchRegion(mInfo, "test");
mTouchTransformer.enableMultipleRegions(true, mInfo);
float y = generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_0) + 1;
tapAndAssertTrue(100, y,
@@ -237,7 +241,7 @@
@Test
public void applyTransform_taskNotFrozen_noRotate_inDefaultRegion() {
- mTouchTransformer.createOrAddTouchRegion(mInfo);
+ mTouchTransformer.createOrAddTouchRegion(mInfo, "test");
float y = generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_0) + 1;
tapAndAssertTrue(100, y,
event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
@@ -246,7 +250,8 @@
@Test
public void applyTransform_taskNotFrozen_90Rotate_inRegion() {
mTouchTransformer
- .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90));
+ .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90),
+ "test");
float y = generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_90) + 1;
tapAndAssertTrue(100, y,
event -> mTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()));
@@ -254,10 +259,11 @@
@Test
public void applyTransform_taskNotFrozen_90Rotate_withTwoRegions() {
- mTouchTransformer.createOrAddTouchRegion(mInfo);
+ mTouchTransformer.createOrAddTouchRegion(mInfo, "test");
mTouchTransformer.enableMultipleRegions(true, mInfo);
mTouchTransformer
- .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90));
+ .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90),
+ "test");
// Landscape point
float y1 = generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_90) + 1;
MotionEvent inRegion1_down = generateMotionEvent(MotionEvent.ACTION_DOWN, 10, y1);
@@ -278,10 +284,11 @@
@Test
public void applyTransform_90Rotate_inRotatedRegion() {
// Create regions for both 0 Rotation and 90 Rotation
- mTouchTransformer.createOrAddTouchRegion(mInfo);
+ mTouchTransformer.createOrAddTouchRegion(mInfo, "test");
mTouchTransformer.enableMultipleRegions(true, mInfo);
mTouchTransformer
- .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90));
+ .createOrAddTouchRegion(createDisplayInfo(NORMAL_SCREEN_SIZE, Surface.ROTATION_90),
+ "test");
// Portrait point in landscape orientation axis
float x1 = generateTouchRegionHeight(NORMAL_SCREEN_SIZE, Surface.ROTATION_0);
// bottom of screen, from landscape perspective right side of screen
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 0d4e79b..326ee06 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -568,4 +568,8 @@
<!-- WindowManagerProxy -->
<dimen name="max_width_and_height_of_small_display_cutout">136px</dimen>
+
+ <!-- App Title Pill -->
+ <dimen name="app_title_pill_horizontal_padding">4dp</dimen>
+ <dimen name="app_title_pill_round_rect_padding">2dp</dimen>
</resources>
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/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 817cc40..ed2ab81 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -127,8 +127,6 @@
private static final int[] STATE_PRESSED = new int[]{android.R.attr.state_pressed};
- private static final int APP_PILL_TITLE_PADDING = 8;
-
private float mScaleForReorderBounce = 1f;
private IntArray mBreakPointsIntArray;
@@ -734,12 +732,18 @@
getDrawingRect(tmpRect);
CharSequence text = getText();
+ int mAppTitleHorizontalPadding = getResources().getDimensionPixelSize(
+ R.dimen.app_title_pill_horizontal_padding);
+ int mRoundRectPadding = getResources().getDimensionPixelSize(
+ R.dimen.app_title_pill_round_rect_padding);
+
float titleLength = (getPaint().measureText(text, 0, text.length())
- + APP_PILL_TITLE_PADDING * 2);
+ + (mAppTitleHorizontalPadding + mRoundRectPadding) * 2);
titleLength = Math.min(titleLength, tmpRect.width());
appTitleBounds = new RectF((tmpRect.width() - titleLength) / 2.f - getCompoundPaddingLeft(),
0, (tmpRect.width() + titleLength) / 2.f + getCompoundPaddingRight(),
(int) Math.ceil(fm.bottom - fm.top));
+ appTitleBounds.inset(mRoundRectPadding * 2, 0);
if (mIcon != null) {
@@ -859,8 +863,13 @@
getPaddingBottom());
}
if (shouldDrawAppContrastTile()) {
- setPadding(getPaddingLeft() + APP_PILL_TITLE_PADDING, getPaddingTop(),
- getPaddingRight() + APP_PILL_TITLE_PADDING,
+ int mAppTitleHorizontalPadding = getResources().getDimensionPixelSize(
+ R.dimen.app_title_pill_horizontal_padding);
+ int mRoundRectPadding = getResources().getDimensionPixelSize(
+ R.dimen.app_title_pill_round_rect_padding);
+
+ setPadding(mAppTitleHorizontalPadding + mRoundRectPadding, getPaddingTop(),
+ mAppTitleHorizontalPadding + mRoundRectPadding,
getPaddingBottom());
}
// Only apply two line for all_apps and device search only if necessary.
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 867bf98..18619f5 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -65,7 +65,7 @@
protected final ActivityContext mActivityContext;
protected final DropTargetHandler mDropTargetHandler;
protected DropTargetBar mDropTargetBar;
- private final MSDLPlayerWrapper mMSDLPlayerWrapper;
+ private MSDLPlayerWrapper mMSDLPlayerWrapper;
/** Whether this drop target is active for the current drag */
protected boolean mActive;
@@ -438,6 +438,11 @@
return textHeight + getPaddingTop() + getPaddingBottom() >= availableHeight;
}
+ @VisibleForTesting
+ public void setMSDLPlayerWrapper(MSDLPlayerWrapper wrapper) {
+ mMSDLPlayerWrapper = wrapper;
+ }
+
/**
* Reduce the size of the text until it fits the measured width or reaches a minimum.
*
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 58789fd..425f277 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -130,7 +130,6 @@
public void completeDrop(DragObject d) {
ItemInfo item = d.dragInfo;
if (canRemove(item)) {
- onAccessibilityDrop(null, item);
mDropTargetHandler.onDeleteComplete(item);
}
}
diff --git a/src/com/android/launcher3/DropTargetHandler.kt b/src/com/android/launcher3/DropTargetHandler.kt
index f1029b1..4d3fe52 100644
--- a/src/com/android/launcher3/DropTargetHandler.kt
+++ b/src/com/android/launcher3/DropTargetHandler.kt
@@ -65,6 +65,7 @@
}
fun onDeleteComplete(item: ItemInfo) {
+ removeItemAndStripEmptyScreens(null /* view */, item)
var pageItem: ItemInfo = item
if (item.container <= 0) {
val v = mLauncher.workspace.getHomescreenIconByItemId(item.container)
@@ -90,11 +91,7 @@
}
fun onAccessibilityDelete(view: View?, item: ItemInfo, announcement: CharSequence) {
- // Remove the item from launcher and the db, we can ignore the containerInfo in this call
- // because we already remove the drag view from the folder (if the drag originated from
- // a folder) in Folder.beginDrag()
- mLauncher.removeItem(view, item, true /* deleteFromDb */, "removed by accessibility drop")
- mLauncher.workspace.stripEmptyScreens()
+ removeItemAndStripEmptyScreens(view, item)
mLauncher.dragLayer.announceForAccessibility(announcement)
}
@@ -105,4 +102,12 @@
fun onClick(buttonDropTarget: ButtonDropTarget) {
mLauncher.accessibilityDelegate.handleAccessibleDrop(buttonDropTarget, null, null)
}
+
+ private fun removeItemAndStripEmptyScreens(view: View?, item: ItemInfo) {
+ // Remove the item from launcher and the db, we can ignore the containerInfo in this call
+ // because we already remove the drag view from the folder (if the drag originated from
+ // a folder) in Folder.beginDrag()
+ mLauncher.removeItem(view, item, true /* deleteFromDb */, "removed by accessibility drop")
+ mLauncher.workspace.stripEmptyScreens()
+ }
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index f61d3f0..8981024 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -785,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(
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/GridSizeMigrationLogic.kt b/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
index 3f52d8a..75fd31e 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
+++ b/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
@@ -58,12 +58,17 @@
}
val isFirstLoad = get(context).get(LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE)
- Log.d(TAG, "Begin grid migration. First load: $isFirstLoad")
+ Log.d(
+ TAG,
+ "Begin grid migration. First load: $isFirstLoad\n srcDeviceState: " +
+ "$srcDeviceState\ndestDeviceState: $destDeviceState\nisDestNewDb: $isDestNewDb",
+ )
// This is a special case where if the grid is the same amount of columns but a larger
// amount of rows we simply copy over the source grid to the destination grid, rather
// than undergoing the general grid migration.
if (shouldMigrateToStrictlyTallerGrid(isDestNewDb, srcDeviceState, destDeviceState)) {
+ Log.d(TAG, "Migrating to strictly taller grid")
copyTable(source, TABLE_NAME, target.writableDatabase, TABLE_NAME, context)
if (oneGridSpecs()) {
val destReader = DbReader(target.writableDatabase, TABLE_NAME, context)
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 eccccc7..8a1d923 100644
--- a/src/com/android/launcher3/util/MSDLPlayerWrapper.java
+++ b/src/com/android/launcher3/util/MSDLPlayerWrapper.java
@@ -69,6 +69,7 @@
/** Print the latest history of MSDL tokens played */
public void dump(String prefix, PrintWriter writer) {
+ writer.println(prefix + mMSDLPlayer.toString());
writer.println(prefix + "MSDLPlayerWrapper history of latest events:");
List<MSDLEvent> events = getHistory();
for (MSDLEvent event: events) {
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/DeleteDropTargetTest.kt b/tests/multivalentTests/src/com/android/launcher3/DeleteDropTargetTest.kt
index 42374a5..fa368e5 100644
--- a/tests/multivalentTests/src/com/android/launcher3/DeleteDropTargetTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/DeleteDropTargetTest.kt
@@ -16,7 +16,12 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyNoMoreInteractions
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -24,6 +29,8 @@
@get:Rule val mSetFlagsRule = SetFlagsRule()
+ @Mock private val msdlPlayerWrapper = mock<MSDLPlayerWrapper>()
+
private var mContext: Context = ActivityContextWrapper(getApplicationContext())
// Use a non-abstract class implementation
@@ -50,13 +57,12 @@
@Test
@EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
fun onDragEnter_performsMSDLSwipeThresholdFeedback() {
+ buttonDropTarget.setMSDLPlayerWrapper(msdlPlayerWrapper)
val target = DropTarget.DragObject(mContext)
target.dragView = mock<DragView<*>>()
buttonDropTarget.onDragEnter(target)
- val wrapper = MSDLPlayerWrapper.INSTANCE.get(mContext)
- val history = wrapper.history
- assertThat(history.size).isEqualTo(1)
- assertThat(history[0].tokenName).isEqualTo(MSDLToken.SWIPE_THRESHOLD_INDICATOR.name)
+ verify(msdlPlayerWrapper, times(1)).playToken(eq(MSDLToken.SWIPE_THRESHOLD_INDICATOR))
+ verifyNoMoreInteractions(msdlPlayerWrapper)
}
}
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>