Merge "More test for taskbar overflow" 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/res/values-cs/strings.xml b/res/values-cs/strings.xml
index f96b907..b9f0ba2 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -127,7 +127,7 @@
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Složka: <xliff:g id="NAME">%1$s</xliff:g>, počet položek: <xliff:g id="SIZE">%2$d</xliff:g> nebo více"</string>
<string name="unnamed_folder" msgid="2420192029474044442">"Nepojmenovaná složka"</string>
<string name="app_pair_name_format" msgid="8134106404716224054">"Dvojice aplikací: <xliff:g id="APP1">%1$s</xliff:g> a <xliff:g id="APP2">%2$s</xliff:g>"</string>
- <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Tapeta a styl"</string>
+ <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Tapety a styl"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Upravit plochu"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Nastavení plochy"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"Zakázáno administrátorem"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index c45d372..abaaa89 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -64,8 +64,8 @@
<string name="widgets_full_sheet_cancel_button_description" msgid="5766167035728653605">"پاک کردن نوشتار از چارگوش جستجو"</string>
<string name="no_widgets_available" msgid="4337693382501046170">"ابزاره و میانبری دردسترس نیست"</string>
<string name="no_search_results" msgid="3787956167293097509">"هیچ ابزاره یا میانبری پیدا نشد"</string>
- <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"ابزارههای شخصی"</string>
- <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ابزارههای کاری"</string>
+ <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"شخصی"</string>
+ <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"کاری"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"مکالمهها"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"یادداشتبرداری"</string>
<string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"نشان دادن دکمه افزودن"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 701bb25..e016f20 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -64,7 +64,7 @@
<string name="widgets_full_sheet_cancel_button_description" msgid="5766167035728653605">"Limpe o texto da caixa de pesquisa"</string>
<string name="no_widgets_available" msgid="4337693382501046170">"Os widgets e os atalhos não estão disponíveis"</string>
<string name="no_search_results" msgid="3787956167293097509">"Nenhum widget ou atalho encontrado"</string>
- <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"Pessoais"</string>
+ <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"Pessoal"</string>
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Trabalho"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Conversas"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Tomar notas"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index ad5e281..d2bca52 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -64,8 +64,8 @@
<string name="widgets_full_sheet_cancel_button_description" msgid="5766167035728653605">"Rensa texten från sökrutan"</string>
<string name="no_widgets_available" msgid="4337693382501046170">"Widgetar och genvägar är inte tillgängliga"</string>
<string name="no_search_results" msgid="3787956167293097509">"Inga widgetar eller genvägar hittades"</string>
- <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"Privat"</string>
- <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Arbete"</string>
+ <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"Personligt"</string>
+ <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Jobb"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Konversationer"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"Anteckna"</string>
<string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Visa knappen Lägg till"</string>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index c3cb31d..7aa709d 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -307,6 +307,7 @@
<!-- Folders -->
<dimen name="page_indicator_dot_size">6dp</dimen>
+ <dimen name="page_indicator_gap_width">4dp</dimen>
<dimen name="page_indicator_size">10dp</dimen>
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/graphics/GridCustomizationsProxy.java b/src/com/android/launcher3/graphics/GridCustomizationsProxy.java
index 40863c4..70b9f46 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProxy.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProxy.java
@@ -55,6 +55,7 @@
import com.android.systemui.shared.Flags;
import java.lang.ref.WeakReference;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@@ -130,8 +131,6 @@
private static final int MESSAGE_ID_UPDATE_GRID = 7414;
private static final int MESSAGE_ID_UPDATE_COLOR = 856;
- private static final String DEFAULT_SHAPE_KEY = "circle";
-
// Set of all active previews used to track duplicate memory allocations
private final Set<PreviewLifecycleObserver> mActivePreviews =
Collections.newSetFromMap(new ConcurrentHashMap<>());
@@ -170,18 +169,18 @@
MatrixCursor cursor = new MatrixCursor(new String[]{
KEY_SHAPE_KEY, KEY_SHAPE_TITLE, KEY_PATH, KEY_IS_DEFAULT});
String currentShapePath = mThemeManager.getIconState().getIconMask();
- Optional<IconShapeModel> selectedShape = ShapesProvider.INSTANCE.getIconShapes()
- .values()
- .stream()
- .filter(shape -> shape.getPathString().equals(currentShapePath))
- .findFirst();
+ Optional<IconShapeModel> selectedShape = Arrays.stream(
+ ShapesProvider.INSTANCE.getIconShapes()).filter(
+ shape -> shape.getPathString().equals(currentShapePath)
+ ).findFirst();
// Handle default for when current shape doesn't match new shapes.
if (selectedShape.isEmpty()) {
- selectedShape = Optional.ofNullable(ShapesProvider.INSTANCE.getIconShapes()
- .get(DEFAULT_SHAPE_KEY));
+ selectedShape = Optional.of(Arrays.stream(
+ ShapesProvider.INSTANCE.getIconShapes()
+ ).findFirst().get());
}
- for (IconShapeModel shape : ShapesProvider.INSTANCE.getIconShapes().values()) {
+ for (IconShapeModel shape : ShapesProvider.INSTANCE.getIconShapes()) {
cursor.newRow()
.add(KEY_SHAPE_KEY, shape.getKey())
.add(KEY_SHAPE_TITLE, shape.getTitle())
diff --git a/src/com/android/launcher3/graphics/ThemeManager.kt b/src/com/android/launcher3/graphics/ThemeManager.kt
index de85460..ebb7ea0 100644
--- a/src/com/android/launcher3/graphics/ThemeManager.kt
+++ b/src/com/android/launcher3/graphics/ThemeManager.kt
@@ -103,7 +103,7 @@
private fun parseIconState(oldState: IconState?): IconState {
val shapeModel =
prefs.get(PREF_ICON_SHAPE).let { shapeOverride ->
- ShapesProvider.iconShapes.values.firstOrNull { it.key == shapeOverride }
+ ShapesProvider.iconShapes.firstOrNull { it.key == shapeOverride }
}
val iconMask =
when {
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index a691e45..37f5189 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -16,6 +16,7 @@
package com.android.launcher3.pageindicators;
+import static com.android.launcher3.Flags.enableLauncherVisualRefresh;
import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
import android.animation.Animator;
@@ -57,8 +58,8 @@
public class PageIndicatorDots extends View implements Insettable, PageIndicator {
private static final float SHIFT_PER_ANIMATION = 0.5f;
- private static final float SHIFT_THRESHOLD = 0.1f;
- private static final long ANIMATION_DURATION = 150;
+ private static final float SHIFT_THRESHOLD = (enableLauncherVisualRefresh() ? 0.5f : 0.2f);
+ private static final long ANIMATION_DURATION = (enableLauncherVisualRefresh() ? 200 : 150);
private static final int PAGINATION_FADE_DELAY = ViewConfiguration.getScrollDefaultDelay();
private static final int PAGINATION_FADE_IN_DURATION = 83;
private static final int PAGINATION_FADE_OUT_DURATION = 167;
@@ -78,6 +79,7 @@
// This value approximately overshoots to 1.5 times the original size.
private static final float ENTER_ANIMATION_OVERSHOOT_TENSION = 4.9f;
+ // This is used to optimize the onDraw method by not constructing a new RectF each draw.
private static final RectF sTempRect = new RectF();
private static final FloatProperty<PageIndicatorDots> CURRENT_POSITION =
@@ -93,7 +95,7 @@
obj.invalidate();
obj.invalidateOutline();
}
- };
+ };
private static final IntProperty<PageIndicatorDots> PAGINATION_ALPHA =
new IntProperty<PageIndicatorDots>("pagination_alpha") {
@@ -111,6 +113,7 @@
private final Handler mDelayedPaginationFadeHandler = new Handler(Looper.getMainLooper());
private final float mDotRadius;
+ private final float mGapWidth;
private final float mCircleGap;
private final boolean mIsRtl;
@@ -130,6 +133,7 @@
* 1.0 => Active dot is at position 1
*/
private float mCurrentPosition;
+ private int mLastPosition;
private float mFinalPosition;
private boolean mIsScrollPaused;
@VisibleForTesting
@@ -157,7 +161,10 @@
mPaginationPaint.setStyle(Style.FILL);
mPaginationPaint.setColor(Themes.getAttrColor(context, R.attr.pageIndicatorDotColor));
mDotRadius = getResources().getDimension(R.dimen.page_indicator_dot_size) / 2;
- mCircleGap = DOT_GAP_FACTOR * mDotRadius;
+ mGapWidth = getResources().getDimension(R.dimen.page_indicator_gap_width);
+ mCircleGap = (enableLauncherVisualRefresh())
+ ? mDotRadius * 2 + mGapWidth
+ : DOT_GAP_FACTOR * mDotRadius;
setOutlineProvider(new MyOutlineProver());
mIsRtl = Utilities.isRtl(getResources());
}
@@ -188,29 +195,40 @@
mTotalScroll = totalScroll;
- int scrollPerPage = totalScroll / (mNumPages - 1);
- int pageToLeft = scrollPerPage == 0 ? 0 : currentScroll / scrollPerPage;
- int pageToLeftScroll = pageToLeft * scrollPerPage;
- int pageToRightScroll = pageToLeftScroll + scrollPerPage;
+ if (enableLauncherVisualRefresh()) {
+ float scrollPerPage = (float) totalScroll / (mNumPages - 1);
+ float position = currentScroll / scrollPerPage;
+ animateToPosition(Math.round(position));
- float scrollThreshold = SHIFT_THRESHOLD * scrollPerPage;
- if (currentScroll < pageToLeftScroll + scrollThreshold) {
- // scroll is within the left page's threshold
- animateToPosition(pageToLeft);
- if (mShouldAutoHide) {
- hideAfterDelay();
- }
- } else if (currentScroll > pageToRightScroll - scrollThreshold) {
- // scroll is far enough from left page to go to the right page
- animateToPosition(pageToLeft + 1);
- if (mShouldAutoHide) {
+ float delta = Math.abs((int) position - position);
+ if (mShouldAutoHide && (delta < 0.1 || delta > 0.9)) {
hideAfterDelay();
}
} else {
- // scroll is between left and right page
- animateToPosition(pageToLeft + SHIFT_PER_ANIMATION);
- if (mShouldAutoHide) {
- mDelayedPaginationFadeHandler.removeCallbacksAndMessages(null);
+ int scrollPerPage = totalScroll / (mNumPages - 1);
+ int pageToLeft = scrollPerPage == 0 ? 0 : currentScroll / scrollPerPage;
+ int pageToLeftScroll = pageToLeft * scrollPerPage;
+ int pageToRightScroll = pageToLeftScroll + scrollPerPage;
+
+ float scrollThreshold = SHIFT_THRESHOLD * scrollPerPage;
+ if (currentScroll < pageToLeftScroll + scrollThreshold) {
+ // scroll is within the left page's threshold
+ animateToPosition(pageToLeft);
+ if (mShouldAutoHide) {
+ hideAfterDelay();
+ }
+ } else if (currentScroll > pageToRightScroll - scrollThreshold) {
+ // scroll is far enough from left page to go to the right page
+ animateToPosition(pageToLeft + 1);
+ if (mShouldAutoHide) {
+ hideAfterDelay();
+ }
+ } else {
+ // scroll is between left and right page
+ animateToPosition(pageToLeft + SHIFT_PER_ANIMATION);
+ if (mShouldAutoHide) {
+ mDelayedPaginationFadeHandler.removeCallbacksAndMessages(null);
+ }
}
}
}
@@ -283,15 +301,23 @@
private void animateToPosition(float position) {
mFinalPosition = position;
- if (Math.abs(mCurrentPosition - mFinalPosition) < SHIFT_THRESHOLD) {
+ if (!enableLauncherVisualRefresh()
+ && Math.abs(mCurrentPosition - mFinalPosition) < SHIFT_THRESHOLD) {
mCurrentPosition = mFinalPosition;
}
- if (mAnimator == null && Float.compare(mCurrentPosition, mFinalPosition) != 0) {
- float positionForThisAnim = mCurrentPosition > mFinalPosition ?
- mCurrentPosition - SHIFT_PER_ANIMATION : mCurrentPosition + SHIFT_PER_ANIMATION;
+ if (mAnimator == null && Float.compare(mCurrentPosition, position) != 0) {
+ float positionForThisAnim = enableLauncherVisualRefresh()
+ ? position
+ : (mCurrentPosition > mFinalPosition
+ ? mCurrentPosition - SHIFT_PER_ANIMATION
+ : mCurrentPosition + SHIFT_PER_ANIMATION);
mAnimator = ObjectAnimator.ofFloat(this, CURRENT_POSITION, positionForThisAnim);
mAnimator.addListener(new AnimationCycleListener());
mAnimator.setDuration(ANIMATION_DURATION);
+ if (enableLauncherVisualRefresh()) {
+ mLastPosition = (int) mCurrentPosition;
+ mAnimator.setInterpolator(new OvershootInterpolator());
+ }
mAnimator.start();
}
}
@@ -314,6 +340,7 @@
invalidate();
}
+ // TODO(b/394355070): Verify Folder Entry Animation works correctly with visual updates
public void playEntryAnimation() {
int count = mEntryAnimationRadiusFactors.length;
if (count == 0) {
@@ -391,6 +418,7 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // TODO(b/394355070): Verify Folder Entry Animation works correctly with visual updates
// Add extra spacing of mDotRadius on all sides so than entry animation could be run.
int width = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY ?
MeasureSpec.getSize(widthMeasureSpec) : (int) ((mNumPages * 3 + 2) * mDotRadius);
@@ -410,17 +438,14 @@
return;
}
- // Draw all page indicators;
float circleGap = mCircleGap;
- float startX = ((float) getWidth() / 2)
- - (mCircleGap * (((float) mNumPages - 1) / 2))
- - mDotRadius;
-
- float x = startX + mDotRadius;
+ float x = ((float) getWidth() / 2) - (mCircleGap * ((float) mNumPages - 1) / 2);
float y = getHeight() / 2;
if (mEntryAnimationRadiusFactors != null) {
// During entry animation, only draw the circles
+ // TODO(b/394355070): Verify Folder Entry Animation works correctly - visual updates
+
if (mIsRtl) {
x = getWidth() - x;
circleGap = -circleGap;
@@ -432,18 +457,84 @@
x += circleGap;
}
} else {
+ // Save the current alpha value, so we can reset to it again after drawing the dots
int alpha = mPaginationPaint.getAlpha();
- // Here we draw the dots
- mPaginationPaint.setAlpha((int) (alpha * DOT_ALPHA_FRACTION));
- for (int i = 0; i < mNumPages; i++) {
- canvas.drawCircle(x, y, mDotRadius, mPaginationPaint);
- x += circleGap;
+ if (enableLauncherVisualRefresh()) {
+ int nonActiveAlpha = (int) (alpha * DOT_ALPHA_FRACTION);
+
+ float diameter = 2 * mDotRadius;
+ sTempRect.top = y - mDotRadius;
+ sTempRect.bottom = y + mDotRadius;
+ sTempRect.left = x - diameter;
+
+ float posDif = Math.abs(mLastPosition - mCurrentPosition);
+ float boundedPosition = (posDif > 1)
+ ? Math.round(mCurrentPosition)
+ : mCurrentPosition;
+ float bounceProgress = (posDif > 1) ? posDif - 1 : 0;
+ float bounceAdjustment = Math.abs(mCurrentPosition - boundedPosition) * diameter;
+
+ // Here we draw the dots, one at a time from the left-most dot to the right-most dot
+ // 1.0 => 000000 000000111111 000000
+ // 1.3 => 000000 0000001111 11000000
+ // 1.6 => 000000 00000011 1111000000
+ // 2.0 => 000000 000000 111111000000
+ for (int i = 0; i < mNumPages; i++) {
+ mPaginationPaint.setAlpha(nonActiveAlpha);
+ float delta = Math.abs(boundedPosition - i);
+ if (delta <= SHIFT_THRESHOLD) {
+ mPaginationPaint.setAlpha(alpha);
+ }
+
+ // If boundedPosition is 3.3, both 3 and 4 should enter this condition.
+ // If boundedPosition is 3, only 3 should enter this condition.
+ if (delta < 1) {
+ sTempRect.right = sTempRect.left + diameter + ((1 - delta) * diameter);
+
+ // While the animation is shifting the active pagination dots size from
+ // the previously active one, to the newly active dot, there is no bounce
+ // adjustment. The bounce happens in the "Overshoot" phase of the animation.
+ // mLastPosition is used to determine when the currentPosition is just
+ // leaving the page, or if it is in the overshoot phase.
+ if (boundedPosition == i && bounceProgress != 0) {
+ if (mLastPosition < mCurrentPosition) {
+ sTempRect.left -= bounceAdjustment;
+ } else {
+ sTempRect.right += bounceAdjustment;
+ }
+ }
+ } else {
+ sTempRect.right = sTempRect.left + diameter;
+
+ if (mLastPosition == i && bounceProgress != 0) {
+ if (mLastPosition > mCurrentPosition) {
+ sTempRect.left += bounceAdjustment;
+ } else {
+ sTempRect.right -= bounceAdjustment;
+ }
+ }
+ }
+ canvas.drawRoundRect(sTempRect, mDotRadius, mDotRadius, mPaginationPaint);
+
+ // TODO(b/394355070) Verify RTL experience works correctly with visual updates
+ sTempRect.left = sTempRect.right + mGapWidth;
+ }
+ } else {
+ // Here we draw the dots
+ mPaginationPaint.setAlpha((int) (alpha * DOT_ALPHA_FRACTION));
+ for (int i = 0; i < mNumPages; i++) {
+ canvas.drawCircle(x, y, mDotRadius, mPaginationPaint);
+ x += circleGap;
+ }
+
+ // Here we draw the current page indicator
+ mPaginationPaint.setAlpha(alpha);
+ canvas.drawRoundRect(getActiveRect(), mDotRadius, mDotRadius, mPaginationPaint);
}
- // Here we draw the current page indicator
+ // Reset the alpha so it doesn't become progressively more transparent each onDraw call
mPaginationPaint.setAlpha(alpha);
- canvas.drawRoundRect(getActiveRect(), mDotRadius, mDotRadius, mPaginationPaint);
}
}
@@ -499,6 +590,7 @@
@Override
public void getOutline(View view, Outline outline) {
if (mEntryAnimationRadiusFactors == null) {
+ // TODO(b/394355070): Verify Outline works correctly with visual updates
RectF activeRect = getActiveRect();
outline.setRoundRect(
(int) activeRect.left,
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/shapes/ShapesProvider.kt b/src/com/android/launcher3/shapes/ShapesProvider.kt
index 5427c89..03e30d8 100644
--- a/src/com/android/launcher3/shapes/ShapesProvider.kt
+++ b/src/com/android/launcher3/shapes/ShapesProvider.kt
@@ -16,192 +16,71 @@
package com.android.launcher3.shapes
+import androidx.annotation.VisibleForTesting
import com.android.launcher3.Flags as LauncherFlags
import com.android.systemui.shared.Flags
object ShapesProvider {
- val folderShapes =
- if (LauncherFlags.enableLauncherIconShapes()) {
- mapOf(
- "clover" to
- "M 39.616 4" +
- "C 46.224 6.87 53.727 6.87 60.335 4" +
- "L 63.884 2.459" +
- "C 85.178 -6.789 106.789 14.822 97.541 36.116" +
- "L 96 39.665" +
- "C 93.13 46.273 93.13 53.776 96 60.384" +
- "L 97.541 63.934" +
- "C 106.789 85.227 85.178 106.839 63.884 97.591" +
- "L 60.335 96.049" +
- "C 53.727 93.179 46.224 93.179 39.616 96.049" +
- "L 36.066 97.591" +
- "C 14.773 106.839 -6.839 85.227 2.409 63.934" +
- "L 3.951 60.384" +
- "C 6.821 53.776 6.821 46.273 3.951 39.665" +
- "L 2.409 36.116" +
- "C -6.839 14.822 14.773 -6.789 36.066 2.459" +
- "Z",
- "complexClover" to
- "M 49.85 6.764" +
- "L 50.013 6.971" +
- "L 50.175 6.764" +
- "C 53.422 2.635 58.309 0.207 63.538 0.207" +
- "C 65.872 0.207 68.175 0.692 70.381 1.648" +
- "L 71.79 2.264" +
- "L 71.792 2.265" +
- "A 3.46 3.46 0 0 0 74.515 2.265" +
- "L 74.517 2.264" +
- "L 75.926 1.652" +
- "A 17.1 17.1 0 0 1 82.769 0.207" +
- "C 88.495 0.207 93.824 3.117 97.022 7.989" +
- "C 100.21 12.848 100.697 18.712 98.36 24.087" +
- "L 97.749 25.496" +
- "V 25.497" +
- "A 3.45 3.45 0 0 0 97.749 28.222" +
- "V 28.223" +
- "L 98.36 29.632" +
- "C 100.697 35.007 100.207 40.871 97.022 45.73" +
- "A 17.5 17.5 0 0 1 93.264 49.838" +
- "L 93.06 50" +
- "L 93.264 50.162" +
- "A 17.5 17.5 0 0 1 97.022 54.27" +
- "C 100.21 59.129 100.697 64.993 98.36 70.368" +
- "V 71.778" +
- "A 3.45 3.45 0 0 0 97.749 74.503" +
- "V 74.504" +
- "L 98.36 75.913" +
- "C 100.697 81.288 100.207 87.152 97.022 92.011" +
- "C 93.824 96.883 88.495 99.793 82.769 99.793" +
- "C 80.435 99.793 78.132 99.308 75.926 98.348" +
- "L 74.517 97.736" +
- "H 74.515" +
- "A 3.5 3.5 0 0 0 73.153 97.455" +
- "C 72.682 97.455 72.225 97.552 71.792 97.736" +
- "H 71.79" +
- "L 70.381 98.348" +
- "A 17.1 17.1 0 0 1 63.538 99.793" +
- "C 58.309 99.793 53.422 97.365 50.175 93.236" +
- "L 50.013 93.029" +
- "L 49.85 93.236" +
- "C 46.603 97.365 41.717 99.793 36.488 99.793" +
- "C 34.154 99.793 31.851 99.308 29.645 98.348" +
- "L 28.236 97.736" +
- "H 28.234" +
- "A 3.5 3.5 0 0 0 26.872 97.455" +
- "C 26.401 97.455 25.944 97.552 25.511 97.736" +
- "H 25.509" +
- "L 24.1 98.348" +
- "A 17.1 17.1 0 0 1 17.257 99.793" +
- "C 11.53 99.793 6.202 96.883 3.004 92.011" +
- "C -0.181 87.152 -0.671 81.288 1.661 75.913" +
- "L 2.277 74.504" +
- "V 74.503" +
- "A 3.45 3.45 0 0 0 2.277 71.778" +
- "V 71.777" +
- "L 1.665 70.368" +
- "C -0.671 64.993 -0.181 59.129 3.004 54.274" +
- "A 17.5 17.5 0 0 1 6.761 50.162" +
- "L 6.965 50" +
- "L 6.761 49.838" +
- "A 17.5 17.5 0 0 1 3.004 45.73" +
- "C -0.181 40.871 -0.671 35.007 1.665 29.632" +
- "L 2.277 28.223" +
- "V 28.222" +
- "A 3.45 3.45 0 0 0 2.277 25.497" +
- "V 25.496" +
- "L 1.665 24.087" +
- "C -0.671 18.712 -0.181 12.848 3.004 7.994" +
- "V 7.993" +
- "C 6.202 3.117 11.53 0.207 17.257 0.207" +
- "C 19.591 0.207 21.894 0.692 24.1 1.652" +
- "L 25.509 2.264" +
- "L 25.511 2.265" +
- "A 3.46 3.46 0 0 0 28.234 2.265" +
- "L 28.236 2.264" +
- "L 29.645 1.652" +
- "A 17.1 17.1 0 0 1 36.488 0.207" +
- "C 41.717 0.207 46.603 2.635 49.85 6.764" +
- "Z",
- "arch" to
- "M 50 0" +
- "L 72.5 0" +
- "A 27.5 27.5 0 0 1 100 27.5" +
- "L 100 86.67" +
- "A 13.33 13.33 0 0 1 86.67 100" +
- "L 13.33 100" +
- "A 13.33 13.33 0 0 1 0 86.67" +
- "L 0 27.5" +
- "A 27.5 27.5 0 0 1 27.5 0" +
- "Z",
- "square" to
- "M 50 0" +
- "L 83.4 0" +
- "A 16.6 16.6 0 0 1 100 16.6" +
- "L 100 83.4" +
- "A 16.6 16.6 0 0 1 83.4 100" +
- "L 16.6 100" +
- "A 16.6 16.6 0 0 1 0 83.4" +
- "L 0 16.6" +
- "A 16.6 16.6 0 0 1 16.6 0" +
- "Z",
- )
- } else {
- mapOf("circle" to "M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0")
- }
+ private const val FOLDER_CLOVER_PATH =
+ "M 39.616 4 C 46.224 6.87 53.727 6.87 60.335 4 L 63.884 2.459 C 85.178 -6.789 106.789 14.822 97.541 36.116 L 96 39.665 C 93.13 46.273 93.13 53.776 96 60.384 L 97.541 63.934 C 106.789 85.227 85.178 106.839 63.884 97.591 L 60.335 96.049 C 53.727 93.179 46.224 93.179 39.616 96.049 L 36.066 97.591 C 14.773 106.839 -6.839 85.227 2.409 63.934 L 3.951 60.384 C 6.821 53.776 6.821 46.273 3.951 39.665 L 2.409 36.116 C -6.839 14.822 14.773 -6.789 36.066 2.459 Z"
+ private const val FOLDER_COMPLEX_CLOVER_PATH =
+ "M 49.85 6.764 L 50.013 6.971 L 50.175 6.764 C 53.422 2.635 58.309 0.207 63.538 0.207 C 65.872 0.207 68.175 0.692 70.381 1.648 L 71.79 2.264 L 71.792 2.265 A 3.46 3.46 0 0 0 74.515 2.265 L 74.517 2.264 L 75.926 1.652 A 17.1 17.1 0 0 1 82.769 0.207 C 88.495 0.207 93.824 3.117 97.022 7.989 C 100.21 12.848 100.697 18.712 98.36 24.087 L 97.749 25.496 V 25.497 A 3.45 3.45 0 0 0 97.749 28.222 V 28.223 L 98.36 29.632 C 100.697 35.007 100.207 40.871 97.022 45.73 A 17.5 17.5 0 0 1 93.264 49.838 L 93.06 50 L 93.264 50.162 A 17.5 17.5 0 0 1 97.022 54.27 C 100.21 59.129 100.697 64.993 98.36 70.368 V 71.778 A 3.45 3.45 0 0 0 97.749 74.503 V 74.504 L 98.36 75.913 C 100.697 81.288 100.207 87.152 97.022 92.011 C 93.824 96.883 88.495 99.793 82.769 99.793 C 80.435 99.793 78.132 99.308 75.926 98.348 L 74.517 97.736 H 74.515 A 3.5 3.5 0 0 0 73.153 97.455 C 72.682 97.455 72.225 97.552 71.792 97.736 H 71.79 L 70.381 98.348 A 17.1 17.1 0 0 1 63.538 99.793 C 58.309 99.793 53.422 97.365 50.175 93.236 L 50.013 93.029 L 49.85 93.236 C 46.603 97.365 41.717 99.793 36.488 99.793 C 34.154 99.793 31.851 99.308 29.645 98.348 L 28.236 97.736 H 28.234 A 3.5 3.5 0 0 0 26.872 97.455 C 26.401 97.455 25.944 97.552 25.511 97.736 H 25.509 L 24.1 98.348 A 17.1 17.1 0 0 1 17.257 99.793 C 11.53 99.793 6.202 96.883 3.004 92.011 C -0.181 87.152 -0.671 81.288 1.661 75.913 L 2.277 74.504 V 74.503 A 3.45 3.45 0 0 0 2.277 71.778 V 71.777 L 1.665 70.368 C -0.671 64.993 -0.181 59.129 3.004 54.274 A 17.5 17.5 0 0 1 6.761 50.162 L 6.965 50 L 6.761 49.838 A 17.5 17.5 0 0 1 3.004 45.73 C -0.181 40.871 -0.671 35.007 1.665 29.632 L 2.277 28.223 V 28.222 A 3.45 3.45 0 0 0 2.277 25.497 V 25.496 L 1.665 24.087 C -0.671 18.712 -0.181 12.848 3.004 7.994 V 7.993 C 6.202 3.117 11.53 0.207 17.257 0.207 C 19.591 0.207 21.894 0.692 24.1 1.652 L 25.509 2.264 L 25.511 2.265 A 3.46 3.46 0 0 0 28.234 2.265 L 28.236 2.264 L 29.645 1.652 A 17.1 17.1 0 0 1 36.488 0.207 C 41.717 0.207 46.603 2.635 49.85 6.764 Z"
+ private const val FOLDER_ARCH_PATH =
+ "M 50 0 L 72.5 0 A 27.5 27.5 0 0 1 100 27.5 L 100 86.67 A 13.33 13.33 0 0 1 86.67 100 L 13.33 100 A 13.33 13.33 0 0 1 0 86.67 L 0 27.5 A 27.5 27.5 0 0 1 27.5 0 Z"
+ private const val FOLDER_SQUARE_PATH =
+ "M 50 0 L 83.4 0 A 16.6 16.6 0 0 1 100 16.6 L 100 83.4 A 16.6 16.6 0 0 1 83.4 100 L 16.6 100 A 16.6 16.6 0 0 1 0 83.4 L 0 16.6 A 16.6 16.6 0 0 1 16.6 0 Z"
+ private const val CIRCLE_PATH = "M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0"
+ private const val SQUARE_PATH =
+ "M53.689 0.82 L53.689 .82 C67.434 .82 74.306 .82 79.758 2.978 87.649 6.103 93.897 12.351 97.022 20.242 99.18 25.694 99.18 32.566 99.18 46.311 V53.689 C99.18 67.434 99.18 74.306 97.022 79.758 93.897 87.649 87.649 93.897 79.758 97.022 74.306 99.18 67.434 99.18 53.689 99.18 H46.311 C32.566 99.18 25.694 99.18 20.242 97.022 12.351 93.897 6.103 87.649 2.978 79.758 .82 74.306 .82 67.434 .82 53.689 L.82 46.311 C.82 32.566 .82 25.694 2.978 20.242 6.103 12.351 12.351 6.103 20.242 2.978 25.694 .82 32.566 .82 46.311 .82Z"
+ private const val FOUR_SIDED_COOKIE_PATH =
+ "M39.888,4.517C46.338 7.319 53.662 7.319 60.112 4.517L63.605 3C84.733 -6.176 106.176 15.268 97 36.395L95.483 39.888C92.681 46.338 92.681 53.662 95.483 60.112L97 63.605C106.176 84.732 84.733 106.176 63.605 97L60.112 95.483C53.662 92.681 46.338 92.681 39.888 95.483L36.395 97C15.267 106.176 -6.176 84.732 3 63.605L4.517 60.112C7.319 53.662 7.319 46.338 4.517 39.888L3 36.395C -6.176 15.268 15.267 -6.176 36.395 3Z"
+ private const val SEVEN_SIDED_COOKIE_PATH =
+ "M35.209 4.878C36.326 3.895 36.884 3.404 37.397 3.006 44.82 -2.742 55.18 -2.742 62.603 3.006 63.116 3.404 63.674 3.895 64.791 4.878 65.164 5.207 65.351 5.371 65.539 5.529 68.167 7.734 71.303 9.248 74.663 9.932 74.902 9.981 75.147 10.025 75.637 10.113 77.1 10.375 77.831 10.506 78.461 10.66 87.573 12.893 94.032 21.011 94.176 30.412 94.186 31.062 94.151 31.805 94.08 33.293 94.057 33.791 94.045 34.04 94.039 34.285 93.958 37.72 94.732 41.121 96.293 44.18 96.404 44.399 96.522 44.618 96.759 45.056 97.467 46.366 97.821 47.021 98.093 47.611 102.032 56.143 99.727 66.266 92.484 72.24 91.983 72.653 91.381 73.089 90.177 73.961 89.774 74.254 89.572 74.4 89.377 74.548 86.647 76.626 84.477 79.353 83.063 82.483 82.962 82.707 82.865 82.936 82.671 83.395 82.091 84.766 81.8 85.451 81.51 86.033 77.31 94.44 67.977 98.945 58.801 96.994 58.166 96.859 57.451 96.659 56.019 96.259 55.54 96.125 55.3 96.058 55.063 95.998 51.74 95.154 48.26 95.154 44.937 95.998 44.699 96.058 44.46 96.125 43.981 96.259 42.549 96.659 41.834 96.859 41.199 96.994 32.023 98.945 22.69 94.44 18.49 86.033 18.2 85.451 17.909 84.766 17.329 83.395 17.135 82.936 17.038 82.707 16.937 82.483 15.523 79.353 13.353 76.626 10.623 74.548 10.428 74.4 10.226 74.254 9.823 73.961 8.619 73.089 8.017 72.653 7.516 72.24 .273 66.266 -2.032 56.143 1.907 47.611 2.179 47.021 2.533 46.366 3.241 45.056 3.478 44.618 3.596 44.399 3.707 44.18 5.268 41.121 6.042 37.72 5.961 34.285 5.955 34.04 5.943 33.791 5.92 33.293 5.849 31.805 5.814 31.062 5.824 30.412 5.968 21.011 12.427 12.893 21.539 10.66 22.169 10.506 22.9 10.375 24.363 10.113 24.853 10.025 25.098 9.981 25.337 9.932 28.697 9.248 31.833 7.734 34.461 5.529 34.649 5.371 34.836 5.207 35.209 4.878Z"
+ private const val ARCH_PATH =
+ "M50 0C77.614 0 100 22.386 100 50C100 85.471 100 86.476 99.9 87.321 99.116 93.916 93.916 99.116 87.321 99.9 86.476 100 85.471 100 83.46 100H16.54C14.529 100 13.524 100 12.679 99.9 6.084 99.116 .884 93.916 .1 87.321 0 86.476 0 85.471 0 83.46L0 50C0 22.386 22.386 0 50 0Z"
+ @VisibleForTesting const val CIRCLE_KEY = "circle"
+ @VisibleForTesting const val SQUARE_KEY = "square"
+ @VisibleForTesting const val FOUR_SIDED_COOKIE_KEY = "four_sided_cookie"
+ @VisibleForTesting const val SEVEN_SIDED_COOKIE_KEY = "seven_sided_cookie"
+ @VisibleForTesting const val ARCH_KEY = "arch"
val iconShapes =
if (Flags.newCustomizationPickerUi() && LauncherFlags.enableLauncherIconShapes()) {
- mapOf(
- "circle" to
- IconShapeModel(
- key = "circle",
- title = "circle",
- pathString = "M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0",
- folderPathString = folderShapes["clover"]!!,
- ),
- "square" to
- IconShapeModel(
- key = "square",
- title = "square",
- pathString =
- "M53.689 0.82 L53.689 .82 C67.434 .82 74.306 .82 79.758 2.978 87.649 6.103 93.897 12.351 97.022 20.242 99.18 25.694 99.18 32.566 99.18 46.311 V53.689 C99.18 67.434 99.18 74.306 97.022 79.758 93.897 87.649 87.649 93.897 79.758 97.022 74.306 99.18 67.434 99.18 53.689 99.18 H46.311 C32.566 99.18 25.694 99.18 20.242 97.022 12.351 93.897 6.103 87.649 2.978 79.758 .82 74.306 .82 67.434 .82 53.689 L.82 46.311 C.82 32.566 .82 25.694 2.978 20.242 6.103 12.351 12.351 6.103 20.242 2.978 25.694 .82 32.566 .82 46.311 .82Z",
- folderShapes["square"]!!,
- ),
- "four_sided_cookie" to
- IconShapeModel(
- key = "four_sided_cookie",
- title = "4 sided cookie",
- pathString =
- "M39.888,4.517C46.338 7.319 53.662 7.319 60.112 4.517L63.605 3C84.733 -6.176 106.176 15.268 97 36.395L95.483 39.888C92.681 46.338 92.681 53.662 95.483 60.112L97 63.605C106.176 84.732 84.733 106.176 63.605 97L60.112 95.483C53.662 92.681 46.338 92.681 39.888 95.483L36.395 97C15.267 106.176 -6.176 84.732 3 63.605L4.517 60.112C7.319 53.662 7.319 46.338 4.517 39.888L3 36.395C -6.176 15.268 15.267 -6.176 36.395 3Z",
- folderPathString = folderShapes["complexClover"]!!,
- iconScale = 72f / 83.4f,
- ),
- "seven_sided_cookie" to
- IconShapeModel(
- key = "seven_sided_cookie",
- title = "7 sided cookie",
- pathString =
- "M35.209 4.878C36.326 3.895 36.884 3.404 37.397 3.006 44.82 -2.742 55.18 -2.742 62.603 3.006 63.116 3.404 63.674 3.895 64.791 4.878 65.164 5.207 65.351 5.371 65.539 5.529 68.167 7.734 71.303 9.248 74.663 9.932 74.902 9.981 75.147 10.025 75.637 10.113 77.1 10.375 77.831 10.506 78.461 10.66 87.573 12.893 94.032 21.011 94.176 30.412 94.186 31.062 94.151 31.805 94.08 33.293 94.057 33.791 94.045 34.04 94.039 34.285 93.958 37.72 94.732 41.121 96.293 44.18 96.404 44.399 96.522 44.618 96.759 45.056 97.467 46.366 97.821 47.021 98.093 47.611 102.032 56.143 99.727 66.266 92.484 72.24 91.983 72.653 91.381 73.089 90.177 73.961 89.774 74.254 89.572 74.4 89.377 74.548 86.647 76.626 84.477 79.353 83.063 82.483 82.962 82.707 82.865 82.936 82.671 83.395 82.091 84.766 81.8 85.451 81.51 86.033 77.31 94.44 67.977 98.945 58.801 96.994 58.166 96.859 57.451 96.659 56.019 96.259 55.54 96.125 55.3 96.058 55.063 95.998 51.74 95.154 48.26 95.154 44.937 95.998 44.699 96.058 44.46 96.125 43.981 96.259 42.549 96.659 41.834 96.859 41.199 96.994 32.023 98.945 22.69 94.44 18.49 86.033 18.2 85.451 17.909 84.766 17.329 83.395 17.135 82.936 17.038 82.707 16.937 82.483 15.523 79.353 13.353 76.626 10.623 74.548 10.428 74.4 10.226 74.254 9.823 73.961 8.619 73.089 8.017 72.653 7.516 72.24 .273 66.266 -2.032 56.143 1.907 47.611 2.179 47.021 2.533 46.366 3.241 45.056 3.478 44.618 3.596 44.399 3.707 44.18 5.268 41.121 6.042 37.72 5.961 34.285 5.955 34.04 5.943 33.791 5.92 33.293 5.849 31.805 5.814 31.062 5.824 30.412 5.968 21.011 12.427 12.893 21.539 10.66 22.169 10.506 22.9 10.375 24.363 10.113 24.853 10.025 25.098 9.981 25.337 9.932 28.697 9.248 31.833 7.734 34.461 5.529 34.649 5.371 34.836 5.207 35.209 4.878Z",
- folderPathString = folderShapes["clover"]!!,
- iconScale = 72f / 80f,
- ),
- "arch" to
- IconShapeModel(
- key = "arch",
- title = "arch",
- pathString =
- "M50 0C77.614 0 100 22.386 100 50C100 85.471 100 86.476 99.9 87.321 99.116 93.916 93.916 99.116 87.321 99.9 86.476 100 85.471 100 83.46 100H16.54C14.529 100 13.524 100 12.679 99.9 6.084 99.116 .884 93.916 .1 87.321 0 86.476 0 85.471 0 83.46L0 50C0 22.386 22.386 0 50 0Z",
- folderPathString = folderShapes["arch"]!!,
- ),
+ arrayOf(
+ IconShapeModel(
+ key = CIRCLE_KEY,
+ title = "circle",
+ pathString = CIRCLE_PATH,
+ folderPathString = FOLDER_CLOVER_PATH,
+ ),
+ IconShapeModel(
+ key = SQUARE_KEY,
+ title = "square",
+ pathString = SQUARE_PATH,
+ folderPathString = FOLDER_SQUARE_PATH,
+ ),
+ IconShapeModel(
+ key = FOUR_SIDED_COOKIE_KEY,
+ title = "4 sided cookie",
+ pathString = FOUR_SIDED_COOKIE_PATH,
+ folderPathString = FOLDER_COMPLEX_CLOVER_PATH,
+ iconScale = 72f / 83.4f,
+ ),
+ IconShapeModel(
+ key = SEVEN_SIDED_COOKIE_KEY,
+ title = "7 sided cookie",
+ pathString = SEVEN_SIDED_COOKIE_PATH,
+ folderPathString = FOLDER_CLOVER_PATH,
+ iconScale = 72f / 80f,
+ ),
+ IconShapeModel(
+ key = ARCH_KEY,
+ title = "arch",
+ pathString = ARCH_PATH,
+ folderPathString = FOLDER_ARCH_PATH,
+ ),
)
} else {
- mapOf(
- "circle" to
- IconShapeModel(
- key = "circle",
- title = "circle",
- pathString = "M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0",
- )
- )
+ arrayOf(IconShapeModel(key = CIRCLE_KEY, title = "circle", pathString = CIRCLE_PATH))
}
}
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/multivalentTests/src/com/android/launcher3/shapes/ShapesProviderTest.kt b/tests/multivalentTests/src/com/android/launcher3/shapes/ShapesProviderTest.kt
index 49d305b..66b8be0 100644
--- a/tests/multivalentTests/src/com/android/launcher3/shapes/ShapesProviderTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/shapes/ShapesProviderTest.kt
@@ -23,6 +23,11 @@
import androidx.test.filters.SmallTest
import com.android.launcher3.Flags.FLAG_ENABLE_LAUNCHER_ICON_SHAPES
import com.android.launcher3.graphics.ShapeDelegate.GenericPathShape
+import com.android.launcher3.shapes.ShapesProvider.ARCH_KEY
+import com.android.launcher3.shapes.ShapesProvider.CIRCLE_KEY
+import com.android.launcher3.shapes.ShapesProvider.FOUR_SIDED_COOKIE_KEY
+import com.android.launcher3.shapes.ShapesProvider.SEVEN_SIDED_COOKIE_KEY
+import com.android.launcher3.shapes.ShapesProvider.SQUARE_KEY
import com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI
import org.junit.Rule
import org.junit.Test
@@ -37,81 +42,99 @@
@Test
@EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
fun `verify valid path arch`() {
- ShapesProvider.iconShapes["arch"]?.apply {
- GenericPathShape(pathString)
- PathParser.createPathFromPathData(pathString)
- }
+ ShapesProvider.iconShapes
+ .find { it.key == ARCH_KEY }!!
+ .run {
+ GenericPathShape(pathString)
+ PathParser.createPathFromPathData(pathString)
+ }
}
@Test
@EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
fun `verify valid path 4_sided_cookie`() {
- ShapesProvider.iconShapes["4_sided_cookie"]?.apply {
- GenericPathShape(pathString)
- PathParser.createPathFromPathData(pathString)
- }
+ ShapesProvider.iconShapes
+ .find { it.key == FOUR_SIDED_COOKIE_KEY }!!
+ .run {
+ GenericPathShape(pathString)
+ PathParser.createPathFromPathData(pathString)
+ }
}
@Test
@EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
fun `verify valid path seven_sided_cookie`() {
- ShapesProvider.iconShapes["seven_sided_cookie"]?.apply {
- GenericPathShape(pathString)
- PathParser.createPathFromPathData(pathString)
- }
+ ShapesProvider.iconShapes
+ .find { it.key == SEVEN_SIDED_COOKIE_KEY }!!
+ .run {
+ GenericPathShape(pathString)
+ PathParser.createPathFromPathData(pathString)
+ }
}
@Test
@EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
fun `verify valid path circle`() {
- ShapesProvider.iconShapes["circle"]?.apply {
- GenericPathShape(pathString)
- PathParser.createPathFromPathData(pathString)
- }
+ ShapesProvider.iconShapes
+ .find { it.key == CIRCLE_KEY }!!
+ .run {
+ GenericPathShape(pathString)
+ PathParser.createPathFromPathData(pathString)
+ }
}
@Test
@EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
fun `verify valid path square`() {
- ShapesProvider.iconShapes["square"]?.apply {
- GenericPathShape(pathString)
- PathParser.createPathFromPathData(pathString)
- }
+ ShapesProvider.iconShapes
+ .find { it.key == ARCH_KEY }!!
+ .run {
+ GenericPathShape(pathString)
+ PathParser.createPathFromPathData(pathString)
+ }
}
@Test
@EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
fun `verify valid folder path clover`() {
- ShapesProvider.folderShapes["clover"]?.let { pathString ->
- GenericPathShape(pathString)
- PathParser.createPathFromPathData(pathString)
- }
+ ShapesProvider.iconShapes
+ .find { it.key == CIRCLE_KEY }!!
+ .run {
+ GenericPathShape(folderPathString)
+ PathParser.createPathFromPathData(folderPathString)
+ }
}
@Test
@EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
fun `verify valid folder path complexClover`() {
- ShapesProvider.folderShapes["complexClover"]?.let { pathString ->
- GenericPathShape(pathString)
- PathParser.createPathFromPathData(pathString)
- }
+ ShapesProvider.iconShapes
+ .find { it.key == FOUR_SIDED_COOKIE_KEY }!!
+ .run {
+ GenericPathShape(folderPathString)
+ PathParser.createPathFromPathData(folderPathString)
+ }
}
@Test
@EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
fun `verify valid folder path arch`() {
- ShapesProvider.folderShapes["arch"]?.let { pathString ->
- GenericPathShape(pathString)
- PathParser.createPathFromPathData(pathString)
- }
+ ShapesProvider.iconShapes
+ .find { it.key == ARCH_KEY }!!
+ .run {
+ GenericPathShape(folderPathString)
+ PathParser.createPathFromPathData(folderPathString)
+ }
}
@Test
@EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
fun `verify valid folder path square`() {
- ShapesProvider.folderShapes["square"]?.let { pathString ->
- GenericPathShape(pathString)
- PathParser.createPathFromPathData(pathString)
- }
+ ShapesProvider.iconShapes
+ .find { it.key == SQUARE_KEY }!!
+ .run {
+ GenericPathShape(folderPathString)
+ PathParser.createPathFromPathData(folderPathString)
+ }
}
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/OWNERS b/tests/multivalentTests/src/com/android/launcher3/widget/picker/OWNERS
index 775b0c7..716ab90 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/OWNERS
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/OWNERS
@@ -5,7 +5,6 @@
#
# Widget Picker OWNERS
-zakcohen@google.com
shamalip@google.com
wvk@google.com
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();
+ }
}
}