Merge "Import translations. DO NOT MERGE ANYWHERE" into main
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 8c39585..201c5f6 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -81,7 +81,7 @@
android:stateNotNeeded="true"
android:theme="@style/LauncherTheme"
android:screenOrientation="behind"
- android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
+ android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
android:resizeableActivity="true"
android:resumeWhilePausing="true"
android:enableOnBackInvokedCallback="false"
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt
index 645bef6..6e36305 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt
@@ -45,7 +45,8 @@
}
remoteWindowLimitUnminimizeTransition =
RemoteTransition(
- DesktopAppLaunchTransition(context, MAIN_EXECUTOR, AppLaunchType.UNMINIMIZE)
+ DesktopAppLaunchTransition(context, MAIN_EXECUTOR, AppLaunchType.UNMINIMIZE),
+ "DesktopWindowLimitUnminimize"
)
systemUiProxy.registerRemoteTransition(
remoteWindowLimitUnminimizeTransition,
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index 3114bc8..cb811d6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.taskbar;
+import static com.android.launcher3.desktop.DesktopAppLaunchTransition.AppLaunchType.UNMINIMIZE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
@@ -285,8 +286,8 @@
) {
// This app is being unminimized - use our own transition runner.
remoteTransition = new RemoteTransition(
- new DesktopAppLaunchTransition(
- context, MAIN_EXECUTOR, AppLaunchType.UNMINIMIZE));
+ new DesktopAppLaunchTransition(context, MAIN_EXECUTOR, UNMINIMIZE),
+ "DesktopKeyboardQuickSwitchUnminimize");
}
mControllers.taskbarActivityContext.handleGroupTaskLaunch(
task,
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 70cb7ce..8e2246b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -907,7 +907,8 @@
ActivityOptions options = ActivityOptions.makeRemoteTransition(
new RemoteTransition(
new DesktopAppLaunchTransition(
- /* context= */ this, getMainExecutor(), launchType)));
+ /* context= */ this, getMainExecutor(), launchType),
+ "TaskbarDesktopLaunch"));
return new ActivityOptionsWrapper(options, new RunnableList());
}
@@ -1497,8 +1498,8 @@
private RemoteTransition createUnminimizeRemoteTransition() {
return new RemoteTransition(
- new DesktopAppLaunchTransition(
- this, getMainExecutor(), AppLaunchType.UNMINIMIZE));
+ new DesktopAppLaunchTransition(this, getMainExecutor(), AppLaunchType.UNMINIMIZE),
+ "TaskbarDesktopUnminimize");
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index ff8e4a8..9407e73 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -113,7 +113,7 @@
private static final Uri NAV_BAR_KIDS_MODE = Settings.Secure.getUriFor(
Settings.Secure.NAV_BAR_KIDS_MODE);
- private final Context mContext;
+ private final Context mWindowContext;
private final @Nullable Context mNavigationBarPanelContext;
private WindowManager mWindowManager;
private boolean mAddedWindow;
@@ -231,7 +231,7 @@
@NonNull DesktopVisibilityController desktopVisibilityController) {
Display display =
context.getSystemService(DisplayManager.class).getDisplay(context.getDisplayId());
- mContext = context.createWindowContext(display,
+ mWindowContext = context.createWindowContext(display,
ENABLE_TASKBAR_NAVBAR_UNIFICATION ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL,
null);
mAllAppsActionManager = allAppsActionManager;
@@ -240,30 +240,47 @@
: null;
mDesktopVisibilityController = desktopVisibilityController;
if (enableTaskbarNoRecreate()) {
- mWindowManager = mContext.getSystemService(WindowManager.class);
- FrameLayout taskbarRootLayout = new FrameLayout(mContext) {
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- // The motion events can be outside the view bounds of task bar, and hence
- // manually dispatching them to the drag layer here.
- TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
- if (taskbar != null && taskbar.getDragLayer().isAttachedToWindow()) {
- return taskbar.getDragLayer().dispatchTouchEvent(ev);
- }
- return super.dispatchTouchEvent(ev);
- }
- };
- addTaskbarRootLayoutToMap(getDefaultDisplayId(), taskbarRootLayout);
+ mWindowManager = mWindowContext.getSystemService(WindowManager.class);
+ createTaskbarRootLayout(getDefaultDisplayId());
}
- mDefaultNavButtonController = new TaskbarNavButtonController(
+ mDefaultNavButtonController = createDefaultNavButtonController(context, navCallbacks);
+ mDefaultComponentCallbacks = createDefaultComponentCallbacks();
+ SettingsCache.INSTANCE.get(mWindowContext)
+ .register(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
+ SettingsCache.INSTANCE.get(mWindowContext)
+ .register(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
+ Log.d(TASKBAR_NOT_DESTROYED_TAG, "registering component callbacks from constructor.");
+ mWindowContext.registerComponentCallbacks(mDefaultComponentCallbacks);
+ mShutdownReceiver.register(mWindowContext, Intent.ACTION_SHUTDOWN);
+ UI_HELPER_EXECUTOR.execute(() -> {
+ mSharedState.taskbarSystemActionPendingIntent = PendingIntent.getBroadcast(
+ mWindowContext,
+ SYSTEM_ACTION_ID_TASKBAR,
+ new Intent(ACTION_SHOW_TASKBAR).setPackage(mWindowContext.getPackageName()),
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ mTaskbarBroadcastReceiver.register(
+ mWindowContext, RECEIVER_NOT_EXPORTED, ACTION_SHOW_TASKBAR);
+ });
+
+ debugWhyTaskbarNotDestroyed("TaskbarManager created");
+ recreateTaskbar();
+ }
+
+ @NonNull
+ private TaskbarNavButtonController createDefaultNavButtonController(Context context,
+ TaskbarNavButtonCallbacks navCallbacks) {
+ return new TaskbarNavButtonController(
context,
navCallbacks,
- SystemUiProxy.INSTANCE.get(mContext),
- ContextualEduStatsManager.INSTANCE.get(mContext),
+ SystemUiProxy.INSTANCE.get(mWindowContext),
+ ContextualEduStatsManager.INSTANCE.get(mWindowContext),
new Handler(),
- new ContextualSearchInvoker(mContext));
- mDefaultComponentCallbacks = new ComponentCallbacks() {
- private Configuration mOldConfig = mContext.getResources().getConfiguration();
+ new ContextualSearchInvoker(mWindowContext));
+ }
+
+ private ComponentCallbacks createDefaultComponentCallbacks() {
+ return new ComponentCallbacks() {
+ private Configuration mOldConfig = mWindowContext.getResources().getConfiguration();
@Override
public void onConfigurationChanged(Configuration newConfig) {
@@ -273,7 +290,7 @@
"TaskbarManager#mComponentCallbacks.onConfigurationChanged: " + newConfig);
// TODO: adapt this logic to be specific to different displays.
DeviceProfile dp = mUserUnlocked
- ? LauncherAppState.getIDP(mContext).getDeviceProfile(mContext)
+ ? LauncherAppState.getIDP(mWindowContext).getDeviceProfile(mWindowContext)
: null;
int configDiff = mOldConfig.diff(newConfig) & ~SKIP_RECREATE_CONFIG_CHANGES;
@@ -317,25 +334,6 @@
@Override
public void onLowMemory() { }
};
- SettingsCache.INSTANCE.get(mContext)
- .register(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
- SettingsCache.INSTANCE.get(mContext)
- .register(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
- Log.d(TASKBAR_NOT_DESTROYED_TAG, "registering component callbacks from constructor.");
- mContext.registerComponentCallbacks(mDefaultComponentCallbacks);
- mShutdownReceiver.register(mContext, Intent.ACTION_SHUTDOWN);
- UI_HELPER_EXECUTOR.execute(() -> {
- mSharedState.taskbarSystemActionPendingIntent = PendingIntent.getBroadcast(
- mContext,
- SYSTEM_ACTION_ID_TASKBAR,
- new Intent(ACTION_SHOW_TASKBAR).setPackage(mContext.getPackageName()),
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
- mTaskbarBroadcastReceiver.register(
- mContext, RECEIVER_NOT_EXPORTED, ACTION_SHOW_TASKBAR);
- });
-
- debugWhyTaskbarNotDestroyed("TaskbarManager created");
- recreateTaskbar();
}
private void destroyAllTaskbars() {
@@ -360,7 +358,7 @@
removeTaskbarFromMap(displayId);
}
DeviceProfile dp = mUserUnlocked ?
- LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
+ LauncherAppState.getIDP(mWindowContext).getDeviceProfile(mWindowContext) : null;
if (dp == null || !isTaskbarEnabled(dp)) {
removeTaskbarRootViewFromWindow(displayId);
}
@@ -407,7 +405,7 @@
*/
public void onUserUnlocked() {
mUserUnlocked = true;
- DisplayController.INSTANCE.get(mContext).addChangeListener(mRecreationListener);
+ DisplayController.INSTANCE.get(mWindowContext).addChangeListener(mRecreationListener);
recreateTaskbar();
addTaskbarRootViewToWindow(getDefaultDisplayId());
}
@@ -470,7 +468,7 @@
return ql.getUnfoldTransitionProgressProvider();
}
} else {
- return SystemUiProxy.INSTANCE.get(mContext).getUnfoldTransitionProvider();
+ return SystemUiProxy.INSTANCE.get(mWindowContext).getUnfoldTransitionProvider();
}
return null;
}
@@ -514,7 +512,7 @@
Trace.beginSection("recreateTaskbar");
try {
DeviceProfile dp = mUserUnlocked ?
- LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
+ LauncherAppState.getIDP(mWindowContext).getDeviceProfile(mWindowContext) : null;
// All Apps action is unrelated to navbar unification, so we only need to check DP.
final boolean isLargeScreenTaskbar = dp != null && dp.isTaskbarPresent;
@@ -528,7 +526,7 @@
+ " FLAG_HIDE_NAVBAR_WINDOW=" + ENABLE_TASKBAR_NAVBAR_UNIFICATION
+ " dp.isTaskbarPresent=" + (dp == null ? "null" : dp.isTaskbarPresent));
if (!isTaskbarEnabled || !isLargeScreenTaskbar) {
- SystemUiProxy.INSTANCE.get(mContext)
+ SystemUiProxy.INSTANCE.get(mWindowContext)
.notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
if (!isTaskbarEnabled) {
return;
@@ -537,9 +535,7 @@
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
if (enableTaskbarNoRecreate() || taskbar == null) {
- taskbar = new TaskbarActivityContext(mContext,
- mNavigationBarPanelContext, dp, mDefaultNavButtonController,
- mUnfoldProgressProvider, mDesktopVisibilityController);
+ taskbar = createTaskbarActivityContext(dp, displayId);
} else {
taskbar.updateDeviceProfile(dp);
}
@@ -560,7 +556,6 @@
taskbarRootLayout.addView(taskbar.getDragLayer());
taskbar.notifyUpdateLayoutParams();
}
- addTaskbarToMap(displayId, taskbar);
} finally {
Trace.endSection();
}
@@ -716,22 +711,23 @@
mRecentsViewContainer = null;
debugWhyTaskbarNotDestroyed("TaskbarManager#destroy()");
removeActivityCallbacksAndListeners();
- mTaskbarBroadcastReceiver.unregisterReceiverSafely(mContext);
+ mTaskbarBroadcastReceiver.unregisterReceiverSafely(mWindowContext);
destroyAllTaskbars();
if (mUserUnlocked) {
- DisplayController.INSTANCE.get(mContext).removeChangeListener(mRecreationListener);
+ DisplayController.INSTANCE.get(mWindowContext).removeChangeListener(
+ mRecreationListener);
}
- SettingsCache.INSTANCE.get(mContext)
+ SettingsCache.INSTANCE.get(mWindowContext)
.unregister(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
- SettingsCache.INSTANCE.get(mContext)
+ SettingsCache.INSTANCE.get(mWindowContext)
.unregister(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
Log.d(TASKBAR_NOT_DESTROYED_TAG, "unregistering component callbacks from destroy().");
- mContext.unregisterComponentCallbacks(mDefaultComponentCallbacks);
- mShutdownReceiver.unregisterReceiverSafely(mContext);
+ mWindowContext.unregisterComponentCallbacks(mDefaultComponentCallbacks);
+ mShutdownReceiver.unregisterReceiverSafely(mWindowContext);
}
public @Nullable TaskbarActivityContext getCurrentActivityContext() {
- return getTaskbarForDisplay(mContext.getDisplayId());
+ return getTaskbarForDisplay(mWindowContext.getDisplayId());
}
public void dumpLogs(String prefix, PrintWriter pw) {
@@ -773,6 +769,19 @@
return mTaskbars.get(displayId);
}
+
+ /**
+ * Creates a {@link TaskbarActivityContext} for the given display and adds it to the map.
+ */
+ private TaskbarActivityContext createTaskbarActivityContext(DeviceProfile dp, int displayId) {
+ TaskbarActivityContext newTaskbar = new TaskbarActivityContext(mWindowContext,
+ mNavigationBarPanelContext, dp, mDefaultNavButtonController,
+ mUnfoldProgressProvider, mDesktopVisibilityController);
+
+ addTaskbarToMap(displayId, newTaskbar);
+ return newTaskbar;
+ }
+
/**
* Adds the {@link TaskbarActivityContext} associated with the given display ID to taskbar
* map if there is not already a taskbar mapped to that displayId.
@@ -796,6 +805,26 @@
}
/**
+ * Creates {@link FrameLayout} for the taskbar on the specified display and adds it to map.
+ * @param displayId The ID of the display for which to create the taskbar root layout.
+ */
+ private void createTaskbarRootLayout(int displayId) {
+ FrameLayout newTaskbarRootLayout = new FrameLayout(mWindowContext) {
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ // The motion events can be outside the view bounds of task bar, and hence
+ // manually dispatching them to the drag layer here.
+ TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
+ if (taskbar != null && taskbar.getDragLayer().isAttachedToWindow()) {
+ return taskbar.getDragLayer().dispatchTouchEvent(ev);
+ }
+ return super.dispatchTouchEvent(ev);
+ }
+ };
+ addTaskbarRootLayoutToMap(displayId, newTaskbarRootLayout);
+ }
+
+ /**
* Retrieves the root layout of the taskbar for the specified display.
*
* @param displayId The ID of the display for which to retrieve the taskbar root layout.
@@ -812,7 +841,7 @@
* @param rootLayout The taskbar root layout {@link FrameLayout} to add to the map.
*/
private void addTaskbarRootLayoutToMap(int displayId, FrameLayout rootLayout) {
- if (!mRootLayouts.contains(displayId)) {
+ if (!mRootLayouts.contains(displayId) && rootLayout != null) {
mRootLayouts.put(displayId, rootLayout);
}
}
@@ -829,7 +858,7 @@
}
private int getDefaultDisplayId() {
- return mContext.getDisplayId();
+ return mWindowContext.getDisplayId();
}
/** Temp logs for b/254119092. */
@@ -839,15 +868,15 @@
boolean activityTaskbarPresent = mActivity != null
&& mActivity.getDeviceProfile().isTaskbarPresent;
- boolean contextTaskbarPresent = mUserUnlocked
- && LauncherAppState.getIDP(mContext).getDeviceProfile(mContext).isTaskbarPresent;
+ boolean contextTaskbarPresent = mUserUnlocked && LauncherAppState.getIDP(mWindowContext)
+ .getDeviceProfile(mWindowContext).isTaskbarPresent;
if (activityTaskbarPresent == contextTaskbarPresent) {
- log.add("mActivity and mContext agree taskbarIsPresent=" + contextTaskbarPresent);
+ log.add("mActivity and mWindowContext agree taskbarIsPresent=" + contextTaskbarPresent);
Log.d(TASKBAR_NOT_DESTROYED_TAG, log.toString());
return;
}
- log.add("mActivity and mContext device profiles have different values, add more logs.");
+ log.add("mActivity & mWindowContext device profiles have different values, add more logs.");
log.add("\tmActivity logs:");
log.add("\t\tmActivity=" + mActivity);
@@ -857,13 +886,13 @@
log.add("\t\tmActivity.getDeviceProfile().isTaskbarPresent="
+ activityTaskbarPresent);
}
- log.add("\tmContext logs:");
- log.add("\t\tmContext=" + mContext);
- log.add("\t\tmContext.getResources().getConfiguration()="
- + mContext.getResources().getConfiguration());
+ log.add("\tmWindowContext logs:");
+ log.add("\t\tmWindowContext=" + mWindowContext);
+ log.add("\t\tmWindowContext.getResources().getConfiguration()="
+ + mWindowContext.getResources().getConfiguration());
if (mUserUnlocked) {
- log.add("\t\tLauncherAppState.getIDP().getDeviceProfile(mContext).isTaskbarPresent="
- + contextTaskbarPresent);
+ log.add("\t\tLauncherAppState.getIDP().getDeviceProfile(mWindowContext)"
+ + ".isTaskbarPresent=" + contextTaskbarPresent);
} else {
log.add("\t\tCouldn't get DeviceProfile because !mUserUnlocked");
}
@@ -876,6 +905,6 @@
@VisibleForTesting
public Context getWindowContext() {
- return mContext;
+ return mWindowContext;
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
index 374db6a..2e2d7cc 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
@@ -63,7 +63,7 @@
override fun createFadeOutAnimOptions(): ActivityOptions =
ActivityOptions.makeBasic().apply {
- remoteTransition = RemoteTransition(FadeOutRemoteTransition())
+ remoteTransition = RemoteTransition(FadeOutRemoteTransition(), "FadeOut")
}
override fun queryAllUsers(): Map<UserHandle, UserIconInfo> {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 9164405..9dec332 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -256,7 +256,7 @@
mRecentsView.setFullscreenProgress(fromState.getOverviewFullscreenProgress());
mLauncher.getActionsView().getVisibilityAlpha().updateValue(
(fromState.getVisibleElements(mLauncher) & OVERVIEW_ACTIONS) != 0 ? 1f : 0f);
- mRecentsView.setTaskIconScaledDown(true);
+ mRecentsView.setTaskIconVisible(false);
float[] scaleAndOffset = toState.getOverviewScaleAndOffset(mLauncher);
// As we drag right, animate the following properties:
@@ -340,7 +340,7 @@
public void onAnimationEnd(Animator animation) {
onAnimationToStateCompleted(OVERVIEW);
// Animate the icon after onAnimationToStateCompleted() so it doesn't clobber.
- mRecentsView.animateUpTaskIconScale();
+ mRecentsView.startIconFadeInOnGestureComplete();
}
});
overviewAnim.start();
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 6075294..61a150b 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -19,6 +19,8 @@
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE;
+import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_RECREATE_TO_UPDATE_THEME;
import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_DURATION;
import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_PRE_DELAY;
@@ -362,6 +364,14 @@
}
@Override
+ public void onUiChangedWhileSleeping() {
+ super.onUiChangedWhileSleeping();
+ // Dismiss recents and navigate to home if the device goes to sleep
+ // while in recents.
+ startHome();
+ }
+
+ @Override
protected void onResume() {
super.onResume();
AccessibilityManagerCompat.sendStateEventToTest(getBaseContext(), OVERVIEW_STATE_ORDINAL);
@@ -382,6 +392,32 @@
// Set screen title for Talkback
setTitle(R.string.accessibility_recent_apps);
+
+ restoreState(savedInstanceState);
+ }
+
+ @Override
+ protected void onSaveInstanceState(@NonNull Bundle outState) {
+ outState.putInt(RUNTIME_STATE, mStateManager.getState().ordinal);
+ super.onSaveInstanceState(outState);
+ }
+
+ /**
+ * Restores the previous state, if it exists.
+ *
+ * @param savedState The previous state.
+ */
+ private void restoreState(Bundle savedState) {
+ if (savedState == null) {
+ return;
+ }
+
+ if (savedState.getBoolean(RUNTIME_STATE_RECREATE_TO_UPDATE_THEME)) {
+ // RecentsState is only restored after theme changes.
+ int stateOrdinal = savedState.getInt(RUNTIME_STATE, RecentsState.DEFAULT.ordinal);
+ RecentsState recentsState = RecentsState.stateFromOrdinal(stateOrdinal);
+ mStateManager.goToState(recentsState, /*animated=*/false);
+ }
}
@Override
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
index 34783c7..c2e7536 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsState.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
@@ -45,6 +45,8 @@
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 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);
@@ -61,6 +63,11 @@
FLAG_SHOW_AS_GRID | FLAG_SCRIM | FLAG_RECENTS_VIEW_VISIBLE | FLAG_CLOSE_POPUPS
| FLAG_DISABLE_RESTORE);
+ /** Returns the corresponding RecentsState from ordinal provided */
+ public static RecentsState stateFromOrdinal(int ordinal) {
+ return sAllStates[ordinal];
+ }
+
public final int ordinal;
private final int mFlags;
@@ -70,6 +77,7 @@
public RecentsState(int id, int flags) {
this.ordinal = id;
this.mFlags = flags;
+ sAllStates[id] = this;
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index e7cb05e..da5411b 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -691,7 +691,7 @@
protected boolean mRunningTaskTileHidden;
protected int mFocusedTaskViewId = INVALID_TASK_ID;
- private boolean mTaskIconScaledDown = false;
+ private boolean mTaskIconVisible = true;
private boolean mRunningTaskShowScreenshot = false;
private float mRunningTaskAttachAlpha;
@@ -1244,24 +1244,21 @@
// - It's the focused task to be moved to the front, we immediately re-add the task
if (child instanceof TaskView && child != mSplitHiddenTaskView
&& child != mMovingTaskView) {
- clearAndRecycleTaskView((TaskView) child);
+ TaskView taskView = (TaskView) child;
+ for (int i : taskView.getTaskIds()) {
+ mHasVisibleTaskData.delete(i);
+ }
+ if (child instanceof GroupedTaskView) {
+ mGroupedTaskViewPool.recycle((GroupedTaskView) taskView);
+ } else if (child instanceof DesktopTaskView) {
+ mDesktopTaskViewPool.recycle((DesktopTaskView) taskView);
+ } else {
+ mTaskViewPool.recycle(taskView);
+ }
+ mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
}
}
- private void clearAndRecycleTaskView(TaskView taskView) {
- for (int taskId : taskView.getTaskIds()) {
- mHasVisibleTaskData.delete(taskId);
- }
- if (taskView instanceof GroupedTaskView) {
- mGroupedTaskViewPool.recycle((GroupedTaskView) taskView);
- } else if (taskView instanceof DesktopTaskView) {
- mDesktopTaskViewPool.recycle((DesktopTaskView) taskView);
- } else {
- mTaskViewPool.recycle(taskView);
- }
- mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
- }
-
@Override
public void onViewAdded(View child) {
super.onViewAdded(child);
@@ -2120,7 +2117,7 @@
if (Arrays.stream(taskView.getTaskIds()).noneMatch(
taskId -> taskId == mIgnoreResetTaskId)) {
taskView.resetViewTransforms();
- taskView.setIconScaleAndDim(mTaskIconScaledDown ? 0 : 1);
+ taskView.setIconVisibleForGesture(mTaskIconVisible);
taskView.setStableAlpha(mContentAlpha);
taskView.setFullscreenProgress(mFullscreenProgress);
taskView.setModalness(mTaskModalness);
@@ -2788,7 +2785,7 @@
setEnableFreeScroll(false);
setEnableDrawingLiveTile(false);
setRunningTaskHidden(true);
- setTaskIconScaledDown(true);
+ setTaskIconVisible(false);
}
/**
@@ -2807,7 +2804,7 @@
* {@link #onGestureAnimationStart} and {@link #onGestureAnimationEnd()}.
*/
public void onSwipeUpAnimationSuccess() {
- animateUpTaskIconScale();
+ startIconFadeInOnGestureComplete();
setSwipeDownShouldLaunchApp(true);
}
@@ -2934,7 +2931,7 @@
setEnableDrawingLiveTile(mCurrentGestureEndTarget == GestureState.GestureEndTarget.RECENTS);
Log.d(TAG, "onGestureAnimationEnd - mEnableDrawingLiveTile: " + mEnableDrawingLiveTile);
setRunningTaskHidden(false);
- animateUpTaskIconScale();
+ startIconFadeInOnGestureComplete();
animateActionsViewIn();
mCurrentGestureEndTarget = null;
@@ -3058,7 +3055,7 @@
if (mRunningTaskViewId != -1) {
// Reset the state on the old running task view
- setTaskIconScaledDown(false);
+ setTaskIconVisible(true);
setRunningTaskViewShowScreenshot(true);
setRunningTaskHidden(false);
}
@@ -3127,25 +3124,31 @@
}
}
- public void setTaskIconScaledDown(boolean isScaledDown) {
- if (mTaskIconScaledDown != isScaledDown) {
- mTaskIconScaledDown = isScaledDown;
+ /**
+ * Updates icon visibility when going in or out of overview.
+ */
+ public void setTaskIconVisible(boolean isVisible) {
+ if (mTaskIconVisible != isVisible) {
+ mTaskIconVisible = isVisible;
for (TaskView taskView : getTaskViews()) {
- taskView.setIconScaleAndDim(mTaskIconScaledDown ? 0 : 1);
+ taskView.setIconVisibleForGesture(mTaskIconVisible);
}
}
}
private void animateActionsViewIn() {
if (!showAsGrid() || isFocusedTaskInExpectedScrollPosition()) {
- animateActionsViewAlpha(1, TaskView.SCALE_ICON_DURATION);
+ animateActionsViewAlpha(1, TaskView.FADE_IN_ICON_DURATION);
}
}
- public void animateUpTaskIconScale() {
- mTaskIconScaledDown = false;
+ /**
+ * Updates icon visibility when gesture is settled.
+ */
+ public void startIconFadeInOnGestureComplete() {
+ mTaskIconVisible = true;
for (TaskView taskView : getTaskViews()) {
- taskView.animateIconScaleAndDimIntoView();
+ taskView.startIconFadeInOnGestureComplete();
}
}
@@ -3935,7 +3938,7 @@
anim.setFloat(taskView, taskView.getSecondaryDismissTranslationProperty(),
secondaryTranslation, clampToProgress(LINEAR, animationStartProgress,
dismissTranslationInterpolationEnd));
- anim.add(taskView.getFocusTransitionScaleAndDimOutAnimator(),
+ anim.add(taskView.getDismissIconFadeOutAnimator(),
clampToProgress(LINEAR, 0f, ANIMATION_DISMISS_PROGRESS_MIDPOINT));
} else if ((isFocusedTaskDismissed && nextFocusedTaskView != null && isSameGridRow(
taskView, nextFocusedTaskView))
@@ -4119,7 +4122,7 @@
? INVALID_TASK_ID
: finalNextFocusedTaskView.getTaskViewId());
mTopRowIdSet.remove(mFocusedTaskViewId);
- finalNextFocusedTaskView.animateIconScaleAndDimIntoView();
+ finalNextFocusedTaskView.getDismissIconFadeInAnimator().start();
}
updateTaskSize();
updateChildTaskOrientations();
@@ -5407,13 +5410,6 @@
mSplitHiddenTaskViewIndex = -1;
if (mSplitHiddenTaskView != null) {
mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID);
- // mSplitHiddenTaskView is set when split select animation starts. The TaskView is only
- // removed when when the animation finishes. So in the case of overview being dismissed
- // during the animation, we should not call clearAndRecycleTaskView() because it has
- // not been removed yet.
- if (mSplitHiddenTaskView.getParent() == null) {
- clearAndRecycleTaskView(mSplitHiddenTaskView);
- }
mSplitHiddenTaskView = null;
}
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 082971c..d207edf 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -434,37 +434,54 @@
// Used to cache thumbnail bounds to avoid recalculating on every hover move.
private var thumbnailBounds = Rect()
- private var focusTransitionProgress = 1f
+ // Progress variable indicating if the TaskView is in a settled state:
+ // 0 = The TaskView is in a transitioning state e.g. during gesture, in quickswitch carousel,
+ // becoming focus task etc.
+ // 1 = The TaskView is settled and no longer transitioning
+ private var settledProgress = 1f
set(value) {
field = value
- onFocusTransitionProgressUpdated(field)
+ onSettledProgressUpdated(field)
}
- private val focusTransitionPropertyFactory =
+ private val settledProgressPropertyFactory =
MultiPropertyFactory(
this,
- FOCUS_TRANSITION,
- FOCUS_TRANSITION_INDEX_COUNT,
+ SETTLED_PROGRESS,
+ SETTLED_PROGRESS_INDEX_COUNT,
{ x: Float, y: Float -> x * y },
1f,
)
- private val focusTransitionFullscreen =
- focusTransitionPropertyFactory.get(FOCUS_TRANSITION_INDEX_FULLSCREEN)
- private val focusTransitionScaleAndDim =
- focusTransitionPropertyFactory.get(FOCUS_TRANSITION_INDEX_SCALE_AND_DIM)
+ private val settledProgressFullscreen =
+ settledProgressPropertyFactory.get(SETTLED_PROGRESS_INDEX_FULLSCREEN)
+ private val settledProgressGesture =
+ settledProgressPropertyFactory.get(SETTLED_PROGRESS_INDEX_GESTURE)
+ private val settledProgressDismiss =
+ settledProgressPropertyFactory.get(SETTLED_PROGRESS_INDEX_DISMISS)
/**
- * Returns an animator of [focusTransitionScaleAndDim] that transition out with a built-in
+ * Returns an animator of [settledProgressDismiss] that transition in with a built-in
* interpolator.
*/
- fun getFocusTransitionScaleAndDimOutAnimator(): ObjectAnimator =
+ fun getDismissIconFadeInAnimator(): ObjectAnimator =
+ ObjectAnimator.ofFloat(settledProgressDismiss, MULTI_PROPERTY_VALUE, 1f).apply {
+ duration = FADE_IN_ICON_DURATION
+ interpolator = FADE_IN_ICON_INTERPOLATOR
+ }
+
+ /**
+ * Returns an animator of [settledProgressDismiss] that transition out with a built-in
+ * interpolator. [AnimatedFloat] is used to apply another level of interpolation, on top of
+ * interpolator set to the [Animator] by the caller.
+ */
+ fun getDismissIconFadeOutAnimator(): ObjectAnimator =
AnimatedFloat { v ->
- focusTransitionScaleAndDim.value =
- FOCUS_TRANSITION_FAST_OUT_INTERPOLATOR.getInterpolation(v)
+ settledProgressDismiss.value =
+ SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR.getInterpolation(v)
}
.animateToValue(1f, 0f)
- private var iconAndDimAnimator: ObjectAnimator? = null
+ private var iconFadeInOnGestureCompleteAnimator: ObjectAnimator? = null
// The current background requests to load the task thumbnail and icon
private val pendingThumbnailLoadRequests = mutableListOf<CancellableTask<*>>()
private val pendingIconLoadRequests = mutableListOf<CancellableTask<*>>()
@@ -1423,23 +1440,23 @@
* Called to animate a smooth transition when going directly from an app into Overview (and vice
* versa). Icons fade in, and DWB banners slide in with a "shift up" animation.
*/
- private fun onFocusTransitionProgressUpdated(focusTransitionProgress: Float) {
+ private fun onSettledProgressUpdated(settledProgress: Float) {
taskContainers.forEach {
- it.iconView.setContentAlpha(focusTransitionProgress)
- it.digitalWellBeingToast?.bannerOffsetPercentage = 1f - focusTransitionProgress
+ it.iconView.setContentAlpha(settledProgress)
+ it.digitalWellBeingToast?.bannerOffsetPercentage = 1f - settledProgress
}
}
- fun animateIconScaleAndDimIntoView() {
- iconAndDimAnimator?.cancel()
- iconAndDimAnimator =
- ObjectAnimator.ofFloat(focusTransitionScaleAndDim, MULTI_PROPERTY_VALUE, 0f, 1f).apply {
- duration = SCALE_ICON_DURATION
+ fun startIconFadeInOnGestureComplete() {
+ iconFadeInOnGestureCompleteAnimator?.cancel()
+ iconFadeInOnGestureCompleteAnimator =
+ ObjectAnimator.ofFloat(settledProgressGesture, MULTI_PROPERTY_VALUE, 1f).apply {
+ duration = FADE_IN_ICON_DURATION
interpolator = Interpolators.LINEAR
addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
- iconAndDimAnimator = null
+ iconFadeInOnGestureCompleteAnimator = null
}
}
)
@@ -1447,9 +1464,9 @@
}
}
- fun setIconScaleAndDim(iconScale: Float) {
- iconAndDimAnimator?.cancel()
- focusTransitionScaleAndDim.value = iconScale
+ fun setIconVisibleForGesture(isVisible: Boolean) {
+ iconFadeInOnGestureCompleteAnimator?.cancel()
+ settledProgressGesture.value = if (isVisible) 1f else 0f
}
/** Set a color tint on the snapshot and supporting views. */
@@ -1544,8 +1561,8 @@
it.iconView.setVisibility(if (fullscreenProgress < 1) VISIBLE else INVISIBLE)
it.overlay.setFullscreenProgress(fullscreenProgress)
}
- focusTransitionFullscreen.value =
- FOCUS_TRANSITION_FAST_OUT_INTERPOLATOR.getInterpolation(1 - fullscreenProgress)
+ settledProgressFullscreen.value =
+ SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR.getInterpolation(1 - fullscreenProgress)
updateFullscreenParams()
}
@@ -1602,7 +1619,8 @@
}
dismissScale = 1f
translationZ = 0f
- setIconScaleAndDim(1f)
+ setIconVisibleForGesture(true)
+ settledProgressDismiss.value = 1f
setColorTint(0f, 0)
}
@@ -1630,9 +1648,10 @@
const val FLAG_UPDATE_ALL =
(FLAG_UPDATE_ICON or FLAG_UPDATE_THUMBNAIL or FLAG_UPDATE_CORNER_RADIUS)
- const val FOCUS_TRANSITION_INDEX_FULLSCREEN = 0
- const val FOCUS_TRANSITION_INDEX_SCALE_AND_DIM = 1
- const val FOCUS_TRANSITION_INDEX_COUNT = 2
+ const val SETTLED_PROGRESS_INDEX_FULLSCREEN = 0
+ const val SETTLED_PROGRESS_INDEX_GESTURE = 1
+ const val SETTLED_PROGRESS_INDEX_DISMISS = 2
+ const val SETTLED_PROGRESS_INDEX_COUNT = 3
private const val ALPHA_INDEX_STABLE = 0
private const val ALPHA_INDEX_ATTACH = 1
@@ -1642,25 +1661,26 @@
/** The maximum amount that a task view can be scrimmed, dimmed or tinted. */
const val MAX_PAGE_SCRIM_ALPHA = 0.4f
- const val SCALE_ICON_DURATION: Long = 120
+ const val FADE_IN_ICON_DURATION: Long = 120
private const val DIM_ANIM_DURATION: Long = 700
- private const val FOCUS_TRANSITION_THRESHOLD =
- SCALE_ICON_DURATION.toFloat() / DIM_ANIM_DURATION
- val FOCUS_TRANSITION_FAST_OUT_INTERPOLATOR =
+ private const val SETTLE_TRANSITION_THRESHOLD =
+ FADE_IN_ICON_DURATION.toFloat() / DIM_ANIM_DURATION
+ val SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR =
Interpolators.clampToProgress(
Interpolators.FAST_OUT_SLOW_IN,
- 1f - FOCUS_TRANSITION_THRESHOLD,
+ 1f - SETTLE_TRANSITION_THRESHOLD,
1f,
)!!
+ private val FADE_IN_ICON_INTERPOLATOR = Interpolators.LINEAR
private val SYSTEM_GESTURE_EXCLUSION_RECT = listOf(Rect())
- private val FOCUS_TRANSITION: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("focusTransition") {
+ private val SETTLED_PROGRESS: FloatProperty<TaskView> =
+ object : FloatProperty<TaskView>("settleTransition") {
override fun setValue(taskView: TaskView, v: Float) {
- taskView.focusTransitionProgress = v
+ taskView.settledProgress = v
}
- override fun get(taskView: TaskView) = taskView.focusTransitionProgress
+ override fun get(taskView: TaskView) = taskView.settledProgress
}
private val SPLIT_SELECT_TRANSLATION_X: FloatProperty<TaskView> =
diff --git a/res/values-night-v31/colors.xml b/res/values-night-v31/colors.xml
index d9f9769..4a6340a 100644
--- a/res/values-night-v31/colors.xml
+++ b/res/values-night-v31/colors.xml
@@ -47,7 +47,7 @@
<color name="widget_picker_unselected_tab_text_color_dark">
@android:color/system_neutral2_200</color>
<color name="widget_picker_collapse_handle_color_dark">
- @android:color/system_neutral2_700</color>
+ @android:color/system_neutral2_400</color>
<color name="widget_picker_add_button_background_color_dark">
@android:color/system_accent1_200</color>
<color name="widget_picker_add_button_text_color_dark">
diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml
index d74e308..8b43f20 100644
--- a/res/values-v31/colors.xml
+++ b/res/values-v31/colors.xml
@@ -98,7 +98,7 @@
<color name="widget_picker_unselected_tab_text_color_light">
@android:color/system_neutral2_700</color>
<color name="widget_picker_collapse_handle_color_light">
- @android:color/system_neutral2_200</color>
+ @android:color/system_neutral2_500</color>
<color name="widget_picker_add_button_background_color_light">
@android:color/system_accent1_600</color>
<color name="widget_picker_add_button_text_color_light">
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 967d97d..914ffd6 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -114,7 +114,7 @@
<color name="widget_picker_tab_background_unselected_light">#E3E3E3</color>
<color name="widget_picker_selected_tab_text_color_light">#FFFFFF</color>
<color name="widget_picker_unselected_tab_text_color_light">#444746</color>
- <color name="widget_picker_collapse_handle_color_light">#C4C7C5</color>
+ <color name="widget_picker_collapse_handle_color_light">#747775</color>
<color name="widget_picker_add_button_background_color_light">#0B57D0</color>
<color name="widget_picker_add_button_text_color_light">#0B57D0</color>
<color name="widget_picker_expand_button_background_color_light">
@@ -141,7 +141,7 @@
<color name="widget_picker_tab_background_unselected_dark">#343535</color>
<color name="widget_picker_selected_tab_text_color_dark">#2D312F</color>
<color name="widget_picker_unselected_tab_text_color_dark">#C4C7C5</color>
- <color name="widget_picker_collapse_handle_color_dark">#444746</color>
+ <color name="widget_picker_collapse_handle_color_dark">#8e918f</color>
<color name="widget_picker_add_button_background_color_dark">#062E6F</color>
<color name="widget_picker_add_button_text_color_dark">#FFFFFF</color>
<color name="widget_picker_expand_button_background_color_dark">
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 5b8d2fc..cb021c7 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -423,8 +423,6 @@
private final SettingsCache.OnChangeListener mNaturalScrollingChangedListener =
enabled -> mIsNaturalScrollingEnabled = enabled;
- private boolean mRecreateToUpdateTheme = false;
-
public static Launcher getLauncher(Context context) {
return fromContext(context);
}
@@ -1751,12 +1749,6 @@
}
@Override
- protected void recreateToUpdateTheme() {
- mRecreateToUpdateTheme = true;
- super.recreateToUpdateTheme();
- }
-
- @Override
public void onRestoreInstanceState(Bundle state) {
super.onRestoreInstanceState(state);
IntSet synchronouslyBoundPages = mModelCallbacks.getSynchronouslyBoundPages();
@@ -1801,8 +1793,6 @@
outState.putParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT, mPendingActivityResult);
}
- outState.putBoolean(RUNTIME_STATE_RECREATE_TO_UPDATE_THEME, mRecreateToUpdateTheme);
-
super.onSaveInstanceState(outState);
}
diff --git a/src/com/android/launcher3/LauncherConstants.java b/src/com/android/launcher3/LauncherConstants.java
index 0ed239d..5cba82f 100644
--- a/src/com/android/launcher3/LauncherConstants.java
+++ b/src/com/android/launcher3/LauncherConstants.java
@@ -68,7 +68,7 @@
// Type int[]
static final String RUNTIME_STATE_CURRENT_SCREEN_IDS = "launcher.current_screen_ids";
// Type: boolean
- static final String RUNTIME_STATE_RECREATE_TO_UPDATE_THEME =
+ public static final String RUNTIME_STATE_RECREATE_TO_UPDATE_THEME =
"launcher.recreate_to_update_theme";
}
}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 71a2589..ebcb5da 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -16,8 +16,6 @@
package com.android.launcher3;
-import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction;
-
import static com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN;
import static com.android.launcher3.Flags.enableSmartspaceAsAWidget;
import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED;
@@ -36,9 +34,6 @@
import android.content.pm.ShortcutInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BlendMode;
-import android.graphics.BlendModeColorFilter;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.LightingColorFilter;
@@ -49,10 +44,8 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.InsetDrawable;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.DeadObjectException;
@@ -85,7 +78,6 @@
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.CacheableShortcutInfo;
import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.icons.ThemedIconDrawable;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.pm.ShortcutConfigActivityInfo;
@@ -696,29 +688,19 @@
return null;
}
- // Inject monochrome icon drawable
+ // Inject theme icon drawable
if (ATLEAST_T && useTheme) {
- result.mutate();
- int[] colors = ThemedIconDrawable.getColors(context);
- Drawable mono = result.getMonochrome();
-
- if (mono != null) {
- mono.setTint(colors[1]);
- } else if (info instanceof ItemInfoWithIcon iiwi) {
- // Inject a previously generated monochrome icon
- Bitmap monoBitmap = iiwi.bitmap.getMono();
- if (monoBitmap != null) {
- // Use BitmapDrawable instead of FastBitmapDrawable so that the colorState is
- // preserved in constantState
- mono = new BitmapDrawable(monoBitmap);
- mono.setColorFilter(new BlendModeColorFilter(colors[1], BlendMode.SRC_IN));
- // Inset the drawable according to the AdaptiveIconDrawable layers
- mono = new InsetDrawable(mono, getExtraInsetFraction() / 2);
+ try (LauncherIcons li = LauncherIcons.obtain(context)) {
+ if (li.getThemeController() != null) {
+ AdaptiveIconDrawable themed = li.getThemeController().createThemedAdaptiveIcon(
+ context,
+ result,
+ info instanceof ItemInfoWithIcon iiwi ? iiwi.bitmap : null);
+ if (themed != null) {
+ result = themed;
+ }
}
}
- if (mono != null) {
- result = new AdaptiveIconDrawable(new ColorDrawable(colors[0]), mono);
- }
}
if (badge == null) {
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
index 884d448..839dfb7 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -17,15 +17,13 @@
package com.android.launcher3.icons;
import android.content.Context;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import androidx.annotation.NonNull;
-import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.graphics.IconShape;
+import com.android.launcher3.icons.mono.MonoIconThemeController;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SafeCloseable;
@@ -57,13 +55,13 @@
private final ConcurrentLinkedQueue<LauncherIcons> mPool;
- private MonochromeIconFactory mMonochromeIconFactory;
-
protected LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize,
ConcurrentLinkedQueue<LauncherIcons> pool) {
super(context, fillResIconDpi, iconBitmapSize,
IconShape.INSTANCE.get(context).getShape().enableShapeDetection());
- mMonoIconEnabled = Themes.isThemedIconEnabled(context);
+ if (Themes.isThemedIconEnabled(context)) {
+ mThemeController = new MonoIconThemeController();
+ }
mPool = pool;
}
@@ -75,18 +73,6 @@
mPool.add(this);
}
- @Override
- protected Drawable getMonochromeDrawable(AdaptiveIconDrawable base) {
- Drawable mono = super.getMonochromeDrawable(base);
- if (mono != null || !Flags.forceMonochromeAppIcons()) {
- return mono;
- }
- if (mMonochromeIconFactory == null) {
- mMonochromeIconFactory = new MonochromeIconFactory(mIconBitmapSize);
- }
- return mMonochromeIconFactory.wrap(base);
- }
-
@NonNull
@Override
protected UserIconInfo getUserInfo(@NonNull UserHandle user) {
diff --git a/src/com/android/launcher3/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java
index 079191f..f21e5da 100644
--- a/src/com/android/launcher3/statemanager/StatefulActivity.java
+++ b/src/com/android/launcher3/statemanager/StatefulActivity.java
@@ -18,6 +18,7 @@
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
+import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_RECREATE_TO_UPDATE_THEME;
import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE;
import android.content.Context;
@@ -29,6 +30,7 @@
import android.view.View;
import androidx.annotation.CallSuper;
+import androidx.annotation.NonNull;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.LauncherRootView;
@@ -54,6 +56,7 @@
protected Configuration mOldConfig;
private int mOldRotation;
+ private boolean mRecreateToUpdateTheme = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -63,6 +66,18 @@
mOldRotation = WindowManagerProxy.INSTANCE.get(this).getRotation(this);
}
+ @Override
+ protected void onSaveInstanceState(@NonNull Bundle outState) {
+ outState.putBoolean(RUNTIME_STATE_RECREATE_TO_UPDATE_THEME, mRecreateToUpdateTheme);
+ super.onSaveInstanceState(outState);
+ }
+
+ @Override
+ protected void recreateToUpdateTheme() {
+ mRecreateToUpdateTheme = true;
+ super.recreateToUpdateTheme();
+ }
+
/**
* Create handlers to control the property changes for this activity
*/
diff --git a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
index 111ffaa..7f481b7 100644
--- a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
@@ -18,6 +18,7 @@
import android.R
import android.content.Context
+import android.graphics.Bitmap
import android.os.Process
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -26,9 +27,9 @@
import com.android.launcher3.LauncherPrefs.Companion.THEMED_ICONS
import com.android.launcher3.LauncherPrefs.Companion.get
import com.android.launcher3.graphics.PreloadIconDrawable
-import com.android.launcher3.icons.BaseIconFactory
import com.android.launcher3.icons.FastBitmapDrawable
import com.android.launcher3.icons.UserBadgeDrawable
+import com.android.launcher3.icons.mono.MonoThemedBitmap
import com.android.launcher3.model.data.FolderInfo
import com.android.launcher3.model.data.ItemInfo
import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED
@@ -88,54 +89,26 @@
// Use getAppContents() to "cast" contents to WorkspaceItemInfo so we can set bitmaps
val folderApps = modelHelper.bgDataModel.collections.valueAt(0).getAppContents()
// Set first icon to be themed.
- folderApps[0]
- .bitmap
- .setMonoIcon(
+ folderApps[0].bitmap.themedBitmap =
+ MonoThemedBitmap(
folderApps[0].bitmap.icon,
- BaseIconFactory(
- context,
- context.resources.configuration.densityDpi,
- previewItemManager.mIconSize,
- ),
+ Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888),
)
// Set second icon to be non-themed.
- folderApps[1]
- .bitmap
- .setMonoIcon(
- null,
- BaseIconFactory(
- context,
- context.resources.configuration.densityDpi,
- previewItemManager.mIconSize,
- ),
- )
+ folderApps[1].bitmap.themedBitmap = null
// Set third icon to be themed with badge.
- folderApps[2]
- .bitmap
- .setMonoIcon(
+ folderApps[2].bitmap.themedBitmap =
+ MonoThemedBitmap(
folderApps[2].bitmap.icon,
- BaseIconFactory(
- context,
- context.resources.configuration.densityDpi,
- previewItemManager.mIconSize,
- ),
+ Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888),
)
folderApps[2].bitmap = folderApps[2].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
// Set fourth icon to be non-themed with badge.
folderApps[3].bitmap = folderApps[3].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
- folderApps[3]
- .bitmap
- .setMonoIcon(
- null,
- BaseIconFactory(
- context,
- context.resources.configuration.densityDpi,
- previewItemManager.mIconSize,
- ),
- )
+ folderApps[3].bitmap.themedBitmap = null
defaultThemedIcons = get(context).get(THEMED_ICONS)
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt b/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt
new file mode 100644
index 0000000..4af564e
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.icons.mono
+
+import android.graphics.Color
+import android.graphics.drawable.AdaptiveIconDrawable
+import android.graphics.drawable.ColorDrawable
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.platform.uiautomatorhelpers.DeviceHelpers.context
+import android.util.DisplayMetrics
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.Flags
+import com.android.launcher3.icons.BaseIconFactory
+import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.util.LauncherMultivalentJUnit.Companion.isRunningInRobolectric
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Assume.assumeFalse
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MonoIconThemeControllerTest {
+
+ @get:Rule val mSetFlagsRule = SetFlagsRule()
+
+ private val iconFactory = BaseIconFactory(context, DisplayMetrics.DENSITY_MEDIUM, 30)
+
+ @Test
+ fun `createThemedBitmap when mono drawable is present`() {
+ val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, ColorDrawable(Color.RED))
+ assertNotNull(
+ MonoIconThemeController().createThemedBitmap(icon, BitmapInfo.LOW_RES_INFO, iconFactory)
+ )
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_FORCE_MONOCHROME_APP_ICONS)
+ fun `createThemedBitmap when mono generation is disabled`() {
+ val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, null)
+ assertNull(
+ MonoIconThemeController().createThemedBitmap(icon, BitmapInfo.LOW_RES_INFO, iconFactory)
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_FORCE_MONOCHROME_APP_ICONS)
+ fun `createThemedBitmap when mono generation is enabled`() {
+ ensureBitmapSerializationSupported()
+ val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, null)
+ assertNotNull(
+ MonoIconThemeController().createThemedBitmap(icon, BitmapInfo.LOW_RES_INFO, iconFactory)
+ )
+ }
+
+ @Test
+ fun `decode bitmap after serialization valid data`() {
+ ensureBitmapSerializationSupported()
+ val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, ColorDrawable(Color.RED))
+ val iconInfo = iconFactory.createBadgedIconBitmap(icon)
+
+ val themeBitmap =
+ MonoIconThemeController().createThemedBitmap(icon, iconInfo, iconFactory)!!
+ assertNotNull(
+ MonoIconThemeController().decode(themeBitmap.serialize(), iconInfo, iconFactory)
+ )
+ }
+
+ @Test
+ fun `decode bitmap after serialization invalid data`() {
+ ensureBitmapSerializationSupported()
+ val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, ColorDrawable(Color.RED))
+ val iconInfo = iconFactory.createBadgedIconBitmap(icon)
+ assertNull(MonoIconThemeController().decode(byteArrayOf(1, 1, 1, 1), iconInfo, iconFactory))
+ }
+
+ @Test
+ fun `createThemedAdaptiveIcon with monochrome drawable`() {
+ val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, ColorDrawable(Color.RED))
+ assertNotNull(MonoIconThemeController().createThemedAdaptiveIcon(context, icon, null))
+ }
+
+ @Test
+ fun `createThemedAdaptiveIcon with bitmap info`() {
+ val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, ColorDrawable(Color.RED))
+ val iconInfo = iconFactory.createBadgedIconBitmap(icon)
+ iconInfo.themedBitmap =
+ MonoIconThemeController().createThemedBitmap(icon, iconInfo, iconFactory)
+
+ val nonMonoIcon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, null)
+ assertNotNull(
+ MonoIconThemeController().createThemedAdaptiveIcon(context, nonMonoIcon, iconInfo)
+ )
+ }
+
+ @Test
+ fun `createThemedAdaptiveIcon invalid bitmap info`() {
+ val nonMonoIcon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, null)
+ assertNull(
+ MonoIconThemeController()
+ .createThemedAdaptiveIcon(context, nonMonoIcon, BitmapInfo.LOW_RES_INFO)
+ )
+ }
+
+ companion object {
+
+ fun ensureBitmapSerializationSupported() {
+ // Robolectric doesn't support serializing 8-bit bitmaps
+ assumeFalse(isRunningInRobolectric)
+ }
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoThemedBitmapTest.kt b/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoThemedBitmapTest.kt
new file mode 100644
index 0000000..32de9e9
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoThemedBitmapTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.icons.mono
+
+import android.graphics.Bitmap
+import android.platform.uiautomatorhelpers.DeviceHelpers.context
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.icons.mono.MonoIconThemeControllerTest.Companion.ensureBitmapSerializationSupported
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MonoThemedBitmapTest {
+
+ @Test
+ fun `newDrawable returns valid drawable`() {
+ val bitmap =
+ MonoThemedBitmap(
+ Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8),
+ Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888),
+ )
+ val d = bitmap.newDrawable(BitmapInfo.LOW_RES_INFO, context)
+ assertTrue(d is ThemedIconDrawable)
+ }
+
+ @Test
+ fun `serialize returns valid bytes`() {
+ ensureBitmapSerializationSupported()
+ val bitmap =
+ MonoThemedBitmap(
+ Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8),
+ Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888),
+ )
+ assertTrue(bitmap.serialize().isNotEmpty())
+ }
+}
diff --git a/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java b/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
index c623513..c852729 100644
--- a/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
@@ -37,7 +37,7 @@
import com.android.launcher3.LauncherState;
import com.android.launcher3.allapps.AllAppsRecyclerView;
import com.android.launcher3.celllayout.FavoriteItemsTransaction;
-import com.android.launcher3.icons.ThemedIconDrawable;
+import com.android.launcher3.icons.mono.ThemedIconDrawable;
import com.android.launcher3.popup.ArrowPopup;
import com.android.launcher3.util.BaseLauncherActivityTest;
import com.android.launcher3.util.Executors;