Merge "Adding option to skip animations for testing" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 4f5b1a0..cc746eb 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -629,3 +629,10 @@
description: "Enable Alt + Tab KQS view to show apps in flattened structure"
bug: "382769617"
}
+
+flag {
+ name: "enable_gesture_nav_on_connected_displays"
+ namespace: "lse_desktop_experience"
+ description: "Enables gesture navigation handling on connected displays"
+ bug: "382130680"
+}
diff --git a/aconfig/launcher_growth.aconfig b/aconfig/launcher_growth.aconfig
new file mode 100644
index 0000000..a880538
--- /dev/null
+++ b/aconfig/launcher_growth.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.launcher3"
+container: "system_ext"
+
+flag {
+ name: "enable_growth_nudge"
+ namespace: "desktop_oobe"
+ description: "Add growth nudge in launcher"
+ bug: "396165728"
+}
diff --git a/go/quickstep/src/com/android/launcher3/util/MainThreadInitializedObject.java b/go/quickstep/src/com/android/launcher3/util/MainThreadInitializedObject.java
new file mode 100644
index 0000000..e1f3508
--- /dev/null
+++ b/go/quickstep/src/com/android/launcher3/util/MainThreadInitializedObject.java
@@ -0,0 +1,71 @@
+/*
+ * 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.launcher3.util;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.content.Context;
+import android.os.Looper;
+
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Utility class for defining singletons which are initiated on main thread.
+ */
+public class MainThreadInitializedObject<T extends SafeCloseable> {
+
+ private final ObjectProvider<T> mProvider;
+ private T mValue;
+
+ public MainThreadInitializedObject(ObjectProvider<T> provider) {
+ mProvider = provider;
+ }
+
+ public T get(Context context) {
+ Context app = context.getApplicationContext();
+ if (app instanceof ObjectSandbox sc) {
+ return sc.getObject(this);
+ }
+
+ if (mValue == null) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ mValue = TraceHelper.allowIpcs("main.thread.object", () -> mProvider.get(app));
+ } else {
+ try {
+ return MAIN_EXECUTOR.submit(() -> get(context)).get();
+ } catch (InterruptedException | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ return mValue;
+ }
+
+ public interface ObjectProvider<T> {
+
+ T get(Context context);
+ }
+
+ /** Sandbox for isolating {@link MainThreadInitializedObject} instances from Launcher. */
+ public interface ObjectSandbox {
+
+ /**
+ * Find a cached object from mObjectMap if we have already created one. If not, generate
+ * an object using the provider.
+ */
+ <T extends SafeCloseable> T getObject(MainThreadInitializedObject<T> object);
+ }
+}
diff --git a/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml b/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml
index 7530c28..8ca59c4 100644
--- a/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml
+++ b/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml
@@ -137,6 +137,7 @@
android:layout_above="@id/gesture_tutorial_fragment_action_button"
android:layout_centerHorizontal="true"
android:background="@android:color/transparent"
+ android:screenReaderFocusable="true"
android:paddingTop="24dp"
android:paddingHorizontal="24dp"
android:layout_marginBottom="16dp">
@@ -146,8 +147,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="104dp"
- android:accessibilityHeading="true"
- android:accessibilityTraversalBefore="@id/gesture_tutorial_fragment_feedback_subtitle"
android:gravity="top"
android:lineSpacingExtra="-1sp"
android:textAppearance="@style/TextAppearance.GestureTutorial.MainTitle"
@@ -162,8 +161,6 @@
android:layout_marginTop="24dp"
android:lineSpacingExtra="4sp"
android:textAppearance="@style/TextAppearance.GestureTutorial.MainSubtitle"
- android:accessibilityTraversalAfter="@id/gesture_tutorial_fragment_feedback_title"
- android:accessibilityTraversalBefore="@id/gesture_tutorial_fragment_action_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/gesture_tutorial_fragment_feedback_title" />
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 9f81124..84ae0fe 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -1784,8 +1784,8 @@
}
/** Get animation duration for taskbar for going to home. */
- public static int getTaskbarToHomeDuration(boolean isPinnedTaskbar) {
- return getTaskbarToHomeDuration(false, isPinnedTaskbar);
+ public static int getTaskbarToHomeDuration(boolean isPinnedTaskbarAndNotInDesktopMode) {
+ return getTaskbarToHomeDuration(false, isPinnedTaskbarAndNotInDesktopMode);
}
/**
@@ -1794,8 +1794,8 @@
* @param shouldOverrideToFastAnimation should overwrite scaling reveal home animation duration
*/
public static int getTaskbarToHomeDuration(boolean shouldOverrideToFastAnimation,
- boolean isPinnedTaskbar) {
- if (isPinnedTaskbar) {
+ boolean isPinnedTaskbarAndNotInDesktopMode) {
+ if (isPinnedTaskbarAndNotInDesktopMode) {
return PINNED_TASKBAR_TRANSITION_DURATION;
} else if (enableScalingRevealHomeAnimation() && !shouldOverrideToFastAnimation) {
return TASKBAR_TO_HOME_DURATION_SLOW;
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt
index 138f40a..eb24df1 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt
+++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt
@@ -32,7 +32,8 @@
import com.android.launcher3.uioverrides.QuickstepLauncher
import com.android.launcher3.util.DaggerSingletonObject
import com.android.launcher3.util.DaggerSingletonTracker
-import com.android.launcher3.util.Executors
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
import com.android.launcher3.util.window.WindowManagerProxy.DesktopVisibilityListener
import com.android.quickstep.GestureState.GestureEndTarget
import com.android.quickstep.SystemUiProxy
@@ -87,9 +88,22 @@
private val desktopVisibilityListeners: MutableSet<DesktopVisibilityListener> = HashSet()
private val taskbarDesktopModeListeners: MutableSet<TaskbarDesktopModeListener> = HashSet()
+ // This simply indicates that user is currently in desktop mode or not.
+ var isInDesktopMode = false
+ private set
+
+ // to track if any pending notification to be done.
+ var isNotifyingDesktopVisibilityPending = false
+
+ // to let launcher hold off on notifying desktop visibility listeners.
+ var launcherAnimationRunning = false
+
// TODO: b/394387739 - Deprecate this and replace it with something that tracks the count per
// desk.
- /** Number of visible desktop windows in desktop mode. */
+ /**
+ * Number of visible desktop windows in desktop mode. This can be > 0 when user goes to overview
+ * from desktop window mode.
+ */
var visibleDesktopTasksCount: Int = 0
/**
* Sets the number of desktop windows that are visible and updates launcher visibility based
@@ -107,13 +121,27 @@
}
if (visibleTasksCount != field) {
+ if (visibleDesktopTasksCount == 0 && visibleTasksCount == 1) {
+ isInDesktopMode = true
+ }
+ if (visibleDesktopTasksCount == 1 && visibleTasksCount == 0) {
+ isInDesktopMode = false
+ }
val wasVisible = field > 0
val isVisible = visibleTasksCount > 0
val wereDesktopTasksVisibleBefore = areDesktopTasksVisibleAndNotInOverview()
field = visibleTasksCount
val areDesktopTasksVisibleNow = areDesktopTasksVisibleAndNotInOverview()
- if (wereDesktopTasksVisibleBefore != areDesktopTasksVisibleNow) {
- notifyIsInDesktopModeChanged(DEFAULT_DISPLAY, areDesktopTasksVisibleNow)
+
+ if (
+ wereDesktopTasksVisibleBefore != areDesktopTasksVisibleNow ||
+ wasVisible != isVisible
+ ) {
+ if (!launcherAnimationRunning) {
+ notifyIsInDesktopModeChanged(DEFAULT_DISPLAY, areDesktopTasksVisibleNow)
+ } else {
+ isNotifyingDesktopVisibilityPending = true
+ }
}
if (
@@ -169,7 +197,7 @@
/** Returns whether a desk is currently active on the display with the given [displayId]. */
fun isInDesktopMode(displayId: Int): Boolean {
if (!DesktopModeStatus.enableMultipleDesktops(context)) {
- return areDesktopTasksVisible()
+ return isInDesktopMode
}
val activeDeskId = getDisplayDeskConfig(displayId)?.activeDeskId ?: INACTIVE_DESK_ID
@@ -196,15 +224,6 @@
}
/** Whether desktop tasks are visible in desktop mode. */
- private fun areDesktopTasksVisible(): Boolean {
- val desktopTasksVisible: Boolean = visibleDesktopTasksCount > 0
- if (DEBUG) {
- Log.d(TAG, "areDesktopTasksVisible: desktopVisible=$desktopTasksVisible")
- }
- return desktopTasksVisible
- }
-
- /** Whether desktop tasks are visible in desktop mode. */
private fun areDesktopTasksVisibleAndNotInOverview(): Boolean {
val desktopTasksVisible: Boolean = visibleDesktopTasksCount > 0
if (DEBUG) {
@@ -237,6 +256,22 @@
)
}
+ /**
+ * Launcher Driven Desktop Mode changes. For example, swipe to home and quick switch from
+ * Desktop Windowing Mode. if there is any pending notification please notify desktop visibility
+ * listeners.
+ */
+ fun onLauncherAnimationFromDesktopEnd() {
+ launcherAnimationRunning = false
+ if (isNotifyingDesktopVisibilityPending) {
+ isNotifyingDesktopVisibilityPending = false
+ notifyIsInDesktopModeChanged(
+ DEFAULT_DISPLAY,
+ isInDesktopModeAndNotInOverview(DEFAULT_DISPLAY),
+ )
+ }
+ }
+
fun onLauncherStateChanged(state: RecentsState) {
onLauncherStateChanged(
state,
@@ -346,6 +381,26 @@
}
}
+ private fun notifyTaskbarDesktopModeListenersForEntry(duration: Int) {
+ if (DEBUG) {
+ Log.d(TAG, "notifyTaskbarDesktopModeListenersForEntry: duration=" + duration)
+ }
+ for (listener in taskbarDesktopModeListeners) {
+ listener.onEnterDesktopMode(duration)
+ }
+ DisplayController.INSTANCE.get(context).notifyConfigChange()
+ }
+
+ private fun notifyTaskbarDesktopModeListenersForExit(duration: Int) {
+ if (DEBUG) {
+ Log.d(TAG, "notifyTaskbarDesktopModeListenersForExit: duration=" + duration)
+ }
+ for (listener in taskbarDesktopModeListeners) {
+ listener.onExitDesktopMode(duration)
+ }
+ DisplayController.INSTANCE.get(context).notifyConfigChange()
+ }
+
/** TODO: b/333533253 - Remove after flag rollout */
private fun setBackgroundStateEnabled(backgroundStateEnabled: Boolean) {
if (DEBUG) {
@@ -552,14 +607,14 @@
displayDeskStates: Array<DisplayDeskState>,
canCreateDesks: Boolean,
) {
- Executors.MAIN_EXECUTOR.execute {
+ MAIN_EXECUTOR.execute {
controller.get()?.onListenerConnected(displayDeskStates, canCreateDesks)
}
}
override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
if (displayId != this.displayId) return
- Executors.MAIN_EXECUTOR.execute {
+ MAIN_EXECUTOR.execute {
controller.get()?.apply {
if (DEBUG) {
Log.d(TAG, "desktop visible tasks count changed=$visibleTasksCount")
@@ -575,7 +630,7 @@
override fun onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding: Boolean) {
if (!DesktopModeStatus.useRoundedCorners()) return
- Executors.MAIN_EXECUTOR.execute {
+ MAIN_EXECUTOR.execute {
controller.get()?.apply {
Log.d(
TAG,
@@ -587,26 +642,46 @@
}
}
- override fun onEnterDesktopModeTransitionStarted(transitionDuration: Int) {}
-
- override fun onExitDesktopModeTransitionStarted(transitionDuration: Int) {}
-
- override fun onCanCreateDesksChanged(canCreateDesks: Boolean) {
- Executors.MAIN_EXECUTOR.execute {
- controller.get()?.onCanCreateDesksChanged(canCreateDesks)
+ override fun onEnterDesktopModeTransitionStarted(transitionDuration: Int) {
+ MAIN_EXECUTOR.execute {
+ Log.d(
+ TAG,
+ ("DesktopTaskListenerImpl: onEnterDesktopModeTransitionStarted with " +
+ "duration= " +
+ transitionDuration),
+ )
+ controller.get()?.isInDesktopMode = true
+ controller.get()?.notifyTaskbarDesktopModeListenersForEntry(transitionDuration)
}
}
+ override fun onExitDesktopModeTransitionStarted(transitionDuration: Int) {
+ MAIN_EXECUTOR.execute {
+ Log.d(
+ TAG,
+ ("DesktopTaskListenerImpl: onExitDesktopModeTransitionStarted with " +
+ "duration= " +
+ transitionDuration),
+ )
+ controller.get()?.isInDesktopMode = false
+ controller.get()?.notifyTaskbarDesktopModeListenersForExit(transitionDuration)
+ }
+ }
+
+ override fun onCanCreateDesksChanged(canCreateDesks: Boolean) {
+ MAIN_EXECUTOR.execute { controller.get()?.onCanCreateDesksChanged(canCreateDesks) }
+ }
+
override fun onDeskAdded(displayId: Int, deskId: Int) {
- Executors.MAIN_EXECUTOR.execute { controller.get()?.onDeskAdded(displayId, deskId) }
+ MAIN_EXECUTOR.execute { controller.get()?.onDeskAdded(displayId, deskId) }
}
override fun onDeskRemoved(displayId: Int, deskId: Int) {
- Executors.MAIN_EXECUTOR.execute { controller.get()?.onDeskRemoved(displayId, deskId) }
+ MAIN_EXECUTOR.execute { controller.get()?.onDeskRemoved(displayId, deskId) }
}
override fun onActiveDeskChanged(displayId: Int, newActiveDesk: Int, oldActiveDesk: Int) {
- Executors.MAIN_EXECUTOR.execute {
+ MAIN_EXECUTOR.execute {
controller.get()?.onActiveDeskChanged(displayId, newActiveDesk, oldActiveDesk)
}
}
@@ -619,7 +694,21 @@
*
* @param doesAnyTaskRequireTaskbarRounding whether task requires taskbar corner roundness.
*/
- fun onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding: Boolean)
+ fun onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding: Boolean) {}
+
+ /**
+ * Callback for when user is exiting desktop mode.
+ *
+ * @param duration for exit transition
+ */
+ fun onExitDesktopMode(duration: Int) {}
+
+ /**
+ * Callback for when user is entering desktop mode.
+ *
+ * @param duration for enter transition
+ */
+ fun onEnterDesktopMode(duration: Int) {}
}
companion object {
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index 5afc5ed..8555376 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -17,6 +17,7 @@
import static com.android.launcher3.Flags.enableAltTabKqsOnConnectedDisplays;
+import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.view.MotionEvent;
@@ -354,6 +355,27 @@
}
}
+ @VisibleForTesting
+ boolean isShownFromTaskbar() {
+ return isShown() && mQuickSwitchViewController.wasOpenedFromTaskbar();
+ }
+
+ @VisibleForTesting
+ boolean isShown() {
+ return mQuickSwitchViewController != null
+ && !mQuickSwitchViewController.isCloseAnimationRunning();
+ }
+
+ @VisibleForTesting
+ List<Integer> shownTaskIds() {
+ if (!isShown()) {
+ return Collections.emptyList();
+ }
+
+ return mTasks.stream().flatMap(
+ groupTask -> groupTask.getTasks().stream().map(task -> task.key.id)).toList();
+ }
+
@Override
public void dumpLogs(String prefix, PrintWriter pw) {
pw.println(prefix + "KeyboardQuickSwitchController:");
@@ -423,7 +445,13 @@
if (task == null) {
return false;
}
- int runningTaskId = ActivityManagerWrapper.getInstance().getRunningTask().taskId;
+ ActivityManager.RunningTaskInfo runningTaskInfo =
+ ActivityManagerWrapper.getInstance().getRunningTask();
+ if (runningTaskInfo == null) {
+ return false;
+ }
+
+ int runningTaskId = runningTaskInfo.taskId;
return task.containsTask(runningTaskId);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 2e42e45..ade75eb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -27,6 +27,7 @@
import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
import static com.android.launcher3.AbstractFloatingView.TYPE_ON_BOARD_POPUP;
import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
@@ -96,12 +97,12 @@
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.R;
+import com.android.launcher3.allapps.ActivityAllAppsContainerView;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.desktop.DesktopAppLaunchTransition;
import com.android.launcher3.desktop.DesktopAppLaunchTransition.AppLaunchType;
-import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.logger.LauncherAtom;
@@ -148,6 +149,7 @@
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.NavigationMode;
import com.android.launcher3.util.RunnableList;
@@ -228,7 +230,6 @@
private boolean mIsDestroyed = false;
// The flag to know if the window is excluded from magnification region computation.
private boolean mIsExcludeFromMagnificationRegion = false;
- private boolean mBindingItems = false;
private boolean mAddedWindow = false;
// The bounds of the taskbar items relative to TaskbarDragLayer
@@ -449,14 +450,25 @@
mControllers.taskbarViewController.adjustTaskbarForBubbleBar();
}
- public void init(@NonNull TaskbarSharedState sharedState) {
+ /**
+ * Init of taskbar activity context.
+ * @param duration If duration is greater than 0, it will be used to create an animation
+ * for the taskbar create/recreate process.
+ */
+ public void init(@NonNull TaskbarSharedState sharedState, int duration) {
mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, getResources(), false);
mLastRequestedNonFullscreenSize = getDefaultTaskbarWindowSize();
mWindowLayoutParams = createAllWindowParams();
mLastUpdatedLayoutParams = new WindowManager.LayoutParams();
+
+ AnimatorSet recreateAnim = null;
+ if (duration > 0) {
+ recreateAnim = onRecreateAnimation(duration);
+ }
+
// Initialize controllers after all are constructed.
- mControllers.init(sharedState);
+ mControllers.init(sharedState, recreateAnim);
// This may not be necessary and can be reverted once we move towards recreating all
// controllers without re-creating the window
mControllers.rotationButtonController.onNavigationModeChanged(mNavMode.resValue);
@@ -484,6 +496,33 @@
} else {
notifyUpdateLayoutParams();
}
+
+
+ if (recreateAnim != null) {
+ recreateAnim.start();
+ }
+ }
+
+ /**
+ * Create AnimatorSet for taskbar create/recreate animation. Further used in init
+ */
+ public AnimatorSet onRecreateAnimation(int duration) {
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.setDuration(duration);
+ return animatorSet;
+ }
+
+ /**
+ * Called when we want destroy current taskbar with animation as part of recreate process.
+ */
+ public AnimatorSet onDestroyAnimation(int duration) {
+ mIsDestroyed = true;
+ AnimatorSet animatorSet = new AnimatorSet();
+ mControllers.taskbarViewController.onDestroyAnimation(animatorSet);
+ mControllers.taskbarDragLayerController.onDestroyAnimation(animatorSet);
+ animatorSet.setInterpolator(LINEAR);
+ animatorSet.setDuration(duration);
+ return animatorSet;
}
/**
@@ -839,32 +878,29 @@
}
}
- @Override
- public DotInfo getDotInfoForItem(ItemInfo info) {
- return getPopupDataProvider().getDotInfoForItem(info);
- }
-
@NonNull
@Override
public PopupDataProvider getPopupDataProvider() {
return mControllers.taskbarPopupController.getPopupDataProvider();
}
+ @NonNull
+ @Override
+ public LauncherBindableItemsContainer getContent() {
+ return mControllers.taskbarViewController.getContent();
+ }
+
+ @Override
+ public ActivityAllAppsContainerView<?> getAppsView() {
+ return mControllers.taskbarAllAppsController.getAppsView();
+ }
+
@Override
public View.AccessibilityDelegate getAccessibilityDelegate() {
return mAccessibilityDelegate;
}
@Override
- public boolean isBindingItems() {
- return mBindingItems;
- }
-
- public void setBindingItems(boolean bindingItems) {
- mBindingItems = bindingItems;
- }
-
- @Override
public void onDragStart() {
setTaskbarWindowFullscreen(true);
}
@@ -1983,6 +2019,10 @@
return mControllers.taskbarStashController.isInApp();
}
+ public boolean isInOverview() {
+ return mControllers.taskbarStashController.isInOverview();
+ }
+
public boolean isInStashedLauncherState() {
return mControllers.taskbarStashController.isInStashedLauncherState();
}
@@ -2006,8 +2046,6 @@
"%s\tmIsUserSetupComplete=%b", prefix, mIsUserSetupComplete));
pw.println(String.format(
"%s\tmWindowLayoutParams.height=%dpx", prefix, mWindowLayoutParams.height));
- pw.println(String.format(
- "%s\tmBindInProgress=%b", prefix, mBindingItems));
mControllers.dumpLogs(prefix + "\t", pw);
mDeviceProfile.dump(this, prefix, pw);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
index 6d23853..89cc991 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
@@ -50,6 +50,8 @@
}
var isAnimatingPinning = false
+ var isAnimatingPersistentTaskbar = false
+ var isAnimatingTransientTaskbar = false
val paint = Paint()
private val strokePaint = Paint()
@@ -144,7 +146,7 @@
/** Draws the background with the given paint and height, on the provided canvas. */
fun draw(canvas: Canvas) {
if (isInSetup) return
- val isTransientTaskbar = backgroundProgress == 0f
+ val isTransientTaskbar = DisplayController.isTransientTaskbar(context)
canvas.save()
if (!isTransientTaskbar || transientBackgroundBounds.isEmpty || isAnimatingPinning) {
drawPersistentBackground(canvas)
@@ -158,7 +160,7 @@
}
private fun drawPersistentBackground(canvas: Canvas) {
- if (isAnimatingPinning) {
+ if (isAnimatingPinning || isAnimatingPersistentTaskbar) {
val persistentTaskbarHeight = maxPersistentTaskbarHeight * backgroundProgress
canvas.translate(0f, canvas.height - persistentTaskbarHeight)
// Draw the background behind taskbar content.
@@ -181,12 +183,13 @@
private fun drawTransientBackground(canvas: Canvas) {
val res = context.resources
val transientTaskbarHeight = maxTransientTaskbarHeight * (1f - backgroundProgress)
+ val isAnimating = isAnimatingPinning || isAnimatingTransientTaskbar
val heightProgressWhileAnimating =
- if (isAnimatingPinning) transientTaskbarHeight else backgroundHeight
+ if (isAnimating) transientTaskbarHeight else backgroundHeight
var progress = heightProgressWhileAnimating / maxTransientTaskbarHeight
progress = Math.round(progress * 100f) / 100f
- if (isAnimatingPinning) {
+ if (isAnimating) {
var scale = transientTaskbarHeight / maxTransientTaskbarHeight
scale = Math.round(scale * 100f) / 100f
bottomMargin =
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index b244be9..6ca9385 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.taskbar;
+import android.animation.AnimatorSet;
import android.content.pm.ActivityInfo.Config;
import androidx.annotation.NonNull;
@@ -149,15 +150,15 @@
* TaskbarControllers instance, but should be careful to only access things that were created
* in constructors for now, as some controllers may still be waiting for init().
*/
- public void init(@NonNull TaskbarSharedState sharedState) {
+ public void init(@NonNull TaskbarSharedState sharedState, AnimatorSet startAnimation) {
mAreAllControllersInitialized = false;
mSharedState = sharedState;
taskbarDragController.init(this);
navbarButtonsViewController.init(this);
rotationButtonController.init();
- taskbarDragLayerController.init(this);
- taskbarViewController.init(this);
+ taskbarDragLayerController.init(this, startAnimation);
+ taskbarViewController.init(this, startAnimation);
taskbarScrimViewController.init(this);
taskbarUnfoldAnimationController.init(this);
taskbarKeyguardController.init(navbarButtonsViewController);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt
index f71dea9..ca8e4ca 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt
@@ -30,6 +30,9 @@
private lateinit var taskbarControllers: TaskbarControllers
private lateinit var taskbarSharedState: TaskbarSharedState
+ val isInDesktopMode: Boolean
+ get() = desktopVisibilityController.isInDesktopMode
+
fun init(controllers: TaskbarControllers, sharedState: TaskbarSharedState) {
taskbarControllers = controllers
taskbarSharedState = sharedState
@@ -42,6 +45,7 @@
desktopVisibilityController.isInDesktopModeAndNotInOverview(displayId)
override fun onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding: Boolean) {
+ if (taskbarControllers.taskbarActivityContext.isDestroyed) return
taskbarSharedState.showCornerRadiusInDesktopMode = doesAnyTaskRequireTaskbarRounding
val cornerRadius = getTaskbarCornerRoundness(doesAnyTaskRequireTaskbarRounding)
taskbarControllers.taskbarCornerRoundness.animateToValue(cornerRadius).start()
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index 59ef577..4dbad8c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -186,6 +186,7 @@
@Override
protected void dispatchDraw(Canvas canvas) {
+ if (mContainer.isDestroyed()) return;
float backgroundHeight = mControllerCallbacks.getTaskbarBackgroundHeight()
* (1f - mTaskbarBackgroundOffset);
mBackgroundRenderer.setBackgroundHeight(backgroundHeight);
@@ -286,6 +287,21 @@
}
/**
+ * Sets animation boolean when only animating persistent taskbar.
+ */
+ public void setIsAnimatingPersistentTaskbarBackground(boolean animatingPersistentTaskbarBg) {
+ mBackgroundRenderer.setAnimatingPersistentTaskbar(animatingPersistentTaskbarBg);
+ }
+
+ /**
+ * Sets animation boolean when only animating transient taskbar.
+ */
+ public void setIsAnimatingTransientTaskbarBackground(boolean animatingTransientTaskbarBg) {
+ mBackgroundRenderer.setAnimatingTransientTaskbar(animatingTransientTaskbarBg);
+ }
+
+
+ /**
* Sets the width percentage to inset the transient taskbar's background from the left and from
* the right.
*/
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index 68c252a..55ecc37 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -15,9 +15,12 @@
*/
package com.android.launcher3.taskbar;
+import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_PERSISTENT;
import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_TRANSIENT;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Point;
@@ -29,6 +32,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatedFloat;
+import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.util.DimensionUtils;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
@@ -58,6 +62,8 @@
private final AnimatedFloat mImeBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
private final AnimatedFloat mAssistantBgTaskbar = new AnimatedFloat(
this::updateBackgroundAlpha);
+ private final AnimatedFloat mBgTaskbarRecreate = new AnimatedFloat(
+ this::updateBackgroundAlpha);
// Used to hide our background color when someone else (e.g. ScrimView) is handling it.
private final AnimatedFloat mBgOverride = new AnimatedFloat(this::updateBackgroundAlpha);
@@ -88,7 +94,10 @@
mFolderMargin = resources.getDimensionPixelSize(R.dimen.taskbar_folder_margin);
}
- public void init(TaskbarControllers controllers) {
+ /**
+ * Init of taskbar drag layer controller
+ */
+ public void init(TaskbarControllers controllers, AnimatorSet startAnimation) {
mControllers = controllers;
mTaskbarStashViaTouchController = new TaskbarStashViaTouchController(mControllers);
mTaskbarDragLayer.init(new TaskbarDragLayerCallbacks());
@@ -96,15 +105,45 @@
mOnBackgroundNavButtonColorIntensity = mControllers.navbarButtonsViewController
.getOnTaskbarBackgroundNavButtonColorOverride();
- mTaskbarBackgroundProgress.updateValue(DisplayController.isTransientTaskbar(mActivity)
- ? PINNING_TRANSIENT
- : PINNING_PERSISTENT);
+
+ if (startAnimation != null) {
+ // set taskbar background render animation boolean
+ if (DisplayController.isTransientTaskbar(mActivity)) {
+ mTaskbarDragLayer.setIsAnimatingTransientTaskbarBackground(true);
+ } else {
+ mTaskbarDragLayer.setIsAnimatingPersistentTaskbarBackground(true);
+ }
+
+ float desiredValue = DisplayController.isTransientTaskbar(mActivity)
+ ? PINNING_TRANSIENT
+ : PINNING_PERSISTENT;
+
+ float nonDesiredvalue = !DisplayController.isTransientTaskbar(mActivity)
+ ? PINNING_TRANSIENT
+ : PINNING_PERSISTENT;
+
+ ObjectAnimator objectAnimator = mTaskbarBackgroundProgress.animateToValue(
+ nonDesiredvalue, desiredValue);
+ objectAnimator.setInterpolator(EMPHASIZED);
+ startAnimation.play(objectAnimator);
+ startAnimation.addListener(AnimatorListeners.forEndCallback(()-> {
+ // reset taskbar background render animation boolean
+ mTaskbarDragLayer.setIsAnimatingPersistentTaskbarBackground(false);
+ mTaskbarDragLayer.setIsAnimatingTransientTaskbarBackground(false);
+ }));
+
+ } else {
+ mTaskbarBackgroundProgress.updateValue(DisplayController.isTransientTaskbar(mActivity)
+ ? PINNING_TRANSIENT
+ : PINNING_PERSISTENT);
+ }
mBgTaskbar.value = 1;
mKeyguardBgTaskbar.value = 1;
mNotificationShadeBgTaskbar.value = 1;
mImeBgTaskbar.value = 1;
mAssistantBgTaskbar.value = 1;
+ mBgTaskbarRecreate.value = 1;
mBgOverride.value = 1;
updateBackgroundAlpha();
@@ -112,6 +151,13 @@
updateTaskbarAlpha();
}
+ /**
+ * Called when destroying Taskbar with animation.
+ */
+ public void onDestroyAnimation(AnimatorSet animatorSet) {
+ animatorSet.play(mBgTaskbarRecreate.animateToValue(0f));
+ }
+
public void onDestroy() {
mTaskbarDragLayer.onDestroy();
}
@@ -172,14 +218,14 @@
}
private void updateBackgroundAlpha() {
- if (mActivity.isPhoneMode()) {
+ if (mActivity.isPhoneMode() || mActivity.isDestroyed()) {
return;
}
final float bgNavbar = mBgNavbar.value;
final float bgTaskbar = mBgTaskbar.value * mKeyguardBgTaskbar.value
* mNotificationShadeBgTaskbar.value * mImeBgTaskbar.value
- * mAssistantBgTaskbar.value;
+ * mAssistantBgTaskbar.value * mBgTaskbarRecreate.value;
mLastSetBackgroundAlpha = mBgOverride.value * Math.max(bgNavbar, bgTaskbar);
mBackgroundRendererAlpha.setValue(mLastSetBackgroundAlpha);
@@ -266,6 +312,7 @@
pw.println(prefix + "\t\tmNotificationShadeBgTaskbar=" + mNotificationShadeBgTaskbar.value);
pw.println(prefix + "\t\tmImeBgTaskbar=" + mImeBgTaskbar.value);
pw.println(prefix + "\t\tmAssistantBgTaskbar=" + mAssistantBgTaskbar.value);
+ pw.println(prefix + "\t\tmBgTaskbarRecreate=" + mBgTaskbarRecreate.value);
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index dd9f61e..10eb64a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -223,9 +223,13 @@
updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, true);
if (!mShouldDelayLauncherStateAnim) {
if (toState == LauncherState.NORMAL) {
- applyState(QuickstepTransitionManager.getTaskbarToHomeDuration(
+ boolean isPinnedTaskbarAndNotInDesktopMode =
DisplayController.isPinnedTaskbar(
- mControllers.taskbarActivityContext)));
+ mControllers.taskbarActivityContext)
+ && !DisplayController.isInDesktopMode(
+ mControllers.taskbarActivityContext);
+ applyState(QuickstepTransitionManager.getTaskbarToHomeDuration(
+ isPinnedTaskbarAndNotInDesktopMode));
} else {
applyState();
}
@@ -680,8 +684,11 @@
} else if (mIconAlignment.isAnimatingToValue(toAlignment)
|| mIconAlignment.isSettledOnValue(toAlignment)) {
// Already at desired value, but make sure we run the callback at the end.
- animatorSet.addListener(AnimatorListeners.forEndCallback(
- this::onIconAlignmentRatioChanged));
+ animatorSet.addListener(AnimatorListeners.forEndCallback(() -> {
+ if (!mIconAlignment.isAnimating()) {
+ onIconAlignmentRatioChanged();
+ }
+ }));
} else {
mIconAlignment.cancelAnimation();
ObjectAnimator iconAlignAnim = mIconAlignment
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 34bb6e0..943c44e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -16,23 +16,27 @@
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;
import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
import static com.android.launcher3.util.DisplayController.CHANGE_DESKTOP_MODE;
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;
+import android.animation.AnimatorSet;
import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.content.ComponentCallbacks;
@@ -60,9 +64,12 @@
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;
import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks;
import com.android.launcher3.taskbar.unfold.NonDestroyableScopedUnfoldTransitionProgressProvider;
@@ -98,9 +105,10 @@
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.
@@ -125,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;
@@ -153,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;
@@ -172,33 +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)) != 0) {
- debugTaskbarManager("onDisplayInfoChanged - Recreating Taskbar!",
+ | CHANGE_TASKBAR_PINNING | CHANGE_SHOW_LOCKED_TASKBAR)) != 0) {
+ debugTaskbarManager("onDisplayInfoChanged: Recreating Taskbar!",
context.getDisplayId());
- recreateTaskbar();
+ TaskbarActivityContext taskbarActivityContext = getCurrentActivityContext();
+ if ((flags & CHANGE_SHOW_LOCKED_TASKBAR) != 0) {
+ 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)) {
+ recreateTaskbars();
+ }
+ } else {
+ recreateTaskbars();
+ }
}
}
}
+
private final SettingsCache.OnChangeListener mOnSettingsChangeListener = c -> {
- debugTaskbarManager("Settings changed! Recreating Taskbar!");
- recreateTaskbar();
+ debugPrimaryTaskbar("Settings changed! Recreating Taskbar!");
+ recreateTaskbars();
};
private final PerceptibleTaskListener mTaskStackListener;
@@ -258,12 +282,57 @@
public void onTaskRemoved(int taskId) {
mPerceptibleTasks.remove(taskId);
}
- };
+ }
+
+ private final DesktopVisibilityController.TaskbarDesktopModeListener
+ mTaskbarDesktopModeListener =
+ new DesktopVisibilityController.TaskbarDesktopModeListener() {
+ @Override
+ public void onExitDesktopMode(int duration) {
+ for (int taskbarIndex = 0; taskbarIndex < mTaskbars.size(); taskbarIndex++) {
+ int displayId = mTaskbars.keyAt(taskbarIndex);
+ TaskbarActivityContext taskbarActivityContext = getTaskbarForDisplay(
+ displayId);
+ if (taskbarActivityContext != null
+ && !taskbarActivityContext.isInOverview()) {
+ AnimatorSet animatorSet = taskbarActivityContext.onDestroyAnimation(
+ TASKBAR_DESTROY_DURATION);
+ animatorSet.addListener(AnimatorListeners.forEndCallback(
+ () -> recreateTaskbarForDisplay(getDefaultDisplayId(),
+ duration)));
+ animatorSet.start();
+ }
+ }
+ }
+
+ @Override
+ public void onEnterDesktopMode(int duration) {
+ for (int taskbarIndex = 0; taskbarIndex < mTaskbars.size(); taskbarIndex++) {
+ int displayId = mTaskbars.keyAt(taskbarIndex);
+ TaskbarActivityContext taskbarActivityContext = getTaskbarForDisplay(
+ displayId);
+ AnimatorSet animatorSet = taskbarActivityContext.onDestroyAnimation(
+ TASKBAR_DESTROY_DURATION);
+ animatorSet.addListener(AnimatorListeners.forEndCallback(
+ () -> recreateTaskbarForDisplay(getDefaultDisplayId(), duration)));
+ animatorSet.start();
+ }
+ }
+
+ @Override
+ public void onTaskbarCornerRoundingUpdate(
+ boolean doesAnyTaskRequireTaskbarRounding) {
+ //NO-OP
+ }
+ };
+
private boolean mUserUnlocked = false;
private final SimpleBroadcastReceiver mTaskbarBroadcastReceiver;
+ private final SimpleBroadcastReceiver mGrowthBroadcastReceiver;
+
private final AllAppsActionManager mAllAppsActionManager;
private final RecentsDisplayModel mRecentsDisplayModel;
@@ -271,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) {
@@ -287,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);
}
@@ -297,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.");
}
};
@@ -334,9 +404,12 @@
// 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(
+ mPrimaryWindowContext).registerTaskbarDesktopModeListener(
+ mTaskbarDesktopModeListener);
createTaskbarRootLayout(primaryDisplayId);
createNavButtonController(primaryDisplayId);
createAndRegisterComponentCallbacks(primaryDisplayId);
@@ -351,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,
@@ -368,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);
}
@@ -401,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.
*/
@@ -437,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);
}
}
@@ -451,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);
@@ -477,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;
}
@@ -500,6 +604,7 @@
*/
private UnfoldTransitionProgressProvider getUnfoldTransitionProgressProviderForActivity(
StatefulActivity activity) {
+ debugPrimaryTaskbar("getUnfoldTransitionProgressProviderForActivity");
if (!enableUnfoldStateAnimation()) {
if (activity instanceof QuickstepLauncher ql) {
return ql.getUnfoldTransitionProgressProvider();
@@ -512,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) {
@@ -527,6 +633,7 @@
*/
private TaskbarUIController createTaskbarUIControllerForRecentsViewContainer(
RecentsViewContainer container) {
+ debugPrimaryTaskbar("createTaskbarUIControllerForRecentsViewContainer");
if (container instanceof QuickstepLauncher quickstepLauncher) {
return new LauncherTaskbarUIController(quickstepLauncher);
}
@@ -547,16 +654,19 @@
* 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) {
- recreateTaskbarForDisplay(getDefaultDisplayId());
+ debugTaskbarManager("recreateTaskbars: create primary taskbar", getDefaultDisplayId());
+ recreateTaskbarForDisplay(getDefaultDisplayId(), 0);
return;
}
for (int i = 0; i < mTaskbars.size(); i++) {
int displayId = mTaskbars.keyAt(i);
- recreateTaskbarForDisplay(displayId);
+ debugTaskbarManager("recreateTaskbars: create external taskbar", displayId);
+ recreateTaskbarForDisplay(displayId, 0);
}
}
@@ -565,37 +675,42 @@
* we fully want to destroy an existing taskbar for a specified display and create a new one.
* In other case (folding/unfolding) we don't need to remove and add window.
*/
- private void recreateTaskbarForDisplay(int displayId) {
+ 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;
boolean isTaskbarEnabled = dp != null && isTaskbarEnabled(dp);
debugTaskbarManager("recreateTaskbarForDisplay: isTaskbarEnabled=" + isTaskbarEnabled
- + " [dp != null (i.e. mUserUnlocked)]=" + (dp != null)
- + " FLAG_HIDE_NAVBAR_WINDOW=" + ENABLE_TASKBAR_NAVBAR_UNIFICATION
+ + " [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);
+ .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(
@@ -603,12 +718,14 @@
return;
}
} else {
+ debugTaskbarManager("recreateTaskbarForDisplay: updating taskbar device profile",
+ displayId);
taskbar.updateDeviceProfile(dp);
}
mSharedState.startTaskbarVariantIsTransient =
DisplayController.isTransientTaskbar(taskbar);
mSharedState.allAppsVisible = mSharedState.allAppsVisible && isLargeScreenTaskbar;
- taskbar.init(mSharedState);
+ taskbar.init(mSharedState, duration);
// Non default displays should not use LauncherTaskbarUIController as they shouldn't
// have access to the Launcher activity.
@@ -621,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 {
@@ -700,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);
@@ -774,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);
- recreateTaskbarForDisplay(displayId);
+ debugTaskbarManager("onDisplayAddSystemDecorations: recreateTaskbarForDisplay!",
+ displayId);
+ recreateTaskbarForDisplay(displayId, 0);
+ } else {
+ debugTaskbarManager("onDisplayAddSystemDecorations: newWindowContext is NULL!",
+ displayId);
}
+
+ debugTaskbarManager("onDisplayAddSystemDecorations: finished!", displayId);
}
/**
@@ -801,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);
}
}
@@ -827,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);
@@ -843,10 +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(
@@ -856,16 +1037,18 @@
.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())) {
- for (Integer taskId: mTaskStackListener.mPerceptibleTasks) {
+ for (Integer taskId : mTaskStackListener.mPerceptibleTasks) {
ActivityManagerWrapper.getInstance().setTaskIsPerceptible(taskId, false);
}
}
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
+ debugPrimaryTaskbar("destroy: destroying all taskbars!");
destroyAllTaskbars();
+ debugPrimaryTaskbar("destroy: finished!");
}
public @Nullable TaskbarActivityContext getCurrentActivityContext() {
@@ -888,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;
}
@@ -906,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;
@@ -929,7 +1116,7 @@
mAddedRootLayouts.put(displayId, false);
removeTaskbarRootLayoutFromMap(displayId);
} else {
- debugTaskbarManager("removeTaskbarRootViewFromWindow - WindowManager is null",
+ debugTaskbarManager("removeTaskbarRootViewFromWindow: WindowManager is null",
displayId);
}
}
@@ -940,7 +1127,7 @@
*
* @param displayId The ID of the display to retrieve the taskbar for.
* @return The {@link TaskbarUIController} for the specified display, or
- * {@code null} if no taskbar is associated with that display.
+ * {@code null} if no taskbar is associated with that display.
*/
@Nullable
public TaskbarUIController getUIControllerForDisplay(int displayId) {
@@ -967,7 +1154,7 @@
*
* @param displayId The ID of the display to retrieve the taskbar for.
* @return The {@link TaskbarActivityContext} for the specified display, or
- * {@code null} if no taskbar is associated with that display.
+ * {@code null} if no taskbar is associated with that display.
*/
private TaskbarActivityContext getTaskbarForDisplay(int displayId) {
return mTaskbars.get(displayId);
@@ -976,7 +1163,8 @@
/**
* Creates a {@link TaskbarActivityContext} for the given display and adds it to the map.
- * @param dp The {@link DeviceProfile} for the display.
+ *
+ * @param dp The {@link DeviceProfile} for the display.
* @param displayId The ID of the display.
*/
private @Nullable TaskbarActivityContext createTaskbarActivityContext(DeviceProfile dp,
@@ -1006,11 +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();
@@ -1019,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);
@@ -1036,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
@@ -1065,7 +1317,8 @@
}
@Override
- public void onLowMemory() { }
+ public void onLowMemory() {
+ }
};
if (isDefaultDisplay(displayId)
|| !DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue()) {
@@ -1080,6 +1333,7 @@
/**
* Unregister {@link ComponentCallbacks} for the given display from its WindowContext. For
* external displays, remove from the map.
+ *
* @param displayId The ID of the display.
*/
private void removeAndUnregisterComponentCallbacks(int displayId) {
@@ -1096,6 +1350,7 @@
/**
* Creates a {@link TaskbarNavButtonController} for the given display and adds it to the map
* if it doesn't already exist.
+ *
* @param displayId The ID of the display
*/
private void createNavButtonController(int displayId) {
@@ -1137,7 +1392,7 @@
* Adds the {@link TaskbarActivityContext} associated with the given display ID to taskbar
* map if there is not already a taskbar mapped to that displayId.
*
- * @param displayId The ID of the display to retrieve the taskbar for.
+ * @param displayId The ID of the display to retrieve the taskbar for.
* @param newTaskbar The new {@link TaskbarActivityContext} to add to the map.
*/
private void addTaskbarToMap(int displayId, TaskbarActivityContext newTaskbar) {
@@ -1157,10 +1412,11 @@
/**
* 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) {
- Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW, "createTaskbarRootLayout: " + displayId);
+ debugTaskbarManager("createTaskbarRootLayout: ", displayId);
if (!enableTaskbarNoRecreate()) {
return;
}
@@ -1168,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);
@@ -1177,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) {
@@ -1192,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;
}
}
@@ -1206,15 +1463,18 @@
/**
* Adds the taskbar root layout {@link FrameLayout} to taskbar map, mapped to display ID.
*
- * @param displayId The ID of the display to associate with the taskbar root layout.
+ * @param displayId The ID of the display to associate with the taskbar root layout.
* @param rootLayout The taskbar root layout {@link FrameLayout} to add to the map.
*/
private void addTaskbarRootLayoutToMap(int displayId, FrameLayout rootLayout) {
+ 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);
}
/**
@@ -1223,23 +1483,26 @@
* @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);
}
/**
* Creates {@link Context} for the taskbar on the specified display.
+ *
* @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;
}
@@ -1312,7 +1575,7 @@
/**
* Adds the window context {@link Context} to taskbar map, mapped to display ID.
*
- * @param displayId The ID of the display to associate with the taskbar root layout.
+ * @param displayId The ID of the display to associate with the taskbar root layout.
* @param windowContext The window context {@link Context} to add to the map.
*/
private void addWindowContextToMap(int displayId, @NonNull Context windowContext) {
@@ -1336,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;
}
@@ -1379,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/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
index 6815f97..0fa82ae 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -61,6 +61,7 @@
// Used to defer any UI updates during the SUW unstash animation.
private boolean mDeferUpdatesForSUW;
private Runnable mDeferredUpdates;
+ private boolean mBindingItems = false;
public TaskbarModelCallbacks(
TaskbarActivityContext context, TaskbarView container) {
@@ -74,14 +75,14 @@
@Override
public void startBinding() {
- mContext.setBindingItems(true);
+ mBindingItems = true;
mHotseatItems.clear();
mPredictedItems = Collections.emptyList();
}
@Override
public void finishBindingItems(IntSet pagesBoundFirst) {
- mContext.setBindingItems(false);
+ mBindingItems = false;
commitItemsToUI();
}
@@ -167,7 +168,7 @@
}
private void commitItemsToUI() {
- if (mContext.isBindingItems()) {
+ if (mBindingItems) {
return;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
index 5d8b821..a9ee584 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -35,11 +35,7 @@
import com.android.launcher3.Flags;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
-import com.android.launcher3.dot.FolderDotInfo;
-import com.android.launcher3.folder.Folder;
-import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.notification.NotificationListener;
@@ -49,8 +45,6 @@
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.splitscreen.SplitShortcut;
import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.LauncherBindableItemsContainer;
-import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.ShortcutUtil;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.views.ActivityContext;
@@ -65,7 +59,6 @@
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
-import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -93,7 +86,7 @@
public TaskbarPopupController(TaskbarActivityContext context) {
mContext = context;
- mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
+ mPopupDataProvider = new PopupDataProvider(mContext);
}
public void init(TaskbarControllers controllers) {
@@ -132,39 +125,6 @@
mAllowInitialSplitSelection = allowInitialSplitSelection;
}
- private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
- final PackageUserKey packageUserKey = new PackageUserKey(null, null);
- Predicate<ItemInfo> matcher = info -> !packageUserKey.updateFromItemInfo(info)
- || updatedDots.test(packageUserKey);
-
- LauncherBindableItemsContainer.ItemOperator op = (info, v) -> {
- if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView) {
- if (matcher.test(info)) {
- ((BubbleTextView) v).applyDotState(info, true /* animate */);
- }
- } else if (info instanceof FolderInfo && v instanceof FolderIcon) {
- FolderInfo fi = (FolderInfo) info;
- if (fi.anyMatch(matcher)) {
- FolderDotInfo folderDotInfo = new FolderDotInfo();
- for (ItemInfo si : fi.getContents()) {
- folderDotInfo.addDotInfo(mPopupDataProvider.getDotInfoForItem(si));
- }
- ((FolderIcon) v).setDotInfo(folderDotInfo);
- }
- }
-
- // process all the shortcuts
- return false;
- };
-
- mControllers.taskbarViewController.mapOverItems(op);
- Folder folder = Folder.getOpen(mContext);
- if (folder != null) {
- folder.iterateOverItems(op);
- }
- mControllers.taskbarAllAppsController.updateNotificationDots(updatedDots);
- }
-
/**
* Shows the notifications and deep shortcuts associated with a Taskbar {@param icon}.
* @return the container if shown or null.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index a59c9e3..de8e286 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -62,7 +62,6 @@
import com.android.launcher3.taskbar.customization.TaskbarDividerContainer;
import com.android.launcher3.uioverrides.PredictedAppIcon;
import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.util.GroupTask;
@@ -392,6 +391,7 @@
/** Inflates/binds the hotseat items and recent tasks to the view. */
protected void updateItems(ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks) {
+ if (mActivityContext.isDestroyed()) return;
// Filter out unsupported items.
hotseatItemInfos = Arrays.stream(hotseatItemInfos)
.filter(Objects::nonNull)
@@ -1097,20 +1097,6 @@
}
/**
- * Maps {@code op} over all the child views.
- */
- public void mapOverItems(LauncherBindableItemsContainer.ItemOperator op) {
- // map over all the shortcuts on the taskbar
- for (int i = 0; i < getChildCount(); i++) {
- View item = getChildAt(i);
- // TODO(b/344657629): Support GroupTask as well for notification dots/popup
- if (item.getTag() instanceof ItemInfo itemInfo && op.evaluate(itemInfo, item)) {
- return;
- }
- }
- }
-
- /**
* Finds the first icon to match one of the given matchers, from highest to lowest priority.
*
* @return The first match, or All Apps button if no match was found.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index cbc5d3d..6ae13d4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -83,14 +83,16 @@
import com.android.launcher3.model.data.TaskItemInfo;
import com.android.launcher3.taskbar.bubbles.BubbleBarController;
import com.android.launcher3.taskbar.bubbles.BubbleControllers;
+import com.android.launcher3.taskbar.customization.TaskbarAllAppsButtonContainer;
+import com.android.launcher3.taskbar.customization.TaskbarDividerContainer;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LauncherBindableItemsContainer;
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.util.MultiValueAlpha;
+import com.android.launcher3.util.SandboxContext;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.SingleTask;
import com.android.systemui.shared.recents.model.Task;
@@ -121,9 +123,10 @@
public static final int ALPHA_INDEX_NOTIFICATION_EXPANDED = 4;
public static final int ALPHA_INDEX_ASSISTANT_INVOKED = 5;
public static final int ALPHA_INDEX_SMALL_SCREEN = 6;
-
public static final int ALPHA_INDEX_BUBBLE_BAR = 7;
- private static final int NUM_ALPHA_CHANNELS = 8;
+ public static final int ALPHA_INDEX_RECREATE = 8;
+
+ private static final int NUM_ALPHA_CHANNELS = 9;
/** Only used for animation purposes, to position the divider between two item indices. */
public static final float DIVIDER_VIEW_POSITION_OFFSET = 0.5f;
@@ -238,9 +241,22 @@
R.dimen.transient_taskbar_padding);
}
- public void init(TaskbarControllers controllers) {
+ /**
+ * Init of taskbar view controller.
+ */
+ public void init(TaskbarControllers controllers, AnimatorSet startAnimation) {
mControllers = controllers;
controllers.bubbleControllers.ifPresent(bc -> mBubbleControllers = bc);
+
+ if (startAnimation != null) {
+ MultiPropertyFactory<View>.MultiProperty multiProperty =
+ mTaskbarIconAlpha.get(ALPHA_INDEX_RECREATE);
+ multiProperty.setValue(0f);
+ Animator animator = multiProperty.animateToValue(1f);
+ animator.setInterpolator(EMPHASIZED);
+ startAnimation.play(animator);
+ }
+
mTaskbarView.init(TaskbarViewCallbacksFactory.newInstance(mActivity).create(
mActivity, mControllers, mTaskbarView));
mTaskbarView.getLayoutParams().height = mActivity.isPhoneMode()
@@ -362,6 +378,15 @@
mTaskbarView.announceAccessibilityChanges();
}
+ /**
+ * Called with destroying Taskbar with animation.
+ */
+ public void onDestroyAnimation(AnimatorSet animatorSet) {
+ animatorSet.play(
+ mTaskbarIconAlpha.get(TaskbarViewController.ALPHA_INDEX_RECREATE).animateToValue(
+ 0f));
+ }
+
public void onDestroy() {
if (enableTaskbarPinning()) {
mTaskbarView.removeOnLayoutChangeListener(mTaskbarViewLayoutChangeListener);
@@ -712,10 +737,21 @@
for (View iconView : getIconViews()) {
if (iconView instanceof BubbleTextView btv) {
btv.updateRunningState(getRunningAppState(btv));
+ if (shouldUpdateIconContentDescription(btv)) {
+ btv.setContentDescription(
+ btv.getContentDescription() + " " + btv.getIconStateDescription());
+ }
}
}
}
+ private boolean shouldUpdateIconContentDescription(BubbleTextView btv) {
+ boolean isInDesktopMode = mControllers.taskbarDesktopModeController.isInDesktopMode();
+ boolean isAllAppsButton = btv instanceof TaskbarAllAppsButtonContainer;
+ boolean isDividerButton = btv instanceof TaskbarDividerContainer;
+ return isInDesktopMode && !isAllAppsButton && !isDividerButton;
+ }
+
/**
* @return A set of Task ids of running apps that are pinned in the taskbar.
*/
@@ -1144,11 +1180,8 @@
mTaskbarNavButtonTranslationY.updateValue(-deviceProfile.getTaskbarOffsetY());
}
- /**
- * Maps the given operator to all the top-level children of TaskbarView.
- */
- public void mapOverItems(LauncherBindableItemsContainer.ItemOperator op) {
- mTaskbarView.mapOverItems(op);
+ public LauncherBindableItemsContainer getContent() {
+ return mModelCallbacks;
}
/**
@@ -1299,7 +1332,7 @@
ObjectAnimator animator = mIconsTranslationXForNavbar.animateToValue(translationX);
animator.setStartDelay(FADE_OUT_ANIM_POSITION_DURATION_MS);
animator.setDuration(FADE_IN_ANIM_ALPHA_DURATION_MS);
- animator.setInterpolator(Interpolators.EMPHASIZED);
+ animator.setInterpolator(EMPHASIZED);
return animator;
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
index ddbf3b7..6c55b28 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
@@ -35,7 +35,6 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
-import java.util.function.Predicate;
/**
* Handles the all apps overlay window initialization, updates, and its data.
* <p>
@@ -120,13 +119,6 @@
mZeroStateSearchSuggestions = zeroStateSearchSuggestions;
}
- /** Updates the current notification dots. */
- public void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
- if (mAppsView != null) {
- mAppsView.getAppsStore().updateNotificationDots(updatedDots);
- }
- }
-
/** Toggles visibility of {@link TaskbarAllAppsContainerView} in the overlay window. */
public void toggle() {
toggle(false);
@@ -218,6 +210,11 @@
mAppsView = null;
}
+ @Nullable
+ public TaskbarAllAppsContainerView getAppsView() {
+ return mAppsView;
+ }
+
@VisibleForTesting
public int getTaskbarAllAppsTopPadding() {
// Allow null-pointer since this should only be null if the apps view is not showing.
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/taskbar/overlay/TaskbarOverlayContext.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
index 64cc47c..636d89b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
@@ -18,12 +18,11 @@
import android.content.Context;
import android.view.View;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
-import com.android.launcher3.dot.DotInfo;
-import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.taskbar.BaseTaskbarContext;
import com.android.launcher3.taskbar.TaskbarActivityContext;
@@ -116,11 +115,6 @@
}
@Override
- public boolean isBindingItems() {
- return mTaskbarContext.isBindingItems();
- }
-
- @Override
public View.OnClickListener getItemOnClickListener() {
return mTaskbarContext.getItemOnClickListener();
}
@@ -130,6 +124,7 @@
return mDragController::startDragOnLongClick;
}
+ @NonNull
@Override
public PopupDataProvider getPopupDataProvider() {
return mTaskbarContext.getPopupDataProvider();
@@ -141,11 +136,6 @@
}
@Override
- public DotInfo getDotInfoForItem(ItemInfo info) {
- return mTaskbarContext.getDotInfoForItem(info);
- }
-
- @Override
public void onDragStart() {}
@Override
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..79328df 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
@@ -27,18 +27,17 @@
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.quickstep.util.AnimUtils
+import com.android.quickstep.views.AddDesktopButton
import com.android.quickstep.views.ClearAllButton
import com.android.quickstep.views.RecentsView
import com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET
@@ -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,
+ AddDesktopButton.VISIBILITY_ALPHA,
+ 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/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 1907b4e..44f8bf1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -109,8 +109,11 @@
// We sync the scrim fade with the taskbar animation duration to avoid any flickers for
// taskbar icons disappearing before hotseat icons show up.
+ boolean isPinnedTaskbarAndNotInDesktopMode =
+ isPinnedTaskbar && !DisplayController.isInDesktopMode(mContainer);
float scrimUpperBoundFromSplit =
- QuickstepTransitionManager.getTaskbarToHomeDuration(isPinnedTaskbar)
+ QuickstepTransitionManager.getTaskbarToHomeDuration(
+ isPinnedTaskbarAndNotInDesktopMode)
/ (float) config.duration;
scrimUpperBoundFromSplit = Math.min(scrimUpperBoundFromSplit, 1f);
config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, clampToProgress(LINEAR, 0, 0.25f));
@@ -142,7 +145,8 @@
if (mContainer.getDeviceProfile().isTaskbarPresent) {
config.duration = Math.min(
config.duration,
- QuickstepTransitionManager.getTaskbarToHomeDuration(isPinnedTaskbar));
+ QuickstepTransitionManager.getTaskbarToHomeDuration(
+ isPinnedTaskbarAndNotInDesktopMode));
}
overview.snapToPage(DEFAULT_PAGE, Math.toIntExact(config.duration));
} else {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
index 88b7155..454a307 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
@@ -36,7 +36,6 @@
import com.android.quickstep.views.TaskView
import com.google.android.msdl.data.model.MSDLToken
import kotlin.math.abs
-import kotlin.math.sign
/** Touch controller for handling task view card dismiss swipes */
class TaskViewDismissTouchController<CONTAINER>(
@@ -53,6 +52,8 @@
recentsView.pagedOrientationHandler.upDownSwipeDirection,
)
private val isRtl = isRtl(container.resources)
+ private val upDirection: Int = recentsView.pagedOrientationHandler.getUpDirection(isRtl)
+
private val tempTaskThumbnailBounds = Rect()
private var taskBeingDragged: TaskView? = null
@@ -96,7 +97,11 @@
}
onControllerTouchEvent(ev)
- return detector.isDraggingState && detector.wasInitialTouchPositive()
+ val upDirectionIsPositive = upDirection == SingleAxisSwipeDetector.DIRECTION_POSITIVE
+ val wasInitialTouchUp =
+ (upDirectionIsPositive && detector.wasInitialTouchPositive()) ||
+ (!upDirectionIsPositive && !detector.wasInitialTouchPositive())
+ return detector.isDraggingState && wasInitialTouchUp
}
override fun onControllerTouchEvent(ev: MotionEvent?): Boolean = detector.onTouchEvent(ev)
@@ -107,25 +112,27 @@
if (!canInterceptTouch(ev)) {
return false
}
-
taskBeingDragged =
recentsView.taskViews
.firstOrNull {
recentsView.isTaskViewVisible(it) && container.dragLayer.isEventOverView(it, ev)
}
?.also {
+ val secondaryLayerDimension =
+ recentsView.pagedOrientationHandler.getSecondaryDimension(
+ container.dragLayer
+ )
// Dismiss length as bottom of task so it is fully off screen when dismissed.
it.getThumbnailBounds(tempTaskThumbnailBounds, relativeToDragLayer = true)
- dismissLength = tempTaskThumbnailBounds.bottom
+ dismissLength =
+ recentsView.pagedOrientationHandler.getTaskDismissLength(
+ secondaryLayerDimension,
+ tempTaskThumbnailBounds,
+ )
verticalFactor =
- recentsView.pagedOrientationHandler.secondaryTranslationDirectionFactor
+ recentsView.pagedOrientationHandler.getTaskDismissVerticalDirection()
}
-
- detector.setDetectableScrollConditions(
- recentsView.pagedOrientationHandler.getUpDirection(isRtl),
- /* ignoreSlop = */ false,
- )
-
+ detector.setDetectableScrollConditions(upDirection, /* ignoreSlop= */ false)
return true
}
@@ -148,8 +155,8 @@
boundToRange(abs(currentDisplacement), 0f, dismissLength.toFloat())
// When swiping below origin, allow slight undershoot to simulate resisting the movement.
val totalDisplacement =
- if (isDisplacementPositiveDirection(currentDisplacement))
- boundedDisplacement * sign(currentDisplacement)
+ if (recentsView.pagedOrientationHandler.isGoingUp(currentDisplacement, isRtl))
+ boundedDisplacement * verticalFactor
else
mapToRange(
boundedDisplacement,
@@ -158,7 +165,7 @@
0f,
container.resources.getDimension(R.dimen.task_dismiss_max_undershoot),
DECELERATE,
- )
+ ) * -verticalFactor
taskBeingDragged.secondaryDismissTranslationProperty.setValue(
taskBeingDragged,
totalDisplacement,
@@ -207,8 +214,9 @@
}
val isBeyondDismissThreshold =
abs(currentDisplacement) > abs(DISMISS_THRESHOLD_FRACTION * dismissLength)
- val isFlingingTowardsDismiss = detector.isFling(velocity) && velocity < 0
- val isFlingingTowardsRestState = detector.isFling(velocity) && velocity > 0
+ val velocityIsGoingUp = recentsView.pagedOrientationHandler.isGoingUp(velocity, isRtl)
+ val isFlingingTowardsDismiss = detector.isFling(velocity) && velocityIsGoingUp
+ val isFlingingTowardsRestState = detector.isFling(velocity) && !velocityIsGoingUp
val isDismissing =
isFlingingTowardsDismiss || (isBeyondDismissThreshold && !isFlingingTowardsRestState)
springAnimation =
@@ -232,10 +240,6 @@
}
}
- // Returns if the current task being dragged is towards "positive" (e.g. dismissal).
- private fun isDisplacementPositiveDirection(displacement: Float): Boolean =
- sign(displacement) == sign(verticalFactor.toFloat())
-
private fun clearState() {
detector.finishedScrolling()
detector.setDetectableScrollConditions(0, false)
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewLaunchTouchController.kt b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewLaunchTouchController.kt
index c740dad..8ee552d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewLaunchTouchController.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewLaunchTouchController.kt
@@ -53,6 +53,7 @@
recentsView.pagedOrientationHandler.upDownSwipeDirection,
)
private val isRtl = isRtl(container.resources)
+ private val downDirection = recentsView.pagedOrientationHandler.getDownDirection(isRtl)
private var taskBeingDragged: TaskView? = null
private var launchEndDisplacement: Float = 0f
@@ -104,7 +105,11 @@
}
}
onControllerTouchEvent(ev)
- return detector.isDraggingState && !detector.wasInitialTouchPositive()
+ val downDirectionIsNegative = downDirection == SingleAxisSwipeDetector.DIRECTION_NEGATIVE
+ val wasInitialTouchDown =
+ (downDirectionIsNegative && !detector.wasInitialTouchPositive()) ||
+ (!downDirectionIsNegative && detector.wasInitialTouchPositive())
+ return detector.isDraggingState && wasInitialTouchDown
}
override fun onControllerTouchEvent(ev: MotionEvent) = detector.onTouchEvent(ev)
@@ -120,15 +125,12 @@
}
?.also {
verticalFactor =
- recentsView.pagedOrientationHandler.secondaryTranslationDirectionFactor
+ recentsView.pagedOrientationHandler.getTaskDragDisplacementFactor(isRtl)
}
if (!canTaskLaunchTaskView(taskBeingDragged)) {
return false
}
- detector.setDetectableScrollConditions(
- recentsView.pagedOrientationHandler.getDownDirection(isRtl),
- /* ignoreSlop = */ false,
- )
+ detector.setDetectableScrollConditions(downDirection, /* ignoreSlop= */ false)
return true
}
@@ -143,7 +145,10 @@
recentsView.createTaskLaunchAnimation(taskBeingDragged, maxDuration, ZOOM_IN)
// Since the thumbnail is what is filling the screen, based the end displacement on it.
taskBeingDragged.getThumbnailBounds(tempRect, /* relativeToDragLayer= */ true)
- launchEndDisplacement = (secondaryLayerDimension - tempRect.bottom).toFloat()
+ launchEndDisplacement =
+ recentsView.pagedOrientationHandler
+ .getTaskLaunchLength(secondaryLayerDimension, tempRect)
+ .toFloat() * verticalFactor
playbackController =
pendingAnimation.createPlaybackController()?.apply {
taskViewRecentsTouchContext.onUserControlledAnimationCreated(this)
@@ -163,8 +168,9 @@
val isBeyondLaunchThreshold =
abs(playbackController.progressFraction) > abs(LAUNCH_THRESHOLD_FRACTION)
- val isFlingingTowardsLaunch = detector.isFling(velocity) && velocity > 0
- val isFlingingTowardsRestState = detector.isFling(velocity) && velocity < 0
+ val velocityIsNegative = !recentsView.pagedOrientationHandler.isGoingUp(velocity, isRtl)
+ val isFlingingTowardsLaunch = detector.isFling(velocity) && velocityIsNegative
+ val isFlingingTowardsRestState = detector.isFling(velocity) && !velocityIsNegative
val isLaunching =
isFlingingTowardsLaunch || (isBeyondLaunchThreshold && !isFlingingTowardsRestState)
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 67a54e6..c51f659 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -120,6 +120,7 @@
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulContainer;
import com.android.launcher3.taskbar.TaskbarThresholdUtils;
@@ -1421,8 +1422,10 @@
}
if (endTarget == HOME) {
boolean isPinnedTaskbar = DisplayController.isPinnedTaskbar(mContext);
+ boolean isNotInDesktop = !DisplayController.isInDesktopMode(mContext);
duration = mContainer != null && mContainer.getDeviceProfile().isTaskbarPresent
- ? QuickstepTransitionManager.getTaskbarToHomeDuration(isPinnedTaskbar)
+ ? QuickstepTransitionManager.getTaskbarToHomeDuration(
+ isPinnedTaskbar && isNotInDesktop)
: StaggeredWorkspaceAnim.DURATION_MS;
SystemUiProxy.INSTANCE.get(mContext).updateContextualEduStats(
mGestureState.isTrackpadGesture(), GestureType.HOME);
@@ -1602,9 +1605,27 @@
if (mParallelRunningAnim != null) {
mParallelRunningAnim.addListener(new AnimatorListenerAdapter() {
@Override
+ public void onAnimationStart(Animator animation) {
+ if (DisplayController.isInDesktopMode(mContext)
+ && mGestureState.getEndTarget() == HOME) {
+ // Set launcher animation started, so we don't notify from
+ // desktop visibility controller
+ DesktopVisibilityController.INSTANCE.get(
+ mContext).setLauncherAnimationRunning(true);
+ }
+ }
+
+ @Override
public void onAnimationEnd(Animator animation) {
mParallelRunningAnim = null;
mStateCallback.setStateOnUiThread(STATE_PARALLEL_ANIM_FINISHED);
+ // Swipe to home animation finished, notify DesktopVisibilityController
+ // to recreate Taskbar
+ if (DisplayController.isInDesktopMode(mContext)
+ && mGestureState.getEndTarget() == HOME) {
+ DesktopVisibilityController.INSTANCE.get(
+ mContext).onLauncherAnimationFromDesktopEnd();
+ }
}
});
mParallelRunningAnim.start();
@@ -1691,7 +1712,6 @@
if (mHandOffAnimationToHome) {
handOffAnimation(velocityPxPerMs);
}
-
windowAnim[0].addAnimatorListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
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/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 2c4c2f9..f506039 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -353,7 +353,7 @@
TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size());
- int numVisibleTasks = 0;
+ boolean isFirstVisibleTaskFound = false;
for (GroupedTaskInfo rawTask : rawTasks) {
if (rawTask.isBaseType(TYPE_DESK)) {
// TYPE_DESK tasks is only created when desktop mode can be entered,
@@ -362,6 +362,14 @@
List<DesktopTask> desktopTasks = createDesktopTasks(
rawTask.getBaseGroupedTask());
allTasks.addAll(desktopTasks);
+
+ // If any task in desktop group task is visible, set isFirstVisibleTaskFound to
+ // true. This way if there is a transparent task in the list later on, it does
+ // not get its own tile in Overview.
+ if (rawTask.getBaseGroupedTask().getTaskInfoList().stream().anyMatch(
+ taskInfo -> taskInfo.isVisible)) {
+ isFirstVisibleTaskFound = true;
+ }
}
continue;
}
@@ -402,7 +410,7 @@
tmpLockedUsers.get(task2Key.userId) /* isLocked */);
} else {
// Is fullscreen task
- if (numVisibleTasks > 0) {
+ if (isFirstVisibleTaskFound) {
boolean isExcluded = (taskInfo1.baseIntent.getFlags()
& FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
if (taskInfo1.isTopActivityTransparent && isExcluded) {
@@ -413,7 +421,7 @@
}
}
if (taskInfo1.isVisible) {
- numVisibleTasks++;
+ isFirstVisibleTaskFound = true;
}
if (task2 != null) {
Objects.requireNonNull(rawTask.getSplitBounds());
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/SystemUiProxy.kt b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
index 1f3eb2a..0f6649b 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.kt
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
@@ -655,20 +655,28 @@
* Tells SysUI to show a shortcut bubble.
*
* @param info the shortcut info used to create or identify the bubble.
+ * @param bubbleBarLocation the optional location of the bubble bar.
*/
- fun showShortcutBubble(info: ShortcutInfo?) =
+ @JvmOverloads
+ fun showShortcutBubble(info: ShortcutInfo?, bubbleBarLocation: BubbleBarLocation? = null) =
executeWithErrorLog({ "Failed call showShortcutBubble" }) {
- bubbles?.showShortcutBubble(info)
+ bubbles?.showShortcutBubble(info, bubbleBarLocation)
}
/**
* Tells SysUI to show a bubble of an app.
*
* @param intent the intent used to create the bubble.
+ * @param bubbleBarLocation the optional location of the bubble bar.
*/
- fun showAppBubble(intent: Intent?, user: UserHandle) =
+ @JvmOverloads
+ fun showAppBubble(
+ intent: Intent?,
+ user: UserHandle,
+ bubbleBarLocation: BubbleBarLocation? = null,
+ ) =
executeWithErrorLog({ "Failed call showAppBubble" }) {
- bubbles?.showAppBubble(intent, user)
+ bubbles?.showAppBubble(intent, user, bubbleBarLocation)
}
/** Tells SysUI to show the expanded view. */
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..2631efe 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -51,6 +51,7 @@
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
+import com.android.quickstep.views.AddDesktopButton;
import com.android.quickstep.views.ClearAllButton;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.RecentsViewContainer;
@@ -97,8 +98,13 @@
private void setProperties(RecentsState state, StateAnimationConfig config,
PropertySetter setter) {
float clearAllButtonAlpha = state.hasClearAllButton() ? 1 : 0;
- setter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
- clearAllButtonAlpha, LINEAR);
+ setter.setFloat(mRecentsView.getClearAllButton(),
+ ClearAllButton.VISIBILITY_ALPHA, clearAllButtonAlpha, LINEAR);
+ if (mRecentsView.getAddDeskButton() != null) {
+ float addDeskButtonAlpha = state.hasAddDeskButton() ? 1 : 0;
+ setter.setFloat(mRecentsView.getAddDeskButton(), AddDesktopButton.VISIBILITY_ALPHA,
+ 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/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index e73fb3b..22227c9 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -36,7 +36,6 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
-import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.Button;
import android.widget.FrameLayout;
@@ -124,13 +123,10 @@
// These runnables should be used when posting callbacks to their views and cleared from their
// views before posting new callbacks.
- private final Runnable mTitleViewCallback;
- private final Runnable mSubtitleViewCallback;
@Nullable private Runnable mFeedbackViewCallback;
@Nullable private Runnable mFakeTaskViewCallback;
@Nullable private Runnable mFakeTaskbarViewCallback;
private final Runnable mShowFeedbackRunnable;
- private final AccessibilityManager mAccessibilityManager;
TutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) {
mTutorialFragment = tutorialFragment;
@@ -187,17 +183,6 @@
outline.setRoundRect(mExitingAppRect, mExitingAppRadius);
}
});
-
- mAccessibilityManager = AccessibilityManager.getInstance(mContext);
- mTitleViewCallback = () -> {
- mFeedbackTitleView.requestFocus();
- mFeedbackTitleView.sendAccessibilityEvent(
- AccessibilityEvent.TYPE_VIEW_FOCUSED);
- };
- mSubtitleViewCallback = () -> {
- mFeedbackSubtitleView.requestFocus();
- mFeedbackSubtitleView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
- };
mShowFeedbackRunnable = () -> {
mFeedbackView.setAlpha(0f);
mFeedbackView.setScaleX(0.95f);
@@ -216,14 +201,14 @@
mFeedbackViewCallback = mTutorialFragment::continueTutorial;
mFeedbackView.postDelayed(
mFeedbackViewCallback,
- mAccessibilityManager.getRecommendedTimeoutMillis(
- ADVANCE_TUTORIAL_TIMEOUT_MS,
- AccessibilityManager.FLAG_CONTENT_TEXT
+ AccessibilityManager.getInstance(mContext)
+ .getRecommendedTimeoutMillis(
+ ADVANCE_TUTORIAL_TIMEOUT_MS,
+ AccessibilityManager.FLAG_CONTENT_TEXT
| AccessibilityManager.FLAG_CONTENT_CONTROLS));
}
})
.start();
- mFeedbackTitleView.postDelayed(mTitleViewCallback, FEEDBACK_ANIMATION_MS);
};
}
@@ -416,8 +401,6 @@
int titleResId,
int subtitleResId,
boolean isGestureSuccessful) {
- mFeedbackTitleView.removeCallbacks(mTitleViewCallback);
- mFeedbackSubtitleView.removeCallbacks(mSubtitleViewCallback);
if (mFeedbackViewCallback != null) {
mFeedbackView.removeCallbacks(mFeedbackViewCallback);
mFeedbackViewCallback = null;
@@ -425,15 +408,6 @@
mFeedbackTitleView.setText(titleResId);
mFeedbackSubtitleView.setText(subtitleResId);
- mFeedbackTitleView.postDelayed(mTitleViewCallback, mAccessibilityManager
- .getRecommendedTimeoutMillis(
- FEEDBACK_ANIMATION_MS,
- AccessibilityManager.FLAG_CONTENT_TEXT));
- mFeedbackSubtitleView.postDelayed(mSubtitleViewCallback, mAccessibilityManager
- .getRecommendedTimeoutMillis(
- SUBTITLE_ANNOUNCE_DELAY_MS,
- AccessibilityManager.FLAG_CONTENT_TEXT));
-
if (isGestureSuccessful) {
if (mTutorialFragment.isAtFinalStep()) {
TypefaceUtils.setTypeface(
@@ -494,8 +468,6 @@
mFakeTaskbarView.removeCallbacks(mFakeTaskbarViewCallback);
mFakeTaskbarViewCallback = null;
}
- mFeedbackTitleView.removeCallbacks(mTitleViewCallback);
- mFeedbackSubtitleView.removeCallbacks(mSubtitleViewCallback);
}
private void playFeedbackAnimation() {
@@ -588,13 +560,6 @@
mSkipButton.setVisibility(GONE);
mDoneButton.setVisibility(View.VISIBLE);
mDoneButton.setOnClickListener(this::onActionButtonClicked);
- mDoneButton.postDelayed(() -> {
- mDoneButton.requestFocus();
- mDoneButton.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
- }, mAccessibilityManager
- .getRecommendedTimeoutMillis(
- DONE_BUTTON_ANNOUNCE_DELAY_MS,
- AccessibilityManager.FLAG_CONTENT_CONTROLS));
}
void hideFakeTaskbar(boolean animateToHotseat) {
diff --git a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
index 59ea8fa..e3c9b2b 100644
--- a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
@@ -103,7 +103,7 @@
target: T,
action: Int2DAction<T>,
primaryParam: Int,
- secondaryParam: Int
+ secondaryParam: Int,
) = action.call(target, secondaryParam, primaryParam)
override fun getPrimaryDirection(event: MotionEvent, pointerIndex: Int): Float =
@@ -171,7 +171,7 @@
override fun getSplitTranslationDirectionFactor(
stagePosition: Int,
- deviceProfile: DeviceProfile
+ deviceProfile: DeviceProfile,
): Int = if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) -1 else 1
override fun getTaskMenuX(
@@ -179,7 +179,7 @@
thumbnailView: View,
deviceProfile: DeviceProfile,
taskInsetMargin: Float,
- taskViewIcon: View
+ taskViewIcon: View,
): Float = thumbnailView.measuredWidth + x - taskInsetMargin
override fun getTaskMenuY(
@@ -188,7 +188,7 @@
stagePosition: Int,
taskMenuView: View,
taskInsetMargin: Float,
- taskViewIcon: View
+ taskViewIcon: View,
): Float {
val layoutParams = taskMenuView.layoutParams as BaseDragLayer.LayoutParams
var taskMenuY = y + taskInsetMargin
@@ -203,7 +203,7 @@
override fun getTaskMenuWidth(
thumbnailView: View,
deviceProfile: DeviceProfile,
- @StagePosition stagePosition: Int
+ @StagePosition stagePosition: Int,
): Int =
when {
Flags.enableOverviewIconMenu() ->
@@ -218,14 +218,14 @@
taskInsetMargin: Float,
deviceProfile: DeviceProfile,
taskMenuX: Float,
- taskMenuY: Float
+ taskMenuY: Float,
): Int = (taskMenuX - taskInsetMargin).toInt()
override fun setTaskOptionsMenuLayoutOrientation(
deviceProfile: DeviceProfile,
taskMenuLayout: LinearLayout,
dividerSpacing: Int,
- dividerDrawable: ShapeDrawable
+ dividerDrawable: ShapeDrawable,
) {
taskMenuLayout.orientation = LinearLayout.VERTICAL
dividerDrawable.intrinsicHeight = dividerSpacing
@@ -235,7 +235,7 @@
override fun setLayoutParamsForTaskMenuOptionItem(
lp: LinearLayout.LayoutParams,
viewGroup: LinearLayout,
- deviceProfile: DeviceProfile
+ deviceProfile: DeviceProfile,
) {
// Phone fake landscape
viewGroup.orientation = LinearLayout.HORIZONTAL
@@ -250,7 +250,7 @@
deviceProfile: DeviceProfile,
snapshotViewWidth: Int,
snapshotViewHeight: Int,
- banner: View
+ banner: View,
) {
banner.pivotX = 0f
banner.pivotY = 0f
@@ -273,7 +273,7 @@
deviceProfile: DeviceProfile,
thumbnailViews: Array<View>,
desiredTaskId: Int,
- banner: View
+ banner: View,
): Pair<Float, Float> {
val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
val translationX = banner.height.toFloat()
@@ -311,13 +311,21 @@
override fun getTaskDragDisplacementFactor(isRtl: Boolean): Int = if (isRtl) 1 else -1
+ override fun getTaskDismissVerticalDirection(): Int = 1
+
+ override fun getTaskDismissLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int =
+ secondaryDimension - taskThumbnailBounds.left
+
+ override fun getTaskLaunchLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int =
+ taskThumbnailBounds.left
+
/* -------------------- */
override fun getChildBounds(
child: View,
childStart: Int,
pageCenter: Int,
- layoutChild: Boolean
+ layoutChild: Boolean,
): ChildBounds {
val childHeight = child.measuredHeight
val childWidth = child.measuredWidth
@@ -338,7 +346,7 @@
R.drawable.ic_split_horizontal,
R.string.recent_task_option_split_screen,
STAGE_POSITION_TOP_OR_LEFT,
- STAGE_TYPE_MAIN
+ STAGE_TYPE_MAIN,
)
)
@@ -347,7 +355,7 @@
placeholderInset: Int,
dp: DeviceProfile,
@StagePosition stagePosition: Int,
- out: Rect
+ out: Rect,
) {
// In fake land/seascape, the placeholder always needs to go to the "top" of the device,
// which is the same bounds as 0 rotation.
@@ -374,7 +382,7 @@
drawableWidth: Int,
drawableHeight: Int,
dp: DeviceProfile,
- @StagePosition stagePosition: Int
+ @StagePosition stagePosition: Int,
) {
val insetAdjustment = getPlaceholderSizeAdjustment(dp) / 2f
out.x = (onScreenRectCenterX / fullscreenScaleX - 1.0f * drawableWidth / 2)
@@ -393,7 +401,7 @@
out: View,
dp: DeviceProfile,
splitInstructionsHeight: Int,
- splitInstructionsWidth: Int
+ splitInstructionsWidth: Int,
) {
out.pivotX = 0f
out.pivotY = splitInstructionsHeight.toFloat()
@@ -421,7 +429,7 @@
dp: DeviceProfile,
@StagePosition stagePosition: Int,
out1: Rect,
- out2: Rect
+ out2: Rect,
) {
// In fake land/seascape, the window bounds are always top and bottom half
val screenHeight = dp.heightPx
@@ -434,7 +442,7 @@
dp: DeviceProfile,
outRect: Rect,
splitInfo: SplitBounds,
- desiredStagePosition: Int
+ desiredStagePosition: Int,
) {
val topLeftTaskPercent = splitInfo.leftTopTaskPercent
val dividerBarPercent = splitInfo.dividerPercent
@@ -448,7 +456,7 @@
/**
* @param inSplitSelection Whether user currently has a task from this task group staged for
- * split screen. Currently this state is not reachable in fake landscape.
+ * split screen. Currently this state is not reachable in fake landscape.
*/
override fun measureGroupedTaskViewThumbnailBounds(
primarySnapshot: View,
@@ -458,7 +466,7 @@
splitBoundsConfig: SplitBounds,
dp: DeviceProfile,
isRtl: Boolean,
- inSplitSelection: Boolean
+ inSplitSelection: Boolean,
) {
val primaryParams = primarySnapshot.layoutParams as FrameLayout.LayoutParams
val secondaryParams = secondarySnapshot.layoutParams as FrameLayout.LayoutParams
@@ -479,13 +487,13 @@
primarySnapshot.translationY = spaceAboveSnapshot.toFloat()
primarySnapshot.measure(
MeasureSpec.makeMeasureSpec(taskViewFirst.x, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(taskViewFirst.y, MeasureSpec.EXACTLY)
+ MeasureSpec.makeMeasureSpec(taskViewFirst.y, MeasureSpec.EXACTLY),
)
val translationY = taskViewFirst.y + spaceAboveSnapshot + dividerBar
secondarySnapshot.translationY = (translationY - spaceAboveSnapshot).toFloat()
secondarySnapshot.measure(
MeasureSpec.makeMeasureSpec(taskViewSecond.x, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(taskViewSecond.y, MeasureSpec.EXACTLY)
+ MeasureSpec.makeMeasureSpec(taskViewSecond.y, MeasureSpec.EXACTLY),
)
}
@@ -493,7 +501,7 @@
dp: DeviceProfile,
splitBoundsConfig: SplitBounds,
parentWidth: Int,
- parentHeight: Int
+ parentHeight: Int,
): Pair<Point, Point> {
val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
@@ -511,7 +519,7 @@
taskIconMargin: Int,
taskIconHeight: Int,
thumbnailTopMargin: Int,
- isRtl: Boolean
+ isRtl: Boolean,
) {
iconParams.gravity =
if (isRtl) {
@@ -527,7 +535,7 @@
override fun setIconAppChipChildrenParams(
iconParams: FrameLayout.LayoutParams,
- chipChildMarginStart: Int
+ chipChildMarginStart: Int,
) {
iconParams.gravity = Gravity.START or Gravity.CENTER_VERTICAL
iconParams.marginStart = chipChildMarginStart
@@ -538,7 +546,7 @@
iconAppChipView: IconAppChipView,
iconMenuParams: FrameLayout.LayoutParams,
iconMenuMargin: Int,
- thumbnailTopMargin: Int
+ thumbnailTopMargin: Int,
) {
val isRtl = iconAppChipView.layoutDirection == View.LAYOUT_DIRECTION_RTL
@@ -564,7 +572,7 @@
/**
* @param inSplitSelection Whether user currently has a task from this task group staged for
- * split screen. Currently this state is not reachable in fake landscape.
+ * split screen. Currently this state is not reachable in fake landscape.
*/
override fun setSplitIconParams(
primaryIconView: View,
@@ -577,7 +585,8 @@
isRtl: Boolean,
deviceProfile: DeviceProfile,
splitConfig: SplitBounds,
- inSplitSelection: Boolean
+ inSplitSelection: Boolean,
+ oneIconHiddenDueToSmallWidth: Boolean,
) {
val spaceAboveSnapshot = deviceProfile.overviewTaskThumbnailTopMarginPx
val totalThumbnailHeight = groupedTaskViewHeight - spaceAboveSnapshot
@@ -590,7 +599,8 @@
totalThumbnailHeight,
isRtl,
deviceProfile.overviewTaskMarginPx,
- dividerBar
+ dividerBar,
+ oneIconHiddenDueToSmallWidth,
)
updateSplitIconsPosition(primaryIconView, topLeftY, isRtl)
@@ -604,20 +614,20 @@
override fun <T> getSplitSelectTaskOffset(
primary: FloatProperty<T>,
secondary: FloatProperty<T>,
- deviceProfile: DeviceProfile
+ deviceProfile: DeviceProfile,
): Pair<FloatProperty<T>, FloatProperty<T>> = Pair(primary, secondary)
override fun getFloatingTaskOffscreenTranslationTarget(
floatingTask: View,
onScreenRect: RectF,
@StagePosition stagePosition: Int,
- dp: DeviceProfile
+ dp: DeviceProfile,
): Float = floatingTask.translationY - onScreenRect.height()
override fun setFloatingTaskPrimaryTranslation(
floatingTask: View,
translation: Float,
- dp: DeviceProfile
+ dp: DeviceProfile,
) {
floatingTask.translationY = translation
}
@@ -647,6 +657,7 @@
isRtl: Boolean,
overviewTaskMarginPx: Int,
dividerSize: Int,
+ oneIconHiddenDueToSmallWidth: Boolean,
): SplitIconPositions {
return if (Flags.enableOverviewIconMenu()) {
if (isRtl) {
@@ -655,11 +666,20 @@
SplitIconPositions(0, primarySnapshotHeight + dividerSize)
}
} else {
- val topLeftY = primarySnapshotHeight + overviewTaskMarginPx
- SplitIconPositions(
- topLeftY = topLeftY,
- bottomRightY = topLeftY + dividerSize + taskIconHeight
- )
+ if (oneIconHiddenDueToSmallWidth) {
+ // Center both icons
+ val centerY =
+ primarySnapshotHeight +
+ overviewTaskMarginPx +
+ ((taskIconHeight + dividerSize) / 2)
+ SplitIconPositions(topLeftY = centerY, bottomRightY = centerY)
+ } else {
+ val topLeftY = primarySnapshotHeight + overviewTaskMarginPx
+ SplitIconPositions(
+ topLeftY = topLeftY,
+ bottomRightY = topLeftY + dividerSize + taskIconHeight,
+ )
+ }
}
}
diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
deleted file mode 100644
index d9ad7ce..0000000
--- a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
+++ /dev/null
@@ -1,837 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep.orientation;
-
-import static android.view.Gravity.BOTTOM;
-import static android.view.Gravity.CENTER_HORIZONTAL;
-import static android.view.Gravity.END;
-import static android.view.Gravity.START;
-import static android.view.Gravity.TOP;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-
-import static com.android.launcher3.Flags.enableOverviewIconMenu;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
-import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
-
-import android.graphics.Matrix;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.ShapeDrawable;
-import android.util.FloatProperty;
-import android.util.Pair;
-import android.view.Gravity;
-import android.view.Surface;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.logger.LauncherAtom;
-import com.android.launcher3.touch.DefaultPagedViewHandler;
-import com.android.launcher3.touch.SingleAxisSwipeDetector;
-import com.android.launcher3.util.SplitConfigurationOptions;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
-import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
-import com.android.quickstep.views.IconAppChipView;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class PortraitPagedViewHandler extends DefaultPagedViewHandler implements
- RecentsPagedOrientationHandler {
-
- private final Matrix mTmpMatrix = new Matrix();
- private final RectF mTmpRectF = new RectF();
-
- @Override
- public <T> T getPrimaryValue(T x, T y) {
- return x;
- }
-
- @Override
- public <T> T getSecondaryValue(T x, T y) {
- return y;
- }
-
- @Override
- public boolean isLayoutNaturalToLauncher() {
- return true;
- }
-
- @Override
- public void adjustFloatingIconStartVelocity(PointF velocity) {
- //no-op
- }
-
- @Override
- public void fixBoundsForHomeAnimStartRect(RectF outStartRect, DeviceProfile deviceProfile) {
- if (outStartRect.left > deviceProfile.widthPx) {
- outStartRect.offsetTo(0, outStartRect.top);
- } else if (outStartRect.left < -deviceProfile.widthPx) {
- outStartRect.offsetTo(0, outStartRect.top);
- }
- }
-
- @Override
- public <T> void setSecondary(T target, Float2DAction<T> action, float param) {
- action.call(target, 0, param);
- }
-
- @Override
- public <T> void set(T target, Int2DAction<T> action, int primaryParam,
- int secondaryParam) {
- action.call(target, primaryParam, secondaryParam);
- }
-
- @Override
- public int getPrimarySize(View view) {
- return view.getWidth();
- }
-
- @Override
- public float getPrimarySize(RectF rect) {
- return rect.width();
- }
-
- @Override
- public float getStart(RectF rect) {
- return rect.left;
- }
-
- @Override
- public float getEnd(RectF rect) {
- return rect.right;
- }
-
- @Override
- public void rotateInsets(@NonNull Rect insets, @NonNull Rect outInsets) {
- outInsets.set(insets);
- }
-
- @Override
- public int getClearAllSidePadding(View view, boolean isRtl) {
- return (isRtl ? view.getPaddingRight() : - view.getPaddingLeft()) / 2;
- }
-
- @Override
- public int getSecondaryDimension(View view) {
- return view.getHeight();
- }
-
- @Override
- public FloatProperty<View> getPrimaryViewTranslate() {
- return VIEW_TRANSLATE_X;
- }
-
- @Override
- public FloatProperty<View> getSecondaryViewTranslate() {
- return VIEW_TRANSLATE_Y;
- }
-
- @Override
- public float getDegreesRotated() {
- return 0;
- }
-
- @Override
- public int getRotation() {
- return Surface.ROTATION_0;
- }
-
- @Override
- public void setPrimaryScale(View view, float scale) {
- view.setScaleX(scale);
- }
-
- @Override
- public void setSecondaryScale(View view, float scale) {
- view.setScaleY(scale);
- }
-
- public int getSecondaryTranslationDirectionFactor() {
- return -1;
- }
-
- @Override
- public int getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile) {
- if (deviceProfile.isLeftRightSplit && stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
- return -1;
- } else {
- return 1;
- }
- }
-
- @Override
- public float getTaskMenuX(float x, View thumbnailView,
- DeviceProfile deviceProfile, float taskInsetMargin, View taskViewIcon) {
- if (deviceProfile.isLandscape) {
- return x + taskInsetMargin
- + (thumbnailView.getMeasuredWidth() - thumbnailView.getMeasuredHeight()) / 2f;
- } else {
- return x + taskInsetMargin;
- }
- }
-
- @Override
- public float getTaskMenuY(float y, View thumbnailView, int stagePosition,
- View taskMenuView, float taskInsetMargin, View taskViewIcon) {
- return y + taskInsetMargin;
- }
-
- @Override
- public int getTaskMenuWidth(View thumbnailView, DeviceProfile deviceProfile,
- @StagePosition int stagePosition) {
- if (enableOverviewIconMenu()) {
- return thumbnailView.getResources().getDimensionPixelSize(
- R.dimen.task_thumbnail_icon_menu_expanded_width);
- }
- int padding = thumbnailView.getResources()
- .getDimensionPixelSize(R.dimen.task_menu_edge_padding);
- return (deviceProfile.isLandscape && !deviceProfile.isTablet
- ? thumbnailView.getMeasuredHeight()
- : thumbnailView.getMeasuredWidth()) - (2 * padding);
- }
-
- @Override
- public int getTaskMenuHeight(float taskInsetMargin, DeviceProfile deviceProfile,
- float taskMenuX, float taskMenuY) {
- return (int) (deviceProfile.heightPx - deviceProfile.getInsets().top - taskMenuY
- - deviceProfile.getOverviewActionsClaimedSpaceBelow());
- }
-
- @Override
- public void setTaskOptionsMenuLayoutOrientation(DeviceProfile deviceProfile,
- LinearLayout taskMenuLayout, int dividerSpacing,
- ShapeDrawable dividerDrawable) {
- taskMenuLayout.setOrientation(LinearLayout.VERTICAL);
- dividerDrawable.setIntrinsicHeight(dividerSpacing);
- taskMenuLayout.setDividerDrawable(dividerDrawable);
- }
-
- @Override
- public void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp,
- LinearLayout viewGroup, DeviceProfile deviceProfile) {
- viewGroup.setOrientation(LinearLayout.HORIZONTAL);
- lp.width = LinearLayout.LayoutParams.MATCH_PARENT;
- lp.height = WRAP_CONTENT;
- }
-
- @Override
- public void updateDwbBannerLayout(int taskViewWidth, int taskViewHeight,
- boolean isGroupedTaskView, @NonNull DeviceProfile deviceProfile,
- int snapshotViewWidth, int snapshotViewHeight, @NonNull View banner) {
- FrameLayout.LayoutParams bannerParams = (FrameLayout.LayoutParams) banner.getLayoutParams();
- banner.setPivotX(0);
- banner.setPivotY(0);
- banner.setRotation(getDegreesRotated());
- if (isGroupedTaskView) {
- bannerParams.gravity =
- BOTTOM | (deviceProfile.isLeftRightSplit ? START : CENTER_HORIZONTAL);
- bannerParams.width = snapshotViewWidth;
- } else {
- bannerParams.width = MATCH_PARENT;
- bannerParams.gravity = BOTTOM | CENTER_HORIZONTAL;
- }
- banner.setLayoutParams(bannerParams);
- }
-
- @NonNull
- @Override
- public Pair<Float, Float> getDwbBannerTranslations(int taskViewWidth,
- int taskViewHeight, SplitBounds splitBounds, @NonNull DeviceProfile deviceProfile,
- @NonNull View[] thumbnailViews, int desiredTaskId, @NonNull View banner) {
- float translationX = 0;
- float translationY = 0;
- if (splitBounds != null) {
- if (deviceProfile.isLeftRightSplit) {
- if (desiredTaskId == splitBounds.rightBottomTaskId) {
- float leftTopTaskPercent = splitBounds.getLeftTopTaskPercent();
- float dividerThicknessPercent = splitBounds.getDividerPercent();
- translationX = ((taskViewWidth * leftTopTaskPercent)
- + (taskViewWidth * dividerThicknessPercent));
- }
- } else {
- if (desiredTaskId == splitBounds.leftTopTaskId) {
- FrameLayout.LayoutParams snapshotParams =
- (FrameLayout.LayoutParams) thumbnailViews[0]
- .getLayoutParams();
- float bottomRightTaskPlusDividerPercent =
- splitBounds.getRightBottomTaskPercent()
- + splitBounds.getDividerPercent();
- translationY = -((taskViewHeight - snapshotParams.topMargin)
- * bottomRightTaskPlusDividerPercent);
- }
- }
- }
- return new Pair<>(translationX, translationY);
- }
-
- /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
-
- @Override
- public SingleAxisSwipeDetector.Direction getUpDownSwipeDirection() {
- return VERTICAL;
- }
-
- @Override
- public int getUpDirection(boolean isRtl) {
- // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
- return SingleAxisSwipeDetector.DIRECTION_POSITIVE;
- }
-
- @Override
- public int getDownDirection(boolean isRtl) {
- // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
- return SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
- }
-
- @Override
- public boolean isGoingUp(float displacement, boolean isRtl) {
- // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
- return displacement < 0;
- }
-
- @Override
- public int getTaskDragDisplacementFactor(boolean isRtl) {
- // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
- return 1;
- }
-
- /* -------------------- */
- @Override
- public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
- return dp.heightPx - rect.bottom;
- }
-
- @Override
- public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
- if (dp.isTablet) {
- return Utilities.getSplitPositionOptions(dp);
- }
-
- List<SplitPositionOption> options = new ArrayList<>();
- if (dp.isSeascape()) {
- options.add(new SplitPositionOption(
- R.drawable.ic_split_horizontal, R.string.recent_task_option_split_screen,
- STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_MAIN));
- } else if (dp.isLeftRightSplit) {
- options.add(new SplitPositionOption(
- R.drawable.ic_split_horizontal, R.string.recent_task_option_split_screen,
- STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
- } else {
- // Only add top option
- options.add(new SplitPositionOption(
- R.drawable.ic_split_vertical, R.string.recent_task_option_split_screen,
- STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
- }
- return options;
- }
-
- @Override
- public void getInitialSplitPlaceholderBounds(int placeholderHeight, int placeholderInset,
- DeviceProfile dp, @StagePosition int stagePosition, Rect out) {
- int screenWidth = dp.widthPx;
- int screenHeight = dp.heightPx;
- boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
- int insetSizeAdjustment = getPlaceholderSizeAdjustment(dp, pinToRight);
-
- out.set(0, 0, screenWidth, placeholderHeight + insetSizeAdjustment);
- if (!dp.isLeftRightSplit) {
- // portrait, phone or tablet - spans width of screen, nothing else to do
- out.inset(placeholderInset, 0);
-
- // Adjust the top to account for content off screen. This will help to animate the view
- // in with rounded corners.
- int totalHeight = (int) (1.0f * screenHeight / 2 * (screenWidth - 2 * placeholderInset)
- / screenWidth);
- out.top -= (totalHeight - placeholderHeight);
- return;
- }
-
- // Now we rotate the portrait rect depending on what side we want pinned
-
- float postRotateScale = (float) screenHeight / screenWidth;
- mTmpMatrix.reset();
- mTmpMatrix.postRotate(pinToRight ? 90 : 270);
- mTmpMatrix.postTranslate(pinToRight ? screenWidth : 0, pinToRight ? 0 : screenWidth);
- // The placeholder height stays constant after rotation, so we don't change width scale
- mTmpMatrix.postScale(1, postRotateScale);
-
- mTmpRectF.set(out);
- mTmpMatrix.mapRect(mTmpRectF);
- mTmpRectF.inset(0, placeholderInset);
- mTmpRectF.roundOut(out);
-
- // Adjust the top to account for content off screen. This will help to animate the view in
- // with rounded corners.
- int totalWidth = (int) (1.0f * screenWidth / 2 * (screenHeight - 2 * placeholderInset)
- / screenHeight);
- int width = out.width();
- if (pinToRight) {
- out.right += totalWidth - width;
- } else {
- out.left -= totalWidth - width;
- }
- }
-
- @Override
- public void updateSplitIconParams(View out, float onScreenRectCenterX,
- float onScreenRectCenterY, float fullscreenScaleX, float fullscreenScaleY,
- int drawableWidth, int drawableHeight, DeviceProfile dp,
- @StagePosition int stagePosition) {
- boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
- float insetAdjustment = getPlaceholderSizeAdjustment(dp, pinToRight) / 2f;
- if (!dp.isLeftRightSplit) {
- out.setX(onScreenRectCenterX / fullscreenScaleX
- - 1.0f * drawableWidth / 2);
- out.setY((onScreenRectCenterY + insetAdjustment) / fullscreenScaleY
- - 1.0f * drawableHeight / 2);
- } else {
- if (pinToRight) {
- out.setX((onScreenRectCenterX - insetAdjustment) / fullscreenScaleX
- - 1.0f * drawableWidth / 2);
- } else {
- out.setX((onScreenRectCenterX + insetAdjustment) / fullscreenScaleX
- - 1.0f * drawableWidth / 2);
- }
- out.setY(onScreenRectCenterY / fullscreenScaleY
- - 1.0f * drawableHeight / 2);
- }
- }
-
- /**
- * The split placeholder comes with a default inset to buffer the icon from the top of the
- * screen. But if the device already has a large inset (from cutouts etc), use that instead.
- */
- private int getPlaceholderSizeAdjustment(DeviceProfile dp, boolean pinToRight) {
- int insetThickness;
- if (!dp.isLandscape) {
- insetThickness = dp.getInsets().top;
- } else {
- insetThickness = pinToRight ? dp.getInsets().right : dp.getInsets().left;
- }
- return Math.max(insetThickness - dp.splitPlaceholderInset, 0);
- }
-
- @Override
- public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
- int splitInstructionsWidth) {
- out.setPivotX(0);
- out.setPivotY(splitInstructionsHeight);
- out.setRotation(getDegreesRotated());
- int distanceToEdge;
- if (dp.isPhone) {
- if (dp.isLandscape) {
- distanceToEdge = out.getResources().getDimensionPixelSize(
- R.dimen.split_instructions_bottom_margin_phone_landscape);
- } else {
- distanceToEdge = out.getResources().getDimensionPixelSize(
- R.dimen.split_instructions_bottom_margin_phone_portrait);
- }
- } else {
- distanceToEdge = dp.getOverviewActionsClaimedSpaceBelow();
- }
-
- // Center the view in case of unbalanced insets on left or right of screen
- int insetCorrectionX = (dp.getInsets().right - dp.getInsets().left) / 2;
- // Adjust for any insets on the bottom edge
- int insetCorrectionY = dp.getInsets().bottom;
- out.setTranslationX(insetCorrectionX);
- out.setTranslationY(-distanceToEdge + insetCorrectionY);
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
- lp.gravity = CENTER_HORIZONTAL | BOTTOM;
- out.setLayoutParams(lp);
- }
-
- @Override
- public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
- @StagePosition int stagePosition, Rect out1, Rect out2) {
- int screenHeight = dp.heightPx;
- int screenWidth = dp.widthPx;
- out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize);
- out2.set(0, screenHeight / 2 + splitDividerSize, screenWidth, screenHeight);
- if (!dp.isLeftRightSplit) {
- // Portrait - the window bounds are always top and bottom half
- return;
- }
-
- // Now we rotate the portrait rect depending on what side we want pinned
- boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
- float postRotateScale = (float) screenHeight / screenWidth;
-
- mTmpMatrix.reset();
- mTmpMatrix.postRotate(pinToRight ? 90 : 270);
- mTmpMatrix.postTranslate(pinToRight ? screenHeight : 0, pinToRight ? 0 : screenWidth);
- mTmpMatrix.postScale(1 / postRotateScale, postRotateScale);
-
- mTmpRectF.set(out1);
- mTmpMatrix.mapRect(mTmpRectF);
- mTmpRectF.roundOut(out1);
-
- mTmpRectF.set(out2);
- mTmpMatrix.mapRect(mTmpRectF);
- mTmpRectF.roundOut(out2);
- }
-
- @Override
- public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect,
- SplitBounds splitInfo, int desiredStagePosition) {
- float topLeftTaskPercent = splitInfo.getLeftTopTaskPercent();
- float dividerBarPercent = splitInfo.getDividerPercent();
-
- int taskbarHeight = dp.isTransientTaskbar ? 0 : dp.taskbarHeight;
- float scale = (float) outRect.height() / (dp.availableHeightPx - taskbarHeight);
- float topTaskHeight = dp.availableHeightPx * topLeftTaskPercent;
- float scaledTopTaskHeight = topTaskHeight * scale;
- float dividerHeight = dp.availableHeightPx * dividerBarPercent;
- float scaledDividerHeight = dividerHeight * scale;
-
- if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
- if (dp.isLeftRightSplit) {
- outRect.right = outRect.left + Math.round(outRect.width() * topLeftTaskPercent);
- } else {
- outRect.bottom = Math.round(outRect.top + scaledTopTaskHeight);
- }
- } else {
- if (dp.isLeftRightSplit) {
- outRect.left += Math.round(outRect.width()
- * (topLeftTaskPercent + dividerBarPercent));
- } else {
- outRect.top += Math.round(scaledTopTaskHeight + scaledDividerHeight);
- }
- }
- }
-
- /**
- * @param inSplitSelection Whether user currently has a task from this task group staged for
- * split screen. If true, we have custom translations/scaling in place
- * for the remaining snapshot, so we'll skip setting translation/scale
- * here.
- */
- @Override
- public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
- int parentWidth, int parentHeight, SplitBounds splitBoundsConfig,
- DeviceProfile dp, boolean isRtl, boolean inSplitSelection) {
- int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
-
- FrameLayout.LayoutParams primaryParams =
- (FrameLayout.LayoutParams) primarySnapshot.getLayoutParams();
- FrameLayout.LayoutParams secondaryParams =
- (FrameLayout.LayoutParams) secondarySnapshot.getLayoutParams();
-
- // Reset margins that aren't used in this method, but are used in other
- // `RecentsPagedOrientationHandler` variants.
- secondaryParams.topMargin = 0;
- primaryParams.topMargin = spaceAboveSnapshot;
-
- int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
- float dividerScale = splitBoundsConfig.getDividerPercent();
- Pair<Point, Point> taskViewSizes =
- getGroupedTaskViewSizes(dp, splitBoundsConfig, parentWidth, parentHeight);
- if (!inSplitSelection) {
- // Reset translations that aren't used in this method, but are used in other
- // `RecentsPagedOrientationHandler` variants.
- primarySnapshot.setTranslationY(0);
-
- if (dp.isLeftRightSplit) {
- int scaledDividerBar = Math.round(parentWidth * dividerScale);
- if (isRtl) {
- int translationX = taskViewSizes.second.x + scaledDividerBar;
- primarySnapshot.setTranslationX(-translationX);
- secondarySnapshot.setTranslationX(0);
- } else {
- int translationX = taskViewSizes.first.x + scaledDividerBar;
- secondarySnapshot.setTranslationX(translationX);
- primarySnapshot.setTranslationX(0);
- }
- secondarySnapshot.setTranslationY(spaceAboveSnapshot);
- } else {
- float finalDividerHeight = Math.round(totalThumbnailHeight * dividerScale);
- float translationY =
- taskViewSizes.first.y + spaceAboveSnapshot + finalDividerHeight;
- secondarySnapshot.setTranslationY(translationY);
-
- // Reset unused translations.
- secondarySnapshot.setTranslationX(0);
- primarySnapshot.setTranslationX(0);
- }
- }
-
- primarySnapshot.measure(
- View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.x, View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.y, View.MeasureSpec.EXACTLY));
- secondarySnapshot.measure(
- View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.x, View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.y,
- View.MeasureSpec.EXACTLY));
- }
-
- @Override
- public Pair<Point, Point> getGroupedTaskViewSizes(
- DeviceProfile dp,
- SplitBounds splitBoundsConfig,
- int parentWidth,
- int parentHeight) {
- int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
- int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
- float dividerScale = splitBoundsConfig.getDividerPercent();
- float taskPercent = splitBoundsConfig.getLeftTopTaskPercent();
-
- Point firstTaskViewSize = new Point();
- Point secondTaskViewSize = new Point();
-
- if (dp.isLeftRightSplit) {
- int scaledDividerBar = Math.round(parentWidth * dividerScale);
- firstTaskViewSize.x = Math.round(parentWidth * taskPercent);
- firstTaskViewSize.y = totalThumbnailHeight;
-
- secondTaskViewSize.x = parentWidth - firstTaskViewSize.x - scaledDividerBar;
- secondTaskViewSize.y = totalThumbnailHeight;
- } else {
- int taskbarHeight = dp.isTransientTaskbar ? 0 : dp.taskbarHeight;
- float scale = (float) totalThumbnailHeight / (dp.availableHeightPx - taskbarHeight);
- float topTaskHeight = dp.availableHeightPx * taskPercent;
- float finalDividerHeight = Math.round(totalThumbnailHeight * dividerScale);
- float scaledTopTaskHeight = topTaskHeight * scale;
- firstTaskViewSize.x = parentWidth;
- firstTaskViewSize.y = Math.round(scaledTopTaskHeight);
-
- secondTaskViewSize.x = parentWidth;
- secondTaskViewSize.y = Math.round(totalThumbnailHeight - firstTaskViewSize.y
- - finalDividerHeight);
- }
-
- return new Pair<>(firstTaskViewSize, secondTaskViewSize);
- }
-
- @Override
- public void setTaskIconParams(FrameLayout.LayoutParams iconParams, int taskIconMargin,
- int taskIconHeight, int thumbnailTopMargin, boolean isRtl) {
- iconParams.gravity = TOP | CENTER_HORIZONTAL;
- // Reset margins, since they may have been set on rotation
- iconParams.leftMargin = iconParams.rightMargin = 0;
- iconParams.topMargin = iconParams.bottomMargin = 0;
- }
-
- @Override
- public void setIconAppChipChildrenParams(FrameLayout.LayoutParams iconParams,
- int chipChildMarginStart) {
- iconParams.setMarginStart(chipChildMarginStart);
- iconParams.gravity = Gravity.START | Gravity.CENTER_VERTICAL;
- iconParams.topMargin = 0;
- }
-
- @Override
- public void setIconAppChipMenuParams(IconAppChipView iconAppChipView,
- FrameLayout.LayoutParams iconMenuParams, int iconMenuMargin, int thumbnailTopMargin) {
- iconMenuParams.gravity = TOP | START;
- iconMenuParams.setMarginStart(iconMenuMargin);
- iconMenuParams.topMargin = thumbnailTopMargin;
- iconMenuParams.bottomMargin = 0;
- iconMenuParams.setMarginEnd(0);
-
- iconAppChipView.setPivotX(0);
- iconAppChipView.setPivotY(0);
- iconAppChipView.setSplitTranslationY(0);
- iconAppChipView.setRotation(getDegreesRotated());
- }
-
- /**
- * @param inSplitSelection Whether user currently has a task from this task group staged for
- * split screen. If true, we have custom translations in place for the
- * remaining icon, so we'll skip setting translations here.
- */
- @Override
- public void setSplitIconParams(View primaryIconView, View secondaryIconView,
- int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
- int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
- DeviceProfile deviceProfile, SplitBounds splitConfig, boolean inSplitSelection) {
- FrameLayout.LayoutParams primaryIconParams =
- (FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
- FrameLayout.LayoutParams secondaryIconParams = enableOverviewIconMenu()
- ? (FrameLayout.LayoutParams) secondaryIconView.getLayoutParams()
- : new FrameLayout.LayoutParams(primaryIconParams);
-
- if (enableOverviewIconMenu()) {
- IconAppChipView primaryAppChipView = (IconAppChipView) primaryIconView;
- IconAppChipView secondaryAppChipView = (IconAppChipView) secondaryIconView;
- primaryIconParams.gravity = TOP | START;
- secondaryIconParams.gravity = TOP | START;
- secondaryIconParams.topMargin = primaryIconParams.topMargin;
- secondaryIconParams.setMarginStart(primaryIconParams.getMarginStart());
- if (!inSplitSelection) {
- if (deviceProfile.isLeftRightSplit) {
- if (isRtl) {
- int secondarySnapshotWidth = groupedTaskViewWidth - primarySnapshotWidth;
- primaryAppChipView.setSplitTranslationX(-secondarySnapshotWidth);
- } else {
- secondaryAppChipView.setSplitTranslationX(primarySnapshotWidth);
- }
- } else {
- primaryAppChipView.setSplitTranslationX(0);
- secondaryAppChipView.setSplitTranslationX(0);
- int dividerThickness = Math.min(splitConfig.visualDividerBounds.width(),
- splitConfig.visualDividerBounds.height());
- secondaryAppChipView.setSplitTranslationY(
- primarySnapshotHeight + (deviceProfile.isTablet ? 0
- : dividerThickness));
- }
- }
- } else if (deviceProfile.isLeftRightSplit) {
- // We calculate the "midpoint" of the thumbnail area, and place the icons there.
- // This is the place where the thumbnail area splits by default, in a near-50/50 split.
- // It is usually not exactly 50/50, due to insets/screen cutouts.
- int fullscreenInsetThickness = deviceProfile.isSeascape()
- ? deviceProfile.getInsets().right
- : deviceProfile.getInsets().left;
- int fullscreenMidpointFromBottom = ((deviceProfile.widthPx
- - fullscreenInsetThickness) / 2);
- float midpointFromEndPct = (float) fullscreenMidpointFromBottom
- / deviceProfile.widthPx;
- float insetPct = (float) fullscreenInsetThickness / deviceProfile.widthPx;
- int spaceAboveSnapshots = 0;
- int overviewThumbnailAreaThickness = groupedTaskViewWidth - spaceAboveSnapshots;
- int bottomToMidpointOffset = (int) (overviewThumbnailAreaThickness
- * midpointFromEndPct);
- int insetOffset = (int) (overviewThumbnailAreaThickness * insetPct);
-
- if (deviceProfile.isSeascape()) {
- primaryIconParams.gravity = TOP | (isRtl ? END : START);
- secondaryIconParams.gravity = TOP | (isRtl ? END : START);
- if (!inSplitSelection) {
- if (splitConfig.initiatedFromSeascape) {
- // if the split was initiated from seascape,
- // the task on the right (secondary) is slightly larger
- primaryIconView.setTranslationX(bottomToMidpointOffset - taskIconHeight);
- secondaryIconView.setTranslationX(bottomToMidpointOffset);
- } else {
- // if not,
- // the task on the left (primary) is slightly larger
- primaryIconView.setTranslationX(bottomToMidpointOffset + insetOffset
- - taskIconHeight);
- secondaryIconView.setTranslationX(bottomToMidpointOffset + insetOffset);
- }
- }
- } else {
- primaryIconParams.gravity = TOP | (isRtl ? START : END);
- secondaryIconParams.gravity = TOP | (isRtl ? START : END);
- if (!inSplitSelection) {
- if (!splitConfig.initiatedFromSeascape) {
- // if the split was initiated from landscape,
- // the task on the left (primary) is slightly larger
- primaryIconView.setTranslationX(-bottomToMidpointOffset);
- secondaryIconView.setTranslationX(-bottomToMidpointOffset + taskIconHeight);
- } else {
- // if not,
- // the task on the right (secondary) is slightly larger
- primaryIconView.setTranslationX(-bottomToMidpointOffset - insetOffset);
- secondaryIconView.setTranslationX(-bottomToMidpointOffset - insetOffset
- + taskIconHeight);
- }
- }
- }
- } else {
- primaryIconParams.gravity = TOP | CENTER_HORIZONTAL;
- secondaryIconParams.gravity = TOP | CENTER_HORIZONTAL;
- if (!inSplitSelection) {
- // shifts icon half a width left (height is used here since icons are square)
- primaryIconView.setTranslationX(-(taskIconHeight / 2f));
- secondaryIconView.setTranslationX(taskIconHeight / 2f);
- }
- }
- if (!enableOverviewIconMenu() && !inSplitSelection) {
- primaryIconView.setTranslationY(0);
- secondaryIconView.setTranslationY(0);
- }
-
-
- primaryIconView.setLayoutParams(primaryIconParams);
- secondaryIconView.setLayoutParams(secondaryIconParams);
- }
-
- @Override
- public int getDefaultSplitPosition(DeviceProfile deviceProfile) {
- if (!deviceProfile.isTablet) {
- throw new IllegalStateException("Default position available only for large screens");
- }
- if (deviceProfile.isLeftRightSplit) {
- return STAGE_POSITION_BOTTOM_OR_RIGHT;
- } else {
- return STAGE_POSITION_TOP_OR_LEFT;
- }
- }
-
- @Override
- public Pair<FloatProperty, FloatProperty> getSplitSelectTaskOffset(FloatProperty primary,
- FloatProperty secondary, DeviceProfile deviceProfile) {
- if (deviceProfile.isLeftRightSplit) { // or seascape
- return new Pair<>(primary, secondary);
- } else {
- return new Pair<>(secondary, primary);
- }
- }
-
- @Override
- public float getFloatingTaskOffscreenTranslationTarget(View floatingTask, RectF onScreenRect,
- @StagePosition int stagePosition, DeviceProfile dp) {
- if (dp.isLeftRightSplit) {
- float currentTranslationX = floatingTask.getTranslationX();
- return stagePosition == STAGE_POSITION_TOP_OR_LEFT
- ? currentTranslationX - onScreenRect.width()
- : currentTranslationX + onScreenRect.width();
- } else {
- float currentTranslationY = floatingTask.getTranslationY();
- return currentTranslationY - onScreenRect.height();
- }
- }
-
- @Override
- public void setFloatingTaskPrimaryTranslation(View floatingTask, float translation,
- DeviceProfile dp) {
- if (dp.isLeftRightSplit) {
- floatingTask.setTranslationX(translation);
- } else {
- floatingTask.setTranslationY(translation);
- }
-
- }
-
- @Override
- public float getFloatingTaskPrimaryTranslation(View floatingTask, DeviceProfile dp) {
- return dp.isLeftRightSplit
- ? floatingTask.getTranslationX()
- : floatingTask.getTranslationY();
- }
-
- @NonNull
- @Override
- public LauncherAtom.TaskSwitcherContainer.OrientationHandler getHandlerTypeForLogging() {
- return LauncherAtom.TaskSwitcherContainer.OrientationHandler.PORTRAIT;
- }
-}
diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt
new file mode 100644
index 0000000..1883649
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt
@@ -0,0 +1,911 @@
+/*
+ * 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.quickstep.orientation
+
+import android.graphics.Matrix
+import android.graphics.Point
+import android.graphics.PointF
+import android.graphics.Rect
+import android.graphics.RectF
+import android.graphics.drawable.ShapeDrawable
+import android.util.FloatProperty
+import android.util.Pair
+import android.view.Gravity
+import android.view.Surface
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import androidx.core.view.updateLayoutParams
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.Flags.enableOverviewIconMenu
+import com.android.launcher3.LauncherAnimUtils
+import com.android.launcher3.R
+import com.android.launcher3.Utilities
+import com.android.launcher3.logger.LauncherAtom
+import com.android.launcher3.touch.DefaultPagedViewHandler
+import com.android.launcher3.touch.PagedOrientationHandler.Float2DAction
+import com.android.launcher3.touch.PagedOrientationHandler.Int2DAction
+import com.android.launcher3.touch.SingleAxisSwipeDetector
+import com.android.launcher3.util.SplitConfigurationOptions
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption
+import com.android.launcher3.util.SplitConfigurationOptions.StagePosition
+import com.android.quickstep.views.IconAppChipView
+import kotlin.math.max
+import kotlin.math.min
+
+class PortraitPagedViewHandler : DefaultPagedViewHandler(), RecentsPagedOrientationHandler {
+ private val tmpMatrix = Matrix()
+ private val tmpRectF = RectF()
+
+ override fun <T> getPrimaryValue(x: T, y: T): T = x
+
+ override fun <T> getSecondaryValue(x: T, y: T): T = y
+
+ override val isLayoutNaturalToLauncher: Boolean = true
+
+ override fun adjustFloatingIconStartVelocity(velocity: PointF) {
+ // no-op
+ }
+
+ override fun fixBoundsForHomeAnimStartRect(outStartRect: RectF, deviceProfile: DeviceProfile) {
+ if (outStartRect.left > deviceProfile.widthPx) {
+ outStartRect.offsetTo(0f, outStartRect.top)
+ } else if (outStartRect.left < -deviceProfile.widthPx) {
+ outStartRect.offsetTo(0f, outStartRect.top)
+ }
+ }
+
+ override fun <T> setSecondary(target: T, action: Float2DAction<T>, param: Float) =
+ action.call(target, 0f, param)
+
+ override fun <T> set(
+ target: T,
+ action: Int2DAction<T>,
+ primaryParam: Int,
+ secondaryParam: Int,
+ ) = action.call(target, primaryParam, secondaryParam)
+
+ override fun getPrimarySize(view: View): Int = view.width
+
+ override fun getPrimarySize(rect: RectF): Float = rect.width()
+
+ override fun getStart(rect: RectF): Float = rect.left
+
+ override fun getEnd(rect: RectF): Float = rect.right
+
+ override fun rotateInsets(insets: Rect, outInsets: Rect) = outInsets.set(insets)
+
+ override fun getClearAllSidePadding(view: View, isRtl: Boolean): Int =
+ (if (isRtl) view.paddingRight else -view.paddingLeft) / 2
+
+ override fun getSecondaryDimension(view: View): Int = view.height
+
+ override val primaryViewTranslate: FloatProperty<View> = LauncherAnimUtils.VIEW_TRANSLATE_X
+
+ override val secondaryViewTranslate: FloatProperty<View> = LauncherAnimUtils.VIEW_TRANSLATE_Y
+
+ override val degreesRotated: Float = 0f
+
+ override val rotation: Int = Surface.ROTATION_0
+
+ override fun setPrimaryScale(view: View, scale: Float) {
+ view.scaleX = scale
+ }
+
+ override fun setSecondaryScale(view: View, scale: Float) {
+ view.scaleY = scale
+ }
+
+ override val secondaryTranslationDirectionFactor: Int
+ get() = -1
+
+ override fun getSplitTranslationDirectionFactor(
+ stagePosition: Int,
+ deviceProfile: DeviceProfile,
+ ): Int =
+ if (
+ deviceProfile.isLeftRightSplit &&
+ stagePosition == SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+ ) {
+ -1
+ } else {
+ 1
+ }
+
+ override fun getTaskMenuX(
+ x: Float,
+ thumbnailView: View,
+ deviceProfile: DeviceProfile,
+ taskInsetMargin: Float,
+ taskViewIcon: View,
+ ): Float =
+ if (deviceProfile.isLandscape) {
+ (x +
+ taskInsetMargin +
+ (thumbnailView.measuredWidth - thumbnailView.measuredHeight) / 2f)
+ } else {
+ x + taskInsetMargin
+ }
+
+ override fun getTaskMenuY(
+ y: Float,
+ thumbnailView: View,
+ stagePosition: Int,
+ taskMenuView: View,
+ taskInsetMargin: Float,
+ taskViewIcon: View,
+ ): Float = y + taskInsetMargin
+
+ override fun getTaskMenuWidth(
+ thumbnailView: View,
+ deviceProfile: DeviceProfile,
+ @StagePosition stagePosition: Int,
+ ): Int =
+ when {
+ enableOverviewIconMenu() -> {
+ thumbnailView.resources.getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_expanded_width
+ )
+ }
+
+ (deviceProfile.isLandscape && !deviceProfile.isTablet) -> {
+ val padding =
+ thumbnailView.resources.getDimensionPixelSize(R.dimen.task_menu_edge_padding)
+ thumbnailView.measuredHeight - (2 * padding)
+ }
+
+ else -> {
+ val padding =
+ thumbnailView.resources.getDimensionPixelSize(R.dimen.task_menu_edge_padding)
+ thumbnailView.measuredWidth - (2 * padding)
+ }
+ }
+
+ override fun getTaskMenuHeight(
+ taskInsetMargin: Float,
+ deviceProfile: DeviceProfile,
+ taskMenuX: Float,
+ taskMenuY: Float,
+ ): Int =
+ deviceProfile.heightPx -
+ deviceProfile.insets.top -
+ taskMenuY.toInt() -
+ deviceProfile.overviewActionsClaimedSpaceBelow
+
+ override fun setTaskOptionsMenuLayoutOrientation(
+ deviceProfile: DeviceProfile,
+ taskMenuLayout: LinearLayout,
+ dividerSpacing: Int,
+ dividerDrawable: ShapeDrawable,
+ ) {
+ taskMenuLayout.orientation = LinearLayout.VERTICAL
+ dividerDrawable.intrinsicHeight = dividerSpacing
+ taskMenuLayout.dividerDrawable = dividerDrawable
+ }
+
+ override fun setLayoutParamsForTaskMenuOptionItem(
+ lp: LinearLayout.LayoutParams,
+ viewGroup: LinearLayout,
+ deviceProfile: DeviceProfile,
+ ) {
+ viewGroup.orientation = LinearLayout.HORIZONTAL
+ lp.width = LinearLayout.LayoutParams.MATCH_PARENT
+ lp.height = ViewGroup.LayoutParams.WRAP_CONTENT
+ }
+
+ override fun updateDwbBannerLayout(
+ taskViewWidth: Int,
+ taskViewHeight: Int,
+ isGroupedTaskView: Boolean,
+ deviceProfile: DeviceProfile,
+ snapshotViewWidth: Int,
+ snapshotViewHeight: Int,
+ banner: View,
+ ) {
+ banner.pivotX = 0f
+ banner.pivotY = 0f
+ banner.rotation = degreesRotated
+ banner.updateLayoutParams<FrameLayout.LayoutParams> {
+ if (isGroupedTaskView) {
+ gravity =
+ Gravity.BOTTOM or
+ (if (deviceProfile.isLeftRightSplit) Gravity.START
+ else Gravity.CENTER_HORIZONTAL)
+ width = snapshotViewWidth
+ } else {
+ width = ViewGroup.LayoutParams.MATCH_PARENT
+ gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
+ }
+ }
+ }
+
+ override fun getDwbBannerTranslations(
+ taskViewWidth: Int,
+ taskViewHeight: Int,
+ splitBounds: SplitConfigurationOptions.SplitBounds?,
+ deviceProfile: DeviceProfile,
+ thumbnailViews: Array<View>,
+ desiredTaskId: Int,
+ banner: View,
+ ): Pair<Float, Float> {
+ var translationX = 0f
+ var translationY = 0f
+ if (splitBounds != null) {
+ if (deviceProfile.isLeftRightSplit) {
+ if (desiredTaskId == splitBounds.rightBottomTaskId) {
+ val leftTopTaskPercent = splitBounds.leftTopTaskPercent
+ val dividerThicknessPercent = splitBounds.dividerPercent
+ translationX =
+ ((taskViewWidth * leftTopTaskPercent) +
+ (taskViewWidth * dividerThicknessPercent))
+ }
+ } else {
+ if (desiredTaskId == splitBounds.leftTopTaskId) {
+ val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
+ val bottomRightTaskPlusDividerPercent =
+ (splitBounds.rightBottomTaskPercent + splitBounds.dividerPercent)
+ translationY =
+ -((taskViewHeight - snapshotParams.topMargin) *
+ bottomRightTaskPlusDividerPercent)
+ }
+ }
+ }
+ return Pair(translationX, translationY)
+ }
+
+ /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
+
+ override val upDownSwipeDirection: SingleAxisSwipeDetector.Direction =
+ SingleAxisSwipeDetector.VERTICAL
+
+ // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
+ override fun getUpDirection(isRtl: Boolean): Int = SingleAxisSwipeDetector.DIRECTION_POSITIVE
+
+ // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
+ override fun getDownDirection(isRtl: Boolean): Int = SingleAxisSwipeDetector.DIRECTION_NEGATIVE
+
+ // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
+ override fun isGoingUp(displacement: Float, isRtl: Boolean): Boolean = displacement < 0
+
+ // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
+ override fun getTaskDragDisplacementFactor(isRtl: Boolean): Int = 1
+
+ override fun getTaskDismissVerticalDirection(): Int = -1
+
+ override fun getTaskDismissLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int =
+ taskThumbnailBounds.bottom
+
+ override fun getTaskLaunchLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int =
+ secondaryDimension - taskThumbnailBounds.bottom
+
+ /* -------------------- */
+
+ override fun getDistanceToBottomOfRect(dp: DeviceProfile, rect: Rect): Int =
+ dp.heightPx - rect.bottom
+
+ override fun getSplitPositionOptions(dp: DeviceProfile): List<SplitPositionOption> =
+ when {
+ dp.isTablet -> {
+ Utilities.getSplitPositionOptions(dp)
+ }
+
+ dp.isSeascape -> {
+ listOf(
+ SplitPositionOption(
+ R.drawable.ic_split_horizontal,
+ R.string.recent_task_option_split_screen,
+ SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT,
+ SplitConfigurationOptions.STAGE_TYPE_MAIN,
+ )
+ )
+ }
+
+ dp.isLeftRightSplit -> {
+ listOf(
+ SplitPositionOption(
+ R.drawable.ic_split_horizontal,
+ R.string.recent_task_option_split_screen,
+ SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
+ SplitConfigurationOptions.STAGE_TYPE_MAIN,
+ )
+ )
+ }
+
+ else -> {
+ // Only add top option
+ listOf(
+ SplitPositionOption(
+ R.drawable.ic_split_vertical,
+ R.string.recent_task_option_split_screen,
+ SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
+ SplitConfigurationOptions.STAGE_TYPE_MAIN,
+ )
+ )
+ }
+ }
+
+ override fun getInitialSplitPlaceholderBounds(
+ placeholderHeight: Int,
+ placeholderInset: Int,
+ dp: DeviceProfile,
+ @StagePosition stagePosition: Int,
+ out: Rect,
+ ) {
+ val screenWidth = dp.widthPx
+ val screenHeight = dp.heightPx
+ val pinToRight = stagePosition == SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+ val insetSizeAdjustment = getPlaceholderSizeAdjustment(dp, pinToRight)
+
+ out.set(0, 0, screenWidth, placeholderHeight + insetSizeAdjustment)
+ if (!dp.isLeftRightSplit) {
+ // portrait, phone or tablet - spans width of screen, nothing else to do
+ out.inset(placeholderInset, 0)
+
+ // Adjust the top to account for content off screen. This will help to animate the view
+ // in with rounded corners.
+ val totalHeight =
+ (1.0f * screenHeight / 2 * (screenWidth - 2 * placeholderInset) / screenWidth)
+ .toInt()
+ out.top -= (totalHeight - placeholderHeight)
+ return
+ }
+
+ // Now we rotate the portrait rect depending on what side we want pinned
+ val postRotateScale = screenHeight.toFloat() / screenWidth
+ tmpMatrix.reset()
+ tmpMatrix.postRotate(if (pinToRight) 90f else 270f)
+ tmpMatrix.postTranslate(
+ (if (pinToRight) screenWidth else 0).toFloat(),
+ (if (pinToRight) 0 else screenWidth).toFloat(),
+ )
+ // The placeholder height stays constant after rotation, so we don't change width scale
+ tmpMatrix.postScale(1f, postRotateScale)
+
+ tmpRectF.set(out)
+ tmpMatrix.mapRect(tmpRectF)
+ tmpRectF.inset(0f, placeholderInset.toFloat())
+ tmpRectF.roundOut(out)
+
+ // Adjust the top to account for content off screen. This will help to animate the view in
+ // with rounded corners.
+ val totalWidth =
+ (1.0f * screenWidth / 2 * (screenHeight - 2 * placeholderInset) / screenHeight).toInt()
+ val width = out.width()
+ if (pinToRight) {
+ out.right += totalWidth - width
+ } else {
+ out.left -= totalWidth - width
+ }
+ }
+
+ override fun updateSplitIconParams(
+ out: View,
+ onScreenRectCenterX: Float,
+ onScreenRectCenterY: Float,
+ fullscreenScaleX: Float,
+ fullscreenScaleY: Float,
+ drawableWidth: Int,
+ drawableHeight: Int,
+ dp: DeviceProfile,
+ @StagePosition stagePosition: Int,
+ ) {
+ val pinToRight = stagePosition == SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+ val insetAdjustment = getPlaceholderSizeAdjustment(dp, pinToRight) / 2f
+ if (!dp.isLeftRightSplit) {
+ out.x = (onScreenRectCenterX / fullscreenScaleX - 1.0f * drawableWidth / 2)
+ out.y =
+ ((onScreenRectCenterY + insetAdjustment) / fullscreenScaleY -
+ 1.0f * drawableHeight / 2)
+ } else {
+ if (pinToRight) {
+ out.x =
+ ((onScreenRectCenterX - insetAdjustment) / fullscreenScaleX -
+ 1.0f * drawableWidth / 2)
+ } else {
+ out.x =
+ ((onScreenRectCenterX + insetAdjustment) / fullscreenScaleX -
+ 1.0f * drawableWidth / 2)
+ }
+ out.y = (onScreenRectCenterY / fullscreenScaleY - 1.0f * drawableHeight / 2)
+ }
+ }
+
+ /**
+ * The split placeholder comes with a default inset to buffer the icon from the top of the
+ * screen. But if the device already has a large inset (from cutouts etc), use that instead.
+ */
+ private fun getPlaceholderSizeAdjustment(dp: DeviceProfile, pinToRight: Boolean): Int {
+ val insetThickness =
+ if (!dp.isLandscape) {
+ dp.insets.top
+ } else {
+ if (pinToRight) dp.insets.right else dp.insets.left
+ }
+ return max((insetThickness - dp.splitPlaceholderInset).toDouble(), 0.0).toInt()
+ }
+
+ override fun setSplitInstructionsParams(
+ out: View,
+ dp: DeviceProfile,
+ splitInstructionsHeight: Int,
+ splitInstructionsWidth: Int,
+ ) {
+ out.pivotX = 0f
+ out.pivotY = splitInstructionsHeight.toFloat()
+ out.rotation = degreesRotated
+ val distanceToEdge =
+ if (dp.isPhone) {
+ if (dp.isLandscape) {
+ out.resources.getDimensionPixelSize(
+ R.dimen.split_instructions_bottom_margin_phone_landscape
+ )
+ } else {
+ out.resources.getDimensionPixelSize(
+ R.dimen.split_instructions_bottom_margin_phone_portrait
+ )
+ }
+ } else {
+ dp.overviewActionsClaimedSpaceBelow
+ }
+
+ // Center the view in case of unbalanced insets on left or right of screen
+ val insetCorrectionX = (dp.insets.right - dp.insets.left) / 2
+ // Adjust for any insets on the bottom edge
+ val insetCorrectionY = dp.insets.bottom
+ out.translationX = insetCorrectionX.toFloat()
+ out.translationY = (-distanceToEdge + insetCorrectionY).toFloat()
+ val lp = out.layoutParams as FrameLayout.LayoutParams
+ lp.gravity = Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
+ out.layoutParams = lp
+ }
+
+ override fun getFinalSplitPlaceholderBounds(
+ splitDividerSize: Int,
+ dp: DeviceProfile,
+ @StagePosition stagePosition: Int,
+ out1: Rect,
+ out2: Rect,
+ ) {
+ val screenHeight = dp.heightPx
+ val screenWidth = dp.widthPx
+ out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize)
+ out2.set(0, screenHeight / 2 + splitDividerSize, screenWidth, screenHeight)
+ if (!dp.isLeftRightSplit) {
+ // Portrait - the window bounds are always top and bottom half
+ return
+ }
+
+ // Now we rotate the portrait rect depending on what side we want pinned
+ val pinToRight = stagePosition == SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+ val postRotateScale = screenHeight.toFloat() / screenWidth
+
+ tmpMatrix.reset()
+ tmpMatrix.postRotate(if (pinToRight) 90f else 270f)
+ tmpMatrix.postTranslate(
+ (if (pinToRight) screenHeight else 0).toFloat(),
+ (if (pinToRight) 0 else screenWidth).toFloat(),
+ )
+ tmpMatrix.postScale(1 / postRotateScale, postRotateScale)
+
+ tmpRectF.set(out1)
+ tmpMatrix.mapRect(tmpRectF)
+ tmpRectF.roundOut(out1)
+
+ tmpRectF.set(out2)
+ tmpMatrix.mapRect(tmpRectF)
+ tmpRectF.roundOut(out2)
+ }
+
+ override fun setSplitTaskSwipeRect(
+ dp: DeviceProfile,
+ outRect: Rect,
+ splitInfo: SplitConfigurationOptions.SplitBounds,
+ desiredStagePosition: Int,
+ ) {
+ val topLeftTaskPercent = splitInfo.leftTopTaskPercent
+ val dividerBarPercent = splitInfo.dividerPercent
+
+ val taskbarHeight = if (dp.isTransientTaskbar) 0 else dp.taskbarHeight
+ val scale = outRect.height().toFloat() / (dp.availableHeightPx - taskbarHeight)
+ val topTaskHeight = dp.availableHeightPx * topLeftTaskPercent
+ val scaledTopTaskHeight = topTaskHeight * scale
+ val dividerHeight = dp.availableHeightPx * dividerBarPercent
+ val scaledDividerHeight = dividerHeight * scale
+
+ if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
+ if (dp.isLeftRightSplit) {
+ outRect.right = outRect.left + Math.round(outRect.width() * topLeftTaskPercent)
+ } else {
+ outRect.bottom = Math.round(outRect.top + scaledTopTaskHeight)
+ }
+ } else {
+ if (dp.isLeftRightSplit) {
+ outRect.left +=
+ Math.round(outRect.width() * (topLeftTaskPercent + dividerBarPercent))
+ } else {
+ outRect.top += Math.round(scaledTopTaskHeight + scaledDividerHeight)
+ }
+ }
+ }
+
+ /**
+ * @param inSplitSelection Whether user currently has a task from this task group staged for
+ * split screen. If true, we have custom translations/scaling in place for the remaining
+ * snapshot, so we'll skip setting translation/scale here.
+ */
+ override fun measureGroupedTaskViewThumbnailBounds(
+ primarySnapshot: View,
+ secondarySnapshot: View,
+ parentWidth: Int,
+ parentHeight: Int,
+ splitBoundsConfig: SplitConfigurationOptions.SplitBounds,
+ dp: DeviceProfile,
+ isRtl: Boolean,
+ inSplitSelection: Boolean,
+ ) {
+ val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
+
+ val primaryParams = primarySnapshot.layoutParams as FrameLayout.LayoutParams
+ val secondaryParams = secondarySnapshot.layoutParams as FrameLayout.LayoutParams
+
+ // Reset margins that aren't used in this method, but are used in other
+ // `RecentsPagedOrientationHandler` variants.
+ secondaryParams.topMargin = 0
+ primaryParams.topMargin = spaceAboveSnapshot
+
+ val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
+ val dividerScale = splitBoundsConfig.dividerPercent
+ val taskViewSizes =
+ getGroupedTaskViewSizes(dp, splitBoundsConfig, parentWidth, parentHeight)
+ if (!inSplitSelection) {
+ // Reset translations that aren't used in this method, but are used in other
+ // `RecentsPagedOrientationHandler` variants.
+ primarySnapshot.translationY = 0f
+
+ if (dp.isLeftRightSplit) {
+ val scaledDividerBar = Math.round(parentWidth * dividerScale)
+ if (isRtl) {
+ val translationX = taskViewSizes.second.x + scaledDividerBar
+ primarySnapshot.translationX = -translationX.toFloat()
+ secondarySnapshot.translationX = 0f
+ } else {
+ val translationX = taskViewSizes.first.x + scaledDividerBar
+ secondarySnapshot.translationX = translationX.toFloat()
+ primarySnapshot.translationX = 0f
+ }
+ secondarySnapshot.translationY = spaceAboveSnapshot.toFloat()
+ } else {
+ val finalDividerHeight = Math.round(totalThumbnailHeight * dividerScale).toFloat()
+ val translationY = taskViewSizes.first.y + spaceAboveSnapshot + finalDividerHeight
+ secondarySnapshot.translationY = translationY
+
+ // Reset unused translations.
+ secondarySnapshot.translationX = 0f
+ primarySnapshot.translationX = 0f
+ }
+ }
+
+ primarySnapshot.measure(
+ View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.x, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.y, View.MeasureSpec.EXACTLY),
+ )
+ secondarySnapshot.measure(
+ View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.x, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.y, View.MeasureSpec.EXACTLY),
+ )
+ }
+
+ override fun getGroupedTaskViewSizes(
+ dp: DeviceProfile,
+ splitBoundsConfig: SplitConfigurationOptions.SplitBounds,
+ parentWidth: Int,
+ parentHeight: Int,
+ ): Pair<Point, Point> {
+ val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
+ val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
+ val dividerScale = splitBoundsConfig.dividerPercent
+ val taskPercent = splitBoundsConfig.leftTopTaskPercent
+
+ val firstTaskViewSize = Point()
+ val secondTaskViewSize = Point()
+
+ if (dp.isLeftRightSplit) {
+ val scaledDividerBar = Math.round(parentWidth * dividerScale)
+ firstTaskViewSize.x = Math.round(parentWidth * taskPercent)
+ firstTaskViewSize.y = totalThumbnailHeight
+
+ secondTaskViewSize.x = parentWidth - firstTaskViewSize.x - scaledDividerBar
+ secondTaskViewSize.y = totalThumbnailHeight
+ } else {
+ val taskbarHeight = if (dp.isTransientTaskbar) 0 else dp.taskbarHeight
+ val scale = totalThumbnailHeight.toFloat() / (dp.availableHeightPx - taskbarHeight)
+ val topTaskHeight = dp.availableHeightPx * taskPercent
+ val finalDividerHeight = Math.round(totalThumbnailHeight * dividerScale).toFloat()
+ val scaledTopTaskHeight = topTaskHeight * scale
+ firstTaskViewSize.x = parentWidth
+ firstTaskViewSize.y = Math.round(scaledTopTaskHeight)
+
+ secondTaskViewSize.x = parentWidth
+ secondTaskViewSize.y =
+ Math.round((totalThumbnailHeight - firstTaskViewSize.y - finalDividerHeight))
+ }
+
+ return Pair(firstTaskViewSize, secondTaskViewSize)
+ }
+
+ override fun setTaskIconParams(
+ iconParams: FrameLayout.LayoutParams,
+ taskIconMargin: Int,
+ taskIconHeight: Int,
+ thumbnailTopMargin: Int,
+ isRtl: Boolean,
+ ) {
+ iconParams.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
+ // Reset margins, since they may have been set on rotation
+ iconParams.rightMargin = 0
+ iconParams.leftMargin = iconParams.rightMargin
+ iconParams.bottomMargin = 0
+ iconParams.topMargin = iconParams.bottomMargin
+ }
+
+ override fun setIconAppChipChildrenParams(
+ iconParams: FrameLayout.LayoutParams,
+ chipChildMarginStart: Int,
+ ) {
+ iconParams.marginStart = chipChildMarginStart
+ iconParams.gravity = Gravity.START or Gravity.CENTER_VERTICAL
+ iconParams.topMargin = 0
+ }
+
+ override fun setIconAppChipMenuParams(
+ iconAppChipView: IconAppChipView,
+ iconMenuParams: FrameLayout.LayoutParams,
+ iconMenuMargin: Int,
+ thumbnailTopMargin: Int,
+ ) {
+ iconMenuParams.gravity = Gravity.TOP or Gravity.START
+ iconMenuParams.marginStart = iconMenuMargin
+ iconMenuParams.topMargin = thumbnailTopMargin
+ iconMenuParams.bottomMargin = 0
+ iconMenuParams.marginEnd = 0
+
+ iconAppChipView.pivotX = 0f
+ iconAppChipView.pivotY = 0f
+ iconAppChipView.setSplitTranslationY(0f)
+ iconAppChipView.rotation = degreesRotated
+ }
+
+ /**
+ * @param inSplitSelection Whether user currently has a task from this task group staged for
+ * split screen. If true, we have custom translations in place for the remaining icon, so
+ * we'll skip setting translations here.
+ */
+ override fun setSplitIconParams(
+ primaryIconView: View,
+ secondaryIconView: View,
+ taskIconHeight: Int,
+ primarySnapshotWidth: Int,
+ primarySnapshotHeight: Int,
+ groupedTaskViewHeight: Int,
+ groupedTaskViewWidth: Int,
+ isRtl: Boolean,
+ deviceProfile: DeviceProfile,
+ splitConfig: SplitConfigurationOptions.SplitBounds,
+ inSplitSelection: Boolean,
+ oneIconHiddenDueToSmallWidth: Boolean,
+ ) {
+ val primaryIconParams = primaryIconView.layoutParams as FrameLayout.LayoutParams
+ val secondaryIconParams =
+ if (enableOverviewIconMenu()) secondaryIconView.layoutParams as FrameLayout.LayoutParams
+ else FrameLayout.LayoutParams(primaryIconParams)
+
+ if (enableOverviewIconMenu()) {
+ val primaryAppChipView = primaryIconView as IconAppChipView
+ val secondaryAppChipView = secondaryIconView as IconAppChipView
+ primaryIconParams.gravity = Gravity.TOP or Gravity.START
+ secondaryIconParams.gravity = Gravity.TOP or Gravity.START
+ secondaryIconParams.topMargin = primaryIconParams.topMargin
+ secondaryIconParams.marginStart = primaryIconParams.marginStart
+ if (!inSplitSelection) {
+ if (deviceProfile.isLeftRightSplit) {
+ if (isRtl) {
+ val secondarySnapshotWidth = groupedTaskViewWidth - primarySnapshotWidth
+ primaryAppChipView.setSplitTranslationX(-secondarySnapshotWidth.toFloat())
+ } else {
+ secondaryAppChipView.setSplitTranslationX(primarySnapshotWidth.toFloat())
+ }
+ } else {
+ primaryAppChipView.setSplitTranslationX(0f)
+ secondaryAppChipView.setSplitTranslationX(0f)
+ val dividerThickness =
+ min(
+ splitConfig.visualDividerBounds.width().toDouble(),
+ splitConfig.visualDividerBounds.height().toDouble(),
+ )
+ .toInt()
+ secondaryAppChipView.setSplitTranslationY(
+ (primarySnapshotHeight +
+ (if (deviceProfile.isTablet) 0 else dividerThickness))
+ .toFloat()
+ )
+ }
+ }
+ } else if (deviceProfile.isLeftRightSplit) {
+ // We calculate the "midpoint" of the thumbnail area, and place the icons there.
+ // This is the place where the thumbnail area splits by default, in a near-50/50 split.
+ // It is usually not exactly 50/50, due to insets/screen cutouts.
+ val fullscreenInsetThickness =
+ if (deviceProfile.isSeascape) deviceProfile.insets.right
+ else deviceProfile.insets.left
+ val fullscreenMidpointFromBottom =
+ ((deviceProfile.widthPx - fullscreenInsetThickness) / 2)
+ val midpointFromEndPct = fullscreenMidpointFromBottom.toFloat() / deviceProfile.widthPx
+ val insetPct = fullscreenInsetThickness.toFloat() / deviceProfile.widthPx
+ val spaceAboveSnapshots = 0
+ val overviewThumbnailAreaThickness = groupedTaskViewWidth - spaceAboveSnapshots
+ val bottomToMidpointOffset =
+ (overviewThumbnailAreaThickness * midpointFromEndPct).toInt()
+ val insetOffset = (overviewThumbnailAreaThickness * insetPct).toInt()
+
+ if (deviceProfile.isSeascape) {
+ primaryIconParams.gravity =
+ Gravity.TOP or (if (isRtl) Gravity.END else Gravity.START)
+ secondaryIconParams.gravity =
+ Gravity.TOP or (if (isRtl) Gravity.END else Gravity.START)
+ if (!inSplitSelection) {
+ if (splitConfig.initiatedFromSeascape) {
+ if (oneIconHiddenDueToSmallWidth) {
+ // Center both icons
+ val centerX = bottomToMidpointOffset - (taskIconHeight / 2f)
+ primaryIconView.translationX = centerX
+ secondaryIconView.translationX = centerX
+ } else {
+ // the task on the right (secondary) is slightly larger
+ primaryIconView.translationX =
+ (bottomToMidpointOffset - taskIconHeight).toFloat()
+ secondaryIconView.translationX = bottomToMidpointOffset.toFloat()
+ }
+ } else {
+ if (oneIconHiddenDueToSmallWidth) {
+ // Center both icons
+ val centerX =
+ bottomToMidpointOffset + insetOffset - (taskIconHeight / 2f)
+ primaryIconView.translationX = centerX
+ secondaryIconView.translationX = centerX
+ } else {
+ // the task on the left (primary) is slightly larger
+ primaryIconView.translationX =
+ (bottomToMidpointOffset + insetOffset - taskIconHeight).toFloat()
+ secondaryIconView.translationX =
+ (bottomToMidpointOffset + insetOffset).toFloat()
+ }
+ }
+ }
+ } else {
+ primaryIconParams.gravity =
+ Gravity.TOP or (if (isRtl) Gravity.START else Gravity.END)
+ secondaryIconParams.gravity =
+ Gravity.TOP or (if (isRtl) Gravity.START else Gravity.END)
+ if (!inSplitSelection) {
+ if (!splitConfig.initiatedFromSeascape) {
+ if (oneIconHiddenDueToSmallWidth) {
+ // Center both icons
+ val centerX = -bottomToMidpointOffset + (taskIconHeight / 2f)
+ primaryIconView.translationX = centerX
+ secondaryIconView.translationX = centerX
+ } else {
+ // the task on the left (primary) is slightly larger
+ primaryIconView.translationX = -bottomToMidpointOffset.toFloat()
+ secondaryIconView.translationX =
+ (-bottomToMidpointOffset + taskIconHeight).toFloat()
+ }
+ } else {
+ if (oneIconHiddenDueToSmallWidth) {
+ // Center both icons
+ val centerX =
+ -bottomToMidpointOffset - insetOffset + (taskIconHeight / 2f)
+ primaryIconView.translationX = centerX
+ secondaryIconView.translationX = centerX
+ } else {
+ // the task on the right (secondary) is slightly larger
+ primaryIconView.translationX =
+ (-bottomToMidpointOffset - insetOffset).toFloat()
+ secondaryIconView.translationX =
+ (-bottomToMidpointOffset - insetOffset + taskIconHeight).toFloat()
+ }
+ }
+ }
+ }
+ } else {
+ primaryIconParams.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
+ secondaryIconParams.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
+ if (!inSplitSelection) {
+ if (oneIconHiddenDueToSmallWidth) {
+ // Center both icons
+ primaryIconView.translationX = 0f
+ secondaryIconView.translationX = 0f
+ } else {
+ // shifts icon half a width left (height is used here since icons are square)
+ primaryIconView.translationX = -(taskIconHeight / 2f)
+ secondaryIconView.translationX = taskIconHeight / 2f
+ }
+ }
+ }
+ if (!enableOverviewIconMenu() && !inSplitSelection) {
+ primaryIconView.translationY = 0f
+ secondaryIconView.translationY = 0f
+ }
+
+ primaryIconView.layoutParams = primaryIconParams
+ secondaryIconView.layoutParams = secondaryIconParams
+ }
+
+ override fun getDefaultSplitPosition(deviceProfile: DeviceProfile): Int {
+ check(deviceProfile.isTablet) { "Default position available only for large screens" }
+ return if (deviceProfile.isLeftRightSplit) {
+ SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+ } else {
+ SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT
+ }
+ }
+
+ override fun <T> getSplitSelectTaskOffset(
+ primary: FloatProperty<T>,
+ secondary: FloatProperty<T>,
+ deviceProfile: DeviceProfile,
+ ): Pair<FloatProperty<T>, FloatProperty<T>> =
+ if (deviceProfile.isLeftRightSplit) { // or seascape
+ Pair(primary, secondary)
+ } else {
+ Pair(secondary, primary)
+ }
+
+ override fun getFloatingTaskOffscreenTranslationTarget(
+ floatingTask: View,
+ onScreenRect: RectF,
+ @StagePosition stagePosition: Int,
+ dp: DeviceProfile,
+ ): Float {
+ if (dp.isLeftRightSplit) {
+ val currentTranslationX = floatingTask.translationX
+ return if (stagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT)
+ currentTranslationX - onScreenRect.width()
+ else currentTranslationX + onScreenRect.width()
+ } else {
+ val currentTranslationY = floatingTask.translationY
+ return currentTranslationY - onScreenRect.height()
+ }
+ }
+
+ override fun setFloatingTaskPrimaryTranslation(
+ floatingTask: View,
+ translation: Float,
+ dp: DeviceProfile,
+ ) {
+ if (dp.isLeftRightSplit) {
+ floatingTask.translationX = translation
+ } else {
+ floatingTask.translationY = translation
+ }
+ }
+
+ override fun getFloatingTaskPrimaryTranslation(floatingTask: View, dp: DeviceProfile): Float =
+ if (dp.isLeftRightSplit) floatingTask.translationX else floatingTask.translationY
+
+ override fun getHandlerTypeForLogging(): LauncherAtom.TaskSwitcherContainer.OrientationHandler =
+ LauncherAtom.TaskSwitcherContainer.OrientationHandler.PORTRAIT
+}
diff --git a/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt b/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
index 78f9a0a..a7bc93b 100644
--- a/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
@@ -82,13 +82,13 @@
fun getSplitTranslationDirectionFactor(
@StagePosition stagePosition: Int,
- deviceProfile: DeviceProfile
+ deviceProfile: DeviceProfile,
): Int
fun <T> getSplitSelectTaskOffset(
primary: FloatProperty<T>,
secondary: FloatProperty<T>,
- deviceProfile: DeviceProfile
+ deviceProfile: DeviceProfile,
): Pair<FloatProperty<T>, FloatProperty<T>>
fun getDistanceToBottomOfRect(dp: DeviceProfile, rect: Rect): Int
@@ -101,7 +101,7 @@
placeholderInset: Int,
dp: DeviceProfile,
@StagePosition stagePosition: Int,
- out: Rect
+ out: Rect,
)
/**
@@ -128,7 +128,7 @@
drawableWidth: Int,
drawableHeight: Int,
dp: DeviceProfile,
- @StagePosition stagePosition: Int
+ @StagePosition stagePosition: Int,
)
/**
@@ -143,7 +143,7 @@
out: View,
dp: DeviceProfile,
splitInstructionsHeight: Int,
- splitInstructionsWidth: Int
+ splitInstructionsWidth: Int,
)
/**
@@ -159,7 +159,7 @@
dp: DeviceProfile,
@StagePosition stagePosition: Int,
out1: Rect,
- out2: Rect
+ out2: Rect,
)
fun getDefaultSplitPosition(deviceProfile: DeviceProfile): Int
@@ -174,7 +174,7 @@
dp: DeviceProfile,
outRect: Rect,
splitInfo: SplitConfigurationOptions.SplitBounds,
- @StagePosition desiredStagePosition: Int
+ @StagePosition desiredStagePosition: Int,
)
fun measureGroupedTaskViewThumbnailBounds(
@@ -185,7 +185,7 @@
splitBoundsConfig: SplitConfigurationOptions.SplitBounds,
dp: DeviceProfile,
isRtl: Boolean,
- inSplitSelection: Boolean
+ inSplitSelection: Boolean,
)
/**
@@ -198,7 +198,7 @@
dp: DeviceProfile,
splitBoundsConfig: SplitConfigurationOptions.SplitBounds,
parentWidth: Int,
- parentHeight: Int
+ parentHeight: Int,
): Pair<Point, Point>
// Overview TaskMenuView methods
@@ -208,7 +208,7 @@
taskIconMargin: Int,
taskIconHeight: Int,
thumbnailTopMargin: Int,
- isRtl: Boolean
+ isRtl: Boolean,
)
/**
@@ -216,14 +216,14 @@
*/
fun setIconAppChipChildrenParams(
iconParams: FrameLayout.LayoutParams,
- chipChildMarginStart: Int
+ chipChildMarginStart: Int,
)
fun setIconAppChipMenuParams(
iconAppChipView: IconAppChipView,
iconMenuParams: FrameLayout.LayoutParams,
iconMenuMargin: Int,
- thumbnailTopMargin: Int
+ thumbnailTopMargin: Int,
)
fun setSplitIconParams(
@@ -237,7 +237,8 @@
isRtl: Boolean,
deviceProfile: DeviceProfile,
splitConfig: SplitConfigurationOptions.SplitBounds,
- inSplitSelection: Boolean
+ inSplitSelection: Boolean,
+ oneIconHiddenDueToSmallWidth: Boolean,
)
/*
@@ -251,7 +252,7 @@
thumbnailView: View,
deviceProfile: DeviceProfile,
taskInsetMargin: Float,
- taskViewIcon: View
+ taskViewIcon: View,
): Float
fun getTaskMenuY(
@@ -260,20 +261,20 @@
stagePosition: Int,
taskMenuView: View,
taskInsetMargin: Float,
- taskViewIcon: View
+ taskViewIcon: View,
): Float
fun getTaskMenuWidth(
thumbnailView: View,
deviceProfile: DeviceProfile,
- @StagePosition stagePosition: Int
+ @StagePosition stagePosition: Int,
): Int
fun getTaskMenuHeight(
taskInsetMargin: Float,
deviceProfile: DeviceProfile,
taskMenuX: Float,
- taskMenuY: Float
+ taskMenuY: Float,
): Int
/**
@@ -284,7 +285,7 @@
deviceProfile: DeviceProfile,
taskMenuLayout: LinearLayout,
dividerSpacing: Int,
- dividerDrawable: ShapeDrawable
+ dividerDrawable: ShapeDrawable,
)
/**
@@ -294,7 +295,7 @@
fun setLayoutParamsForTaskMenuOptionItem(
lp: LinearLayout.LayoutParams,
viewGroup: LinearLayout,
- deviceProfile: DeviceProfile
+ deviceProfile: DeviceProfile,
)
/** Layout a Digital Wellbeing Banner on its parent. TaskView. */
@@ -305,7 +306,7 @@
deviceProfile: DeviceProfile,
snapshotViewWidth: Int,
snapshotViewHeight: Int,
- banner: View
+ banner: View,
)
/**
@@ -321,7 +322,7 @@
deviceProfile: DeviceProfile,
thumbnailViews: Array<View>,
desiredTaskId: Int,
- banner: View
+ banner: View,
): Pair<Float, Float>
// The following are only used by TaskViewTouchHandler.
@@ -341,6 +342,15 @@
/** @return Either 1 or -1, a factor to multiply by so the animation goes the correct way. */
fun getTaskDragDisplacementFactor(isRtl: Boolean): Int
+ /** @return Either 1 or -1, the direction sign towards task dismiss. */
+ fun getTaskDismissVerticalDirection(): Int
+
+ /** @return the length to drag a task off screen for dismiss. */
+ fun getTaskDismissLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int
+
+ /** @return the length to drag a task to full screen for launch. */
+ fun getTaskLaunchLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int
+
/**
* Maps the velocity from the coordinate plane of the foreground app to that of Launcher's
* (which now will always be portrait)
@@ -370,7 +380,7 @@
floatingTask: View,
onScreenRect: RectF,
@StagePosition stagePosition: Int,
- dp: DeviceProfile
+ dp: DeviceProfile,
): Float
/**
diff --git a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
index 9bfa2bf..1f9f752 100644
--- a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
@@ -53,7 +53,7 @@
override fun getSplitTranslationDirectionFactor(
stagePosition: Int,
- deviceProfile: DeviceProfile
+ deviceProfile: DeviceProfile,
): Int = if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) -1 else 1
override fun getRecentsRtlSetting(resources: Resources): Boolean = Utilities.isRtl(resources)
@@ -70,7 +70,7 @@
thumbnailView: View,
deviceProfile: DeviceProfile,
taskInsetMargin: Float,
- taskViewIcon: View
+ taskViewIcon: View,
): Float = x + taskInsetMargin
override fun getTaskMenuY(
@@ -79,7 +79,7 @@
stagePosition: Int,
taskMenuView: View,
taskInsetMargin: Float,
- taskViewIcon: View
+ taskViewIcon: View,
): Float {
if (Flags.enableOverviewIconMenu()) {
return y
@@ -97,14 +97,14 @@
taskInsetMargin: Float,
deviceProfile: DeviceProfile,
taskMenuX: Float,
- taskMenuY: Float
+ taskMenuY: Float,
): Int = (deviceProfile.availableWidthPx - taskInsetMargin - taskMenuX).toInt()
override fun setSplitTaskSwipeRect(
dp: DeviceProfile,
outRect: Rect,
splitInfo: SplitBounds,
- desiredStagePosition: Int
+ desiredStagePosition: Int,
) {
val topLeftTaskPercent = splitInfo.leftTopTaskPercent
val dividerBarPercent = splitInfo.dividerPercent
@@ -126,7 +126,7 @@
deviceProfile: DeviceProfile,
snapshotViewWidth: Int,
snapshotViewHeight: Int,
- banner: View
+ banner: View,
) {
banner.pivotX = 0f
banner.pivotY = 0f
@@ -149,7 +149,7 @@
deviceProfile: DeviceProfile,
thumbnailViews: Array<View>,
desiredTaskId: Int,
- banner: View
+ banner: View,
): Pair<Float, Float> {
val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
val translationX: Float = (taskViewWidth - banner.height).toFloat()
@@ -181,7 +181,7 @@
R.drawable.ic_split_horizontal,
R.string.recent_task_option_split_screen,
STAGE_POSITION_BOTTOM_OR_RIGHT,
- STAGE_TYPE_MAIN
+ STAGE_TYPE_MAIN,
)
)
@@ -189,7 +189,7 @@
out: View,
dp: DeviceProfile,
splitInstructionsHeight: Int,
- splitInstructionsWidth: Int
+ splitInstructionsWidth: Int,
) {
out.pivotX = 0f
out.pivotY = splitInstructionsHeight.toFloat()
@@ -217,7 +217,7 @@
taskIconMargin: Int,
taskIconHeight: Int,
thumbnailTopMargin: Int,
- isRtl: Boolean
+ isRtl: Boolean,
) {
iconParams.gravity =
if (isRtl) {
@@ -230,7 +230,7 @@
override fun setIconAppChipChildrenParams(
iconParams: FrameLayout.LayoutParams,
- chipChildMarginStart: Int
+ chipChildMarginStart: Int,
) {
iconParams.setMargins(0, 0, 0, 0)
iconParams.marginStart = chipChildMarginStart
@@ -241,7 +241,7 @@
iconAppChipView: IconAppChipView,
iconMenuParams: FrameLayout.LayoutParams,
iconMenuMargin: Int,
- thumbnailTopMargin: Int
+ thumbnailTopMargin: Int,
) {
val isRtl = iconAppChipView.layoutDirection == View.LAYOUT_DIRECTION_RTL
val iconCenter = iconAppChipView.getHeight() / 2f
@@ -268,7 +268,7 @@
/**
* @param inSplitSelection Whether user currently has a task from this task group staged for
- * split screen. Currently this state is not reachable in fake seascape.
+ * split screen. Currently this state is not reachable in fake seascape.
*/
override fun measureGroupedTaskViewThumbnailBounds(
primarySnapshot: View,
@@ -278,7 +278,7 @@
splitBoundsConfig: SplitBounds,
dp: DeviceProfile,
isRtl: Boolean,
- inSplitSelection: Boolean
+ inSplitSelection: Boolean,
) {
val primaryParams = primarySnapshot.layoutParams as FrameLayout.LayoutParams
val secondaryParams = secondarySnapshot.layoutParams as FrameLayout.LayoutParams
@@ -300,11 +300,11 @@
(taskViewSecond.y + spaceAboveSnapshot + dividerBar).toFloat()
primarySnapshot.measure(
MeasureSpec.makeMeasureSpec(taskViewFirst.x, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(taskViewFirst.y, MeasureSpec.EXACTLY)
+ MeasureSpec.makeMeasureSpec(taskViewFirst.y, MeasureSpec.EXACTLY),
)
secondarySnapshot.measure(
MeasureSpec.makeMeasureSpec(taskViewSecond.x, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(taskViewSecond.y, MeasureSpec.EXACTLY)
+ MeasureSpec.makeMeasureSpec(taskViewSecond.y, MeasureSpec.EXACTLY),
)
}
@@ -312,7 +312,7 @@
dp: DeviceProfile,
splitBoundsConfig: SplitBounds,
parentWidth: Int,
- parentHeight: Int
+ parentHeight: Int,
): Pair<Point, Point> {
// Measure and layout the thumbnails bottom up, since the primary is on the visual left
// (portrait bottom) and secondary is on the right (portrait top)
@@ -344,6 +344,14 @@
override fun getTaskDragDisplacementFactor(isRtl: Boolean): Int = if (isRtl) -1 else 1
+ override fun getTaskDismissVerticalDirection(): Int = -1
+
+ override fun getTaskDismissLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int =
+ taskThumbnailBounds.right
+
+ override fun getTaskLaunchLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int =
+ secondaryDimension - taskThumbnailBounds.right
+
/* -------------------- */
override fun getSplitIconsPosition(
@@ -353,17 +361,18 @@
isRtl: Boolean,
overviewTaskMarginPx: Int,
dividerSize: Int,
+ oneIconHiddenDueToSmallWidth: Boolean,
): SplitIconPositions {
return if (Flags.enableOverviewIconMenu()) {
if (isRtl) {
SplitIconPositions(
topLeftY = totalThumbnailHeight - primarySnapshotHeight,
- bottomRightY = 0
+ bottomRightY = 0,
)
} else {
SplitIconPositions(
topLeftY = 0,
- bottomRightY = -(primarySnapshotHeight + dividerSize)
+ bottomRightY = -(primarySnapshotHeight + dividerSize),
)
}
} else {
@@ -372,10 +381,16 @@
// from the bottom to the almost-center of the screen using the bottom margin.
// The primary snapshot is placed at the bottom, thus we translate the icons using
// the size of the primary snapshot minus the icon size for the top-left icon.
- SplitIconPositions(
- topLeftY = primarySnapshotHeight - taskIconHeight,
- bottomRightY = primarySnapshotHeight + dividerSize
- )
+ if (oneIconHiddenDueToSmallWidth) {
+ // Center both icons
+ val centerY = primarySnapshotHeight + ((dividerSize - taskIconHeight) / 2)
+ SplitIconPositions(topLeftY = centerY, bottomRightY = centerY)
+ } else {
+ SplitIconPositions(
+ topLeftY = primarySnapshotHeight - taskIconHeight,
+ bottomRightY = primarySnapshotHeight + dividerSize,
+ )
+ }
}
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index a9dbbf2..96a5733 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -143,7 +143,7 @@
} else {
// Initiating split from overview on fullscreen task TaskView
val taskView = taskViewSupplier.get()
- taskView.taskContainers.first().let {
+ taskView.firstTaskContainer!!.let {
val drawable = getDrawable(it.iconView, splitSelectSource)
return SplitAnimInitProps(
it.snapshotView,
diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 12ca257..a2856a6 100644
--- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -81,8 +81,10 @@
public StaggeredWorkspaceAnim(QuickstepLauncher launcher, float velocity,
boolean animateOverviewScrim, @Nullable View ignoredView, boolean staggerWorkspace) {
+ boolean isPinnedTaskbarAndNotInDesktopMode = DisplayController.isPinnedTaskbar(launcher)
+ && !DisplayController.isInDesktopMode(launcher);
mTaskbarDurationInMs = QuickstepTransitionManager.getTaskbarToHomeDuration(
- DisplayController.isPinnedTaskbar(launcher));
+ isPinnedTaskbarAndNotInDesktopMode);
prepareToAnimate(launcher, animateOverviewScrim);
mIgnoredView = ignoredView;
diff --git a/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt b/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
index 9943770..b83acf0 100644
--- a/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
+++ b/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
@@ -20,10 +20,14 @@
import android.graphics.Canvas
import android.graphics.Rect
import android.util.AttributeSet
+import android.util.FloatProperty
import android.widget.ImageButton
import com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X
import com.android.launcher3.R
+import com.android.launcher3.util.KFloatProperty
+import com.android.launcher3.util.MultiPropertyDelegate
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,28 +38,17 @@
class AddDesktopButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
ImageButton(context, attrs) {
- private enum class TranslationX {
- GRID,
- OFFSET,
- }
+ private val addDeskButtonAlpha = MultiValueAlpha(this, Alpha.entries.size)
+ var contentAlpha by MultiPropertyDelegate(addDeskButtonAlpha, Alpha.CONTENT)
+ var visibilityAlpha by MultiPropertyDelegate(addDeskButtonAlpha, Alpha.VISIBILITY)
private val multiTranslationX =
MultiPropertyFactory(this, VIEW_TRANSLATE_X, TranslationX.entries.size) { a: Float, b: Float
->
a + b
}
-
- var gridTranslationX
- get() = multiTranslationX[TranslationX.GRID.ordinal].value
- set(value) {
- multiTranslationX[TranslationX.GRID.ordinal].value = value
- }
-
- var offsetTranslationX
- get() = multiTranslationX[TranslationX.OFFSET.ordinal].value
- set(value) {
- multiTranslationX[TranslationX.OFFSET.ordinal].value = value
- }
+ var gridTranslationX by MultiPropertyDelegate(multiTranslationX, TranslationX.GRID)
+ var offsetTranslationX by MultiPropertyDelegate(multiTranslationX, TranslationX.OFFSET)
private val focusBorderAnimator: BorderAnimator =
createSimpleBorderAnimator(
@@ -91,7 +84,7 @@
}
}
- protected fun getScrollAdjustment(showAsGrid: Boolean): Int =
+ fun getScrollAdjustment(showAsGrid: Boolean): Int =
if (showAsGrid) gridTranslationX.toInt() else 0
private fun getBorderBounds(bounds: Rect) {
@@ -105,4 +98,20 @@
focusBorderAnimator.drawBorder(canvas)
super.draw(canvas)
}
+
+ companion object {
+ private enum class Alpha {
+ CONTENT,
+ VISIBILITY,
+ }
+
+ private enum class TranslationX {
+ GRID,
+ OFFSET,
+ }
+
+ @JvmField
+ val VISIBILITY_ALPHA: FloatProperty<AddDesktopButton> =
+ KFloatProperty(AddDesktopButton::visibilityAlpha)
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
deleted file mode 100644
index 2426697..0000000
--- a/quickstep/src/com/android/quickstep/views/ClearAllButton.java
+++ /dev/null
@@ -1,333 +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.quickstep.util.BorderAnimator.DEFAULT_BORDER_COLOR;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.util.FloatProperty;
-import android.widget.Button;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.Flags;
-import com.android.launcher3.R;
-import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
-import com.android.quickstep.util.BorderAnimator;
-
-import kotlin.Unit;
-
-public class ClearAllButton extends Button {
-
- public static final FloatProperty<ClearAllButton> VISIBILITY_ALPHA =
- new FloatProperty<ClearAllButton>("visibilityAlpha") {
- @Override
- public Float get(ClearAllButton view) {
- return view.mVisibilityAlpha;
- }
-
- @Override
- public void setValue(ClearAllButton view, float v) {
- view.setVisibilityAlpha(v);
- }
- };
-
- public static final FloatProperty<ClearAllButton> DISMISS_ALPHA =
- new FloatProperty<ClearAllButton>("dismissAlpha") {
- @Override
- public Float get(ClearAllButton view) {
- return view.mDismissAlpha;
- }
-
- @Override
- public void setValue(ClearAllButton view, float v) {
- view.setDismissAlpha(v);
- }
- };
-
- private float mScrollAlpha = 1;
- private float mContentAlpha = 1;
- private float mVisibilityAlpha = 1;
- private float mDismissAlpha = 1;
- private float mFullscreenProgress = 1;
- private float mGridProgress = 1;
-
- private boolean mIsRtl;
- private float mNormalTranslationPrimary;
- private float mFullscreenTranslationPrimary;
- private float mGridTranslationPrimary;
- private float mTaskAlignmentTranslationY;
- private float mGridScrollOffset;
- private float mScrollOffsetPrimary;
-
- private int mSidePadding;
- private int mOutlinePadding;
- private boolean mBorderEnabled;
- @Nullable
- private final BorderAnimator mFocusBorderAnimator;
-
- public ClearAllButton(Context context, AttributeSet attrs) {
- super(context, attrs);
- mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
-
- if (Flags.enableFocusOutline()) {
- TypedArray styledAttrs = context.obtainStyledAttributes(attrs,
- R.styleable.ClearAllButton);
- Resources resources = getResources();
- mOutlinePadding = resources.getDimensionPixelSize(
- R.dimen.recents_clear_all_outline_padding);
- mFocusBorderAnimator =
- BorderAnimator.createSimpleBorderAnimator(
- /* borderRadiusPx= */ resources.getDimensionPixelSize(
- R.dimen.recents_clear_all_outline_radius),
- /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
- R.dimen.keyboard_quick_switch_border_width),
- /* boundsBuilder= */ this::updateBorderBounds,
- /* targetView= */ this,
- /* borderColor= */ styledAttrs.getColor(
- R.styleable.ClearAllButton_focusBorderColor,
- DEFAULT_BORDER_COLOR));
- styledAttrs.recycle();
- } else {
- mFocusBorderAnimator = null;
- }
- }
-
- private Unit updateBorderBounds(@NonNull Rect bounds) {
- bounds.set(0, 0, getWidth(), getHeight());
- // Make the value negative to form a padding between button and outline
- bounds.inset(-mOutlinePadding, -mOutlinePadding);
- return Unit.INSTANCE;
- }
-
- @Override
- public void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
- super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
- if (mFocusBorderAnimator != null && mBorderEnabled) {
- mFocusBorderAnimator.setBorderVisibility(gainFocus, /* animated= */ true);
- }
- }
-
- /**
- * Enable or disable showing border on focus change
- */
- public void setBorderEnabled(boolean enabled) {
- if (mBorderEnabled == enabled) {
- return;
- }
-
- mBorderEnabled = enabled;
- if (mFocusBorderAnimator != null) {
- mFocusBorderAnimator.setBorderVisibility(/* visible= */
- enabled && isFocused(), /* animated= */true);
- }
- }
-
- @Override
- public void draw(Canvas canvas) {
- if (mFocusBorderAnimator != null) {
- mFocusBorderAnimator.drawBorder(canvas);
- }
- super.draw(canvas);
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- RecentsPagedOrientationHandler orientationHandler =
- getRecentsView().getPagedOrientationHandler();
- mSidePadding = orientationHandler.getClearAllSidePadding(getRecentsView(), mIsRtl);
- }
-
- private RecentsView getRecentsView() {
- return (RecentsView) getParent();
- }
-
- @Override
- public void onRtlPropertiesChanged(int layoutDirection) {
- super.onRtlPropertiesChanged(layoutDirection);
- mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
- }
-
- @Override
- public boolean hasOverlappingRendering() {
- return false;
- }
-
- public float getScrollAlpha() {
- return mScrollAlpha;
- }
-
- public void setContentAlpha(float alpha) {
- if (mContentAlpha != alpha) {
- mContentAlpha = alpha;
- updateAlpha();
- }
- }
-
- public void setVisibilityAlpha(float alpha) {
- if (mVisibilityAlpha != alpha) {
- mVisibilityAlpha = alpha;
- updateAlpha();
- }
- }
-
- public void setDismissAlpha(float alpha) {
- if (mDismissAlpha != alpha) {
- mDismissAlpha = alpha;
- updateAlpha();
- }
- }
-
- public void onRecentsViewScroll(int scroll, boolean gridEnabled) {
- RecentsView recentsView = getRecentsView();
- if (recentsView == null) {
- return;
- }
-
- RecentsPagedOrientationHandler orientationHandler =
- recentsView.getPagedOrientationHandler();
- float orientationSize = orientationHandler.getPrimaryValue(getWidth(), getHeight());
- if (orientationSize == 0) {
- return;
- }
-
- int clearAllScroll = recentsView.getClearAllScroll();
- int adjustedScrollFromEdge = Math.abs(scroll - clearAllScroll);
- float shift = Math.min(adjustedScrollFromEdge, orientationSize);
- mNormalTranslationPrimary = mIsRtl ? -shift : shift;
- if (!gridEnabled) {
- mNormalTranslationPrimary += mSidePadding;
- }
- applyPrimaryTranslation();
- applySecondaryTranslation();
- float clearAllSpacing =
- recentsView.getPageSpacing() + recentsView.getClearAllExtraPageSpacing();
- clearAllSpacing = mIsRtl ? -clearAllSpacing : clearAllSpacing;
- mScrollAlpha = Math.max((clearAllScroll + clearAllSpacing - scroll) / clearAllSpacing, 0);
- updateAlpha();
- }
-
- private void updateAlpha() {
- final float alpha = mScrollAlpha * mContentAlpha * mVisibilityAlpha * mDismissAlpha;
- setAlpha(alpha);
- setClickable(Math.min(alpha, 1) == 1);
- }
-
- public void setFullscreenTranslationPrimary(float fullscreenTranslationPrimary) {
- mFullscreenTranslationPrimary = fullscreenTranslationPrimary;
- applyPrimaryTranslation();
- }
-
- /**
- * Sets `mTaskAlignmentTranslationY` to the given `value`. In order to put the button at the
- * middle in the secondary coordinate.
- */
- public void setTaskAlignmentTranslationY(float value) {
- mTaskAlignmentTranslationY = value;
- applySecondaryTranslation();
- }
-
- public void setGridTranslationPrimary(float gridTranslationPrimary) {
- mGridTranslationPrimary = gridTranslationPrimary;
- applyPrimaryTranslation();
- }
-
- public void setGridScrollOffset(float gridScrollOffset) {
- mGridScrollOffset = gridScrollOffset;
- }
-
- public void setScrollOffsetPrimary(float scrollOffsetPrimary) {
- mScrollOffsetPrimary = scrollOffsetPrimary;
- }
-
- public float getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
- float scrollAdjustment = 0;
- if (fullscreenEnabled) {
- scrollAdjustment += mFullscreenTranslationPrimary;
- }
- if (gridEnabled) {
- scrollAdjustment += mGridTranslationPrimary + mGridScrollOffset;
- }
- scrollAdjustment += mScrollOffsetPrimary;
- return scrollAdjustment;
- }
-
- public float getOffsetAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
- return getScrollAdjustment(fullscreenEnabled, gridEnabled);
- }
-
- /**
- * Adjust translation when this TaskView is about to be shown fullscreen.
- *
- * @param progress: 0 = no translation; 1 = translate according to TaskVIew translations.
- */
- public void setFullscreenProgress(float progress) {
- mFullscreenProgress = progress;
- applyPrimaryTranslation();
- }
-
- /**
- * Moves ClearAllButton between carousel and 2 row grid.
- *
- * @param gridProgress 0 = carousel; 1 = 2 row grid.
- */
- public void setGridProgress(float gridProgress) {
- mGridProgress = gridProgress;
- applyPrimaryTranslation();
- }
-
- private void applyPrimaryTranslation() {
- RecentsView recentsView = getRecentsView();
- if (recentsView == null) {
- return;
- }
-
- RecentsPagedOrientationHandler orientationHandler =
- recentsView.getPagedOrientationHandler();
- orientationHandler.getPrimaryViewTranslate().set(this,
- orientationHandler.getPrimaryValue(0f, mTaskAlignmentTranslationY)
- + mNormalTranslationPrimary + getFullscreenTrans(
- mFullscreenTranslationPrimary) + getGridTrans(mGridTranslationPrimary));
- }
-
- private void applySecondaryTranslation() {
- RecentsView recentsView = getRecentsView();
- if (recentsView == null) {
- return;
- }
-
- RecentsPagedOrientationHandler orientationHandler =
- recentsView.getPagedOrientationHandler();
- orientationHandler.getSecondaryViewTranslate().set(this,
- orientationHandler.getSecondaryValue(0f, mTaskAlignmentTranslationY));
- }
-
- private float getFullscreenTrans(float endTranslation) {
- return mFullscreenProgress > 0 ? endTranslation : 0;
- }
-
- private float getGridTrans(float endTranslation) {
- return mGridProgress > 0 ? endTranslation : 0;
- }
-}
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.kt b/quickstep/src/com/android/quickstep/views/ClearAllButton.kt
new file mode 100644
index 0000000..69c85ee
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.kt
@@ -0,0 +1,249 @@
+/*
+ * 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.quickstep.views
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Rect
+import android.util.AttributeSet
+import android.util.FloatProperty
+import android.widget.Button
+import com.android.launcher3.Flags.enableFocusOutline
+import com.android.launcher3.R
+import com.android.launcher3.util.KFloatProperty
+import com.android.launcher3.util.MultiPropertyDelegate
+import com.android.launcher3.util.MultiValueAlpha
+import com.android.quickstep.util.BorderAnimator
+import com.android.quickstep.util.BorderAnimator.Companion.createSimpleBorderAnimator
+import kotlin.math.abs
+import kotlin.math.min
+
+class ClearAllButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
+ Button(context, attrs) {
+
+ private val clearAllButtonAlpha =
+ object : MultiValueAlpha(this, Alpha.entries.size) {
+ override fun apply(value: Float) {
+ super.apply(value)
+ isClickable = value >= 1f
+ }
+ }
+ var scrollAlpha by MultiPropertyDelegate(clearAllButtonAlpha, Alpha.SCROLL)
+ var contentAlpha by MultiPropertyDelegate(clearAllButtonAlpha, Alpha.CONTENT)
+ var visibilityAlpha by MultiPropertyDelegate(clearAllButtonAlpha, Alpha.VISIBILITY)
+ var dismissAlpha by MultiPropertyDelegate(clearAllButtonAlpha, Alpha.DISMISS)
+
+ var fullscreenProgress = 1f
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ applyPrimaryTranslation()
+ }
+
+ /**
+ * Moves ClearAllButton between carousel and 2 row grid.
+ *
+ * 0 = carousel; 1 = 2 row grid.
+ */
+ var gridProgress = 1f
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ applyPrimaryTranslation()
+ }
+
+ private var normalTranslationPrimary = 0f
+ var fullscreenTranslationPrimary = 0f
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ applyPrimaryTranslation()
+ }
+
+ var gridTranslationPrimary = 0f
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ applyPrimaryTranslation()
+ }
+
+ /** Used to put the button at the middle in the secondary coordinate. */
+ var taskAlignmentTranslationY = 0f
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ applySecondaryTranslation()
+ }
+
+ var gridScrollOffset = 0f
+ var scrollOffsetPrimary = 0f
+
+ private var sidePadding = 0
+ var borderEnabled = false
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ focusBorderAnimator?.setBorderVisibility(visible = field && isFocused, animated = true)
+ }
+
+ private val focusBorderAnimator: BorderAnimator? =
+ if (enableFocusOutline())
+ createSimpleBorderAnimator(
+ context.resources.getDimensionPixelSize(R.dimen.recents_clear_all_outline_radius),
+ context.resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_border_width),
+ this::getBorderBounds,
+ this,
+ context
+ .obtainStyledAttributes(attrs, R.styleable.ClearAllButton)
+ .getColor(
+ R.styleable.ClearAllButton_focusBorderColor,
+ BorderAnimator.DEFAULT_BORDER_COLOR,
+ ),
+ )
+ else null
+
+ private fun getBorderBounds(bounds: Rect) {
+ bounds.set(0, 0, width, height)
+ val outlinePadding =
+ context.resources.getDimensionPixelSize(R.dimen.recents_clear_all_outline_padding)
+ // Make the value negative to form a padding between button and outline
+ bounds.inset(-outlinePadding, -outlinePadding)
+ }
+
+ public override fun onFocusChanged(
+ gainFocus: Boolean,
+ direction: Int,
+ previouslyFocusedRect: Rect?,
+ ) {
+ super.onFocusChanged(gainFocus, direction, previouslyFocusedRect)
+ if (borderEnabled) {
+ focusBorderAnimator?.setBorderVisibility(gainFocus, /* animated= */ true)
+ }
+ }
+
+ override fun draw(canvas: Canvas) {
+ focusBorderAnimator?.drawBorder(canvas)
+ super.draw(canvas)
+ }
+
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+ super.onLayout(changed, left, top, right, bottom)
+ sidePadding =
+ recentsView?.let { it.pagedOrientationHandler?.getClearAllSidePadding(it, isLayoutRtl) }
+ ?: 0
+ }
+
+ private val recentsView: RecentsView<*, *>?
+ get() = parent as? RecentsView<*, *>?
+
+ override fun hasOverlappingRendering() = false
+
+ fun onRecentsViewScroll(scroll: Int, gridEnabled: Boolean) {
+ val recentsView = recentsView ?: return
+
+ val orientationSize =
+ recentsView.pagedOrientationHandler.getPrimaryValue(width, height).toFloat()
+ if (orientationSize == 0f) {
+ return
+ }
+
+ val clearAllScroll = recentsView.clearAllScroll
+ val adjustedScrollFromEdge = abs((scroll - clearAllScroll)).toFloat()
+ val shift = min(adjustedScrollFromEdge, orientationSize)
+ normalTranslationPrimary = if (isLayoutRtl) -shift else shift
+ if (!gridEnabled) {
+ normalTranslationPrimary += sidePadding.toFloat()
+ }
+ applyPrimaryTranslation()
+ applySecondaryTranslation()
+ var clearAllSpacing = recentsView.pageSpacing + recentsView.clearAllExtraPageSpacing
+ clearAllSpacing = if (isLayoutRtl) -clearAllSpacing else clearAllSpacing
+ scrollAlpha =
+ ((clearAllScroll + clearAllSpacing - scroll) / clearAllSpacing.toFloat()).coerceAtLeast(
+ 0f
+ )
+ }
+
+ fun getScrollAdjustment(fullscreenEnabled: Boolean, gridEnabled: Boolean): Float {
+ var scrollAdjustment = 0f
+ if (fullscreenEnabled) {
+ scrollAdjustment += fullscreenTranslationPrimary
+ }
+ if (gridEnabled) {
+ scrollAdjustment += gridTranslationPrimary + gridScrollOffset
+ }
+ scrollAdjustment += scrollOffsetPrimary
+ return scrollAdjustment
+ }
+
+ fun getOffsetAdjustment(fullscreenEnabled: Boolean, gridEnabled: Boolean) =
+ getScrollAdjustment(fullscreenEnabled, gridEnabled)
+
+ private fun applyPrimaryTranslation() {
+ val recentsView = recentsView ?: return
+ val orientationHandler = recentsView.pagedOrientationHandler
+ orientationHandler.primaryViewTranslate.set(
+ this,
+ (orientationHandler.getPrimaryValue(0f, taskAlignmentTranslationY) +
+ normalTranslationPrimary +
+ getFullscreenTrans(fullscreenTranslationPrimary) +
+ getGridTrans(gridTranslationPrimary)),
+ )
+ }
+
+ private fun applySecondaryTranslation() {
+ val recentsView = recentsView ?: return
+ val orientationHandler = recentsView.pagedOrientationHandler
+ orientationHandler.secondaryViewTranslate.set(
+ this,
+ orientationHandler.getSecondaryValue(0f, taskAlignmentTranslationY),
+ )
+ }
+
+ private fun getFullscreenTrans(endTranslation: Float) =
+ if (fullscreenProgress > 0) endTranslation else 0f
+
+ private fun getGridTrans(endTranslation: Float) = if (gridProgress > 0) endTranslation else 0f
+
+ companion object {
+ private enum class Alpha {
+ SCROLL,
+ CONTENT,
+ VISIBILITY,
+ DISMISS,
+ }
+
+ @JvmField
+ val VISIBILITY_ALPHA: FloatProperty<ClearAllButton> =
+ KFloatProperty(ClearAllButton::visibilityAlpha)
+
+ @JvmField
+ val DISMISS_ALPHA: FloatProperty<ClearAllButton> =
+ KFloatProperty(ClearAllButton::dismissAlpha)
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
index 25011d7..a8eee0a 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
@@ -187,14 +187,30 @@
val taskIconHeight = deviceProfile.overviewTaskIconSizePx
val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
val inSplitSelection = getThisTaskCurrentlyInSplitSelection() != INVALID_TASK_ID
+ var oneIconHiddenDueToSmallWidth = false
if (enableFlexibleTwoAppSplit()) {
- val topLeftTaskPercent = splitBoundsConfig.leftTopTaskPercent
- val bottomRightTaskPercent = splitBoundsConfig.rightBottomTaskPercent
- val hideTopLeftIcon = topLeftTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON
- val hideBottomRightIcon = bottomRightTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON
- leftTopTaskContainer.iconView.setFlexSplitAlpha(if (hideTopLeftIcon) 0f else 1f)
- rightBottomTaskContainer.iconView.setFlexSplitAlpha(if (hideBottomRightIcon) 0f else 1f)
+ // Update values for both icons' setFlexSplitAlpha. Mainly, we want to hide an icon if
+ // its app tile is too small. But we also have to set the alphas back if we go to
+ // split selection.
+ val hideLeftTopIcon: Boolean
+ val hideRightBottomIcon: Boolean
+ if (inSplitSelection) {
+ hideLeftTopIcon =
+ getThisTaskCurrentlyInSplitSelection() == splitBoundsConfig.leftTopTaskId
+ hideRightBottomIcon =
+ getThisTaskCurrentlyInSplitSelection() == splitBoundsConfig.rightBottomTaskId
+ } else {
+ hideLeftTopIcon = splitBoundsConfig.leftTopTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON
+ hideRightBottomIcon =
+ splitBoundsConfig.rightBottomTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON
+ if (hideLeftTopIcon || hideRightBottomIcon) {
+ oneIconHiddenDueToSmallWidth = true
+ }
+ }
+
+ leftTopTaskContainer.iconView.setFlexSplitAlpha(if (hideLeftTopIcon) 0f else 1f)
+ rightBottomTaskContainer.iconView.setFlexSplitAlpha(if (hideRightBottomIcon) 0f else 1f)
}
if (enableOverviewIconMenu()) {
@@ -217,6 +233,7 @@
deviceProfile,
splitBoundsConfig,
inSplitSelection,
+ oneIconHiddenDueToSmallWidth,
)
} else {
pagedOrientationHandler.setSplitIconParams(
@@ -231,6 +248,7 @@
deviceProfile,
splitBoundsConfig,
inSplitSelection,
+ oneIconHiddenDueToSmallWidth,
)
}
}
diff --git a/quickstep/src/com/android/quickstep/views/IconAppChipView.kt b/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
index 85d14cc..8d53552 100644
--- a/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
+++ b/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
@@ -296,7 +296,7 @@
fun getMenuTranslationY(): MultiPropertyFactory<View>.MultiProperty =
viewTranslationY[INDEX_MENU_TRANSLATION]
- internal fun revealAnim(isRevealing: Boolean) {
+ internal fun revealAnim(isRevealing: Boolean, animated: Boolean = true) {
cancelInProgressAnimations()
val collapsedBackgroundBounds = getCollapsedBackgroundLtrBounds()
val expandedBackgroundBounds = getExpandedBackgroundLtrBounds()
@@ -392,6 +392,7 @@
animator!!.setDuration(MENU_BACKGROUND_HIDE_DURATION.toLong())
}
+ if (!animated) animator!!.duration = 0
animator!!.interpolator = Interpolators.EMPHASIZED
animator!!.start()
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index d17dfb8..44bf82c 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -74,6 +74,7 @@
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_SPLIT_SELECT_ACTIVE;
import static com.android.quickstep.views.RecentsViewUtils.DESK_EXPLODE_PROGRESS;
+import static com.android.quickstep.views.TaskView.SPLIT_ALPHA;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -1462,7 +1463,8 @@
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- finishRecentsAnimation(false /* toRecents */, null);
+ finishRecentsAnimation(false /* toRecents */, true /*shouldPip*/,
+ allAppsAreTranslucent(apps), null);
}
});
} else {
@@ -1473,6 +1475,18 @@
anim.start();
}
+ private boolean allAppsAreTranslucent(RemoteAnimationTarget[] apps) {
+ if (apps == null) {
+ return false;
+ }
+ for (int i = apps.length - 1; i >= 0; --i) {
+ if (!apps[i].isTranslucent) {
+ return false;
+ }
+ }
+ return true;
+ }
+
public boolean isTaskViewVisible(TaskView tv) {
if (showAsGrid()) {
int screenStart = getPagedOrientationHandler().getPrimaryScroll(this);
@@ -4762,9 +4776,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);
@@ -5342,8 +5355,7 @@
clampToProgress(timings.getDesktopTaskScaleInterpolator(), 0f,
timings.getDesktopFadeSplitAnimationEndOffset()));
}
- builder.addFloat(taskView.getSplitAlphaProperty(),
- MULTI_PROPERTY_VALUE, 1f, 0f,
+ builder.addFloat(taskView, SPLIT_ALPHA, 1f, 0f,
clampToProgress(deskTopFadeInterPolator, 0f,
timings.getDesktopFadeSplitAnimationEndOffset()));
}
@@ -6329,6 +6341,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/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
index 67318ac..31ae890 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
@@ -28,6 +28,7 @@
import com.android.quickstep.views.RecentsView.RUNNING_TASK_ATTACH_ALPHA
import com.android.systemui.shared.recents.model.ThumbnailData
import java.util.function.BiConsumer
+import kotlin.reflect.KMutableProperty1
/**
* Helper class for [RecentsView]. This util class contains refactored and extracted functions from
@@ -305,16 +306,19 @@
}
companion object {
- @JvmField
- val DESK_EXPLODE_PROGRESS =
- object : FloatProperty<RecentsView<*, *>>("deskExplodeProgress") {
- override fun setValue(recentsView: RecentsView<*, *>, value: Float) {
- recentsView.mUtils.deskExplodeProgress = value
- }
+ class RecentsViewFloatProperty(
+ private val utilsProperty: KMutableProperty1<RecentsViewUtils, Float>
+ ) : FloatProperty<RecentsView<*, *>>(utilsProperty.name) {
+ override fun get(recentsView: RecentsView<*, *>): Float =
+ utilsProperty.get(recentsView.mUtils)
- override fun get(recentsView: RecentsView<*, *>) =
- recentsView.mUtils.deskExplodeProgress
+ override fun setValue(recentsView: RecentsView<*, *>, value: Float) {
+ utilsProperty.set(recentsView.mUtils, value)
}
+ }
+
+ @JvmField
+ val DESK_EXPLODE_PROGRESS = RecentsViewFloatProperty(RecentsViewUtils::deskExplodeProgress)
val TEMP_RECT = Rect()
}
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..4777f4f
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.kt
@@ -0,0 +1,456 @@
+/*
+ * 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?
+ animateOpenOrClosed(true)
+ return true
+ }
+ }
+ return false
+ }
+
+ override fun handleClose(animate: Boolean) {
+ animateOpenOrClosed(true, animated = false)
+ }
+
+ 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 animateOpenOrClosed(closing: Boolean, animated: Boolean = true) {
+ 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 =
+ when {
+ animated && closing -> REVEAL_CLOSE_DURATION
+ animated && !closing -> REVEAL_OPEN_DURATION
+ else -> 0L
+ }
+ 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..276318c 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -61,8 +61,9 @@
import com.android.launcher3.testing.shared.TestProtocol
import com.android.launcher3.util.CancellableTask
import com.android.launcher3.util.Executors
+import com.android.launcher3.util.KFloatProperty
+import com.android.launcher3.util.MultiPropertyDelegate
import com.android.launcher3.util.MultiPropertyFactory
-import com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE
import com.android.launcher3.util.MultiValueAlpha
import com.android.launcher3.util.RunnableList
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
@@ -199,7 +200,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).
@@ -301,7 +302,7 @@
var sysUiStatusNavFlags: Int = 0
get() =
if (enableRefactorTaskThumbnail()) field
- else taskContainers.first().thumbnailViewDeprecated.sysUiStatusNavFlags
+ else firstTaskContainer?.thumbnailViewDeprecated?.sysUiStatusNavFlags ?: 0
private set
// Various animation progress variables.
@@ -440,28 +441,10 @@
applyTranslationX()
}
- private val taskViewAlpha = MultiValueAlpha(this, NUM_ALPHA_CHANNELS)
-
- protected var stableAlpha
- set(value) {
- taskViewAlpha.get(ALPHA_INDEX_STABLE).value = value
- }
- get() = taskViewAlpha.get(ALPHA_INDEX_STABLE).value
-
- var attachAlpha
- set(value) {
- taskViewAlpha.get(ALPHA_INDEX_ATTACH).value = value
- }
- get() = taskViewAlpha.get(ALPHA_INDEX_ATTACH).value
-
- var splitAlpha
- set(value) {
- splitAlphaProperty.value = value
- }
- get() = splitAlphaProperty.value
-
- val splitAlphaProperty: MultiPropertyFactory<View>.MultiProperty
- get() = taskViewAlpha.get(ALPHA_INDEX_SPLIT)
+ private val taskViewAlpha = MultiValueAlpha(this, Alpha.entries.size)
+ protected var stableAlpha by MultiPropertyDelegate(taskViewAlpha, Alpha.STABLE)
+ var attachAlpha by MultiPropertyDelegate(taskViewAlpha, Alpha.ATTACH)
+ var splitAlpha by MultiPropertyDelegate(taskViewAlpha, Alpha.SPLIT)
protected var shouldShowScreenshot = false
get() = !isRunningTask || field
@@ -515,16 +498,16 @@
MultiPropertyFactory(
this,
SETTLED_PROGRESS,
- SETTLED_PROGRESS_INDEX_COUNT,
+ SettledProgress.entries.size,
{ x: Float, y: Float -> x * y },
1f,
)
- 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)
+ private var settledProgressFullscreen by
+ MultiPropertyDelegate(settledProgressPropertyFactory, SettledProgress.Fullscreen)
+ private var settledProgressGesture by
+ MultiPropertyDelegate(settledProgressPropertyFactory, SettledProgress.Gesture)
+ private var settledProgressDismiss by
+ MultiPropertyDelegate(settledProgressPropertyFactory, SettledProgress.Dismiss)
private var viewModel: TaskViewModel? = null
private val dispatcherProvider: DispatcherProvider by RecentsDependencies.inject()
@@ -536,7 +519,7 @@
* interpolator.
*/
fun getDismissIconFadeInAnimator(): ObjectAnimator =
- ObjectAnimator.ofFloat(settledProgressDismiss, MULTI_PROPERTY_VALUE, 1f).apply {
+ ObjectAnimator.ofFloat(this, SETTLED_PROGRESS_DISMISS, 1f).apply {
duration = FADE_IN_ICON_DURATION
interpolator = FADE_IN_ICON_INTERPOLATOR
}
@@ -548,8 +531,7 @@
*/
fun getDismissIconFadeOutAnimator(): ObjectAnimator =
AnimatedFloat { v ->
- settledProgressDismiss.value =
- SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR.getInterpolation(v)
+ settledProgressDismiss = SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR.getInterpolation(v)
}
.animateToValue(1f, 0f)
@@ -1476,7 +1458,8 @@
return if (enableOverviewIconMenu() && menuContainer.iconView is IconAppChipView) {
menuContainer.iconView.revealAnim(/* isRevealing= */ true)
TaskMenuView.showForTask(menuContainer) {
- menuContainer.iconView.revealAnim(/* isRevealing= */ false)
+ val isAnimated = !recentsView.isSplitSelectionActive
+ menuContainer.iconView.revealAnim(/* isRevealing= */ false, isAnimated)
if (enableHoverOfChildElementsInTaskview()) {
recentsView.setTaskBorderEnabled(true)
}
@@ -1602,7 +1585,7 @@
fun startIconFadeInOnGestureComplete() {
iconFadeInOnGestureCompleteAnimator?.cancel()
iconFadeInOnGestureCompleteAnimator =
- ObjectAnimator.ofFloat(settledProgressGesture, MULTI_PROPERTY_VALUE, 1f).apply {
+ ObjectAnimator.ofFloat(this, SETTLED_PROGRESS_GESTURE, 1f).apply {
duration = FADE_IN_ICON_DURATION
interpolator = Interpolators.LINEAR
addListener(
@@ -1618,7 +1601,7 @@
fun setIconVisibleForGesture(isVisible: Boolean) {
iconFadeInOnGestureCompleteAnimator?.cancel()
- settledProgressGesture.value = if (isVisible) 1f else 0f
+ settledProgressGesture = if (isVisible) 1f else 0f
}
/** Set a color tint on the snapshot and supporting views. */
@@ -1707,7 +1690,7 @@
it.iconView.setVisibility(if (fullscreenProgress < 1) VISIBLE else INVISIBLE)
it.overlay.setFullscreenProgress(fullscreenProgress)
}
- settledProgressFullscreen.value =
+ settledProgressFullscreen =
SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR.getInterpolation(1 - fullscreenProgress)
updateFullscreenParams()
}
@@ -1765,7 +1748,7 @@
dismissScale = 1f
translationZ = 0f
setIconVisibleForGesture(true)
- settledProgressDismiss.value = 1f
+ settledProgressDismiss = 1f
setColorTint(0f, 0)
}
@@ -1787,23 +1770,25 @@
companion object {
private const val TAG = "TaskView"
+
+ private enum class Alpha {
+ STABLE,
+ ATTACH,
+ SPLIT,
+ }
+
+ private enum class SettledProgress {
+ Fullscreen,
+ Gesture,
+ Dismiss,
+ }
+
const val FLAG_UPDATE_ICON = 1
const val FLAG_UPDATE_THUMBNAIL = FLAG_UPDATE_ICON shl 1
const val FLAG_UPDATE_CORNER_RADIUS = FLAG_UPDATE_THUMBNAIL shl 1
const val FLAG_UPDATE_ALL =
(FLAG_UPDATE_ICON or FLAG_UPDATE_THUMBNAIL or FLAG_UPDATE_CORNER_RADIUS)
- 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
- private const val ALPHA_INDEX_SPLIT = 2
-
- private const val NUM_ALPHA_CHANNELS = 3
-
/** The maximum amount that a task view can be scrimmed, dimmed or tinted. */
const val MAX_PAGE_SCRIM_ALPHA = 0.4f
const val FADE_IN_ICON_DURATION: Long = 120
@@ -1820,104 +1805,45 @@
private val SYSTEM_GESTURE_EXCLUSION_RECT = listOf(Rect())
private val SETTLED_PROGRESS: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("settleTransition") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.settledProgress = v
- }
+ KFloatProperty(TaskView::settledProgress)
- override fun get(taskView: TaskView) = taskView.settledProgress
- }
+ private val SETTLED_PROGRESS_GESTURE: FloatProperty<TaskView> =
+ KFloatProperty(TaskView::settledProgressGesture)
+
+ private val SETTLED_PROGRESS_DISMISS: FloatProperty<TaskView> =
+ KFloatProperty(TaskView::settledProgressDismiss)
private val SPLIT_SELECT_TRANSLATION_X: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("splitSelectTranslationX") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.splitSelectTranslationX = v
- }
-
- override fun get(taskView: TaskView) = taskView.splitSelectTranslationX
- }
+ KFloatProperty(TaskView::splitSelectTranslationX)
private val SPLIT_SELECT_TRANSLATION_Y: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("splitSelectTranslationY") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.splitSelectTranslationY = v
- }
-
- override fun get(taskView: TaskView) = taskView.splitSelectTranslationY
- }
+ KFloatProperty(TaskView::splitSelectTranslationY)
private val DISMISS_TRANSLATION_X: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("dismissTranslationX") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.dismissTranslationX = v
- }
-
- override fun get(taskView: TaskView) = taskView.dismissTranslationX
- }
+ KFloatProperty(TaskView::dismissTranslationX)
private val DISMISS_TRANSLATION_Y: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("dismissTranslationY") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.dismissTranslationY = v
- }
-
- override fun get(taskView: TaskView) = taskView.dismissTranslationY
- }
+ KFloatProperty(TaskView::dismissTranslationY)
private val TASK_OFFSET_TRANSLATION_X: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("taskOffsetTranslationX") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.taskOffsetTranslationX = v
- }
-
- override fun get(taskView: TaskView) = taskView.taskOffsetTranslationX
- }
+ KFloatProperty(TaskView::taskOffsetTranslationX)
private val TASK_OFFSET_TRANSLATION_Y: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("taskOffsetTranslationY") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.taskOffsetTranslationY = v
- }
-
- override fun get(taskView: TaskView) = taskView.taskOffsetTranslationY
- }
+ KFloatProperty(TaskView::taskOffsetTranslationY)
private val TASK_RESISTANCE_TRANSLATION_X: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("taskResistanceTranslationX") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.taskResistanceTranslationX = v
- }
-
- override fun get(taskView: TaskView) = taskView.taskResistanceTranslationX
- }
+ KFloatProperty(TaskView::taskResistanceTranslationX)
private val TASK_RESISTANCE_TRANSLATION_Y: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("taskResistanceTranslationY") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.taskResistanceTranslationY = v
- }
-
- override fun get(taskView: TaskView) = taskView.taskResistanceTranslationY
- }
+ KFloatProperty(TaskView::taskResistanceTranslationY)
@JvmField
val GRID_END_TRANSLATION_X: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("gridEndTranslationX") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.gridEndTranslationX = v
- }
-
- override fun get(taskView: TaskView) = taskView.gridEndTranslationX
- }
+ KFloatProperty(TaskView::gridEndTranslationX)
@JvmField
- val DISMISS_SCALE: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("dismissScale") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.dismissScale = v
- }
+ val DISMISS_SCALE: FloatProperty<TaskView> = KFloatProperty(TaskView::dismissScale)
- override fun get(taskView: TaskView) = taskView.dismissScale
- }
+ @JvmField val SPLIT_ALPHA: FloatProperty<TaskView> = KFloatProperty(TaskView::splitAlpha)
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java
index 06a939a..91f9e53 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java
@@ -41,7 +41,7 @@
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.AllModulesForTest;
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
+import com.android.launcher3.util.SandboxContext;
import com.android.launcher3.util.UserIconInfo;
import com.android.systemui.shared.system.SysUiStatsLog;
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
index adfbca5..8d20ba8 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
@@ -28,7 +28,7 @@
import com.android.launcher3.model.data.TaskViewItemInfo.Companion.createTaskViewAtom
import com.android.launcher3.pm.UserCache
import com.android.launcher3.util.AllModulesForTest
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
+import com.android.launcher3.util.SandboxContext
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.launcher3.util.TransformingTouchDelegate
import com.android.launcher3.util.UserIconInfo
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
index 2cd09cc..3761044 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
@@ -19,6 +19,7 @@
import android.animation.AnimatorTestRule
import android.content.ComponentName
import android.content.Intent
+import android.os.Process
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
@@ -27,6 +28,7 @@
import com.android.launcher3.Flags.FLAG_TASKBAR_OVERFLOW
import com.android.launcher3.R
import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.model.data.ItemInfo
import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
import com.android.launcher3.taskbar.TaskbarViewTestUtil.createHotseatItems
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
@@ -111,6 +113,7 @@
@InjectController lateinit var recentAppsController: TaskbarRecentAppsController
@InjectController lateinit var bubbleBarViewController: BubbleBarViewController
@InjectController lateinit var bubbleStashController: BubbleStashController
+ @InjectController lateinit var keyboardQuickSwitchController: KeyboardQuickSwitchController
private var desktopTaskListener: IDesktopTaskListener? = null
@@ -209,8 +212,10 @@
runOnMainSync {
val taskbarView: TaskbarView =
taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
+ val hotseatItems = createHotseatItems(maxNumberOfTaskbarIcons - initialIconCount)
+
taskbarView.updateItems(
- createHotseatItems(maxNumberOfTaskbarIcons - initialIconCount),
+ recentAppsController.updateHotseatItemInfos(hotseatItems as Array<ItemInfo?>),
recentAppsController.shownTasks,
)
}
@@ -327,16 +332,127 @@
assertThat(taskbarIconsCentered).isTrue()
}
- private fun createDesktopTask(tasksToAdd: Int) {
- val tasks =
- (0..<tasksToAdd).map {
- Task(Task.TaskKey(it, 0, Intent(), ComponentName("", ""), 0, 2000))
- }
- recentsModel.updateRecentTasks(listOf(DesktopTask(deskId = 0, tasks)))
- desktopTaskListener?.onTasksVisibilityChanged(
- context.virtualDisplay.display.displayId,
- tasksToAdd,
+ @Test
+ @TaskbarMode(PINNED)
+ fun testPressingOverflowButtonOpensKeyboardQuickSwitch() {
+ val maxNumIconViews = maxNumberOfTaskbarIcons
+ // Assume there are at least all apps and divider icon, as they would appear once running
+ // apps are added, even if not present initially.
+ val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2)
+
+ val targetOverflowSize = 5
+ val createdTasks = maxNumIconViews - initialIconCount + targetOverflowSize
+ createDesktopTask(createdTasks)
+
+ assertThat(taskbarOverflowIconIndex).isEqualTo(initialIconCount)
+ tapOverflowIcon()
+ // Keyboard quick switch view is shown only after list of recent task is asynchronously
+ // retrieved from the recents model.
+ runOnMainSync { recentsModel.resolvePendingTaskRequests() }
+
+ assertThat(getOnUiThread { keyboardQuickSwitchController.isShownFromTaskbar }).isTrue()
+ assertThat(getOnUiThread { keyboardQuickSwitchController.shownTaskIds() })
+ .containsExactlyElementsIn(0..<createdTasks)
+
+ tapOverflowIcon()
+ assertThat(keyboardQuickSwitchController.isShown).isFalse()
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testHotseatItemTasksNotShownInRecents() {
+ val maxNumIconViews = maxNumberOfTaskbarIcons
+ // Assume there are at least all apps and divider icon, as they would appear once running
+ // apps are added, even if not present initially.
+ val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2)
+ val hotseatItems = createHotseatItems(1)
+
+ val targetOverflowSize = 5
+ val createdTasks = maxNumIconViews - initialIconCount + targetOverflowSize
+ createDesktopTaskWithTasksFromPackages(
+ listOf("fake") +
+ listOf(hotseatItems[0]?.targetPackage ?: "") +
+ List(createdTasks - 2) { "fake" }
)
+
+ runOnMainSync {
+ val taskbarView: TaskbarView =
+ taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
+ taskbarView.updateItems(
+ recentAppsController.updateHotseatItemInfos(hotseatItems as Array<ItemInfo?>),
+ recentAppsController.shownTasks,
+ )
+ }
+
+ assertThat(maxNumberOfTaskbarIcons).isEqualTo(maxNumIconViews)
+ assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumIconViews)
+ assertThat(taskbarOverflowIconIndex).isEqualTo(initialIconCount + hotseatItems.size)
+ assertThat(overflowItems)
+ .containsExactlyElementsIn(listOf(0) + (2..targetOverflowSize + 1).toList())
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testHotseatItemTasksNotShownInKQS() {
+ val maxNumIconViews = maxNumberOfTaskbarIcons
+ // Assume there are at least all apps and divider icon, as they would appear once running
+ // apps are added, even if not present initially.
+ val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2)
+ val hotseatItems = createHotseatItems(1)
+
+ val targetOverflowSize = 5
+ val createdTasks = maxNumIconViews - initialIconCount + targetOverflowSize
+ createDesktopTaskWithTasksFromPackages(
+ listOf("fake") +
+ listOf(hotseatItems[0]?.targetPackage ?: "") +
+ List(createdTasks - 2) { "fake" }
+ )
+
+ runOnMainSync {
+ val taskbarView: TaskbarView =
+ taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
+ taskbarView.updateItems(
+ recentAppsController.updateHotseatItemInfos(hotseatItems as Array<ItemInfo?>),
+ recentAppsController.shownTasks,
+ )
+ }
+
+ tapOverflowIcon()
+ // Keyboard quick switch view is shown only after list of recent task is asynchronously
+ // retrieved from the recents model.
+ runOnMainSync { recentsModel.resolvePendingTaskRequests() }
+
+ assertThat(getOnUiThread { keyboardQuickSwitchController.isShownFromTaskbar }).isTrue()
+ assertThat(getOnUiThread { keyboardQuickSwitchController.shownTaskIds() })
+ .containsExactlyElementsIn(listOf(0) + (2..<createdTasks).toList())
+ }
+
+ private fun createDesktopTask(tasksToAdd: Int) {
+ createDesktopTaskWithTasksFromPackages((0..<tasksToAdd).map { "fake" })
+ }
+
+ private fun createDesktopTaskWithTasksFromPackages(packages: List<String>) {
+ val tasks =
+ packages.mapIndexed({ index, p ->
+ Task(
+ Task.TaskKey(
+ index,
+ 0,
+ Intent().apply { `package` = p },
+ ComponentName(p, ""),
+ Process.myUserHandle().identifier,
+ 2000,
+ )
+ )
+ })
+
+ recentsModel.updateRecentTasks(listOf(DesktopTask(deskId = 0, tasks)))
+ for (task in 1..tasks.size) {
+ desktopTaskListener?.onTasksVisibilityChanged(
+ context.virtualDisplay.display.displayId,
+ task,
+ )
+ }
runOnMainSync { recentsModel.resolvePendingTaskRequests() }
}
@@ -392,6 +508,14 @@
}
}
+ private fun tapOverflowIcon() {
+ runOnMainSync {
+ val overflowIcon =
+ taskbarViewController.iconViews.firstOrNull { it is TaskbarOverflowView }
+ assertThat(overflowIcon?.callOnClick()).isTrue()
+ }
+ }
+
/**
* Adds enough running apps for taskbar to enter overflow of `targetOverflowSize`, and verifies
* * max number of icons in the taskbar remains unchanged
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
index 002c988..8376bc1 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
@@ -959,6 +959,8 @@
private fun setInDesktopMode(inDesktopMode: Boolean) {
whenever(taskbarControllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar())
.thenReturn(inDesktopMode)
+ whenever(taskbarControllers.taskbarDesktopModeController.isInDesktopMode)
+ .thenReturn(inDesktopMode)
}
private fun createItemInfo(
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelHelper.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelHelper.kt
index a7bfa9a..5f7b360 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelHelper.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelHelper.kt
@@ -19,6 +19,7 @@
import com.android.quickstep.RecentsModel
import com.android.quickstep.RecentsModel.RecentTasksChangedListener
import com.android.quickstep.TaskIconCache
+import com.android.quickstep.TaskThumbnailCache
import com.android.quickstep.util.GroupTask
import java.util.function.Consumer
import org.mockito.kotlin.any
@@ -27,9 +28,11 @@
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
-/** Helper class to mock the {@link RecentsModel} object in test */
+/** Helper class to mock the [RecentsModel] object in test */
class MockedRecentsModelHelper {
private val mockIconCache: TaskIconCache = mock()
+ private val mockThumbnailCache: TaskThumbnailCache = mock()
+
var taskListId = 0
var recentTasksChangedListener: RecentTasksChangedListener? = null
var taskRequests: MutableList<(List<GroupTask>) -> Unit> = mutableListOf()
@@ -37,6 +40,8 @@
val mockRecentsModel: RecentsModel = mock {
on { iconCache } doReturn mockIconCache
+ on { thumbnailCache } doReturn mockThumbnailCache
+
on { unregisterRecentTasksChangedListener() } doAnswer { recentTasksChangedListener = null }
on { registerRecentTasksChangedListener(any<RecentTasksChangedListener>()) } doAnswer
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt
index 3cf912c..f225807 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt
@@ -20,7 +20,6 @@
import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode
import com.android.launcher3.taskbar.rules.TaskbarModeRule.TaskbarMode
import com.android.launcher3.util.DisplayController
-import com.android.launcher3.util.MainThreadInitializedObject
import com.android.launcher3.util.NavigationMode
import org.junit.rules.TestRule
import org.junit.runner.Description
@@ -31,8 +30,8 @@
/**
* Allows tests to specify which Taskbar [Mode] to run under.
*
- * [context] should match the test's target context, so that [MainThreadInitializedObject] instances
- * are properly sandboxed.
+ * [context] should match the test's target context, so that Dagger singleton instances are properly
+ * sandboxed.
*
* Annotate tests with [TaskbarMode] to set a mode. If the annotation is omitted for any tests, this
* rule is a no-op.
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..6d53e8e 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,11 +26,10 @@
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
-import com.android.launcher3.util.MainThreadInitializedObject.ObjectSandbox
import com.android.launcher3.util.SandboxApplication
import com.android.launcher3.util.SettingsCache
import com.android.launcher3.util.SettingsCacheSandbox
@@ -58,7 +57,7 @@
private val base: SandboxApplication,
val virtualDisplay: VirtualDisplay,
private val params: SandboxParams,
-) : ContextWrapper(base), ObjectSandbox by base, TestRule {
+) : ContextWrapper(base), TestRule {
val settingsCacheSandbox = SettingsCacheSandbox()
@@ -138,7 +137,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/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
index 99a34ea..b3056f5 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
@@ -55,7 +55,7 @@
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.util.AllModulesForTest;
import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
+import com.android.launcher3.util.SandboxContext;
import com.android.quickstep.DeviceConfigWrapper;
import com.android.quickstep.GestureState;
import com.android.quickstep.InputConsumer;
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt
index ea52842..0570c26 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt
@@ -62,6 +62,7 @@
isRTL,
OVERVIEW_TASK_MARGIN_PX,
DIVIDER_SIZE_PX,
+ oneIconHiddenDueToSmallWidth = false,
)
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt
index 2bc182c..3788688 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt
@@ -62,6 +62,7 @@
isRTL,
OVERVIEW_TASK_MARGIN_PX,
DIVIDER_SIZE_PX,
+ oneIconHiddenDueToSmallWidth = false,
)
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
index 9d000a4..c9d7e1d 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
@@ -94,6 +94,7 @@
whenever(mockTaskContainer.task).thenReturn(mockTask)
whenever(mockIconView.drawable).thenReturn(mockTaskViewDrawable)
whenever(mockTaskView.taskContainers).thenReturn(List(1) { mockTaskContainer })
+ whenever(mockTaskView.firstTaskContainer).thenReturn(mockTaskContainer)
whenever(splitSelectSource.drawable).thenReturn(mockSplitSourceDrawable)
whenever(splitSelectSource.view).thenReturn(mockSplitSourceView)
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/res/values/strings.xml b/res/values/strings.xml
index a02516a..56befd6 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -293,6 +293,9 @@
<!-- Description for a new page on homescreen[CHAR_LIMIT=none] -->
<string name="workspace_new_page">New home screen page</string>
+ <string name="app_running_state_description">Active</string>
+ <string name="app_minimized_state_description">Minimized</string>
+
<!-- Folder accessibility -->
<!-- The format string for when a folder is opened, speaks the dimensions -->
<string name="folder_opened">Folder opened, <xliff:g id="width" example="5">%1$d</xliff:g> by <xliff:g id="height" example="3">%2$d</xliff:g></string>
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 6277e41..84c8040 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -481,10 +481,8 @@
public static <T extends BaseActivity> T fromContext(Context context) {
if (context instanceof BaseActivity) {
return (T) context;
- } else if (context instanceof ActivityContextDelegate) {
- return (T) ((ActivityContextDelegate) context).mDelegate;
- } else if (context instanceof ContextWrapper) {
- return fromContext(((ContextWrapper) context).getBaseContext());
+ } else if (context instanceof ContextWrapper cw) {
+ return fromContext(cw.getBaseContext());
} else {
throw new IllegalArgumentException("Cannot find BaseActivity in parent tree");
}
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index bd42b2b..730ad78 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -20,6 +20,9 @@
import static android.graphics.fonts.FontStyle.FONT_WEIGHT_NORMAL;
import static android.text.Layout.Alignment.ALIGN_NORMAL;
+import static com.android.launcher3.BubbleTextView.RunningAppState.RUNNING;
+import static com.android.launcher3.BubbleTextView.RunningAppState.NOT_RUNNING;
+import static com.android.launcher3.BubbleTextView.RunningAppState.MINIMIZED;
import static com.android.launcher3.Flags.enableContrastTiles;
import static com.android.launcher3.Flags.enableCursorHoverStates;
import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
@@ -208,6 +211,9 @@
private final int mRunningAppIndicatorColor;
private final int mMinimizedAppIndicatorColor;
+ private final String mMinimizedStateDescription;
+ private final String mRunningStateDescription;
+
/**
* Various options for the running state of an app.
*/
@@ -240,6 +246,9 @@
super(context, attrs, defStyle);
mActivity = ActivityContext.lookupContext(context);
FastBitmapDrawable.setFlagHoverEnabled(enableCursorHoverStates());
+ mMinimizedStateDescription = getContext().getString(
+ R.string.app_minimized_state_description);
+ mRunningStateDescription = getContext().getString(R.string.app_running_state_description);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.BubbleTextView, defStyle, 0);
@@ -432,6 +441,19 @@
invalidate();
}
+ /**
+ * Returns state description of this icon.
+ */
+ public String getIconStateDescription() {
+ if (mRunningAppState == MINIMIZED) {
+ return mMinimizedStateDescription;
+ } else if (mRunningAppState == RUNNING) {
+ return mRunningStateDescription;
+ } else {
+ return "";
+ }
+ }
+
protected void setItemInfo(ItemInfoWithIcon itemInfo) {
setTag(itemInfo);
}
@@ -768,13 +790,13 @@
/** Draws a line under the app icon if this is representing a running app in Desktop Mode. */
protected void drawRunningAppIndicatorIfNecessary(Canvas canvas) {
- if (mRunningAppState == RunningAppState.NOT_RUNNING || mDisplay != DISPLAY_TASKBAR) {
+ if (mRunningAppState == NOT_RUNNING || mDisplay != DISPLAY_TASKBAR) {
return;
}
getIconBounds(mRunningAppIconBounds);
Utilities.scaleRectAboutCenter(mRunningAppIconBounds, ICON_VISIBLE_AREA_FACTOR);
- final boolean isMinimized = mRunningAppState == RunningAppState.MINIMIZED;
+ final boolean isMinimized = mRunningAppState == MINIMIZED;
final int indicatorTop = mRunningAppIconBounds.bottom + mRunningAppIndicatorTopMargin;
final int indicatorWidth =
isMinimized ? mMinimizedAppIndicatorWidth : mRunningAppIndicatorWidth;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 3edba99..728bc34 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -185,7 +185,6 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.debug.TestEventEmitter;
import com.android.launcher3.debug.TestEventEmitter.TestEvent;
-import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragView;
@@ -237,6 +236,7 @@
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.ItemInflater;
import com.android.launcher3.util.KeyboardShortcutsDelegate;
+import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.MSDLPlayerWrapper;
import com.android.launcher3.util.PackageUserKey;
@@ -544,7 +544,7 @@
mItemInflater = new ItemInflater<>(this, mAppWidgetHolder, getItemOnClickListener(),
mFocusHandler, new CellLayout(mWorkspace.getContext(), mWorkspace));
- mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
+ mPopupDataProvider = new PopupDataProvider(this);
mWidgetPickerDataProvider = new WidgetPickerDataProvider();
PillColorProvider.getInstance(mWorkspace.getContext()).registerObserver();
@@ -1598,11 +1598,6 @@
private final ScreenOnListener mScreenOnListener = this::onScreenOnChanged;
- private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
- mWorkspace.updateNotificationDots(updatedDots);
- mAppsView.getAppsStore().updateNotificationDots(updatedDots);
- }
-
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -2948,11 +2943,6 @@
return mModelCallbacks.getWorkspaceLoading();
}
- @Override
- public boolean isBindingItems() {
- return isWorkspaceLoading();
- }
-
/**
* Returns true if a touch interaction is in progress
*/
@@ -3027,11 +3017,6 @@
return mWidgetPickerDataProvider;
}
- @Override
- public DotInfo getDotInfoForItem(ItemInfo info) {
- return mPopupDataProvider.getDotInfoForItem(info);
- }
-
@NonNull
public LauncherOverlayManager getOverlayManager() {
return mOverlayManager;
@@ -3046,6 +3031,12 @@
return mDragLayer;
}
+ @NonNull
+ @Override
+ public LauncherBindableItemsContainer getContent() {
+ return mWorkspace;
+ }
+
@Override
public ActivityAllAppsContainerView<Launcher> getAppsView() {
return mAppsView;
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/Workspace.java b/src/com/android/launcher3/Workspace.java
index 94ff441..5595828 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -83,7 +83,6 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.debug.TestEventEmitter;
import com.android.launcher3.debug.TestEventEmitter.TestEvent;
-import com.android.launcher3.dot.FolderDotInfo;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
@@ -119,7 +118,6 @@
import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.MSDLPlayerWrapper;
import com.android.launcher3.util.OverlayEdgeEffect;
-import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.WallpaperOffsetInterpolator;
@@ -3419,38 +3417,6 @@
return null;
}
- public void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
- final PackageUserKey packageUserKey = new PackageUserKey(null, null);
- Predicate<ItemInfo> matcher = info -> !packageUserKey.updateFromItemInfo(info)
- || updatedDots.test(packageUserKey);
-
- ItemOperator op = (info, v) -> {
- if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView) {
- if (matcher.test(info)) {
- ((BubbleTextView) v).applyDotState(info, true /* animate */);
- }
- } else if (info instanceof FolderInfo && v instanceof FolderIcon) {
- FolderInfo fi = (FolderInfo) info;
- if (fi.anyMatch(matcher)) {
- FolderDotInfo folderDotInfo = new FolderDotInfo();
- for (ItemInfo si : fi.getContents()) {
- folderDotInfo.addDotInfo(mLauncher.getDotInfoForItem(si));
- }
- ((FolderIcon) v).setDotInfo(folderDotInfo);
- }
- }
-
- // process all the shortcuts
- return false;
- };
-
- mapOverItems(op);
- Folder folder = Folder.getOpen(mLauncher);
- if (folder != null) {
- folder.iterateOverItems(op);
- }
- }
-
/**
* Remove workspace icons & widget information related to items in matcher.
*
diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
index cf5150a..36dad89 100644
--- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
@@ -53,8 +53,18 @@
} else if (index >= MAX_NUM_ITEMS_IN_PREVIEW) {
// Items beyond those displayed in the preview are animated to the center
mTmpPoint[0] = mTmpPoint[1] = mAvailableSpace / 2 - (mIconSize * totalScale) / 2;
- } else {
- getPosition(index, curNumItems, mTmpPoint);
+ } else if (index == 0) {
+ // top left
+ getGridPosition(0, 0, mTmpPoint);
+ } else if (index == 1) {
+ // top right
+ getGridPosition(0, 1, mTmpPoint);
+ } else if (index == 2) {
+ // bottom left
+ getGridPosition(1, 0, mTmpPoint);
+ } else if (index == 3) {
+ // bottom right
+ getGridPosition(1, 1, mTmpPoint);
}
transX = mTmpPoint[0];
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index fb48a4d..0ce7249 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -65,6 +65,7 @@
import android.widget.TextView;
import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.core.content.res.ResourcesCompat;
@@ -261,7 +262,7 @@
@Nullable
private KeyboardInsetAnimationCallback mKeyboardInsetAnimationCallback;
- private GradientDrawable mBackground;
+ private final @NonNull GradientDrawable mBackground;
/**
* Used to inflate the Workspace from XML.
@@ -283,6 +284,10 @@
// click).
setFocusableInTouchMode(true);
+ mBackground = (GradientDrawable) Objects.requireNonNull(
+ ResourcesCompat.getDrawable(getResources(),
+ R.drawable.round_rect_folder, getContext().getTheme()));
+ mBackground.setCallback(this);
}
@Override
@@ -296,9 +301,6 @@
final DeviceProfile dp = mActivityContext.getDeviceProfile();
final int paddingLeftRight = dp.folderContentPaddingLeftRight;
- mBackground = (GradientDrawable) ResourcesCompat.getDrawable(getResources(),
- R.drawable.round_rect_folder, getContext().getTheme());
-
mContent = findViewById(R.id.folder_content);
mContent.setPadding(paddingLeftRight, dp.folderContentPaddingTop, paddingLeftRight, 0);
mContent.setFolder(this);
@@ -345,6 +347,11 @@
return true;
}
+ @Override
+ protected boolean verifyDrawable(@NonNull Drawable who) {
+ return super.verifyDrawable(who) || (who == mBackground);
+ }
+
void callBeginDragShared(View v, DragOptions options) {
mLauncherDelegate.beginDragShared(v, this, options);
}
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/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 740b87b..3836f7d 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -94,7 +94,7 @@
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
+import com.android.launcher3.util.SandboxContext;
import com.android.launcher3.util.WindowBounds;
import com.android.launcher3.util.window.WindowManagerProxy;
import com.android.launcher3.views.ActivityContext;
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index 3f3847f..3641896 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -64,7 +64,6 @@
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.LoaderTask;
import com.android.launcher3.model.ModelDbController;
-import com.android.launcher3.provider.LauncherDbUtils;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.Themes;
@@ -339,16 +338,6 @@
// Start the migration
PreviewContext previewContext =
new PreviewContext(inflationContext, mGridName, mShapeKey);
- // Copy existing data to preview DB
- LauncherDbUtils.copyTable(LauncherAppState.getInstance(mContext)
- .getModel().getModelDbController().getDb(),
- TABLE_NAME,
- LauncherAppState.getInstance(previewContext)
- .getModel().getModelDbController().getDb(),
- TABLE_NAME,
- mContext);
- LauncherAppState.getInstance(previewContext)
- .getModel().getModelDbController().clearEmptyDbFlag();
BgDataModel bgModel = new BgDataModel();
new LoaderTask(
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/model/DeviceGridState.java b/src/com/android/launcher3/model/DeviceGridState.java
index 6f12c97..d06f541 100644
--- a/src/com/android/launcher3/model/DeviceGridState.java
+++ b/src/com/android/launcher3/model/DeviceGridState.java
@@ -33,7 +33,6 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
import java.util.Locale;
import java.util.Objects;
@@ -103,9 +102,6 @@
* Stores the device state to shared preferences
*/
public void writeToPrefs(Context context) {
- if (context instanceof SandboxContext) {
- return;
- }
LauncherPrefs.get(context).put(
WORKSPACE_SIZE.to(mGridSizeString),
HOTSEAT_COUNT.to(mNumHotseat),
diff --git a/src/com/android/launcher3/model/ModelDbController.java b/src/com/android/launcher3/model/ModelDbController.java
index 3a55aa7..feae632 100644
--- a/src/com/android/launcher3/model/ModelDbController.java
+++ b/src/com/android/launcher3/model/ModelDbController.java
@@ -80,7 +80,6 @@
import com.android.launcher3.provider.RestoreDbTask;
import com.android.launcher3.util.IOUtils;
import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
import com.android.launcher3.util.Partner;
import com.android.launcher3.widget.LauncherWidgetHolder;
@@ -143,14 +142,11 @@
}
protected DatabaseHelper createDatabaseHelper(boolean forMigration, String dbFile) {
- boolean isSandbox = mContext instanceof SandboxContext;
- String dbName = isSandbox ? null : dbFile;
-
// Set the flag for empty DB
Runnable onEmptyDbCreateCallback = forMigration ? () -> { }
- : () -> LauncherPrefs.get(mContext).putSync(getEmptyDbCreatedKey(dbName).to(true));
+ : () -> LauncherPrefs.get(mContext).putSync(getEmptyDbCreatedKey(dbFile).to(true));
- DatabaseHelper databaseHelper = new DatabaseHelper(mContext, dbName,
+ DatabaseHelper databaseHelper = new DatabaseHelper(mContext, dbFile,
this::getSerialNumberForUser, onEmptyDbCreateCallback);
// Table creation sometimes fails silently, which leads to a crash loop.
// This way, we will try to create a table every time after crash, so the device
@@ -380,8 +376,7 @@
.filter(dbName -> mContext.getDatabasePath(dbName).exists())
.collect(Collectors.toList());
- mOpenHelper = (mContext instanceof SandboxContext) ? oldHelper
- : createDatabaseHelper(true, new DeviceGridState(idp).getDbFile());
+ mOpenHelper = createDatabaseHelper(true, new DeviceGridState(idp).getDbFile());
try {
// This is the current grid we have, given by the mContext
DeviceGridState srcDeviceState = new DeviceGridState(mContext);
@@ -462,8 +457,7 @@
List<String> existingDBs = LauncherFiles.GRID_DB_FILES.stream()
.filter(dbName -> mContext.getDatabasePath(dbName).exists())
.collect(Collectors.toList());
- mOpenHelper = (mContext instanceof SandboxContext) ? oldHelper
- : createDatabaseHelper(true /* forMigration */, targetDbName);
+ mOpenHelper = createDatabaseHelper(true /* forMigration */, targetDbName);
try {
// This is the current grid we have, given by the mContext
DeviceGridState srcDeviceState = new DeviceGridState(mContext);
@@ -763,10 +757,6 @@
* string will be "EMPTY_DATABASE_CREATED@minimal.db".
*/
private ConstantItem<Boolean> getEmptyDbCreatedKey(String dbName) {
- if (mContext instanceof SandboxContext) {
- return LauncherPrefs.nonRestorableItem(EMPTY_DATABASE_CREATED,
- false /* default value */, EncryptionType.ENCRYPTED);
- }
String key = TextUtils.equals(dbName, LauncherFiles.LAUNCHER_DB)
? EMPTY_DATABASE_CREATED : EMPTY_DATABASE_CREATED + "@" + dbName;
return LauncherPrefs.backedUpItem(key, false /* default value */, EncryptionType.ENCRYPTED);
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/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index 8a5e388..318b3ce 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -23,20 +23,27 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.allapps.ActivityAllAppsContainerView;
import com.android.launcher3.dot.DotInfo;
+import com.android.launcher3.dot.FolderDotInfo;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.notification.NotificationKeyData;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.ShortcutUtil;
+import com.android.launcher3.views.ActivityContext;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.function.Consumer;
import java.util.function.Predicate;
/**
@@ -47,19 +54,49 @@
private static final boolean LOGD = false;
private static final String TAG = "PopupDataProvider";
- private final Consumer<Predicate<PackageUserKey>> mNotificationDotsChangeListener;
+ private final ActivityContext mContext;
+
+ /** Maps packages to their DotInfo's . */
+ private final Map<PackageUserKey, DotInfo> mPackageUserToDotInfos = new HashMap<>();
/** Maps launcher activity components to a count of how many shortcuts they have. */
private HashMap<ComponentKey, Integer> mDeepShortcutMap = new HashMap<>();
- /** Maps packages to their DotInfo's . */
- private Map<PackageUserKey, DotInfo> mPackageUserToDotInfos = new HashMap<>();
- public PopupDataProvider(Consumer<Predicate<PackageUserKey>> notificationDotsChangeListener) {
- mNotificationDotsChangeListener = notificationDotsChangeListener;
+ public PopupDataProvider(ActivityContext context) {
+ mContext = context;
}
private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
- mNotificationDotsChangeListener.accept(updatedDots);
+ final PackageUserKey packageUserKey = new PackageUserKey(null, null);
+ Predicate<ItemInfo> matcher = info -> !packageUserKey.updateFromItemInfo(info)
+ || updatedDots.test(packageUserKey);
+
+ ItemOperator op = (info, v) -> {
+ if (v instanceof BubbleTextView && info != null && matcher.test(info)) {
+ ((BubbleTextView) v).applyDotState(info, true /* animate */);
+ } else if (v instanceof FolderIcon icon
+ && info instanceof FolderInfo fi && fi.anyMatch(matcher)) {
+ FolderDotInfo folderDotInfo = new FolderDotInfo();
+ for (ItemInfo si : fi.getContents()) {
+ folderDotInfo.addDotInfo(getDotInfoForItem(si));
+ }
+ icon.setDotInfo(folderDotInfo);
+ }
+
+ // process all the shortcuts
+ return false;
+ };
+
+ mContext.getContent().mapOverItems(op);
+ Folder folder = Folder.getOpen(mContext);
+ if (folder != null) {
+ folder.iterateOverItems(op);
+ }
+
+ ActivityAllAppsContainerView<?> appsView = mContext.getAppsView();
+ if (appsView != null) {
+ appsView.getAppsStore().updateNotificationDots(updatedDots);
+ }
}
@Override
diff --git a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
index e4c50f0..2d1a5f5 100644
--- a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
+++ b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.util.Log
+import android.view.ContextThemeWrapper
import android.view.InflateException
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.Companion.PROTECTED
@@ -33,8 +34,6 @@
import com.android.launcher3.util.Executors.VIEW_PREINFLATION_EXECUTOR
import com.android.launcher3.util.Themes
import com.android.launcher3.views.ActivityContext
-import com.android.launcher3.views.ActivityContext.ActivityContextDelegate
-import java.lang.IllegalStateException
const val PREINFLATE_ICONS_ROW_COUNT = 4
const val EXTRA_ICONS_COUNT = 2
@@ -80,11 +79,9 @@
// create a separate AssetManager obj internally to avoid lock contention with
// AssetManager obj that is associated with the launcher context on the main thread.
val allAppsPreInflationContext =
- ActivityContextDelegate(
- context.createConfigurationContext(context.resources.configuration),
- Themes.getActivityThemeRes(context),
- context,
- )
+ ContextThemeWrapper(context, Themes.getActivityThemeRes(context)).apply {
+ applyOverrideConfiguration(context.resources.configuration)
+ }
// Because we perform onCreateViewHolder() on worker thread, we need a separate
// adapter/inflator object as they are not thread-safe. Note that the adapter
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index c20d655..fd8b0e7 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;
@@ -28,6 +29,7 @@
import android.view.ViewAnimationUtils;
import android.view.inputmethod.InputMethodManager;
+import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
import com.android.launcher3.AbstractFloatingView;
@@ -56,7 +58,6 @@
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.touch.ItemClickHandler.ItemClickProxy;
import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.Themes;
@@ -82,7 +83,6 @@
private boolean mAppDrawerShown = false;
private StringCache mStringCache;
- private boolean mBindingItems = false;
private SecondaryDisplayPredictions mSecondaryDisplayPredictions;
private final int[] mTempXY = new int[2];
@@ -124,10 +124,13 @@
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(
- mAppsView.getAppsStore()::updateNotificationDots);
+ mPopupDataProvider = new PopupDataProvider(this);
mModel.addCallbacksAndLoad(this);
}
@@ -243,7 +246,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();
}
});
@@ -253,21 +258,10 @@
@Override
public void startBinding() {
- mBindingItems = true;
mDragController.cancelDrag();
}
@Override
- public boolean isBindingItems() {
- return mBindingItems;
- }
-
- @Override
- public void finishBindingItems(IntSet pagesBoundFirst) {
- mBindingItems = false;
- }
-
- @Override
public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap) {
mPopupDataProvider.setDeepShortcutMap(deepShortcutMap);
}
@@ -299,6 +293,8 @@
mStringCache = cache;
}
+ @Override
+ @NonNull
public PopupDataProvider getPopupDataProvider() {
return mPopupDataProvider;
}
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/DaggerSingletonObject.java b/src/com/android/launcher3/util/DaggerSingletonObject.java
index a245761..a4bd30a 100644
--- a/src/com/android/launcher3/util/DaggerSingletonObject.java
+++ b/src/com/android/launcher3/util/DaggerSingletonObject.java
@@ -24,8 +24,7 @@
import java.util.function.Function;
/**
- * A class to provide DaggerSingleton objects in a traditional way for
- * {@link MainThreadInitializedObject}.
+ * A class to provide DaggerSingleton objects in a traditional way.
* We should delete this class at the end and use @Inject to get dagger provided singletons.
*/
diff --git a/src/com/android/launcher3/util/DaggerSingletonTracker.java b/src/com/android/launcher3/util/DaggerSingletonTracker.java
index b7a88db..34b3760 100644
--- a/src/com/android/launcher3/util/DaggerSingletonTracker.java
+++ b/src/com/android/launcher3/util/DaggerSingletonTracker.java
@@ -45,7 +45,7 @@
* Adds the SafeCloseable Singletons to the mLauncherAppSingletons list.
* This helps to track the singletons and close them appropriately.
* See {@link DaggerSingletonTracker#close()} and
- * {@link MainThreadInitializedObject.SandboxContext#onDestroy()}
+ * {@link SandboxContext#onDestroy()}
*/
public void addCloseable(SafeCloseable closeable) {
MAIN_EXECUTOR.execute(() -> {
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 376a61e..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;
@@ -99,29 +103,29 @@
public static final int CHANGE_NAVIGATION_MODE = 1 << 4;
public static final int CHANGE_TASKBAR_PINNING = 1 << 5;
public static final int CHANGE_DESKTOP_MODE = 1 << 6;
+ public static final int CHANGE_SHOW_LOCKED_TASKBAR = 1 << 7;
public static final int CHANGE_ALL = CHANGE_ACTIVE_SCREEN | CHANGE_ROTATION
| CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS | CHANGE_NAVIGATION_MODE
- | CHANGE_TASKBAR_PINNING | CHANGE_DESKTOP_MODE;
+ | CHANGE_TASKBAR_PINNING | CHANGE_DESKTOP_MODE | CHANGE_SHOW_LOCKED_TASKBAR;
private static final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
private static final String TARGET_OVERLAY_PACKAGE = "android";
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
@@ -129,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);
}
};
@@ -150,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);
});
@@ -212,6 +244,13 @@
}
/**
+ * Returns whether the taskbar is pinned in gesture navigation mode.
+ */
+ public static boolean isInDesktopMode(Context context) {
+ return INSTANCE.get(context).getInfo().isInDesktopMode();
+ }
+
+ /**
* Returns whether the taskbar is forced to be pinned when home is visible.
*/
public static boolean showLockedTaskbarOnHome(Context context) {
@@ -228,9 +267,7 @@
@Override
public void onIsInDesktopModeChanged(int displayId, boolean isInDesktopModeAndNotInOverview) {
- if (DEFAULT_DISPLAY == displayId) {
- notifyConfigChange();
- }
+ notifyConfigChange(displayId);
}
/**
@@ -253,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;
@@ -328,34 +393,82 @@
}
if ((newInfo.mIsTaskbarPinned != oldInfo.mIsTaskbarPinned)
|| (newInfo.mIsTaskbarPinnedInDesktopMode
- != oldInfo.mIsTaskbarPinnedInDesktopMode)
+ != oldInfo.mIsTaskbarPinnedInDesktopMode)
|| newInfo.isPinnedTaskbar() != oldInfo.isPinnedTaskbar()) {
change |= CHANGE_TASKBAR_PINNING;
}
if (newInfo.mIsInDesktopMode != oldInfo.mIsInDesktopMode) {
change |= CHANGE_DESKTOP_MODE;
}
+ if (newInfo.mShowLockedTaskbarOnHome != oldInfo.mShowLockedTaskbarOnHome) {
+ change |= CHANGE_SHOW_LOCKED_TASKBAR;
+ }
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 {
@@ -494,6 +607,13 @@
}
/**
+ * Returns whether the taskbar is in desktop mode.
+ */
+ public boolean isInDesktopMode() {
+ return mIsInDesktopMode;
+ }
+
+ /**
* Returns {@code true} if the bounds represent a tablet.
*/
public boolean isTablet(WindowBounds bounds) {
@@ -575,6 +695,7 @@
appendFlag(result, change, CHANGE_NAVIGATION_MODE, "CHANGE_NAVIGATION_MODE");
appendFlag(result, change, CHANGE_TASKBAR_PINNING, "CHANGE_TASKBAR_VARIANT");
appendFlag(result, change, CHANGE_DESKTOP_MODE, "CHANGE_DESKTOP_MODE");
+ appendFlag(result, change, CHANGE_SHOW_LOCKED_TASKBAR, "CHANGE_SHOW_LOCKED_TASKBAR");
return result.toString();
}
@@ -582,20 +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(" 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());
+ }
}
/**
@@ -623,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/src/com/android/launcher3/util/KFloatProperty.kt b/src/com/android/launcher3/util/KFloatProperty.kt
new file mode 100644
index 0000000..5579241
--- /dev/null
+++ b/src/com/android/launcher3/util/KFloatProperty.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.util
+
+import android.util.FloatProperty
+import kotlin.reflect.KMutableProperty1
+
+/** Maps any Kotlin mutable property (var) to [FloatProperty]. */
+class KFloatProperty<T>(private val kProperty: KMutableProperty1<T, Float>) :
+ FloatProperty<T>(kProperty.name) {
+ override fun get(target: T) = kProperty.get(target)
+
+ override fun setValue(target: T, value: Float) {
+ kProperty.set(target, value)
+ }
+}
diff --git a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
deleted file mode 100644
index 20e3eaf..0000000
--- a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2021 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.util;
-
-import android.view.View;
-
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.apppairs.AppPairIcon;
-import com.android.launcher3.folder.Folder;
-import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.model.data.AppPairInfo;
-import com.android.launcher3.model.data.FolderInfo;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.widget.PendingAppWidgetHostView;
-
-import java.util.Set;
-
-/**
- * Interface representing a container which can bind Launcher items with some utility methods
- */
-public interface LauncherBindableItemsContainer {
-
- /**
- * Called to update workspace items as a result of
- * {@link com.android.launcher3.model.BgDataModel.Callbacks#bindItemsUpdated(Set)}
- */
- default void updateContainerItems(Set<ItemInfo> updates, ActivityContext context) {
- ItemOperator op = (info, v) -> {
- if (v instanceof BubbleTextView shortcut
- && info instanceof WorkspaceItemInfo wii
- && updates.contains(info)) {
- shortcut.applyFromWorkspaceItem(wii);
- } else if (info instanceof FolderInfo && v instanceof FolderIcon folderIcon) {
- folderIcon.updatePreviewItems(updates::contains);
- } else if (info instanceof AppPairInfo && v instanceof AppPairIcon appPairIcon) {
- appPairIcon.maybeRedrawForWorkspaceUpdate(updates::contains);
- } else if (v instanceof PendingAppWidgetHostView pendingView
- && updates.contains(info)) {
- pendingView.applyState();
- pendingView.postProviderAvailabilityCheck();
- }
-
- // Iterate all items
- return false;
- };
-
- mapOverItems(op);
- Folder openFolder = Folder.getOpen(context);
- if (openFolder != null) {
- openFolder.iterateOverItems(op);
- }
- }
-
- /**
- * Map the operator over the shortcuts and widgets.
- *
- * @param op the operator to map over the shortcuts
- */
- void mapOverItems(ItemOperator op);
-
- interface ItemOperator {
- /**
- * Process the next itemInfo, possibly with side-effect on the next item.
- *
- * @param info info for the shortcut
- * @param view view for the shortcut
- * @return true if done, false to continue the map
- */
- boolean evaluate(ItemInfo info, View view);
- }
-}
diff --git a/src/com/android/launcher3/util/LauncherBindableItemsContainer.kt b/src/com/android/launcher3/util/LauncherBindableItemsContainer.kt
new file mode 100644
index 0000000..1661796
--- /dev/null
+++ b/src/com/android/launcher3/util/LauncherBindableItemsContainer.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 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.util
+
+import android.view.View
+import com.android.launcher3.BubbleTextView
+import com.android.launcher3.apppairs.AppPairIcon
+import com.android.launcher3.folder.Folder
+import com.android.launcher3.folder.FolderIcon
+import com.android.launcher3.model.data.AppPairInfo
+import com.android.launcher3.model.data.FolderInfo
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.views.ActivityContext
+import com.android.launcher3.widget.PendingAppWidgetHostView
+
+/** Interface representing a container which can bind Launcher items with some utility methods */
+interface LauncherBindableItemsContainer {
+
+ /**
+ * Called to update workspace items as a result of {@link
+ * com.android.launcher3.model.BgDataModel.Callbacks#bindItemsUpdated(Set)}
+ */
+ fun updateContainerItems(updates: Set<ItemInfo>, context: ActivityContext) {
+ val op = ItemOperator { info, v ->
+ when {
+ v is BubbleTextView && info is WorkspaceItemInfo && updates.contains(info) ->
+ v.applyFromWorkspaceItem(info)
+ v is FolderIcon && info is FolderInfo -> v.updatePreviewItems(updates::contains)
+ v is AppPairIcon && info is AppPairInfo ->
+ v.maybeRedrawForWorkspaceUpdate(updates::contains)
+ v is PendingAppWidgetHostView && updates.contains(info) -> {
+ v.applyState()
+ v.postProviderAvailabilityCheck()
+ }
+ }
+
+ // Iterate all items
+ false
+ }
+
+ mapOverItems(op)
+ Folder.getOpen(context)?.iterateOverItems(op)
+ }
+
+ /** Map the [op] over the shortcuts and widgets. */
+ fun mapOverItems(op: ItemOperator)
+
+ fun interface ItemOperator {
+
+ /**
+ * Process the next itemInfo, possibly with side-effect on the next item.
+ *
+ * @param info info for the shortcut
+ * @param view view for the shortcut
+ * @return true if done, false to continue the map
+ */
+ fun evaluate(info: ItemInfo?, view: View): Boolean
+ }
+}
diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java
deleted file mode 100644
index 356a551..0000000
--- a/src/com/android/launcher3/util/MainThreadInitializedObject.java
+++ /dev/null
@@ -1,214 +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.launcher3.util;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
-import android.content.Context;
-import android.os.Looper;
-import android.util.Log;
-
-import androidx.annotation.UiThread;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.launcher3.LauncherApplication;
-import com.android.launcher3.util.ResourceBasedOverride.Overrides;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.ExecutionException;
-import java.util.function.Consumer;
-
-/**
- * Utility class for defining singletons which are initiated on main thread.
- *
- * TODO(b/361850561): Do not delete MainThreadInitializedObject until we find a way to
- * unregister and understand how singleton objects are destroyed in dagger graph.
- */
-public class MainThreadInitializedObject<T extends SafeCloseable> {
-
- private final ObjectProvider<T> mProvider;
- private T mValue;
-
- public MainThreadInitializedObject(ObjectProvider<T> provider) {
- mProvider = provider;
- }
-
- public T get(Context context) {
- Context app = context.getApplicationContext();
- if (app instanceof ObjectSandbox sc) {
- return sc.getObject(this);
- }
-
- if (mValue == null) {
- if (Looper.myLooper() == Looper.getMainLooper()) {
- mValue = TraceHelper.allowIpcs("main.thread.object", () -> mProvider.get(app));
- } else {
- try {
- return MAIN_EXECUTOR.submit(() -> get(context)).get();
- } catch (InterruptedException|ExecutionException e) {
- throw new RuntimeException(e);
- }
- }
- }
- return mValue;
- }
-
- /**
- * Executes the callback is the value is already created
- * @return true if the callback was executed, false otherwise
- */
- public boolean executeIfCreated(Consumer<T> callback) {
- T v = mValue;
- if (v != null) {
- callback.accept(v);
- return true;
- } else {
- return false;
- }
- }
-
- @VisibleForTesting
- public void initializeForTesting(T value) {
- mValue = value;
- }
-
- /**
- * Initializes a provider based on resource overrides
- */
- public static <T extends ResourceBasedOverride & SafeCloseable> MainThreadInitializedObject<T>
- forOverride(Class<T> clazz, int resourceId) {
- return new MainThreadInitializedObject<>(c -> Overrides.getObject(clazz, c, resourceId));
- }
-
- public interface ObjectProvider<T> {
-
- T get(Context context);
- }
-
- /** Sandbox for isolating {@link MainThreadInitializedObject} instances from Launcher. */
- public interface ObjectSandbox {
-
- /**
- * Find a cached object from mObjectMap if we have already created one. If not, generate
- * an object using the provider.
- */
- <T extends SafeCloseable> T getObject(MainThreadInitializedObject<T> object);
-
-
- /**
- * Put a value into cache, can be used to put mocked MainThreadInitializedObject
- * instances.
- */
- <T extends SafeCloseable> void putObject(MainThreadInitializedObject<T> object, T value);
-
- /**
- * Returns whether this sandbox should cleanup all objects when its destroyed or leave it
- * to the GC.
- * These objects can have listeners attached to the system server and mey not be able to get
- * GCed themselves when running on a device.
- * Some environments like Robolectric tear down the whole system at the end of the test,
- * so manual cleanup may not be required.
- */
- default boolean shouldCleanUpOnDestroy() {
- return true;
- }
-
- @UiThread
- default <T extends SafeCloseable> T createObject(MainThreadInitializedObject<T> object) {
- return object.mProvider.get((Context) this);
- }
- }
-
- /**
- * Abstract Context which allows custom implementations for
- * {@link MainThreadInitializedObject} providers
- */
- public static class SandboxContext extends LauncherApplication implements ObjectSandbox {
-
- private static final String TAG = "SandboxContext";
-
- private final Map<MainThreadInitializedObject, Object> mObjectMap = new HashMap<>();
- private final ArrayList<SafeCloseable> mOrderedObjects = new ArrayList<>();
-
- private final Object mDestroyLock = new Object();
- private boolean mDestroyed = false;
-
- public SandboxContext(Context base) {
- attachBaseContext(base);
- }
-
- @Override
- public Context getApplicationContext() {
- return this;
- }
-
- @Override
- public boolean shouldCleanUpOnDestroy() {
- return (getBaseContext().getApplicationContext() instanceof ObjectSandbox os)
- ? os.shouldCleanUpOnDestroy() : true;
- }
-
- public void onDestroy() {
- if (shouldCleanUpOnDestroy()) {
- cleanUpObjects();
- }
- }
-
- protected void cleanUpObjects() {
- getAppComponent().getDaggerSingletonTracker().close();
- synchronized (mDestroyLock) {
- // Destroy in reverse order
- for (int i = mOrderedObjects.size() - 1; i >= 0; i--) {
- mOrderedObjects.get(i).close();
- }
- mDestroyed = true;
- }
- }
-
- @Override
- public <T extends SafeCloseable> T getObject(MainThreadInitializedObject<T> object) {
- synchronized (mDestroyLock) {
- if (mDestroyed) {
- Log.e(TAG, "Static object access with a destroyed context");
- }
- T t = (T) mObjectMap.get(object);
- if (t != null) {
- return t;
- }
- if (Looper.myLooper() == Looper.getMainLooper()) {
- t = createObject(object);
- mObjectMap.put(object, t);
- mOrderedObjects.add(t);
- return t;
- }
- }
-
- try {
- return MAIN_EXECUTOR.submit(() -> getObject(object)).get();
- } catch (InterruptedException | ExecutionException e) {
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public <T extends SafeCloseable> void putObject(
- MainThreadInitializedObject<T> object, T value) {
- mObjectMap.put(object, value);
- }
- }
-}
diff --git a/src/com/android/launcher3/util/MultiPropertyDelegate.kt b/src/com/android/launcher3/util/MultiPropertyDelegate.kt
new file mode 100644
index 0000000..837a586
--- /dev/null
+++ b/src/com/android/launcher3/util/MultiPropertyDelegate.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.util
+
+import kotlin.reflect.KProperty
+
+/** Delegate Kotlin mutable property (var) to a property in [MultiPropertyFactory] */
+class MultiPropertyDelegate(private val property: MultiPropertyFactory<*>.MultiProperty) {
+ constructor(factory: MultiPropertyFactory<*>, enum: Enum<*>) : this(factory[enum.ordinal])
+
+ operator fun getValue(thisRef: Any?, kProperty: KProperty<*>): Float = property.value
+
+ operator fun setValue(thisRef: Any?, kProperty: KProperty<*>, value: Float) {
+ property.value = value
+ }
+}
diff --git a/src/com/android/launcher3/util/SandboxContext.kt b/src/com/android/launcher3/util/SandboxContext.kt
new file mode 100644
index 0000000..c6224e2
--- /dev/null
+++ b/src/com/android/launcher3/util/SandboxContext.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.util
+
+import android.content.Context
+import com.android.launcher3.LauncherApplication
+
+/** Abstract Context which allows custom implementations for dagger components. */
+open class SandboxContext(base: Context?) : LauncherApplication() {
+ init {
+ base?.let { attachBaseContext(it) }
+ }
+
+ override fun getApplicationContext(): Context {
+ return this
+ }
+
+ /**
+ * Returns whether this sandbox should cleanup all objects when its destroyed or leave it to the
+ * GC. These objects can have listeners attached to the system server and mey not be able to get
+ * GCed themselves when running on a device. Some environments like Robolectric tear down the
+ * whole system at the end of the test, so manual cleanup may not be required.
+ */
+ open fun shouldCleanUpOnDestroy(): Boolean {
+ return (getBaseContext().getApplicationContext() as? SandboxContext)
+ ?.shouldCleanUpOnDestroy() ?: true
+ }
+
+ fun onDestroy() {
+ if (shouldCleanUpOnDestroy()) {
+ cleanUpObjects()
+ }
+ }
+
+ open protected fun cleanUpObjects() {
+ appComponent.daggerSingletonTracker.close()
+ }
+
+ companion object {
+ private const val TAG = "SandboxContext"
+ }
+}
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index 81968fc..30af586 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -43,7 +43,6 @@
import android.os.Process;
import android.os.UserHandle;
import android.util.Log;
-import android.view.ContextThemeWrapper;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.View;
@@ -81,6 +80,7 @@
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.util.ActivityOptionsWrapper;
import com.android.launcher3.util.ApplicationInfoWrapper;
+import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SplitConfigurationOptions;
@@ -102,10 +102,6 @@
return false;
}
- default DotInfo getDotInfoForItem(ItemInfo info) {
- return null;
- }
-
default AccessibilityDelegate getAccessibilityDelegate() {
return null;
}
@@ -195,6 +191,14 @@
}
/**
+ * Returns the primary content of this context
+ */
+ @NonNull
+ default LauncherBindableItemsContainer getContent() {
+ return op -> { };
+ }
+
+ /**
* The all apps container, if it exists in this context.
*/
default ActivityAllAppsContainerView<?> getAppsView() {
@@ -271,11 +275,6 @@
*/
default void applyOverwritesToLogItem(LauncherAtom.ItemInfo.Builder itemInfoBuilder) { }
- /** Returns {@code true} if items are currently being bound within this context. */
- default boolean isBindingItems() {
- return false;
- }
-
default View.OnClickListener getItemOnClickListener() {
return v -> {
// No op.
@@ -287,9 +286,13 @@
return v -> false;
}
- @Nullable
+ @NonNull
default PopupDataProvider getPopupDataProvider() {
- return null;
+ return new PopupDataProvider(this);
+ }
+
+ default DotInfo getDotInfoForItem(ItemInfo info) {
+ return getPopupDataProvider().getDotInfoForItem(info);
}
/**
@@ -552,21 +555,10 @@
static <T extends Context & ActivityContext> T lookupContextNoThrow(Context context) {
if (context instanceof ActivityContext) {
return (T) context;
- } else if (context instanceof ActivityContextDelegate acd) {
- return (T) acd.mDelegate;
- } else if (context instanceof ContextWrapper) {
- return lookupContextNoThrow(((ContextWrapper) context).getBaseContext());
+ } else if (context instanceof ContextWrapper cw) {
+ return lookupContextNoThrow(cw.getBaseContext());
} else {
return null;
}
}
-
- class ActivityContextDelegate extends ContextThemeWrapper {
- public final ActivityContext mDelegate;
-
- public ActivityContextDelegate(Context base, int themeResId, ActivityContext delegate) {
- super(base, themeResId);
- mDelegate = delegate;
- }
- }
}
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index b07d807..7a44c6a 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -79,7 +79,8 @@
private Runnable mAutoAdvanceRunnable;
private long mDeferUpdatesUntilMillis = 0;
- RemoteViews mLastRemoteViews;
+ private RemoteViews mLastRemoteViews;
+ private boolean mReapplyOnResumeUpdates = false;
private boolean mTrackingWidgetUpdate = false;
@@ -138,11 +139,11 @@
TRACE_METHOD_NAME + getAppWidgetInfo().provider, getAppWidgetId());
mTrackingWidgetUpdate = false;
}
- if (isDeferringUpdates()) {
- mLastRemoteViews = remoteViews;
+ mLastRemoteViews = remoteViews;
+ mReapplyOnResumeUpdates = isDeferringUpdates();
+ if (mReapplyOnResumeUpdates) {
return;
}
- mLastRemoteViews = null;
super.updateAppWidget(remoteViews);
@@ -150,6 +151,18 @@
checkIfAutoAdvance();
}
+ @Override
+ public void onViewAdded(View child) {
+ super.onViewAdded(child);
+ mReapplyOnResumeUpdates |= isDeferringUpdates();
+ }
+
+ @Override
+ public void onViewRemoved(View child) {
+ super.onViewRemoved(child);
+ mReapplyOnResumeUpdates |= isDeferringUpdates();
+ }
+
private boolean checkScrollableRecursively(ViewGroup viewGroup) {
if (viewGroup instanceof AdapterView) {
return true;
@@ -204,18 +217,16 @@
* {@link #updateAppWidget} and apply any deferred updates.
*/
public void endDeferringUpdates() {
- RemoteViews remoteViews;
mDeferUpdatesUntilMillis = 0;
- remoteViews = mLastRemoteViews;
-
- if (remoteViews != null) {
- updateAppWidget(remoteViews);
+ if (mReapplyOnResumeUpdates) {
+ updateAppWidget(mLastRemoteViews);
}
}
+ @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- BaseDragLayer dragLayer = mActivityContext.getDragLayer();
+ BaseDragLayer<?> dragLayer = mActivityContext.getDragLayer();
if (mIsScrollable) {
dragLayer.requestDisallowInterceptTouchEvent(true);
}
@@ -225,6 +236,7 @@
return mLongPressHelper.hasPerformedLongPress();
}
+ @Override
public boolean onTouchEvent(MotionEvent ev) {
mLongPressHelper.onTouchEvent(ev);
// We want to keep receiving though events to be able to cancel long press on ACTION_UP
diff --git a/tests/Launcher3Tests.xml b/tests/Launcher3Tests.xml
index 56dd6a4..da86357 100644
--- a/tests/Launcher3Tests.xml
+++ b/tests/Launcher3Tests.xml
@@ -62,6 +62,7 @@
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<option name="directory-keys" value="/data/user/0/com.android.launcher3/files" />
+ <option name="directory-keys" value="/data/user/10/com.android.launcher3/files" />
<option name="collect-on-run-ended-only" value="true" />
</metrics_collector>
diff --git a/tests/multivalentTests/shared/com/android/launcher3/testing/OWNERS b/tests/multivalentTests/shared/com/android/launcher3/testing/OWNERS
new file mode 100644
index 0000000..02e8ebc
--- /dev/null
+++ b/tests/multivalentTests/shared/com/android/launcher3/testing/OWNERS
@@ -0,0 +1,4 @@
+vadimt@google.com
+sunnygoyal@google.com
+winsonc@google.com
+hyunyoungs@google.com
diff --git a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index 3658989..ad6afcf 100644
--- a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -35,8 +35,8 @@
import com.android.launcher3.util.AllModulesMinusWMProxy
import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.FakePrefsModule
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
import com.android.launcher3.util.NavigationMode
+import com.android.launcher3.util.SandboxContext
import com.android.launcher3.util.WindowBounds
import com.android.launcher3.util.rule.TestStabilityRule
import com.android.launcher3.util.rule.setFlags
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/util/DisplayControllerTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
index aa1451b..0ecb38e 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
@@ -34,6 +34,7 @@
import com.android.launcher3.util.DisplayController.CHANGE_DENSITY
import com.android.launcher3.util.DisplayController.CHANGE_DESKTOP_MODE
import com.android.launcher3.util.DisplayController.CHANGE_ROTATION
+import com.android.launcher3.util.DisplayController.CHANGE_SHOW_LOCKED_TASKBAR
import com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING
import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener
import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
@@ -209,8 +210,13 @@
assertTrue(displayController.getInfo().isTransientTaskbar())
displayController.notifyConfigChange()
+
verify(displayInfoChangeListener)
- .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING))
+ .onDisplayInfoChanged(
+ any(),
+ any(),
+ eq(CHANGE_TASKBAR_PINNING or CHANGE_SHOW_LOCKED_TASKBAR),
+ )
assertFalse(displayController.getInfo().isTransientTaskbar())
}
@@ -227,7 +233,11 @@
displayController.onConfigurationChanged(configuration)
verify(displayInfoChangeListener)
- .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING))
+ .onDisplayInfoChanged(
+ any(),
+ any(),
+ eq(CHANGE_TASKBAR_PINNING or CHANGE_SHOW_LOCKED_TASKBAR),
+ )
assertFalse(displayController.getInfo().isTransientTaskbar())
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
index cee5559..4458e8f 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -56,7 +56,6 @@
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.ModelDbController;
import com.android.launcher3.testing.TestInformationProvider;
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt b/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt
index 547d05e..ceefb0d 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt
@@ -1,6 +1,7 @@
package com.android.launcher3.util
import android.content.ContentValues
+import android.os.Process
import com.android.launcher3.Flags
import com.android.launcher3.LauncherModel
import com.android.launcher3.LauncherSettings.Favorites
@@ -66,7 +67,7 @@
spanX: Int = 1,
spanY: Int = 1,
id: Int = 0,
- profileId: Int = 0,
+ profileId: Int = Process.myUserHandle().identifier,
tableName: String = Favorites.TABLE_NAME,
appWidgetId: Int = -1,
appWidgetSource: Int = -1,
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt
index 0da8891..2fa4cad 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt
@@ -27,7 +27,6 @@
import android.view.Display
import androidx.test.core.app.ApplicationProvider
import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
-import com.android.launcher3.util.MainThreadInitializedObject.ObjectSandbox
import org.junit.Rule
import org.junit.rules.ExternalResource
import org.junit.rules.TestRule
@@ -69,7 +68,7 @@
// Defer to the true application to decide whether to clean up. For instance, we do not want
// to cleanup under Robolectric.
val app = ApplicationProvider.getApplicationContext<Context>()
- return if (app is ObjectSandbox) app.shouldCleanUpOnDestroy() else true
+ return (app as? SandboxContext)?.shouldCleanUpOnDestroy() ?: true
}
override fun apply(statement: Statement, description: Description): Statement {
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplicationTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplicationTest.kt
index d87a406..8a21cff 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplicationTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplicationTest.kt
@@ -16,7 +16,6 @@
package com.android.launcher3.util
-import android.content.Context
import android.hardware.display.DisplayManager
import android.view.Display
import android.view.Display.DEFAULT_DISPLAY
@@ -63,16 +62,4 @@
onDestroy()
}
}
-
- @Test
- fun testGetObject_objectCreatesDisplayContext_isSandboxed() {
- class TestSingleton(context: Context) : SafeCloseable {
- override fun close() = Unit
-
- val displayContext = context.createDisplayContext(display)
- }
-
- val displayContext = MainThreadInitializedObject { TestSingleton(it) }[app].displayContext
- assertThat(displayContext.applicationContext).isEqualTo(app)
- }
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java b/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java
index 393282f..8be1341 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java
@@ -18,10 +18,9 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-import static com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
-
import android.content.ContextWrapper;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -44,7 +43,7 @@
* There are 2 constructors in this class. The base context can be {@link SandboxContext} or
* Instrumentation target context.
* Using {@link SandboxContext} as base context allows custom implementations for
- * MainThreadInitializedObject providers.
+ * providing objects in Dagger components.
*/
public class TestSandboxModelContextWrapper extends ActivityContextWrapper implements
@@ -57,7 +56,7 @@
protected ActivityAllAppsContainerView<ActivityContextWrapper> mAppsView;
- private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(i -> {});
+ private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(this);
private final WidgetPickerDataProvider mWidgetPickerDataProvider =
new WidgetPickerDataProvider();
protected final UserCache mUserCache;
@@ -80,7 +79,7 @@
mAllAppsStore = mAppsView.getAppsStore();
}
- @Nullable
+ @NonNull
@Override
public PopupDataProvider getPopupDataProvider() {
return mPopupDataProvider;
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();
+ }
}
}