Merge "Import translations. DO NOT MERGE ANYWHERE" into main
diff --git a/aconfig/launcher_growth.aconfig b/aconfig/launcher_growth.aconfig
new file mode 100644
index 0000000..35a91d7
--- /dev/null
+++ b/aconfig/launcher_growth.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.launcher3"
+container: "system"
+
+flag {
+ name: "enable_growth_nudge"
+ namespace: "desktop_oobe"
+ description: "Add growth nudge in launcher"
+ bug: "396165728"
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 5155ffc..943c44e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -16,10 +16,12 @@
package com.android.launcher3.taskbar;
import static android.content.Context.RECEIVER_NOT_EXPORTED;
+import static android.content.Context.RECEIVER_EXPORTED;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static com.android.launcher3.BaseActivity.EVENT_DESTROYED;
+import static com.android.launcher3.Flags.enableGrowthNudge;
import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
import static com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate;
@@ -28,9 +30,9 @@
import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
import static com.android.launcher3.util.DisplayController.CHANGE_SHOW_LOCKED_TASKBAR;
import static com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING;
-import static com.android.launcher3.util.DisplayController.TASKBAR_NOT_DESTROYED_TAG;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
+import static com.android.launcher3.taskbar.growth.GrowthConstants.BROADCAST_SHOW_NUDGE;
import static com.android.quickstep.util.SystemActionConstants.ACTION_SHOW_TASKBAR;
import static com.android.quickstep.util.SystemActionConstants.SYSTEM_ACTION_ID_TASKBAR;
@@ -62,6 +64,7 @@
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.anim.AnimatorListeners;
@@ -102,11 +105,11 @@
public class TaskbarManager {
private static final String TAG = "TaskbarManager";
private static final boolean DEBUG = false;
- // TODO(b/382378283) remove all logs with this tag
- public static final String NULL_TASKBAR_ROOT_LAYOUT_TAG = "b/382378283";
- public static final String ILLEGAL_ARGUMENT_WM_ADD_VIEW = "b/391653300";
private static final int TASKBAR_DESTROY_DURATION = 100;
+ // TODO: b/397738606 - Remove all logs with this tag after the growth framework is integrated.
+ public static final String GROWTH_FRAMEWORK_TAG = "Growth Framework";
+
/**
* All the configurations which do not initiate taskbar recreation.
* This includes all the configurations defined in Launcher's manifest entry and
@@ -130,10 +133,10 @@
Settings.Secure.NAV_BAR_KIDS_MODE);
private final Context mBaseContext;
- private TaskbarNavButtonCallbacks mNavCallbacks;
+ private final TaskbarNavButtonCallbacks mNavCallbacks;
// TODO: Remove this during the connected displays lifecycle refactor.
private final Context mPrimaryWindowContext;
- private WindowManager mPrimaryWindowManager;
+ private final WindowManager mPrimaryWindowManager;
private TaskbarNavButtonController mPrimaryNavButtonController;
private ComponentCallbacks mPrimaryComponentCallbacks;
@@ -158,6 +161,8 @@
new SparseArray<>();
/** DisplayId - {@link ComponentCallbacks} map for Connected Display. */
private final SparseArray<ComponentCallbacks> mComponentCallbacks = new SparseArray<>();
+ /** DisplayId - {@link DeviceProfile} map for Connected Display. */
+ private final SparseArray<DeviceProfile> mExternalDeviceProfiles = new SparseArray<>();
private StatefulActivity mActivity;
private RecentsViewContainer mRecentsViewContainer;
@@ -177,48 +182,47 @@
@Override
public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
if ((flags & CHANGE_DENSITY) != 0) {
- debugTaskbarManager("onDisplayInfoChanged - Display density changed",
+ debugTaskbarManager("onDisplayInfoChanged: Display density changed",
context.getDisplayId());
}
if ((flags & CHANGE_NAVIGATION_MODE) != 0) {
- debugTaskbarManager("onDisplayInfoChanged - Navigation mode changed",
+ debugTaskbarManager("onDisplayInfoChanged: Navigation mode changed",
context.getDisplayId());
}
if ((flags & CHANGE_DESKTOP_MODE) != 0) {
- debugTaskbarManager("onDisplayInfoChanged - Desktop mode changed",
+ debugTaskbarManager("onDisplayInfoChanged: Desktop mode changed",
context.getDisplayId());
}
if ((flags & CHANGE_TASKBAR_PINNING) != 0) {
- debugTaskbarManager("onDisplayInfoChanged - Taskbar pinning changed",
+ debugTaskbarManager("onDisplayInfoChanged: Taskbar pinning changed",
context.getDisplayId());
}
if ((flags & (CHANGE_DENSITY | CHANGE_NAVIGATION_MODE | CHANGE_DESKTOP_MODE
| CHANGE_TASKBAR_PINNING | CHANGE_SHOW_LOCKED_TASKBAR)) != 0) {
- debugTaskbarManager("onDisplayInfoChanged - Recreating Taskbar!",
+ debugTaskbarManager("onDisplayInfoChanged: Recreating Taskbar!",
context.getDisplayId());
TaskbarActivityContext taskbarActivityContext = getCurrentActivityContext();
if ((flags & CHANGE_SHOW_LOCKED_TASKBAR) != 0) {
- recreateTaskbar();
+ recreateTaskbars();
} else if ((flags & CHANGE_DESKTOP_MODE) != 0) {
// Only Handles Special Exit Cases for Desktop Mode Taskbar Recreation.
if (taskbarActivityContext != null
&& !DesktopVisibilityController.INSTANCE.get(taskbarActivityContext)
.isInDesktopMode()
&& !DisplayController.showLockedTaskbarOnHome(context)) {
- recreateTaskbar();
+ recreateTaskbars();
}
} else {
- recreateTaskbar();
+ recreateTaskbars();
}
-
}
}
}
private final SettingsCache.OnChangeListener mOnSettingsChangeListener = c -> {
- debugTaskbarManager("Settings changed! Recreating Taskbar!");
- recreateTaskbar();
+ debugPrimaryTaskbar("Settings changed! Recreating Taskbar!");
+ recreateTaskbars();
};
private final PerceptibleTaskListener mTaskStackListener;
@@ -280,8 +284,6 @@
}
}
- ;
-
private final DesktopVisibilityController.TaskbarDesktopModeListener
mTaskbarDesktopModeListener =
new DesktopVisibilityController.TaskbarDesktopModeListener() {
@@ -329,6 +331,8 @@
private final SimpleBroadcastReceiver mTaskbarBroadcastReceiver;
+ private final SimpleBroadcastReceiver mGrowthBroadcastReceiver;
+
private final AllAppsActionManager mAllAppsActionManager;
private final RecentsDisplayModel mRecentsDisplayModel;
@@ -336,14 +340,12 @@
@Override
public void run() {
int displayId = getDefaultDisplayId();
- debugTaskbarManager("mActivityOnDestroyCallback running!", displayId);
+ debugTaskbarManager("onActivityDestroyed:", displayId);
if (mActivity != null) {
displayId = mActivity.getDisplayId();
mActivity.removeOnDeviceProfileChangeListener(
mDebugActivityDeviceProfileChanged);
- Log.d(TASKBAR_NOT_DESTROYED_TAG,
- "unregistering activity lifecycle callbacks from "
- + "onActivityDestroyed.");
+ debugTaskbarManager("onActivityDestroyed: unregistering callbacks", displayId);
mActivity.removeEventCallback(EVENT_DESTROYED, this);
}
if (mActivity == mRecentsViewContainer) {
@@ -352,7 +354,10 @@
mActivity = null;
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
if (taskbar != null) {
+ debugTaskbarManager("onActivityDestroyed: setting taskbarUIController", displayId);
taskbar.setUIController(TaskbarUIController.DEFAULT);
+ } else {
+ debugTaskbarManager("onActivityDestroyed: taskbar is null!", displayId);
}
mUnfoldProgressProvider.setSourceProvider(null);
}
@@ -362,26 +367,26 @@
new UnfoldTransitionProgressProvider.TransitionProgressListener() {
@Override
public void onTransitionStarted() {
- debugTaskbarManager("fold/unfold transition started getting called.");
+ debugPrimaryTaskbar("fold/unfold transition started getting called.");
}
@Override
public void onTransitionProgress(float progress) {
- debugTaskbarManager(
+ debugPrimaryTaskbar(
"fold/unfold transition progress getting called. | progress="
+ progress);
}
@Override
public void onTransitionFinishing() {
- debugTaskbarManager(
+ debugPrimaryTaskbar(
"fold/unfold transition finishing getting called.");
}
@Override
public void onTransitionFinished() {
- debugTaskbarManager(
+ debugPrimaryTaskbar(
"fold/unfold transition finished getting called.");
}
};
@@ -399,7 +404,7 @@
// Set up primary display.
int primaryDisplayId = getDefaultDisplayId();
- debugTaskbarManager("TaskbarManager constructor", primaryDisplayId);
+ debugPrimaryTaskbar("TaskbarManager constructor");
mPrimaryWindowContext = createWindowContext(primaryDisplayId);
mPrimaryWindowManager = mPrimaryWindowContext.getSystemService(WindowManager.class);
DesktopVisibilityController.INSTANCE.get(
@@ -419,7 +424,18 @@
mTaskbarBroadcastReceiver =
new SimpleBroadcastReceiver(mPrimaryWindowContext,
UI_HELPER_EXECUTOR, this::showTaskbarFromBroadcast);
+
mShutdownReceiver.register(Intent.ACTION_SHUTDOWN);
+ if (enableGrowthNudge()) {
+ // TODO: b/397739323 - Add permission to limit access to Growth Framework.
+ mGrowthBroadcastReceiver =
+ new SimpleBroadcastReceiver(
+ mPrimaryWindowContext, UI_HELPER_EXECUTOR, this::showGrowthNudge);
+ mGrowthBroadcastReceiver.register(RECEIVER_EXPORTED,
+ BROADCAST_SHOW_NUDGE);
+ } else {
+ mGrowthBroadcastReceiver = null;
+ }
UI_HELPER_EXECUTOR.execute(() -> {
mSharedState.taskbarSystemActionPendingIntent = PendingIntent.getBroadcast(
mPrimaryWindowContext,
@@ -436,30 +452,35 @@
} else {
mTaskStackListener = null;
}
- debugTaskbarManager("TaskbarManager created");
- recreateTaskbar();
+ recreateTaskbars();
+ debugPrimaryTaskbar("TaskbarManager created");
}
private void destroyAllTaskbars() {
+ debugPrimaryTaskbar("destroyAllTaskbars");
for (int i = 0; i < mTaskbars.size(); i++) {
int displayId = mTaskbars.keyAt(i);
+ debugTaskbarManager("destroyAllTaskbars: call destroyTaskbarForDisplay", displayId);
destroyTaskbarForDisplay(displayId);
+
+ debugTaskbarManager("destroyAllTaskbars: call removeTaskbarRootViewFromWindow",
+ displayId);
removeTaskbarRootViewFromWindow(displayId);
}
}
private void destroyTaskbarForDisplay(int displayId) {
- Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW, "destroyTaskbarForDisplay: " + displayId);
+ debugTaskbarManager("destroyTaskbarForDisplay", displayId);
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
- debugTaskbarManager("destroyTaskbarForDisplay: " + taskbar, displayId);
if (taskbar != null) {
taskbar.onDestroy();
// remove all defaults that we store
removeTaskbarFromMap(displayId);
+ } else {
+ debugTaskbarManager("destroyTaskbarForDisplay: taskbar is NULL!", displayId);
}
- // TODO (b/381113004): make this display-specific via getWindowContext()
- DeviceProfile dp = mUserUnlocked ? LauncherAppState.getIDP(
- mPrimaryWindowContext).getDeviceProfile(mPrimaryWindowContext) : null;
+
+ DeviceProfile dp = getDeviceProfile(displayId);
if (dp == null || !isTaskbarEnabled(dp)) {
removeTaskbarRootViewFromWindow(displayId);
}
@@ -469,13 +490,24 @@
* Show Taskbar upon receiving broadcast
*/
private void showTaskbarFromBroadcast(Intent intent) {
+ debugPrimaryTaskbar("destroyTaskbarForDisplay");
// TODO: make this code displayId specific
TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
- if (ACTION_SHOW_TASKBAR.equals(intent.getAction()) && taskbar != null) {
+ if (ACTION_SHOW_TASKBAR.equals(intent.getAction())) {
taskbar.showTaskbarFromBroadcast();
}
}
+ private void showGrowthNudge(Intent intent) {
+ if (!enableGrowthNudge()) {
+ return;
+ }
+ if (BROADCAST_SHOW_NUDGE.equals(intent.getAction())) {
+ // TODO: b/397738606 - extract the details and create a nudge payload.
+ Log.d(GROWTH_FRAMEWORK_TAG, "Intent received");
+ }
+ }
+
/**
* Toggles All Apps for Taskbar or Launcher depending on the current state.
*/
@@ -505,12 +537,15 @@
* Called when the user is unlocked
*/
public void onUserUnlocked() {
+ debugPrimaryTaskbar("onUserUnlocked");
mUserUnlocked = true;
DisplayController.INSTANCE.get(mPrimaryWindowContext).addChangeListener(
mRecreationListener);
- recreateTaskbar();
+ debugPrimaryTaskbar("onUserUnlocked: recreating all taskbars!");
+ recreateTaskbars();
for (int i = 0; i < mTaskbars.size(); i++) {
int displayId = mTaskbars.keyAt(i);
+ debugTaskbarManager("onUserUnlocked: addTaskbarRootViewToWindow()", displayId);
addTaskbarRootViewToWindow(displayId);
}
}
@@ -519,15 +554,15 @@
* Sets a {@link StatefulActivity} to act as taskbar callback
*/
public void setActivity(@NonNull StatefulActivity activity) {
+ debugPrimaryTaskbar("setActivity: mActivity=" + mActivity);
if (mActivity == activity) {
+ debugPrimaryTaskbar("setActivity: No need to set activity!");
return;
}
removeActivityCallbacksAndListeners();
mActivity = activity;
- debugTaskbarManager("Set mActivity=" + mActivity);
mActivity.addOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
- Log.d(TASKBAR_NOT_DESTROYED_TAG,
- "registering activity lifecycle callbacks from setActivity().");
+ debugPrimaryTaskbar("setActivity: registering activity lifecycle callbacks.");
mActivity.addEventCallback(EVENT_DESTROYED, mActivityOnDestroyCallback);
UnfoldTransitionProgressProvider unfoldTransitionProgressProvider =
getUnfoldTransitionProgressProviderForActivity(activity);
@@ -545,6 +580,7 @@
* Sets the current RecentsViewContainer, from which we create a TaskbarUIController.
*/
public void setRecentsViewContainer(@NonNull RecentsViewContainer recentsViewContainer) {
+ debugPrimaryTaskbar("setRecentsViewContainer");
if (mRecentsViewContainer == recentsViewContainer) {
return;
}
@@ -568,6 +604,7 @@
*/
private UnfoldTransitionProgressProvider getUnfoldTransitionProgressProviderForActivity(
StatefulActivity activity) {
+ debugPrimaryTaskbar("getUnfoldTransitionProgressProviderForActivity");
if (!enableUnfoldStateAnimation()) {
if (activity instanceof QuickstepLauncher ql) {
return ql.getUnfoldTransitionProgressProvider();
@@ -580,6 +617,7 @@
/** Creates a {@link TaskbarUIController} to use with non default displays. */
private TaskbarUIController createTaskbarUIControllerForNonDefaultDisplay(int displayId) {
+ debugPrimaryTaskbar("createTaskbarUIControllerForNonDefaultDisplay");
if (RecentsDisplayModel.enableOverviewInWindow()) {
RecentsViewContainer rvc = mRecentsDisplayModel.getRecentsWindowManager(displayId);
if (rvc != null) {
@@ -595,6 +633,7 @@
*/
private TaskbarUIController createTaskbarUIControllerForRecentsViewContainer(
RecentsViewContainer container) {
+ debugPrimaryTaskbar("createTaskbarUIControllerForRecentsViewContainer");
if (container instanceof QuickstepLauncher quickstepLauncher) {
return new LauncherTaskbarUIController(quickstepLauncher);
}
@@ -615,15 +654,18 @@
* In other case (folding/unfolding) we don't need to remove and add window.
*/
@VisibleForTesting
- public synchronized void recreateTaskbar() {
+ public synchronized void recreateTaskbars() {
+ debugPrimaryTaskbar("recreateTaskbars");
// Handles initial creation case.
if (mTaskbars.size() == 0) {
+ debugTaskbarManager("recreateTaskbars: create primary taskbar", getDefaultDisplayId());
recreateTaskbarForDisplay(getDefaultDisplayId(), 0);
return;
}
for (int i = 0; i < mTaskbars.size(); i++) {
int displayId = mTaskbars.keyAt(i);
+ debugTaskbarManager("recreateTaskbars: create external taskbar", displayId);
recreateTaskbarForDisplay(displayId, 0);
}
}
@@ -634,17 +676,17 @@
* In other case (folding/unfolding) we don't need to remove and add window.
*/
private void recreateTaskbarForDisplay(int displayId, int duration) {
+ debugTaskbarManager("recreateTaskbarForDisplay: ", displayId);
Trace.beginSection("recreateTaskbarForDisplay");
try {
- Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW, "recreateTaskbarForDisplay: " + displayId);
+ debugTaskbarManager("recreateTaskbarForDisplay: getting device profile", displayId);
// TODO (b/381113004): make this display-specific via getWindowContext()
- DeviceProfile dp = mUserUnlocked ? LauncherAppState.getIDP(
- mPrimaryWindowContext).getDeviceProfile(mPrimaryWindowContext) : null;
+ DeviceProfile dp = getDeviceProfile(displayId);
// All Apps action is unrelated to navbar unification, so we only need to check DP.
final boolean isLargeScreenTaskbar = dp != null && dp.isTaskbarPresent;
mAllAppsActionManager.setTaskbarPresent(isLargeScreenTaskbar);
-
+ debugTaskbarManager("recreateTaskbarForDisplay: destroying taskbar", displayId);
destroyTaskbarForDisplay(displayId);
boolean displayExists = getDisplay(displayId) != null;
@@ -653,17 +695,22 @@
+ " [dp != null (i.e. mUserUnlocked)]=" + (dp != null)
+ " FLAG_HIDE_NAVBAR_WINDOW=" + ENABLE_TASKBAR_NAVBAR_UNIFICATION
+ " dp.isTaskbarPresent=" + (dp == null ? "null" : dp.isTaskbarPresent)
- + " displayExists=" + displayExists);
+ + " displayExists=" + displayExists, displayId);
if (!isTaskbarEnabled || !isLargeScreenTaskbar || !displayExists) {
SystemUiProxy.INSTANCE.get(mBaseContext)
.notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
if (!isTaskbarEnabled || !displayExists) {
+ debugTaskbarManager(
+ "recreateTaskbarForDisplay: exiting bc (!isTaskbarEnabled || "
+ + "!displayExists)",
+ displayId);
return;
}
}
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
if (enableTaskbarNoRecreate() || taskbar == null) {
+ debugTaskbarManager("recreateTaskbarForDisplay: creating taskbar", displayId);
taskbar = createTaskbarActivityContext(dp, displayId);
if (taskbar == null) {
debugTaskbarManager(
@@ -671,6 +718,8 @@
return;
}
} else {
+ debugTaskbarManager("recreateTaskbarForDisplay: updating taskbar device profile",
+ displayId);
taskbar.updateDeviceProfile(dp);
}
mSharedState.startTaskbarVariantIsTransient =
@@ -689,15 +738,17 @@
}
if (enableTaskbarNoRecreate()) {
+ debugTaskbarManager("recreateTaskbarForDisplay: adding rootView", displayId);
addTaskbarRootViewToWindow(displayId);
FrameLayout taskbarRootLayout = getTaskbarRootLayoutForDisplay(displayId);
if (taskbarRootLayout != null) {
+ debugTaskbarManager("recreateTaskbarForDisplay: adding root layout", displayId);
taskbarRootLayout.removeAllViews();
taskbarRootLayout.addView(taskbar.getDragLayer());
taskbar.notifyUpdateLayoutParams();
} else {
- Log.e(NULL_TASKBAR_ROOT_LAYOUT_TAG,
- "taskbarRootLayout is null for displayId=" + displayId);
+ debugTaskbarManager("recreateTaskbarForDisplay: taskbarRootLayout is null!",
+ displayId);
}
}
} finally {
@@ -768,7 +819,7 @@
}
public void transitionTo(int displayId, @BarTransitions.TransitionMode int barMode,
- boolean animate) {
+ boolean animate) {
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
if (taskbar != null) {
taskbar.transitionTo(barMode, animate);
@@ -842,26 +893,62 @@
* primary device or a previously mirroring display is switched to extended mode.
*/
public void onDisplayAddSystemDecorations(int displayId) {
+ debugTaskbarManager("onDisplayAddSystemDecorations: ", displayId);
Display display = getDisplay(displayId);
- if (!DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue() || isDefaultDisplay(
- displayId) || display == null) {
- debugTaskbarManager("onDisplayAddSystemDecorations: not adding display");
+ if (display == null) {
+ debugTaskbarManager("onDisplayAddSystemDecorations: can't find display!", displayId);
return;
}
+ if (!DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue() || isDefaultDisplay(
+ displayId)) {
+ debugTaskbarManager(
+ "onDisplayAddSystemDecorations: not an external display! | "
+ + "ENABLE_TASKBAR_CONNECTED_DISPLAYS="
+ + DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue()
+ + " isDefaultDisplay=" + isDefaultDisplay(displayId), displayId);
+ return;
+ }
+ debugTaskbarManager("onDisplayAddSystemDecorations: creating new windowContext!",
+ displayId);
Context newWindowContext = createWindowContext(displayId);
if (newWindowContext != null) {
+ debugTaskbarManager("onDisplayAddSystemDecorations: add new windowContext to map!",
+ displayId);
addWindowContextToMap(displayId, newWindowContext);
- // TODO (b/391965805): remove once onDisplayAddSystemDecorations is working.
WindowManager wm = getWindowManager(displayId);
if (wm == null || !wm.shouldShowSystemDecors(displayId)) {
+ String wmStatus = wm == null ? "WindowManager is null!" : "WindowManager exists";
+ boolean showDecor = wm != null && wm.shouldShowSystemDecors(displayId);
+ debugTaskbarManager(
+ "onDisplayAddSystemDecorations:\n\t" + wmStatus + "\n\tshowSystemDecors="
+ + showDecor, displayId);
return;
}
+ debugTaskbarManager("onDisplayAddSystemDecorations: creating RootLayout!", displayId);
+
+ createExternalDeviceProfile(displayId);
+
+ debugTaskbarManager("onDisplayAddSystemDecorations: creating RootLayout!", displayId);
createTaskbarRootLayout(displayId);
+
+ debugTaskbarManager("onDisplayAddSystemDecorations: creating NavButtonController!",
+ displayId);
createNavButtonController(displayId);
+
+ debugTaskbarManager(
+ "onDisplayAddSystemDecorations: createAndRegisterComponentCallbacks!",
+ displayId);
createAndRegisterComponentCallbacks(displayId);
+ debugTaskbarManager("onDisplayAddSystemDecorations: recreateTaskbarForDisplay!",
+ displayId);
recreateTaskbarForDisplay(displayId, 0);
+ } else {
+ debugTaskbarManager("onDisplayAddSystemDecorations: newWindowContext is NULL!",
+ displayId);
}
+
+ debugTaskbarManager("onDisplayAddSystemDecorations: finished!", displayId);
}
/**
@@ -869,17 +956,38 @@
* removed from the primary device.
*/
public void onDisplayRemoved(int displayId) {
+ debugTaskbarManager("onDisplayRemoved: ", displayId);
if (!DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue() || isDefaultDisplay(
displayId)) {
+ debugTaskbarManager(
+ "onDisplayRemoved: not an external display! | "
+ + "ENABLE_TASKBAR_CONNECTED_DISPLAYS="
+ + DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue()
+ + " isDefaultDisplay=" + isDefaultDisplay(displayId), displayId);
return;
}
Context windowContext = getWindowContext(displayId);
if (windowContext != null) {
+ debugTaskbarManager("onDisplayRemoved: removing NavButtonController!", displayId);
removeNavButtonController(displayId);
+
+ debugTaskbarManager("onDisplayRemoved: removeAndUnregisterComponentCallbacks!",
+ displayId);
removeAndUnregisterComponentCallbacks(displayId);
+
+ debugTaskbarManager("onDisplayRemoved: destroying Taskbar!", displayId);
destroyTaskbarForDisplay(displayId);
+
+ debugTaskbarManager("onDisplayRemoved: removing DeviceProfile from map!", displayId);
+ removeDeviceProfileFromMap(displayId);
+
+ debugTaskbarManager("onDisplayRemoved: removing WindowContext from map!", displayId);
removeWindowContextFromMap(displayId);
+
+ debugTaskbarManager("onDisplayRemoved: finished!", displayId);
+ } else {
+ debugTaskbarManager("onDisplayRemoved: removing NavButtonController!", displayId);
}
}
@@ -895,9 +1003,7 @@
private void removeActivityCallbacksAndListeners() {
if (mActivity != null) {
mActivity.removeOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
- Log.d(TASKBAR_NOT_DESTROYED_TAG,
- "unregistering activity lifecycle callbacks from "
- + "removeActivityCallbackAndListeners().");
+ debugPrimaryTaskbar("unregistering activity lifecycle callbacks");
mActivity.removeEventCallback(EVENT_DESTROYED, mActivityOnDestroyCallback);
UnfoldTransitionProgressProvider unfoldTransitionProgressProvider =
getUnfoldTransitionProgressProviderForActivity(mActivity);
@@ -911,13 +1017,17 @@
* Called when the manager is no longer needed
*/
public void destroy() {
+ debugPrimaryTaskbar("TaskbarManager#destroy()");
mRecentsViewContainer = null;
- debugTaskbarManager("TaskbarManager#destroy()");
+ debugPrimaryTaskbar("destroy: removing activity callbacks");
DesktopVisibilityController.INSTANCE.get(
mPrimaryWindowContext).unregisterTaskbarDesktopModeListener(
mTaskbarDesktopModeListener);
removeActivityCallbacksAndListeners();
mTaskbarBroadcastReceiver.unregisterReceiverSafely();
+ if (mGrowthBroadcastReceiver != null) {
+ mGrowthBroadcastReceiver.unregisterReceiverSafely();
+ }
if (mUserUnlocked) {
DisplayController.INSTANCE.get(mPrimaryWindowContext).removeChangeListener(
@@ -927,7 +1037,7 @@
.unregister(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
SettingsCache.INSTANCE.get(mPrimaryWindowContext)
.unregister(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
- Log.d(TASKBAR_NOT_DESTROYED_TAG, "unregistering component callbacks from destroy().");
+ debugPrimaryTaskbar("destroy: unregistering component callbacks");
removeAndUnregisterComponentCallbacks(getDefaultDisplayId());
mShutdownReceiver.unregisterReceiverSafely();
if (ActivityManagerWrapper.usePerceptibleTasks(getPrimaryWindowContext())) {
@@ -936,7 +1046,9 @@
}
}
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
+ debugPrimaryTaskbar("destroy: destroying all taskbars!");
destroyAllTaskbars();
+ debugPrimaryTaskbar("destroy: finished!");
}
public @Nullable TaskbarActivityContext getCurrentActivityContext() {
@@ -959,14 +1071,15 @@
}
private void addTaskbarRootViewToWindow(int displayId) {
+ debugTaskbarManager("addTaskbarRootViewToWindow:", displayId);
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
if (!enableTaskbarNoRecreate() || taskbar == null) {
- debugTaskbarManager("addTaskbarRootViewToWindow - taskbar null", displayId);
+ debugTaskbarManager("addTaskbarRootViewToWindow: taskbar null", displayId);
return;
}
if (getDisplay(displayId) == null) {
- debugTaskbarManager("addTaskbarRootViewToWindow - display null", displayId);
+ debugTaskbarManager("addTaskbarRootViewToWindow: display null", displayId);
return;
}
@@ -977,18 +1090,21 @@
windowManager.addView(rootLayout, taskbar.getWindowLayoutParams());
mAddedRootLayouts.put(displayId, true);
} else {
- Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW,
- "addTaskbarRootViewToWindow - root layout null | displayId=" + displayId);
+ String rootLayoutStatus =
+ (rootLayout == null) ? "rootLayout is NULL!" : "rootLayout exists!";
+ String wmStatus = (windowManager == null) ? "windowManager is NULL!"
+ : "windowManager exists!";
+ debugTaskbarManager(
+ "addTaskbarRootViewToWindow: \n\t" + rootLayoutStatus + "\n\t" + wmStatus,
+ displayId);
}
} else {
- Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG,
- "addTaskbarRootViewToWindow - root layout already added | displayId="
- + displayId);
+ debugTaskbarManager("addTaskbarRootViewToWindow: rootLayout already added!", displayId);
}
}
private void removeTaskbarRootViewFromWindow(int displayId) {
- Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW, "removeTaskbarRootViewFromWindow: " + displayId);
+ debugTaskbarManager("removeTaskbarRootViewFromWindow", displayId);
FrameLayout rootLayout = getTaskbarRootLayoutForDisplay(displayId);
if (!enableTaskbarNoRecreate() || rootLayout == null) {
return;
@@ -1000,7 +1116,7 @@
mAddedRootLayouts.put(displayId, false);
removeTaskbarRootLayoutFromMap(displayId);
} else {
- debugTaskbarManager("removeTaskbarRootViewFromWindow - WindowManager is null",
+ debugTaskbarManager("removeTaskbarRootViewFromWindow: WindowManager is null",
displayId);
}
}
@@ -1078,12 +1194,68 @@
}
/**
+ * Creates a {@link DeviceProfile} for the given display and adds it to the map.
+ * @param displayId The ID of the display.
+ */
+ private void createExternalDeviceProfile(int displayId) {
+ if (!mUserUnlocked) {
+ return;
+ }
+
+ InvariantDeviceProfile idp = LauncherAppState.getIDP(mPrimaryWindowContext);
+ if (idp == null) {
+ return;
+ }
+
+ Context displayContext = getWindowContext(displayId);
+ if (displayContext == null) {
+ return;
+ }
+
+ DeviceProfile externalDeviceProfile = idp.createDeviceProfileForSecondaryDisplay(
+ displayContext);
+ mExternalDeviceProfiles.put(displayId, externalDeviceProfile);
+ }
+
+ /**
+ * Gets a {@link DeviceProfile} for the given displayId.
+ * @param displayId The ID of the display.
+ */
+ private @Nullable DeviceProfile getDeviceProfile(int displayId) {
+ if (!mUserUnlocked) {
+ return null;
+ }
+
+ InvariantDeviceProfile idp = LauncherAppState.getIDP(mPrimaryWindowContext);
+ if (idp == null) {
+ return null;
+ }
+
+ boolean isPrimary = isDefaultDisplay(displayId)
+ || !DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue();
+ if (isPrimary) {
+ return idp.getDeviceProfile(mPrimaryWindowContext);
+ }
+
+ return mExternalDeviceProfiles.get(displayId);
+ }
+
+ /**
+ * Removes the {@link DeviceProfile} associated with the given display ID from the map.
+ * @param displayId The ID of the display for which to remove the taskbar.
+ */
+ private void removeDeviceProfileFromMap(int displayId) {
+ mExternalDeviceProfiles.delete(displayId);
+ }
+
+ /**
* Create {@link ComponentCallbacks} for the given display and register it to the relevant
* WindowContext. For external displays, populate maps.
*
* @param displayId The ID of the display.
*/
private void createAndRegisterComponentCallbacks(int displayId) {
+ debugTaskbarManager("createAndRegisterComponentCallbacks", displayId);
ComponentCallbacks callbacks = new ComponentCallbacks() {
private Configuration mOldConfig =
getWindowContext(displayId).getResources().getConfiguration();
@@ -1092,15 +1264,13 @@
public void onConfigurationChanged(Configuration newConfig) {
Trace.instantForTrack(Trace.TRACE_TAG_APP, "TaskbarManager",
"onConfigurationChanged: " + newConfig);
- debugTaskbarManager(
- "TaskbarManager#mComponentCallbacks.onConfigurationChanged: " + newConfig);
- // TODO (b/381113004): make this display-specific via getWindowContext()
- DeviceProfile dp = mUserUnlocked ? LauncherAppState.getIDP(
- mPrimaryWindowContext).getDeviceProfile(mPrimaryWindowContext) : null;
+ debugTaskbarManager("onConfigurationChanged: " + newConfig, displayId);
+
+ DeviceProfile dp = getDeviceProfile(displayId);
int configDiff = mOldConfig.diff(newConfig) & ~SKIP_RECREATE_CONFIG_CHANGES;
if ((configDiff & ActivityInfo.CONFIG_UI_MODE) != 0) {
- Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW, "onConfigurationChanged: theme changed");
+ debugTaskbarManager("onConfigurationChanged: theme changed", displayId);
// Only recreate for theme changes, not other UI mode changes such as docking.
int oldUiNightMode = (mOldConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK);
int newUiNightMode = (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK);
@@ -1109,27 +1279,36 @@
}
}
- debugTaskbarManager("ComponentCallbacks#onConfigurationChanged() "
- + "configDiff=" + Configuration.configurationDiffToString(configDiff));
+ debugTaskbarManager("onConfigurationChanged: | configDiff="
+ + Configuration.configurationDiffToString(configDiff), displayId);
if (configDiff != 0 || getCurrentActivityContext() == null) {
- recreateTaskbar();
- } else {
+ debugTaskbarManager("onConfigurationChanged: call recreateTaskbars", displayId);
+ recreateTaskbars();
+ } else if (dp != null) {
// Config change might be handled without re-creating the taskbar
- if (dp != null && !isTaskbarEnabled(dp)) {
+ if (!isTaskbarEnabled(dp)) {
+ debugPrimaryTaskbar(
+ "onConfigurationChanged: isTaskbarEnabled(dp)=False | "
+ + "destroyTaskbarForDisplay");
destroyTaskbarForDisplay(getDefaultDisplayId());
} else {
- if (dp != null && isTaskbarEnabled(dp)) {
- if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
- // Re-initialize for screen size change? Should this be done
- // by looking at screen-size change flag in configDiff in the
- // block above?
- recreateTaskbar();
- } else {
- getCurrentActivityContext().updateDeviceProfile(dp);
- }
+ debugPrimaryTaskbar("onConfigurationChanged: isTaskbarEnabled(dp)=True");
+ if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
+ // Re-initialize for screen size change? Should this be done
+ // by looking at screen-size change flag in configDiff in the
+ // block above?
+ debugPrimaryTaskbar("onConfigurationChanged: call recreateTaskbars");
+ recreateTaskbars();
+ } else {
+ debugPrimaryTaskbar(
+ "onConfigurationChanged: updateDeviceProfile for current "
+ + "taskbar.");
+ getCurrentActivityContext().updateDeviceProfile(dp);
}
- getCurrentActivityContext().onConfigurationChanged(configDiff);
}
+ } else {
+
+ getCurrentActivityContext().onConfigurationChanged(configDiff);
}
mOldConfig = new Configuration(newConfig);
// reset taskbar was pinned value, so we don't automatically unstash taskbar upon
@@ -1237,7 +1416,7 @@
* @param displayId The ID of the display for which to create the taskbar root layout.
*/
private void createTaskbarRootLayout(int displayId) {
- Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW, "createTaskbarRootLayout: " + displayId);
+ debugTaskbarManager("createTaskbarRootLayout: ", displayId);
if (!enableTaskbarNoRecreate()) {
return;
}
@@ -1245,6 +1424,7 @@
FrameLayout newTaskbarRootLayout = new FrameLayout(getWindowContext(displayId)) {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
+ debugTaskbarManager("dispatchTouchEvent: ", displayId);
// The motion events can be outside the view bounds of task bar, and hence
// manually dispatching them to the drag layer here.
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
@@ -1254,8 +1434,9 @@
return super.dispatchTouchEvent(ev);
}
};
+
+ debugTaskbarManager("createTaskbarRootLayout: adding to map", displayId);
addTaskbarRootLayoutToMap(displayId, newTaskbarRootLayout);
- Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "created new root layout - displayId=" + displayId);
}
private boolean isDefaultDisplay(int displayId) {
@@ -1269,13 +1450,12 @@
* @return The taskbar root layout {@link FrameLayout} for a given display or {@code null}.
*/
private FrameLayout getTaskbarRootLayoutForDisplay(int displayId) {
- Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW, "getTaskbarRootLayoutForDisplay: " + displayId);
+ debugTaskbarManager("getTaskbarRootLayoutForDisplay:", displayId);
FrameLayout frameLayout = mRootLayouts.get(displayId);
if (frameLayout != null) {
return frameLayout;
} else {
- Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG,
- "getTaskbarRootLayoutForDisplay == null | displayId=" + displayId);
+ debugTaskbarManager("getTaskbarRootLayoutForDisplay: rootLayout is null!", displayId);
return null;
}
}
@@ -1287,11 +1467,14 @@
* @param rootLayout The taskbar root layout {@link FrameLayout} to add to the map.
*/
private void addTaskbarRootLayoutToMap(int displayId, FrameLayout rootLayout) {
+ debugTaskbarManager("addTaskbarRootLayoutToMap: ", displayId);
if (!mRootLayouts.contains(displayId) && rootLayout != null) {
mRootLayouts.put(displayId, rootLayout);
}
- Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "mRootLayouts.size()=" + mRootLayouts.size());
+ debugTaskbarManager(
+ "addTaskbarRootLayoutToMap: finished! mRootLayouts.size()=" + mRootLayouts.size(),
+ displayId);
}
/**
@@ -1300,12 +1483,14 @@
* @param displayId The ID of the display for which to remove the taskbar root layout.
*/
private void removeTaskbarRootLayoutFromMap(int displayId) {
+ debugTaskbarManager("removeTaskbarRootLayoutFromMap:", displayId);
if (mRootLayouts.contains(displayId)) {
mAddedRootLayouts.delete(displayId);
mRootLayouts.delete(displayId);
}
- Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "mRootLayouts.size()=" + mRootLayouts.size());
+ debugTaskbarManager("removeTaskbarRootLayoutFromMap: finished! mRootLayouts.size="
+ + mRootLayouts.size(), displayId);
}
/**
@@ -1314,10 +1499,10 @@
* @param displayId The ID of the display for which to create the window context.
*/
private @Nullable Context createWindowContext(int displayId) {
- debugTaskbarManager("createWindowContext: " + displayId);
+ debugTaskbarManager("createWindowContext: ", displayId);
Display display = getDisplay(displayId);
if (display == null) {
- debugTaskbarManager("createWindowContext: display null", displayId);
+ debugTaskbarManager("createWindowContext: display null!", displayId);
return null;
}
@@ -1414,31 +1599,73 @@
return mBaseContext.getDisplayId();
}
- /** Temp logs for b/254119092. */
- public void debugTaskbarManager(String debugReason) {
- debugTaskbarManager(debugReason, getDefaultDisplayId());
- }
-
- /** Temp logs for b/254119092. */
+ /**
+ * Logs debug information about the TaskbarManager for primary display.
+ * @param debugReason A string describing the reason for the debug log.
+ * @param displayId The ID of the display for which to log debug information.
+ */
public void debugTaskbarManager(String debugReason, int displayId) {
StringJoiner log = new StringJoiner("\n");
log.add(debugReason + " displayId=" + displayId + " isDefaultDisplay=" + isDefaultDisplay(
displayId));
+ Log.d(TAG, log.toString());
+ }
+ /**
+ * Logs verbose debug information about the TaskbarManager for primary display.
+ * @param debugReason A string describing the reason for the debug log.
+ * @param displayId The ID of the display for which to log debug information.
+ * @param verbose Indicates whether or not to debug with detail.
+ */
+ public void debugTaskbarManager(String debugReason, int displayId, boolean verbose) {
+ StringJoiner log = new StringJoiner("\n");
+ log.add(debugReason + " displayId=" + displayId + " isDefaultDisplay=" + isDefaultDisplay(
+ displayId));
+ if (verbose) {
+ generateVerboseLogs(log, displayId);
+ }
+ Log.d(TAG, log.toString());
+ }
+
+ /**
+ * Logs debug information about the TaskbarManager for primary display.
+ * @param debugReason A string describing the reason for the debug log.
+ *
+ */
+ public void debugPrimaryTaskbar(String debugReason) {
+ debugTaskbarManager(debugReason, getDefaultDisplayId(), false);
+ }
+
+ /**
+ * Logs debug information about the TaskbarManager for primary display.
+ * @param debugReason A string describing the reason for the debug log.
+ *
+ */
+ public void debugPrimaryTaskbar(String debugReason, boolean verbose) {
+ debugTaskbarManager(debugReason, getDefaultDisplayId(), verbose);
+ }
+
+ /**
+ * Logs verbose debug information about the TaskbarManager for a specific display.
+ */
+ private void generateVerboseLogs(StringJoiner log, int displayId) {
boolean activityTaskbarPresent = mActivity != null
&& mActivity.getDeviceProfile().isTaskbarPresent;
// TODO (b/381113004): make this display-specific via getWindowContext()
Context windowContext = mPrimaryWindowContext;
if (windowContext == null) {
- log.add("window context for displayId" + displayId);
+ log.add("windowContext is null!");
return;
}
- boolean contextTaskbarPresent = mUserUnlocked && LauncherAppState.getIDP(windowContext)
- .getDeviceProfile(windowContext).isTaskbarPresent;
+ boolean contextTaskbarPresent = false;
+ if (mUserUnlocked) {
+ DeviceProfile dp = getDeviceProfile(displayId);
+ contextTaskbarPresent = dp != null && dp.isTaskbarPresent;
+ }
if (activityTaskbarPresent == contextTaskbarPresent) {
log.add("mActivity and mWindowContext agree taskbarIsPresent=" + contextTaskbarPresent);
- Log.d(TASKBAR_NOT_DESTROYED_TAG, log.toString());
+ Log.d(TAG, log.toString());
return;
}
@@ -1457,16 +1684,14 @@
log.add("\t\tWindowContext.getResources().getConfiguration()="
+ windowContext.getResources().getConfiguration());
if (mUserUnlocked) {
- log.add("\t\tLauncherAppState.getIDP().getDeviceProfile(mPrimaryWindowContext)"
- + ".isTaskbarPresent=" + contextTaskbarPresent);
+ log.add("\t\tgetDeviceProfile(mPrimaryWindowContext).isTaskbarPresent="
+ + contextTaskbarPresent);
} else {
log.add("\t\tCouldn't get DeviceProfile because !mUserUnlocked");
}
-
- Log.d(TASKBAR_NOT_DESTROYED_TAG, log.toString());
}
private final DeviceProfile.OnDeviceProfileChangeListener mDebugActivityDeviceProfileChanged =
- dp -> debugTaskbarManager("mActivity onDeviceProfileChanged");
+ dp -> debugPrimaryTaskbar("mActivity onDeviceProfileChanged", true);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/growth/GrowthConstants.java b/quickstep/src/com/android/launcher3/taskbar/growth/GrowthConstants.java
new file mode 100644
index 0000000..78ef152
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/growth/GrowthConstants.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar.growth;
+
+/**
+ * Constants for registering Growth framework.
+ */
+public final class GrowthConstants {
+ /**
+ * For Taskbar broadcast intent filter.
+ */
+ public static final String BROADCAST_SHOW_NUDGE =
+ "com.android.launcher3.growth.BROADCAST_SHOW_NUDGE";
+ private GrowthConstants() {}
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 019e746..aab8ad1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -1380,7 +1380,8 @@
SystemUiProxy.INSTANCE.get(this).setLauncherAppIconSize(mDeviceProfile.iconSizePx);
TaskbarManager taskbarManager = mTISBindHelper.getTaskbarManager();
if (taskbarManager != null) {
- taskbarManager.debugTaskbarManager("QuickstepLauncher#onDeviceProfileChanged");
+ taskbarManager.debugPrimaryTaskbar("QuickstepLauncher#onDeviceProfileChanged",
+ true);
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
index cca8bf8..04e1905 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
@@ -27,17 +27,16 @@
import com.android.launcher3.anim.AnimatorListeners.forSuccessCallback
import com.android.launcher3.anim.PendingAnimation
import com.android.launcher3.anim.PropertySetter
-import com.android.launcher3.logging.StatsLogManager.LauncherEvent
import com.android.launcher3.statemanager.StateManager.StateHandler
import com.android.launcher3.states.StateAnimationConfig
import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE
import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE
import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL
import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE
-import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE
import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X
import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y
import com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW
+import com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE
import com.android.quickstep.util.AnimUtils
import com.android.quickstep.views.ClearAllButton
import com.android.quickstep.views.RecentsView
@@ -226,8 +225,8 @@
builder: PendingAnimation,
animate: Boolean,
) {
- val goingToOverviewFromWorkspaceContextual = toState == LauncherState.OVERVIEW &&
- launcher.isSplitSelectionActive
+ val goingToOverviewFromWorkspaceContextual =
+ toState == LauncherState.OVERVIEW && launcher.isSplitSelectionActive
if (
toState != LauncherState.OVERVIEW_SPLIT_SELECT &&
!goingToOverviewFromWorkspaceContextual
@@ -302,6 +301,14 @@
overviewButtonAlpha,
config.getInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, LINEAR),
)
+ recentsView.addDeskButton?.let {
+ propertySetter.setFloat(
+ it.visibilityAlphaProperty,
+ MULTI_PROPERTY_VALUE,
+ if (state.areElementsVisible(launcher, LauncherState.ADD_DESK_BUTTON)) 1f else 0f,
+ LINEAR,
+ )
+ }
}
private fun getOverviewInterpolator(fromState: LauncherState, toState: LauncherState) =
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index ca388c6..b1196af 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -77,7 +77,8 @@
return super.getVisibleElements(launcher)
& ~OVERVIEW_ACTIONS
& ~CLEAR_ALL_BUTTON
- & ~VERTICAL_SWIPE_INDICATOR;
+ & ~VERTICAL_SWIPE_INDICATOR
+ & ~ADD_DESK_BUTTON;
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
index 80fc5fa..0c0b4fd 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
@@ -45,7 +45,7 @@
@Override
public int getVisibleElements(Launcher launcher) {
- return OVERVIEW_ACTIONS | CLEAR_ALL_BUTTON;
+ return OVERVIEW_ACTIONS;
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 15216fe..5fdedcc 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -110,7 +110,7 @@
@Override
public int getVisibleElements(Launcher launcher) {
- int elements = CLEAR_ALL_BUTTON | OVERVIEW_ACTIONS;
+ int elements = CLEAR_ALL_BUTTON | OVERVIEW_ACTIONS | ADD_DESK_BUTTON;
DeviceProfile dp = launcher.getDeviceProfile();
boolean showFloatingSearch;
if (dp.isPhone) {
@@ -124,7 +124,7 @@
elements |= FLOATING_SEARCH_BAR;
}
if (launcher.isSplitSelectionActive()) {
- elements &= ~CLEAR_ALL_BUTTON;
+ elements &= ~CLEAR_ALL_BUTTON & ~ADD_DESK_BUTTON;
}
return elements;
}
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 9b0e75c..f47937c 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -177,7 +177,7 @@
case TestProtocol.REQUEST_RECREATE_TASKBAR:
// Allow null-pointer to catch illegal states.
- runOnTISBinder(tisBinder -> tisBinder.getTaskbarManager().recreateTaskbar());
+ runOnTISBinder(tisBinder -> tisBinder.getTaskbarManager().recreateTaskbars());
return response;
case TestProtocol.REQUEST_TASKBAR_IME_DOCKED:
return getTISBinderUIProperty(Bundle::putBoolean, tisBinder ->
diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
index a614327..ae6cfa0 100644
--- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -142,15 +142,17 @@
mContext = context;
mDisplayController = displayController;
mSystemUiProxy = systemUiProxy;
+ // TODO (b/398195845): this needs updating so non-default displays do not rotate with the
+ // default display.
mDisplayId = DEFAULT_DISPLAY;
Resources resources = mContext.getResources();
mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
() -> QuickStepContract.getWindowCornerRadius(mContext));
- // Register for navigation mode changes
- mDisplayController.addChangeListener(this);
- DisplayController.Info info = mDisplayController.getInfo();
+ // Register for navigation mode and rotation changes
+ mDisplayController.addChangeListenerForDisplay(this, mDisplayId);
+ DisplayController.Info info = mDisplayController.getInfoForDisplay(mDisplayId);
onDisplayInfoChanged(context, info, CHANGE_ALL);
mOrientationListener = new OrientationEventListener(mContext) {
@@ -174,7 +176,7 @@
};
lifeCycle.addCloseable(() -> {
- mDisplayController.removeChangeListener(this);
+ mDisplayController.removeChangeListenerForDisplay(this, mDisplayId);
mOrientationListener.disable();
TaskStackChangeListeners.getInstance()
.unregisterTaskStackListener(mFrozenTaskListener);
@@ -201,7 +203,8 @@
return;
}
- mOrientationTouchTransformer.createOrAddTouchRegion(mDisplayController.getInfo(),
+ mOrientationTouchTransformer.createOrAddTouchRegion(
+ mDisplayController.getInfoForDisplay(mDisplayId),
"RTH.updateGestureTouchRegions");
}
@@ -258,7 +261,8 @@
if ((flags & CHANGE_NAVIGATION_MODE) != 0) {
NavigationMode newMode = info.getNavigationMode();
- mOrientationTouchTransformer.setNavigationMode(newMode, mDisplayController.getInfo(),
+ mOrientationTouchTransformer.setNavigationMode(newMode,
+ mDisplayController.getInfoForDisplay(mDisplayId),
mContext.getResources());
TaskStackChangeListeners.getInstance()
@@ -280,7 +284,8 @@
*/
void setGesturalHeight(int newGesturalHeight) {
mOrientationTouchTransformer.setGesturalHeight(
- newGesturalHeight, mDisplayController.getInfo(), mContext.getResources());
+ newGesturalHeight, mDisplayController.getInfoForDisplay(mDisplayId),
+ mContext.getResources());
}
/**
@@ -296,7 +301,8 @@
}
private void enableMultipleRegions(boolean enable) {
- mOrientationTouchTransformer.enableMultipleRegions(enable, mDisplayController.getInfo());
+ mOrientationTouchTransformer.enableMultipleRegions(enable,
+ mDisplayController.getInfoForDisplay(mDisplayId));
notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getQuickStepStartingRotation());
if (enable && !mInOverview && !TestProtocol.sDisableSensorRotation) {
// Clear any previous state from sensor manager
@@ -359,7 +365,8 @@
* notifies system UI of the primary rotation the user is interacting with
*/
private void toggleSecondaryNavBarsForRotation() {
- mOrientationTouchTransformer.setSingleActiveRegion(mDisplayController.getInfo());
+ mOrientationTouchTransformer.setSingleActiveRegion(
+ mDisplayController.getInfoForDisplay(mDisplayId));
notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
}
diff --git a/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java
index d2a491d..de7fb89 100644
--- a/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java
@@ -15,6 +15,8 @@
*/
package com.android.quickstep;
+import static android.view.Display.DEFAULT_DISPLAY;
+
import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
import static com.android.launcher3.util.DisplayController.CHANGE_ALL;
import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION;
@@ -42,15 +44,20 @@
private OrientationRectF mOrientationRectF;
private OrientationRectF mTouchingOrientationRectF;
private int mViewRotation;
+ private final int mDisplayId;
@Inject
public SimpleOrientationTouchTransformer(@ApplicationContext Context context,
DisplayController displayController,
DaggerSingletonTracker tracker) {
- displayController.addChangeListener(this);
- tracker.addCloseable(() -> displayController.removeChangeListener(this));
+ // TODO (b/398195845): make sure non-default displays don't get affected by default display
+ // changes.
+ mDisplayId = DEFAULT_DISPLAY;
+ displayController.addChangeListenerForDisplay(this, mDisplayId);
+ tracker.addCloseable(
+ () -> displayController.removeChangeListenerForDisplay(this, mDisplayId));
- onDisplayInfoChanged(context, displayController.getInfo(), CHANGE_ALL);
+ onDisplayInfoChanged(context, displayController.getInfoForDisplay(mDisplayId), CHANGE_ALL);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.kt b/quickstep/src/com/android/quickstep/TaskIconCache.kt
index 6a7f1af..f0b9b7b 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.kt
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.kt
@@ -72,6 +72,8 @@
var taskVisualsChangeListener: TaskVisualsChangeListener? = null
init {
+ // TODO (b/397205964): this will need to be updated when we support caches for different
+ // displays.
displayController.addChangeListener(this)
}
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 56dd696..537092f 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -26,6 +26,7 @@
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_SCRIM_FADE;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
+import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
import static com.android.quickstep.fallback.RecentsState.OVERVIEW_SPLIT_SELECT;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
import static com.android.quickstep.views.RecentsView.DESKTOP_CAROUSEL_DETACH_PROGRESS;
@@ -99,6 +100,11 @@
float clearAllButtonAlpha = state.hasClearAllButton() ? 1 : 0;
setter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
clearAllButtonAlpha, LINEAR);
+ if (mRecentsView.getAddDeskButton() != null) {
+ float addDeskButtonAlpha = state.hasAddDeskButton() ? 1 : 0;
+ setter.setFloat(mRecentsView.getAddDeskButton().getVisibilityAlphaProperty(),
+ MULTI_PROPERTY_VALUE, addDeskButtonAlpha, LINEAR);
+ }
float overviewButtonAlpha = state.hasOverviewActions() ? 1 : 0;
setter.setFloat(mRecentsViewContainer.getActionsView().getVisibilityAlpha(),
AnimatedFloat.VALUE, overviewButtonAlpha, LINEAR);
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
index f27b60c..f722c5d 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsState.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
@@ -45,14 +45,16 @@
private static final int FLAG_RECENTS_VIEW_VISIBLE = BaseState.getFlag(7);
private static final int FLAG_TASK_THUMBNAIL_SPLASH = BaseState.getFlag(8);
private static final int FLAG_DETACH_DESKTOP_CAROUSEL = BaseState.getFlag(9);
+ private static final int FLAG_ADD_DESK_BUTTON = BaseState.getFlag(10);
private static final RecentsState[] sAllStates = new RecentsState[6];
public static final RecentsState DEFAULT = new RecentsState(0,
FLAG_DISABLE_RESTORE | FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_SHOW_AS_GRID
- | FLAG_SCRIM | FLAG_LIVE_TILE | FLAG_RECENTS_VIEW_VISIBLE);
+ | FLAG_SCRIM | FLAG_LIVE_TILE | FLAG_RECENTS_VIEW_VISIBLE
+ | FLAG_ADD_DESK_BUTTON);
public static final RecentsState MODAL_TASK = new ModalState(1,
- FLAG_DISABLE_RESTORE | FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_MODAL
+ FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_ACTIONS | FLAG_MODAL
| FLAG_SHOW_AS_GRID | FLAG_SCRIM | FLAG_LIVE_TILE | FLAG_RECENTS_VIEW_VISIBLE);
public static final RecentsState BACKGROUND_APP = new BackgroundAppState(2,
FLAG_DISABLE_RESTORE | FLAG_NON_INTERACTIVE | FLAG_FULL_SCREEN
@@ -122,6 +124,13 @@
}
/**
+ * For this state, whether add desk button should be shown.
+ */
+ public boolean hasAddDeskButton() {
+ return hasFlag(FLAG_ADD_DESK_BUTTON);
+ }
+
+ /**
* For this state, whether overview actions should be shown.
*/
public boolean hasOverviewActions() {
diff --git a/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt b/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
index 9943770..244c3b2 100644
--- a/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
+++ b/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
@@ -20,10 +20,12 @@
import android.graphics.Canvas
import android.graphics.Rect
import android.util.AttributeSet
+import android.view.View
import android.widget.ImageButton
import com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X
import com.android.launcher3.R
import com.android.launcher3.util.MultiPropertyFactory
+import com.android.launcher3.util.MultiValueAlpha
import com.android.quickstep.util.BorderAnimator
import com.android.quickstep.util.BorderAnimator.Companion.createSimpleBorderAnimator
@@ -34,6 +36,22 @@
class AddDesktopButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
ImageButton(context, attrs) {
+ private enum class Alpha {
+ CONTENT,
+ VISIBILITY,
+ }
+
+ private val addDeskButtonAlpha = MultiValueAlpha(this, Alpha.entries.size)
+
+ var contentAlpha
+ set(value) {
+ addDeskButtonAlpha.get(Alpha.CONTENT.ordinal).value = value
+ }
+ get() = addDeskButtonAlpha.get(Alpha.CONTENT.ordinal).value
+
+ val visibilityAlphaProperty: MultiPropertyFactory<View>.MultiProperty
+ get() = addDeskButtonAlpha.get(Alpha.VISIBILITY.ordinal)
+
private enum class TranslationX {
GRID,
OFFSET,
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index d17dfb8..6b91df1 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -4762,9 +4762,8 @@
}
mClearAllButton.setContentAlpha(mContentAlpha);
- // TODO(b/389209338): Handle the visibility of the `mAddDesktopButton`.
if (mAddDesktopButton != null) {
- mAddDesktopButton.setAlpha(mContentAlpha);
+ mAddDesktopButton.setContentAlpha(mContentAlpha);
}
int alphaInt = Math.round(alpha * 255);
mEmptyMessagePaint.setAlpha(alphaInt);
@@ -6329,6 +6328,11 @@
return mClearAllButton;
}
+ @Nullable
+ public AddDesktopButton getAddDeskButton() {
+ return mAddDesktopButton;
+ }
+
/**
* @return How many pixels the running task is offset on the currently laid out dominant axis.
*/
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
deleted file mode 100644
index 0b3eb75..0000000
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ /dev/null
@@ -1,429 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep.views;
-
-import static com.android.app.animation.Interpolators.EMPHASIZED;
-import static com.android.launcher3.Flags.enableOverviewIconMenu;
-import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
-import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.quickstep.views.TaskThumbnailViewDeprecated.DIM_ALPHA;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.graphics.Outline;
-import android.graphics.Rect;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.ShapeDrawable;
-import android.graphics.drawable.shapes.RectShape;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewOutlineProvider;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.annotation.Nullable;
-
-import com.android.app.animation.Interpolators;
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
-import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
-import com.android.launcher3.popup.SystemShortcut;
-import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.TaskOverlayFactory;
-import com.android.quickstep.TaskUtils;
-import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
-import com.android.quickstep.util.TaskCornerRadius;
-
-/**
- * Contains options for a recent task when long-pressing its icon.
- */
-public class TaskMenuView extends AbstractFloatingView {
-
- private static final Rect sTempRect = new Rect();
-
- private static final int REVEAL_OPEN_DURATION = enableOverviewIconMenu() ? 417 : 150;
- private static final int REVEAL_CLOSE_DURATION = enableOverviewIconMenu() ? 333 : 100;
-
- private RecentsViewContainer mContainer;
- private TextView mTaskName;
- @Nullable
- private AnimatorSet mOpenCloseAnimator;
- @Nullable
- private ValueAnimator mRevealAnimator;
- @Nullable private Runnable mOnClosingStartCallback;
- private TaskView mTaskView;
- private TaskContainer mTaskContainer;
- private LinearLayout mOptionLayout;
- private float mMenuTranslationYBeforeOpen;
- private float mMenuTranslationXBeforeOpen;
-
- public TaskMenuView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public TaskMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
-
- mContainer = RecentsViewContainer.containerFromContext(context);
- setClipToOutline(true);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mTaskName = findViewById(R.id.task_name);
- mOptionLayout = findViewById(R.id.menu_option_layout);
- }
-
- @Override
- public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- BaseDragLayer dl = mContainer.getDragLayer();
- if (!dl.isEventOverView(this, ev)) {
- // TODO: log this once we have a new container type for it?
- close(true);
- return true;
- }
- }
- return false;
- }
-
- @Override
- protected void handleClose(boolean animate) {
- animateClose();
- }
-
- @Override
- protected boolean isOfType(int type) {
- return (type & TYPE_TASK_MENU) != 0;
- }
-
- @Override
- public ViewOutlineProvider getOutlineProvider() {
- return new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(),
- TaskCornerRadius.get(view.getContext()));
- }
- };
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (!(enableOverviewIconMenu()
- && ((RecentsView) mContainer.getOverviewPanel()).isOnGridBottomRow(mTaskView))) {
- // TODO(b/326952853): Cap menu height for grid bottom row in a way that doesn't break
- // additionalTranslationY.
- int maxMenuHeight = calculateMaxHeight();
- if (MeasureSpec.getSize(heightMeasureSpec) > maxMenuHeight) {
- heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxMenuHeight, MeasureSpec.AT_MOST);
- }
- }
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- public void onRotationChanged() {
- if (mOpenCloseAnimator != null && mOpenCloseAnimator.isRunning()) {
- mOpenCloseAnimator.end();
- }
- if (mIsOpen) {
- mOptionLayout.removeAllViews();
- if (enableOverviewIconMenu() || !populateAndLayoutMenu()) {
- close(false);
- }
- }
- }
-
- /**
- * Show a task menu for the given taskContainer.
- */
- public static boolean showForTask(TaskContainer taskContainer,
- @Nullable Runnable onClosingStartCallback) {
- RecentsViewContainer container = RecentsViewContainer.containerFromContext(
- taskContainer.getTaskView().getContext());
- final TaskMenuView taskMenuView = (TaskMenuView) container.getLayoutInflater().inflate(
- R.layout.task_menu, container.getDragLayer(), false);
- taskMenuView.setOnClosingStartCallback(onClosingStartCallback);
- return taskMenuView.populateAndShowForTask(taskContainer);
- }
-
- /**
- * Show a task menu for the given taskContainer.
- */
- public static boolean showForTask(TaskContainer taskContainer) {
- return showForTask(taskContainer, null);
- }
-
- private boolean populateAndShowForTask(TaskContainer taskContainer) {
- if (isAttachedToWindow()) {
- return false;
- }
- mContainer.getDragLayer().addView(this);
- mTaskView = taskContainer.getTaskView();
- mTaskContainer = taskContainer;
- if (!populateAndLayoutMenu()) {
- return false;
- }
- post(this::animateOpen);
- return true;
- }
-
- /** @return true if successfully able to populate task view menu, false otherwise */
- private boolean populateAndLayoutMenu() {
- addMenuOptions(mTaskContainer);
- orientAroundTaskView(mTaskContainer);
- return true;
- }
-
- private void addMenuOptions(TaskContainer taskContainer) {
- if (enableOverviewIconMenu()) {
- removeView(mTaskName);
- } else {
- mTaskName.setText(TaskUtils.getTitle(getContext(), taskContainer.getTask()));
- mTaskName.setOnClickListener(v -> close(true));
- }
- TaskOverlayFactory.getEnabledShortcuts(mTaskView, taskContainer)
- .forEach(this::addMenuOption);
- }
-
- private void addMenuOption(SystemShortcut menuOption) {
- LinearLayout menuOptionView = (LinearLayout) mContainer.getLayoutInflater().inflate(
- R.layout.task_view_menu_option, this, false);
- if (enableOverviewIconMenu()) {
- ((GradientDrawable) menuOptionView.getBackground()).setCornerRadius(0);
- }
- menuOption.setIconAndLabelFor(
- menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text));
- LayoutParams lp = (LayoutParams) menuOptionView.getLayoutParams();
- mTaskView.getPagedOrientationHandler().setLayoutParamsForTaskMenuOptionItem(lp,
- menuOptionView, mContainer.getDeviceProfile());
- // Set an onClick listener on each menu option. The onClick method is responsible for
- // ending LiveTile mode on the thumbnail if needed.
- menuOptionView.setOnClickListener(menuOption::onClick);
- mOptionLayout.addView(menuOptionView);
- }
-
- private void orientAroundTaskView(TaskContainer taskContainer) {
- RecentsView recentsView = mContainer.getOverviewPanel();
- RecentsPagedOrientationHandler orientationHandler =
- recentsView.getPagedOrientationHandler();
- measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
-
- // Get Position
- DeviceProfile deviceProfile = mContainer.getDeviceProfile();
- mContainer.getDragLayer().getDescendantRectRelativeToSelf(
- enableOverviewIconMenu()
- ? getIconView().findViewById(R.id.icon_view_menu_anchor)
- : taskContainer.getSnapshotView(),
- sTempRect);
- Rect insets = mContainer.getDragLayer().getInsets();
- BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) getLayoutParams();
- params.width = orientationHandler.getTaskMenuWidth(
- taskContainer.getSnapshotView(), deviceProfile,
- taskContainer.getStagePosition());
- // Gravity set to Left instead of Start as sTempRect.left measures Left distance not Start
- params.gravity = Gravity.LEFT;
- setLayoutParams(params);
- setScaleX(mTaskView.getScaleX());
- setScaleY(mTaskView.getScaleY());
-
- // Set divider spacing
- ShapeDrawable divider = new ShapeDrawable(new RectShape());
- divider.getPaint().setColor(getResources().getColor(android.R.color.transparent));
- int dividerSpacing = (int) getResources().getDimension(R.dimen.task_menu_spacing);
- mOptionLayout.setShowDividers(
- enableOverviewIconMenu() ? SHOW_DIVIDER_NONE : SHOW_DIVIDER_MIDDLE);
-
- orientationHandler.setTaskOptionsMenuLayoutOrientation(
- deviceProfile, mOptionLayout, dividerSpacing, divider);
- float thumbnailAlignedX = sTempRect.left - insets.left;
- float thumbnailAlignedY = sTempRect.top - insets.top;
-
- // Changing pivot to make computations easier
- // NOTE: Changing the pivots means the rotated view gets rotated about the new pivots set,
- // which would render the X and Y position set here incorrect
- setPivotX(0);
- setPivotY(0);
- setRotation(orientationHandler.getDegreesRotated());
-
- if (enableOverviewIconMenu()) {
- setTranslationX(thumbnailAlignedX);
- setTranslationY(thumbnailAlignedY);
- } else {
- // Margin that insets the menuView inside the taskView
- float taskInsetMargin = getResources().getDimension(R.dimen.task_card_margin);
- setTranslationX(orientationHandler.getTaskMenuX(thumbnailAlignedX,
- mTaskContainer.getSnapshotView(), deviceProfile, taskInsetMargin,
- getIconView()));
- setTranslationY(orientationHandler.getTaskMenuY(
- thumbnailAlignedY, mTaskContainer.getSnapshotView(),
- mTaskContainer.getStagePosition(), this, taskInsetMargin,
- getIconView()));
- }
- }
-
- private void animateOpen() {
- mMenuTranslationYBeforeOpen = getTranslationY();
- mMenuTranslationXBeforeOpen = getTranslationX();
- animateOpenOrClosed(false);
- mIsOpen = true;
- }
-
- private View getIconView() {
- return mTaskContainer.getIconView().asView();
- }
-
- private void animateClose() {
- animateOpenOrClosed(true);
- }
-
- private void animateOpenOrClosed(boolean closing) {
- if (mOpenCloseAnimator != null && mOpenCloseAnimator.isRunning()) {
- mOpenCloseAnimator.cancel();
- }
- mOpenCloseAnimator = new AnimatorSet();
- // If we're opening, we just start from the beginning as a new `TaskMenuView` is created
- // each time we do the open animation so there will never be a partial value here.
- float revealAnimationStartProgress = 0f;
- if (closing && mRevealAnimator != null) {
- revealAnimationStartProgress = 1f - mRevealAnimator.getAnimatedFraction();
- }
- mRevealAnimator = createOpenCloseOutlineProvider()
- .createRevealAnimator(this, closing, revealAnimationStartProgress);
- mRevealAnimator.setInterpolator(enableOverviewIconMenu() ? Interpolators.EMPHASIZED
- : Interpolators.DECELERATE);
- AnimatorSet.Builder openCloseAnimatorBuilder = mOpenCloseAnimator.play(mRevealAnimator);
- if (enableOverviewIconMenu()) {
- IconAppChipView iconAppChip = (IconAppChipView) mTaskContainer.getIconView().asView();
-
- float additionalTranslationY = 0;
- if (((RecentsView) mContainer.getOverviewPanel()).isOnGridBottomRow(mTaskView)) {
- // Animate menu up for enough room to display full menu when task on bottom row.
- float menuBottom = getHeight() + mMenuTranslationYBeforeOpen;
- float taskBottom = mTaskView.getHeight() + mTaskView.getPersistentTranslationY();
- float taskbarTop = mContainer.getDeviceProfile().heightPx
- - mContainer.getDeviceProfile().getOverviewActionsClaimedSpaceBelow();
- float midpoint = (taskBottom + taskbarTop) / 2f;
- additionalTranslationY = -Math.max(menuBottom - midpoint, 0);
- }
- ObjectAnimator translationYAnim = ObjectAnimator.ofFloat(this, TRANSLATION_Y,
- closing ? mMenuTranslationYBeforeOpen
- : mMenuTranslationYBeforeOpen + additionalTranslationY);
- translationYAnim.setInterpolator(EMPHASIZED);
- openCloseAnimatorBuilder.with(translationYAnim);
-
- ObjectAnimator menuTranslationYAnim = ObjectAnimator.ofFloat(
- iconAppChip.getMenuTranslationY(),
- MULTI_PROPERTY_VALUE, closing ? 0 : additionalTranslationY);
- menuTranslationYAnim.setInterpolator(EMPHASIZED);
- openCloseAnimatorBuilder.with(menuTranslationYAnim);
-
- float additionalTranslationX = 0;
- if (mContainer.getDeviceProfile().isLandscape
- && mTaskContainer.getStagePosition() == STAGE_POSITION_BOTTOM_OR_RIGHT) {
- // Animate menu and icon when split task would display off the side of the screen.
- additionalTranslationX = Math.max(
- getTranslationX() + getWidth() - (mContainer.getDeviceProfile().widthPx
- - getResources().getDimensionPixelSize(
- R.dimen.task_menu_edge_padding) * 2), 0);
- }
-
- ObjectAnimator translationXAnim = ObjectAnimator.ofFloat(this, TRANSLATION_X,
- closing ? mMenuTranslationXBeforeOpen
- : mMenuTranslationXBeforeOpen - additionalTranslationX);
- translationXAnim.setInterpolator(EMPHASIZED);
- openCloseAnimatorBuilder.with(translationXAnim);
-
- ObjectAnimator menuTranslationXAnim = ObjectAnimator.ofFloat(
- iconAppChip.getMenuTranslationX(),
- MULTI_PROPERTY_VALUE, closing ? 0 : -additionalTranslationX);
- menuTranslationXAnim.setInterpolator(EMPHASIZED);
- openCloseAnimatorBuilder.with(menuTranslationXAnim);
- }
- openCloseAnimatorBuilder.with(ObjectAnimator.ofFloat(this, ALPHA, closing ? 0 : 1));
- if (enableRefactorTaskThumbnail()) {
- mRevealAnimator.addUpdateListener(animation -> {
- float animatedFraction = animation.getAnimatedFraction();
- float openProgress = closing ? (1 - animatedFraction) : animatedFraction;
- mTaskContainer.updateMenuOpenProgress(openProgress);
- });
- } else {
- openCloseAnimatorBuilder.with(ObjectAnimator.ofFloat(
- mTaskContainer.getThumbnailViewDeprecated(), DIM_ALPHA,
- closing ? 0 : TaskView.MAX_PAGE_SCRIM_ALPHA));
- }
- mOpenCloseAnimator.addListener(new AnimationSuccessListener() {
- @Override
- public void onAnimationStart(Animator animation) {
- setVisibility(VISIBLE);
- if (closing && mOnClosingStartCallback != null) {
- mOnClosingStartCallback.run();
- }
- }
-
- @Override
- public void onAnimationSuccess(Animator animator) {
- if (closing) {
- closeComplete();
- }
- }
- });
- mOpenCloseAnimator.setDuration(closing ? REVEAL_CLOSE_DURATION: REVEAL_OPEN_DURATION);
- mOpenCloseAnimator.start();
- }
-
- private void closeComplete() {
- mIsOpen = false;
- mContainer.getDragLayer().removeView(this);
- mRevealAnimator = null;
- }
-
- private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() {
- float radius = TaskCornerRadius.get(mContext);
- Rect fromRect = new Rect(
- enableOverviewIconMenu() && isLayoutRtl() ? getWidth() : 0,
- 0,
- enableOverviewIconMenu() && !isLayoutRtl() ? 0 : getWidth(),
- 0);
- Rect toRect = new Rect(0, 0, getWidth(), getHeight());
- return new RoundedRectRevealOutlineProvider(radius, radius, fromRect, toRect);
- }
-
- /**
- * Calculates max height based on how much space we have available.
- * If not enough space then the view will scroll. The maximum menu size will sit inside the task
- * with a margin on the top and bottom.
- */
- private int calculateMaxHeight() {
- float taskInsetMargin = getResources().getDimension(R.dimen.task_card_margin);
- return mTaskView.getPagedOrientationHandler().getTaskMenuHeight(taskInsetMargin,
- mContainer.getDeviceProfile(), getTranslationX(), getTranslationY());
- }
-
- private void setOnClosingStartCallback(Runnable onClosingStartCallback) {
- mOnClosingStartCallback = onClosingStartCallback;
- }
-}
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.kt b/quickstep/src/com/android/quickstep/views/TaskMenuView.kt
new file mode 100644
index 0000000..95336cf
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.kt
@@ -0,0 +1,455 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.views
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.Outline
+import android.graphics.Rect
+import android.graphics.drawable.GradientDrawable
+import android.graphics.drawable.ShapeDrawable
+import android.graphics.drawable.shapes.RectShape
+import android.util.AttributeSet
+import android.view.Gravity
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewOutlineProvider
+import android.widget.LinearLayout
+import android.widget.TextView
+import com.android.app.animation.Interpolators
+import com.android.launcher3.AbstractFloatingView
+import com.android.launcher3.Flags.enableOverviewIconMenu
+import com.android.launcher3.Flags.enableRefactorTaskThumbnail
+import com.android.launcher3.R
+import com.android.launcher3.anim.AnimationSuccessListener
+import com.android.launcher3.anim.RoundedRectRevealOutlineProvider
+import com.android.launcher3.popup.SystemShortcut
+import com.android.launcher3.util.MultiPropertyFactory
+import com.android.launcher3.util.SplitConfigurationOptions
+import com.android.launcher3.views.BaseDragLayer
+import com.android.quickstep.TaskOverlayFactory
+import com.android.quickstep.TaskUtils
+import com.android.quickstep.util.TaskCornerRadius
+import java.util.function.Consumer
+import kotlin.math.max
+
+/** Contains options for a recent task when long-pressing its icon. */
+class TaskMenuView
+@JvmOverloads
+constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int = 0) :
+ AbstractFloatingView(context, attrs, defStyleAttr) {
+ private val recentsViewContainer: RecentsViewContainer =
+ RecentsViewContainer.containerFromContext(context)
+ private val tempRect = Rect()
+ private val taskName: TextView by lazy { findViewById(R.id.task_name) }
+ private val optionLayout: LinearLayout by lazy { findViewById(R.id.menu_option_layout) }
+ private var openCloseAnimator: AnimatorSet? = null
+ private var revealAnimator: ValueAnimator? = null
+ private var onClosingStartCallback: Runnable? = null
+ private lateinit var taskView: TaskView
+ private lateinit var taskContainer: TaskContainer
+ private var menuTranslationXBeforeOpen = 0f
+ private var menuTranslationYBeforeOpen = 0f
+
+ init {
+ clipToOutline = true
+ }
+
+ override fun onControllerInterceptTouchEvent(ev: MotionEvent): Boolean {
+ if (ev.action == MotionEvent.ACTION_DOWN) {
+ if (!recentsViewContainer.dragLayer.isEventOverView(this, ev)) {
+ // TODO: log this once we have a new container type for it?
+ close(true)
+ return true
+ }
+ }
+ return false
+ }
+
+ override fun handleClose(animate: Boolean) {
+ animateClose()
+ }
+
+ override fun isOfType(type: Int): Boolean = (type and TYPE_TASK_MENU) != 0
+
+ override fun getOutlineProvider(): ViewOutlineProvider =
+ object : ViewOutlineProvider() {
+ override fun getOutline(view: View, outline: Outline) {
+ outline.setRoundRect(
+ 0,
+ 0,
+ view.width,
+ view.height,
+ TaskCornerRadius.get(view.context),
+ )
+ }
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ var heightMeasure = heightMeasureSpec
+ if (!(enableOverviewIconMenu() && taskView.isOnGridBottomRow())) {
+ // TODO(b/326952853): Cap menu height for grid bottom row in a way that doesn't break
+ // additionalTranslationY.
+ val maxMenuHeight = calculateMaxHeight()
+ if (MeasureSpec.getSize(heightMeasure) > maxMenuHeight) {
+ heightMeasure = MeasureSpec.makeMeasureSpec(maxMenuHeight, MeasureSpec.AT_MOST)
+ }
+ }
+ super.onMeasure(widthMeasureSpec, heightMeasure)
+ }
+
+ fun onRotationChanged() {
+ openCloseAnimator?.let { if (it.isRunning) it.end() }
+ if (mIsOpen) {
+ optionLayout.removeAllViews()
+ if (enableOverviewIconMenu() || !populateAndLayoutMenu()) {
+ close(false)
+ }
+ }
+ }
+
+ private fun populateAndShowForTask(taskContainer: TaskContainer): Boolean {
+ if (isAttachedToWindow) return false
+ recentsViewContainer.dragLayer.addView(this)
+ taskView = taskContainer.taskView
+ this.taskContainer = taskContainer
+ if (!populateAndLayoutMenu()) return false
+ post { this.animateOpen() }
+ return true
+ }
+
+ /** @return true if successfully able to populate task view menu, false otherwise */
+ private fun populateAndLayoutMenu(): Boolean {
+ addMenuOptions(taskContainer)
+ orientAroundTaskView(taskContainer)
+ return true
+ }
+
+ private fun addMenuOptions(taskContainer: TaskContainer) {
+ if (enableOverviewIconMenu()) {
+ removeView(taskName)
+ } else {
+ taskName.text = TaskUtils.getTitle(context, taskContainer.task)
+ taskName.setOnClickListener { close(true) }
+ }
+ TaskOverlayFactory.getEnabledShortcuts(taskView, taskContainer)
+ .forEach(Consumer { menuOption: SystemShortcut<*> -> this.addMenuOption(menuOption) })
+ }
+
+ private fun addMenuOption(menuOption: SystemShortcut<*>) {
+ val menuOptionView =
+ recentsViewContainer.layoutInflater.inflate(R.layout.task_view_menu_option, this, false)
+ as LinearLayout
+ if (enableOverviewIconMenu()) {
+ (menuOptionView.background as GradientDrawable).cornerRadius = 0f
+ }
+ menuOption.setIconAndLabelFor(
+ menuOptionView.findViewById(R.id.icon),
+ menuOptionView.findViewById(R.id.text),
+ )
+ val lp = menuOptionView.layoutParams as LayoutParams
+ taskView.pagedOrientationHandler.setLayoutParamsForTaskMenuOptionItem(
+ lp,
+ menuOptionView,
+ recentsViewContainer.deviceProfile,
+ )
+ // Set an onClick listener on each menu option. The onClick method is responsible for
+ // ending LiveTile mode on the thumbnail if needed.
+ menuOptionView.setOnClickListener { v: View? -> menuOption.onClick(v) }
+ optionLayout.addView(menuOptionView)
+ }
+
+ private fun orientAroundTaskView(taskContainer: TaskContainer) {
+ val recentsView = recentsViewContainer.getOverviewPanel<RecentsView<*, *>>()
+ val orientationHandler = recentsView.pagedOrientationHandler
+ measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
+
+ // Get Position
+ val deviceProfile = recentsViewContainer.deviceProfile
+ recentsViewContainer.dragLayer.getDescendantRectRelativeToSelf(
+ if (enableOverviewIconMenu()) iconView.findViewById(R.id.icon_view_menu_anchor)
+ else taskContainer.snapshotView,
+ tempRect,
+ )
+ val insets = recentsViewContainer.dragLayer.getInsets()
+ val params = layoutParams as BaseDragLayer.LayoutParams
+ params.width =
+ orientationHandler.getTaskMenuWidth(
+ taskContainer.snapshotView,
+ deviceProfile,
+ taskContainer.stagePosition,
+ )
+ // Gravity set to Left instead of Start as sTempRect.left measures Left distance not Start
+ params.gravity = Gravity.START
+ layoutParams = params
+ scaleX = taskView.scaleX
+ scaleY = taskView.scaleY
+
+ // Set divider spacing
+ val divider = ShapeDrawable(RectShape())
+ divider.paint.color = resources.getColor(android.R.color.transparent)
+ val dividerSpacing = resources.getDimension(R.dimen.task_menu_spacing).toInt()
+ optionLayout.showDividers =
+ if (enableOverviewIconMenu()) SHOW_DIVIDER_NONE else SHOW_DIVIDER_MIDDLE
+
+ orientationHandler.setTaskOptionsMenuLayoutOrientation(
+ deviceProfile,
+ optionLayout,
+ dividerSpacing,
+ divider,
+ )
+ val thumbnailAlignedX = (tempRect.left - insets.left).toFloat()
+ val thumbnailAlignedY = (tempRect.top - insets.top).toFloat()
+
+ // Changing pivot to make computations easier
+ // NOTE: Changing the pivots means the rotated view gets rotated about the new pivots set,
+ // which would render the X and Y position set here incorrect
+ pivotX = 0f
+ pivotY = 0f
+ rotation = orientationHandler.degreesRotated
+
+ if (enableOverviewIconMenu()) {
+ translationX = thumbnailAlignedX
+ translationY = thumbnailAlignedY
+ } else {
+ // Margin that insets the menuView inside the taskView
+ val taskInsetMargin = resources.getDimension(R.dimen.task_card_margin)
+ translationX =
+ orientationHandler.getTaskMenuX(
+ thumbnailAlignedX,
+ this.taskContainer.snapshotView,
+ deviceProfile,
+ taskInsetMargin,
+ iconView,
+ )
+ translationY =
+ orientationHandler.getTaskMenuY(
+ thumbnailAlignedY,
+ this.taskContainer.snapshotView,
+ this.taskContainer.stagePosition,
+ this,
+ taskInsetMargin,
+ iconView,
+ )
+ }
+ }
+
+ private fun animateOpen() {
+ menuTranslationYBeforeOpen = translationY
+ menuTranslationXBeforeOpen = translationX
+ animateOpenOrClosed(false)
+ mIsOpen = true
+ }
+
+ private val iconView: View
+ get() = taskContainer.iconView.asView()
+
+ private fun animateClose() {
+ animateOpenOrClosed(true)
+ }
+
+ private fun animateOpenOrClosed(closing: Boolean) {
+ openCloseAnimator?.let { if (it.isRunning) it.cancel() }
+ openCloseAnimator = AnimatorSet()
+ // If we're opening, we just start from the beginning as a new `TaskMenuView` is created
+ // each time we do the open animation so there will never be a partial value here.
+ var revealAnimationStartProgress = 0f
+ if (closing && revealAnimator != null) {
+ revealAnimationStartProgress = 1f - revealAnimator!!.animatedFraction
+ }
+ revealAnimator =
+ createOpenCloseOutlineProvider()
+ .createRevealAnimator(this, closing, revealAnimationStartProgress)
+ revealAnimator!!.interpolator =
+ if (enableOverviewIconMenu()) Interpolators.EMPHASIZED else Interpolators.DECELERATE
+ val openCloseAnimatorBuilder = openCloseAnimator!!.play(revealAnimator)
+ if (enableOverviewIconMenu()) {
+ animateOpenOrCloseAppChip(closing, openCloseAnimatorBuilder)
+ }
+ openCloseAnimatorBuilder.with(
+ ObjectAnimator.ofFloat(this, ALPHA, (if (closing) 0 else 1).toFloat())
+ )
+ if (enableRefactorTaskThumbnail()) {
+ revealAnimator?.addUpdateListener { animation: ValueAnimator ->
+ val animatedFraction = animation.animatedFraction
+ val openProgress = if (closing) (1 - animatedFraction) else animatedFraction
+ taskContainer.updateMenuOpenProgress(openProgress)
+ }
+ } else {
+ openCloseAnimatorBuilder.with(
+ ObjectAnimator.ofFloat(
+ taskContainer.thumbnailViewDeprecated,
+ TaskThumbnailViewDeprecated.DIM_ALPHA,
+ if (closing) 0f else TaskView.MAX_PAGE_SCRIM_ALPHA,
+ )
+ )
+ }
+ openCloseAnimator!!.addListener(
+ object : AnimationSuccessListener() {
+ override fun onAnimationStart(animation: Animator) {
+ visibility = VISIBLE
+ if (closing) onClosingStartCallback?.run()
+ }
+
+ override fun onAnimationSuccess(animator: Animator) {
+ if (closing) closeComplete()
+ }
+ }
+ )
+ val animationDuration = if (closing) REVEAL_CLOSE_DURATION else REVEAL_OPEN_DURATION
+ openCloseAnimator!!.setDuration(animationDuration)
+ openCloseAnimator!!.start()
+ }
+
+ private fun TaskView.isOnGridBottomRow(): Boolean =
+ (recentsViewContainer.getOverviewPanel<View>() as RecentsView<*, *>).isOnGridBottomRow(this)
+
+ private fun closeComplete() {
+ mIsOpen = false
+ recentsViewContainer.dragLayer.removeView(this)
+ revealAnimator = null
+ }
+
+ private fun createOpenCloseOutlineProvider(): RoundedRectRevealOutlineProvider {
+ val radius = TaskCornerRadius.get(mContext)
+ val fromRect =
+ Rect(
+ if (enableOverviewIconMenu() && isLayoutRtl) width else 0,
+ 0,
+ if (enableOverviewIconMenu() && !isLayoutRtl) 0 else width,
+ 0,
+ )
+ val toRect = Rect(0, 0, width, height)
+ return RoundedRectRevealOutlineProvider(radius, radius, fromRect, toRect)
+ }
+
+ /**
+ * Calculates max height based on how much space we have available. If not enough space then the
+ * view will scroll. The maximum menu size will sit inside the task with a margin on the top and
+ * bottom.
+ */
+ private fun calculateMaxHeight(): Int {
+ val taskInsetMargin = resources.getDimension(R.dimen.task_card_margin)
+ return taskView.pagedOrientationHandler.getTaskMenuHeight(
+ taskInsetMargin,
+ recentsViewContainer.deviceProfile,
+ translationX,
+ translationY,
+ )
+ }
+
+ private fun setOnClosingStartCallback(onClosingStartCallback: Runnable?) {
+ this.onClosingStartCallback = onClosingStartCallback
+ }
+
+ private fun animateOpenOrCloseAppChip(closing: Boolean, animatorBuilder: AnimatorSet.Builder) {
+ val iconAppChip = taskContainer.iconView.asView() as IconAppChipView
+
+ var additionalTranslationY = 0f
+ if (taskView.isOnGridBottomRow()) {
+ // Animate menu up for enough room to display full menu when task on bottom row.
+ val menuBottom = height + menuTranslationYBeforeOpen
+ val taskBottom = taskView.height + taskView.persistentTranslationY
+ val taskbarTop =
+ (recentsViewContainer.deviceProfile.heightPx -
+ recentsViewContainer.deviceProfile.overviewActionsClaimedSpaceBelow)
+ .toFloat()
+ val midpoint = (taskBottom + taskbarTop) / 2f
+ additionalTranslationY = (-max((menuBottom - midpoint).toDouble(), 0.0)).toFloat()
+ }
+ val translationYAnim =
+ ObjectAnimator.ofFloat(
+ this,
+ TRANSLATION_Y,
+ if (closing) menuTranslationYBeforeOpen
+ else menuTranslationYBeforeOpen + additionalTranslationY,
+ )
+ translationYAnim.interpolator = Interpolators.EMPHASIZED
+ animatorBuilder.with(translationYAnim)
+
+ val menuTranslationYAnim: ObjectAnimator =
+ ObjectAnimator.ofFloat(
+ iconAppChip.getMenuTranslationY(),
+ MultiPropertyFactory.MULTI_PROPERTY_VALUE,
+ if (closing) 0f else additionalTranslationY,
+ )
+ menuTranslationYAnim.interpolator = Interpolators.EMPHASIZED
+ animatorBuilder.with(menuTranslationYAnim)
+
+ var additionalTranslationX = 0f
+ if (
+ recentsViewContainer.deviceProfile.isLandscape &&
+ taskContainer.stagePosition ==
+ SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+ ) {
+ // Animate menu and icon when split task would display off the side of the screen.
+ additionalTranslationX =
+ max(
+ (translationX + width -
+ (recentsViewContainer.deviceProfile.widthPx -
+ resources.getDimensionPixelSize(
+ R.dimen.task_menu_edge_padding
+ ) * 2))
+ .toDouble(),
+ 0.0,
+ )
+ .toFloat()
+ }
+
+ val translationXAnim =
+ ObjectAnimator.ofFloat(
+ this,
+ TRANSLATION_X,
+ if (closing) menuTranslationXBeforeOpen
+ else menuTranslationXBeforeOpen - additionalTranslationX,
+ )
+ translationXAnim.interpolator = Interpolators.EMPHASIZED
+ animatorBuilder.with(translationXAnim)
+
+ val menuTranslationXAnim: ObjectAnimator =
+ ObjectAnimator.ofFloat(
+ iconAppChip.getMenuTranslationX(),
+ MultiPropertyFactory.MULTI_PROPERTY_VALUE,
+ if (closing) 0f else -additionalTranslationX,
+ )
+ menuTranslationXAnim.interpolator = Interpolators.EMPHASIZED
+ animatorBuilder.with(menuTranslationXAnim)
+ }
+
+ companion object {
+ private val REVEAL_OPEN_DURATION = if (enableOverviewIconMenu()) 417L else 150L
+ private val REVEAL_CLOSE_DURATION = if (enableOverviewIconMenu()) 333L else 100L
+
+ /** Show a task menu for the given taskContainer. */
+ /** Show a task menu for the given taskContainer. */
+ @JvmOverloads
+ fun showForTask(
+ taskContainer: TaskContainer,
+ onClosingStartCallback: Runnable? = null,
+ ): Boolean {
+ val container: RecentsViewContainer =
+ RecentsViewContainer.containerFromContext(taskContainer.taskView.context)
+ val taskMenuView =
+ container.layoutInflater.inflate(R.layout.task_menu, container.dragLayer, false)
+ as TaskMenuView
+ taskMenuView.setOnClosingStartCallback(onClosingStartCallback)
+ return taskMenuView.populateAndShowForTask(taskContainer)
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 0e5382a..27db6d6 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -199,7 +199,7 @@
*/
get() = (getNonGridTrans(nonGridTranslationX) + getGridTrans(this.gridTranslationX))
- protected val persistentTranslationY: Float
+ val persistentTranslationY: Float
/**
* Returns addition of translationY that is persistent (e.g. fullscreen and grid), and does
* not change according to a temporary state (e.g. task offset).
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
index 0204b2d..2dacf69 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
@@ -113,8 +113,8 @@
object : TaskbarNavButtonCallbacks {},
RecentsDisplayModel.INSTANCE.get(context),
) {
- override fun recreateTaskbar() {
- super.recreateTaskbar()
+ override fun recreateTaskbars() {
+ super.recreateTaskbars()
if (currentActivityContext != null) {
injectControllers()
controllerInjectionCallback.invoke()
@@ -146,7 +146,7 @@
}
/** Simulates Taskbar recreation lifecycle. */
- fun recreateTaskbar() = instrumentation.runOnMainSync { taskbarManager.recreateTaskbar() }
+ fun recreateTaskbar() = instrumentation.runOnMainSync { taskbarManager.recreateTaskbars() }
private fun injectControllers() {
val bubbleControllerTypes =
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
index 95e8980..e6806b7 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
@@ -26,7 +26,7 @@
import com.android.launcher3.dagger.ApplicationContext
import com.android.launcher3.dagger.LauncherAppComponent
import com.android.launcher3.dagger.LauncherAppSingleton
-import com.android.launcher3.util.AllModulesForTest
+import com.android.launcher3.util.AllModulesMinusWMProxy
import com.android.launcher3.util.DaggerSingletonTracker
import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.FakePrefsModule
@@ -138,7 +138,13 @@
@LauncherAppSingleton
@Component(
- modules = [AllModulesForTest::class, FakePrefsModule::class, DisplayControllerModule::class]
+ modules =
+ [
+ AllModulesMinusWMProxy::class,
+ FakePrefsModule::class,
+ DisplayControllerModule::class,
+ TaskbarSandboxModule::class,
+ ]
)
interface TaskbarSandboxComponent : LauncherAppComponent {
diff --git a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
index a0ec635..154d86d 100644
--- a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
+++ b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
@@ -300,7 +300,7 @@
@Test
public void testSimpleOrientationTouchTransformer() {
final DisplayController displayController = mock(DisplayController.class);
- doReturn(mInfo).when(displayController).getInfo();
+ doReturn(mInfo).when(displayController).getInfoForDisplay(anyInt());
final SimpleOrientationTouchTransformer transformer =
new SimpleOrientationTouchTransformer(getApplicationContext(), displayController,
mock(DaggerSingletonTracker.class));
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 8c6555e..78ad04b 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -70,6 +70,7 @@
public static final int WORKSPACE_PAGE_INDICATOR = 1 << 5;
public static final int SPLIT_PLACHOLDER_VIEW = 1 << 6;
public static final int FLOATING_SEARCH_BAR = 1 << 7;
+ public static final int ADD_DESK_BUTTON = 1 << 8;
// Flag indicating workspace has multiple pages visible.
public static final int FLAG_MULTI_PAGE = BaseState.getFlag(0);
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index c20d655..b1653d0 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -16,6 +16,7 @@
package com.android.launcher3.secondarydisplay;
import static com.android.launcher3.util.WallpaperThemeManager.setWallpaperDependentTheme;
+import static com.android.window.flags.Flags.enableTaskbarConnectedDisplays;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -124,6 +125,10 @@
mDragLayer = findViewById(R.id.drag_layer);
mAppsView = findViewById(R.id.apps_view);
mAppsButton = findViewById(R.id.all_apps_button);
+ // TODO (b/391965805): Replace this flag with DesktopExperiences flag.
+ if (enableTaskbarConnectedDisplays()) {
+ mAppsButton.setVisibility(View.INVISIBLE);
+ }
mDragController.addDragListener(this);
mPopupDataProvider = new PopupDataProvider(
@@ -243,7 +248,9 @@
@Override
public void onAnimationEnd(Animator animation) {
mAppsView.setVisibility(View.INVISIBLE);
- mAppsButton.setVisibility(View.VISIBLE);
+ // TODO (b/391965805): Replace this flag with DesktopExperiences flag.
+ mAppsButton.setVisibility(
+ enableTaskbarConnectedDisplays() ? View.INVISIBLE : View.VISIBLE);
mAppsView.getSearchUiManager().resetSearch();
}
});
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index ceece4d..52f8887 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -18,6 +18,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static com.android.launcher3.Flags.enableOverviewOnConnectedDisplays;
import static com.android.launcher3.InvariantDeviceProfile.TYPE_MULTI_DISPLAY;
import static com.android.launcher3.InvariantDeviceProfile.TYPE_PHONE;
import static com.android.launcher3.InvariantDeviceProfile.TYPE_TABLET;
@@ -42,9 +43,11 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
+import android.util.SparseArray;
import android.view.Display;
import androidx.annotation.AnyThread;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
@@ -63,8 +66,10 @@
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -78,8 +83,7 @@
*/
@SuppressLint("NewApi")
@LauncherAppSingleton
-public class DisplayController implements ComponentCallbacks,
- DesktopVisibilityListener {
+public class DisplayController implements DesktopVisibilityListener {
private static final String TAG = "DisplayController";
private static final boolean DEBUG = false;
@@ -110,19 +114,18 @@
private final WindowManagerProxy mWMProxy;
- // Null for SDK < S
- private final Context mWindowContext;
+ private final @ApplicationContext Context mAppContext;
// The callback in this listener updates DeviceProfile, which other listeners might depend on
private DisplayInfoChangeListener mPriorityListener;
- private final CopyOnWriteArrayList<DisplayInfoChangeListener> mListeners =
- new CopyOnWriteArrayList<>();
+
+ private final SparseArray<PerDisplayInfo> mPerDisplayInfo =
+ new SparseArray<>();
// We will register broadcast receiver on main thread to ensure not missing changes on
// TARGET_OVERLAY_PACKAGE and ACTION_OVERLAY_CHANGED.
private final SimpleBroadcastReceiver mReceiver;
- private Info mInfo;
private boolean mDestroyed = false;
@Inject
@@ -130,18 +133,20 @@
WindowManagerProxy wmProxy,
LauncherPrefs prefs,
DaggerSingletonTracker lifecycle) {
+ mAppContext = context;
mWMProxy = wmProxy;
if (enableTaskbarPinning()) {
LauncherPrefChangeListener prefListener = key -> {
+ Info info = getInfo();
boolean isTaskbarPinningChanged = TASKBAR_PINNING_KEY.equals(key)
- && mInfo.mIsTaskbarPinned != prefs.get(TASKBAR_PINNING);
+ && info.mIsTaskbarPinned != prefs.get(TASKBAR_PINNING);
boolean isTaskbarPinningDesktopModeChanged =
TASKBAR_PINNING_DESKTOP_MODE_KEY.equals(key)
- && mInfo.mIsTaskbarPinnedInDesktopMode != prefs.get(
+ && info.mIsTaskbarPinnedInDesktopMode != prefs.get(
TASKBAR_PINNING_IN_DESKTOP_MODE);
if (isTaskbarPinningChanged || isTaskbarPinningDesktopModeChanged) {
- notifyConfigChange();
+ notifyConfigChange(DEFAULT_DISPLAY);
}
};
@@ -151,23 +156,49 @@
prefListener, TASKBAR_PINNING, TASKBAR_PINNING_IN_DESKTOP_MODE));
}
- Display display = context.getSystemService(DisplayManager.class)
- .getDisplay(DEFAULT_DISPLAY);
- mWindowContext = context.createWindowContext(display, TYPE_APPLICATION, null);
- mWindowContext.registerComponentCallbacks(this);
+ DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+ Display defaultDisplay = displayManager.getDisplay(DEFAULT_DISPLAY);
+ PerDisplayInfo defaultPerDisplayInfo = getOrCreatePerDisplayInfo(defaultDisplay);
// Initialize navigation mode change listener
mReceiver = new SimpleBroadcastReceiver(context, MAIN_EXECUTOR, this::onIntent);
mReceiver.registerPkgActions(TARGET_OVERLAY_PACKAGE, ACTION_OVERLAY_CHANGED);
- mInfo = new Info(mWindowContext, wmProxy,
- wmProxy.estimateInternalDisplayBounds(mWindowContext));
wmProxy.registerDesktopVisibilityListener(this);
- FileLog.i(TAG, "(CTOR) perDisplayBounds: " + mInfo.mPerDisplayBounds);
+ FileLog.i(TAG, "(CTOR) perDisplayBounds: "
+ + defaultPerDisplayInfo.mInfo.mPerDisplayBounds);
+
+ if (enableOverviewOnConnectedDisplays()) {
+ final DisplayManager.DisplayListener displayListener =
+ new DisplayManager.DisplayListener() {
+ @Override
+ public void onDisplayAdded(int displayId) {
+ getOrCreatePerDisplayInfo(displayManager.getDisplay(displayId));
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ removePerDisplayInfo(displayId);
+ }
+ };
+ displayManager.registerDisplayListener(displayListener, MAIN_EXECUTOR.getHandler());
+ lifecycle.addCloseable(() -> {
+ displayManager.unregisterDisplayListener(displayListener);
+ });
+ // Add any PerDisplayInfos for already-connected displays.
+ Arrays.stream(displayManager.getDisplays())
+ .forEach((it) ->
+ getOrCreatePerDisplayInfo(
+ displayManager.getDisplay(it.getDisplayId())));
+ }
lifecycle.addCloseable(() -> {
mDestroyed = true;
- mWindowContext.unregisterComponentCallbacks(this);
+ defaultPerDisplayInfo.cleanup();
mReceiver.unregisterReceiverSafely();
wmProxy.unregisterDesktopVisibilityListener(this);
});
@@ -236,9 +267,7 @@
@Override
public void onIsInDesktopModeChanged(int displayId, boolean isInDesktopModeAndNotInOverview) {
- if (DEFAULT_DISPLAY == displayId) {
- notifyConfigChange();
- }
+ notifyConfigChange(displayId);
}
/**
@@ -261,60 +290,88 @@
}
if (ACTION_OVERLAY_CHANGED.equals(intent.getAction())) {
Log.d(TAG, "Overlay changed, notifying listeners");
- notifyConfigChange();
+ notifyConfigChange(DEFAULT_DISPLAY);
}
}
+ @VisibleForTesting
+ public void onConfigurationChanged(Configuration config) {
+ onConfigurationChanged(config, DEFAULT_DISPLAY);
+ }
+
@UiThread
- @Override
- public final void onConfigurationChanged(Configuration config) {
+ private void onConfigurationChanged(Configuration config, int displayId) {
Log.d(TASKBAR_NOT_DESTROYED_TAG, "DisplayController#onConfigurationChanged: " + config);
- if (config.densityDpi != mInfo.densityDpi
- || config.fontScale != mInfo.fontScale
- || !mInfo.mScreenSizeDp.equals(
- new PortraitSize(config.screenHeightDp, config.screenWidthDp))
- || mWindowContext.getDisplay().getRotation() != mInfo.rotation
- || mWMProxy.showLockedTaskbarOnHome(mWindowContext)
- != mInfo.showLockedTaskbarOnHome()
- || mWMProxy.showDesktopTaskbarForFreeformDisplay(mWindowContext)
- != mInfo.showDesktopTaskbarForFreeformDisplay()) {
- notifyConfigChange();
+ PerDisplayInfo perDisplayInfo = mPerDisplayInfo.get(displayId);
+ Context windowContext = perDisplayInfo.mWindowContext;
+ Info info = perDisplayInfo.mInfo;
+ if (config.densityDpi != info.densityDpi
+ || config.fontScale != info.fontScale
+ || !info.mScreenSizeDp.equals(
+ new PortraitSize(config.screenHeightDp, config.screenWidthDp))
+ || windowContext.getDisplay().getRotation() != info.rotation
+ || mWMProxy.showLockedTaskbarOnHome(windowContext)
+ != info.showLockedTaskbarOnHome()
+ || mWMProxy.showDesktopTaskbarForFreeformDisplay(windowContext)
+ != info.showDesktopTaskbarForFreeformDisplay()) {
+ notifyConfigChange(displayId);
}
}
- @Override
- public final void onLowMemory() { }
-
public void setPriorityListener(DisplayInfoChangeListener listener) {
mPriorityListener = listener;
}
public void addChangeListener(DisplayInfoChangeListener listener) {
- mListeners.add(listener);
+ addChangeListenerForDisplay(listener, DEFAULT_DISPLAY);
}
public void removeChangeListener(DisplayInfoChangeListener listener) {
- mListeners.remove(listener);
+ removeChangeListenerForDisplay(listener, DEFAULT_DISPLAY);
+ }
+
+ public void addChangeListenerForDisplay(DisplayInfoChangeListener listener, int displayId) {
+ PerDisplayInfo perDisplayInfo = mPerDisplayInfo.get(displayId);
+ if (perDisplayInfo != null) {
+ perDisplayInfo.addListener(listener);
+ }
+ }
+
+ public void removeChangeListenerForDisplay(DisplayInfoChangeListener listener, int displayId) {
+ PerDisplayInfo perDisplayInfo = mPerDisplayInfo.get(displayId);
+ if (perDisplayInfo != null) {
+ perDisplayInfo.removeListener(listener);
+ }
}
public Info getInfo() {
- return mInfo;
+ return mPerDisplayInfo.get(DEFAULT_DISPLAY).mInfo;
+ }
+
+ public @Nullable Info getInfoForDisplay(int displayId) {
+ if (enableOverviewOnConnectedDisplays()) {
+ PerDisplayInfo perDisplayInfo = mPerDisplayInfo.get(displayId);
+ if (perDisplayInfo != null) {
+ return perDisplayInfo.mInfo;
+ } else {
+ return null;
+ }
+ } else {
+ return getInfo();
+ }
}
@AnyThread
public void notifyConfigChange() {
- Info oldInfo = mInfo;
+ notifyConfigChange(DEFAULT_DISPLAY);
+ }
- Context displayInfoContext = mWindowContext;
- Info newInfo = new Info(displayInfoContext, mWMProxy, oldInfo.mPerDisplayBounds);
+ @AnyThread
+ public void notifyConfigChange(int displayId) {
+ notifyConfigChangeForDisplay(displayId);
+ }
- if (newInfo.densityDpi != oldInfo.densityDpi || newInfo.fontScale != oldInfo.fontScale
- || newInfo.getNavigationMode() != oldInfo.getNavigationMode()) {
- // Cache may not be valid anymore, recreate without cache
- newInfo = new Info(displayInfoContext, mWMProxy,
- mWMProxy.estimateInternalDisplayBounds(displayInfoContext));
- }
-
+ private int calculateChange(Info oldInfo, Info newInfo) {
int change = 0;
if (!newInfo.normalizedDisplayInfo.equals(oldInfo.normalizedDisplayInfo)) {
change |= CHANGE_ACTIVE_SCREEN;
@@ -336,7 +393,7 @@
}
if ((newInfo.mIsTaskbarPinned != oldInfo.mIsTaskbarPinned)
|| (newInfo.mIsTaskbarPinnedInDesktopMode
- != oldInfo.mIsTaskbarPinnedInDesktopMode)
+ != oldInfo.mIsTaskbarPinnedInDesktopMode)
|| newInfo.isPinnedTaskbar() != oldInfo.isPinnedTaskbar()) {
change |= CHANGE_TASKBAR_PINNING;
}
@@ -350,23 +407,68 @@
if (DEBUG) {
Log.d(TAG, "handleInfoChange - change: " + getChangeFlagsString(change));
}
+ return change;
+ }
- if (change != 0) {
- mInfo = newInfo;
- final int flags = change;
- MAIN_EXECUTOR.execute(() -> notifyChange(displayInfoContext, flags));
+ private Info getNewInfo(Info oldInfo, Context displayInfoContext) {
+ Info newInfo = new Info(displayInfoContext, mWMProxy, oldInfo.mPerDisplayBounds);
+
+ if (newInfo.densityDpi != oldInfo.densityDpi || newInfo.fontScale != oldInfo.fontScale
+ || newInfo.getNavigationMode() != oldInfo.getNavigationMode()) {
+ // Cache may not be valid anymore, recreate without cache
+ newInfo = new Info(displayInfoContext, mWMProxy,
+ mWMProxy.estimateInternalDisplayBounds(displayInfoContext));
+ }
+ return newInfo;
+ }
+
+ @AnyThread
+ public void notifyConfigChangeForDisplay(int displayId) {
+ PerDisplayInfo perDisplayInfo = mPerDisplayInfo.get(displayId);
+ if (perDisplayInfo == null) return;
+ Info oldInfo = perDisplayInfo.mInfo;
+ final Info newInfo = getNewInfo(oldInfo, perDisplayInfo.mWindowContext);
+ final int flags = calculateChange(oldInfo, newInfo);
+ if (flags != 0) {
+ MAIN_EXECUTOR.execute(() -> {
+ perDisplayInfo.mInfo = newInfo;
+ if (displayId == DEFAULT_DISPLAY && mPriorityListener != null) {
+ mPriorityListener.onDisplayInfoChanged(perDisplayInfo.mWindowContext, newInfo,
+ flags);
+ }
+ perDisplayInfo.notifyListeners(newInfo, flags);
+ });
}
}
- private void notifyChange(Context context, int flags) {
- if (mPriorityListener != null) {
- mPriorityListener.onDisplayInfoChanged(context, mInfo, flags);
+ private PerDisplayInfo getOrCreatePerDisplayInfo(Display display) {
+ int displayId = display.getDisplayId();
+ PerDisplayInfo perDisplayInfo = mPerDisplayInfo.get(displayId);
+ if (perDisplayInfo != null) {
+ return perDisplayInfo;
}
+ if (DEBUG) {
+ Log.d(TAG,
+ String.format("getOrCreatePerDisplayInfo - no cached value found for %d",
+ displayId));
+ }
+ Context windowContext = mAppContext.createWindowContext(display, TYPE_APPLICATION, null);
+ Info info = new Info(windowContext, mWMProxy,
+ mWMProxy.estimateInternalDisplayBounds(windowContext));
+ perDisplayInfo = new PerDisplayInfo(displayId, windowContext, info);
+ mPerDisplayInfo.put(displayId, perDisplayInfo);
+ return perDisplayInfo;
+ }
- int count = mListeners.size();
- for (int i = 0; i < count; i++) {
- mListeners.get(i).onDisplayInfoChanged(context, mInfo, flags);
- }
+ /**
+ * Clean up resources for the given display id.
+ * @param displayId The display id
+ */
+ void removePerDisplayInfo(int displayId) {
+ PerDisplayInfo info = mPerDisplayInfo.get(displayId);
+ if (info == null) return;
+ info.cleanup();
+ mPerDisplayInfo.remove(displayId);
}
public static class Info {
@@ -601,21 +703,29 @@
* Dumps the current state information
*/
public void dump(PrintWriter pw) {
- Info info = mInfo;
- pw.println("DisplayController.Info:");
- pw.println(" normalizedDisplayInfo=" + info.normalizedDisplayInfo);
- pw.println(" rotation=" + info.rotation);
- pw.println(" fontScale=" + info.fontScale);
- pw.println(" densityDpi=" + info.densityDpi);
- pw.println(" navigationMode=" + info.getNavigationMode().name());
- pw.println(" isTaskbarPinned=" + info.mIsTaskbarPinned);
- pw.println(" isTaskbarPinnedInDesktopMode=" + info.mIsTaskbarPinnedInDesktopMode);
- pw.println(" isInDesktopMode=" + info.mIsInDesktopMode);
- pw.println(" showLockedTaskbarOnHome=" + info.showLockedTaskbarOnHome());
- pw.println(" currentSize=" + info.currentSize);
- info.mPerDisplayBounds.forEach((key, value) -> pw.println(
- " perDisplayBounds - " + key + ": " + value));
- pw.println(" isTransientTaskbar=" + info.isTransientTaskbar());
+ int count = mPerDisplayInfo.size();
+ for (int i = 0; i < count; ++i) {
+ int displayId = mPerDisplayInfo.keyAt(i);
+ Info info = getInfoForDisplay(displayId);
+ if (info == null) {
+ continue;
+ }
+ pw.println(String.format(Locale.ENGLISH, "DisplayController.Info (displayId=%d):",
+ displayId));
+ pw.println(" normalizedDisplayInfo=" + info.normalizedDisplayInfo);
+ pw.println(" rotation=" + info.rotation);
+ pw.println(" fontScale=" + info.fontScale);
+ pw.println(" densityDpi=" + info.densityDpi);
+ pw.println(" navigationMode=" + info.getNavigationMode().name());
+ pw.println(" isTaskbarPinned=" + info.mIsTaskbarPinned);
+ pw.println(" isTaskbarPinnedInDesktopMode=" + info.mIsTaskbarPinnedInDesktopMode);
+ pw.println(" isInDesktopMode=" + info.mIsInDesktopMode);
+ pw.println(" showLockedTaskbarOnHome=" + info.showLockedTaskbarOnHome());
+ pw.println(" currentSize=" + info.currentSize);
+ info.mPerDisplayBounds.forEach((key, value) -> pw.println(
+ " perDisplayBounds - " + key + ": " + value));
+ pw.println(" isTransientTaskbar=" + info.isTransientTaskbar());
+ }
}
/**
@@ -643,4 +753,47 @@
}
}
+ private class PerDisplayInfo implements ComponentCallbacks {
+ final int mDisplayId;
+ final CopyOnWriteArrayList<DisplayInfoChangeListener> mListeners =
+ new CopyOnWriteArrayList<>();
+ final Context mWindowContext;
+ Info mInfo;
+
+ PerDisplayInfo(int displayId, Context windowContext, Info info) {
+ this.mDisplayId = displayId;
+ this.mWindowContext = windowContext;
+ this.mInfo = info;
+ windowContext.registerComponentCallbacks(this);
+ }
+
+ void addListener(DisplayInfoChangeListener listener) {
+ mListeners.add(listener);
+ }
+
+ void removeListener(DisplayInfoChangeListener listener) {
+ mListeners.remove(listener);
+ }
+
+ void notifyListeners(Info info, int flags) {
+ int count = mListeners.size();
+ for (int i = 0; i < count; ++i) {
+ mListeners.get(i).onDisplayInfoChanged(mWindowContext, info, flags);
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ DisplayController.this.onConfigurationChanged(newConfig, mDisplayId);
+ }
+
+ @Override
+ public void onLowMemory() {}
+
+ void cleanup() {
+ mWindowContext.unregisterComponentCallbacks(this);
+ mListeners.clear();
+ }
+ }
+
}
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index f633d48..214f158 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -157,7 +157,7 @@
LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"dismissing all tasks")) {
final BySelector clearAllSelector = mLauncher.getOverviewObjectSelector("clear_all");
- flingForwardUntilClearAllVisible();
+ flingForwardUntilClearAllVisibleImpl();
final Runnable clickClearAll = () -> mLauncher.clickLauncherObject(
mLauncher.waitForObjectInContainer(verifyActiveContainer(),
@@ -181,10 +181,19 @@
* Scrolls until Clear-all button is visible.
*/
public void flingForwardUntilClearAllVisible() {
- final BySelector clearAllSelector = mLauncher.getOverviewObjectSelector("clear_all");
- for (int i = 0; i < FLINGS_FOR_DISMISS_LIMIT
- && !verifyActiveContainer().hasObject(clearAllSelector); ++i) {
- flingForwardImpl();
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ flingForwardUntilClearAllVisibleImpl();
+ }
+ }
+
+ private void flingForwardUntilClearAllVisibleImpl() {
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "flinging forward to clear all")) {
+ final BySelector clearAllSelector = mLauncher.getOverviewObjectSelector("clear_all");
+ for (int i = 0; i < FLINGS_FOR_DISMISS_LIMIT && !verifyActiveContainer().hasObject(
+ clearAllSelector); ++i) {
+ flingForwardImpl();
+ }
}
}