out) {
- if (ShortcutUtil.supportsShortcuts(item) && FeatureFlags.ENABLE_TASKBAR_POPUP_MENU.get()) {
+ if (ShortcutUtil.supportsShortcuts(item)) {
out.add(mActions.get(NotificationListener.getInstanceIfConnected() != null
? SHORTCUTS_AND_NOTIFICATIONS : DEEP_SHORTCUTS));
}
@@ -112,7 +111,8 @@ public class TaskbarShortcutMenuAccessibilityDelegate
item.getIntent().getComponent(),
/* startActivityOptions= */null,
item.user),
- new Intent(), side, null, instanceIds.first);
+ item.user.getIdentifier(), new Intent(), side, null,
+ instanceIds.first);
}
return true;
} else if (action == DEEP_SHORTCUTS || action == SHORTCUTS_AND_NOTIFICATIONS) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarSpringOnStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarSpringOnStashController.java
index d65b5c0f61..f87c21ece0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarSpringOnStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarSpringOnStashController.java
@@ -17,6 +17,7 @@ package com.android.launcher3.taskbar;
import static com.android.launcher3.anim.AnimatedFloat.VALUE;
+import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import androidx.annotation.Nullable;
@@ -85,6 +86,15 @@ public class TaskbarSpringOnStashController implements LoggableTaskbarController
.build(mTranslationForStash, VALUE);
}
+ /**
+ * Returns an animation to reset the stash translation back to 0 when unstashing.
+ */
+ public @Nullable ObjectAnimator createResetAnimForUnstash() {
+ if (!mIsTransientTaskbar) {
+ return null;
+ }
+ return mTranslationForStash.animateToValue(0);
+ }
@Override
public void dumpLogs(String prefix, PrintWriter pw) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 69ea9fd03a..c51d9d30f8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -18,11 +18,12 @@ package com.android.launcher3.taskbar;
import static android.view.HapticFeedbackConstants.LONG_PRESS;
import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
+import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_KEY;
import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.FORCE_PERSISTENT_TASKBAR;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_PINNING;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_HIDE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_SHOW;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_HIDE;
@@ -65,7 +66,6 @@ import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.AnimatorListeners;
-import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
import com.android.quickstep.SystemUiProxy;
@@ -106,15 +106,13 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
| FLAG_STASHED_IN_APP_IME | FLAG_STASHED_IN_TASKBAR_ALL_APPS
| FLAG_STASHED_SMALL_SCREEN | FLAG_STASHED_IN_APP_AUTO;
- private static final int FLAGS_STASHED_IN_APP_IGNORING_IME =
- FLAGS_STASHED_IN_APP & ~FLAG_STASHED_IN_APP_IME;
-
// If any of these flags are enabled, inset apps by our stashed height instead of our unstashed
// height. This way the reported insets are consistent even during transitions out of the app.
// Currently any flag that causes us to stash in an app is included, except for IME or All Apps
// since those cover the underlying app anyway and thus the app shouldn't change insets.
private static final int FLAGS_REPORT_STASHED_INSETS_TO_APP = FLAGS_STASHED_IN_APP
- & ~FLAG_STASHED_IN_APP_IME & ~FLAG_STASHED_IN_TASKBAR_ALL_APPS;
+ & ~FLAG_STASHED_IN_APP_IME & ~FLAG_STASHED_IN_TASKBAR_ALL_APPS
+ & ~FLAG_STASHED_IN_APP_SYSUI;
// If any of these flags are enabled, the taskbar must be stashed.
private static final int FLAGS_FORCE_STASHED = FLAG_STASHED_SYSUI | FLAG_STASHED_DEVICE_LOCKED
@@ -234,6 +232,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
// Taskbar background properties.
private AnimatedFloat mTaskbarBackgroundOffset;
private AnimatedFloat mTaskbarImeBgAlpha;
+ private MultiProperty mTaskbarBackgroundAlphaForStash;
// TaskbarView icon properties.
private MultiProperty mIconAlphaForStash;
private AnimatedFloat mIconScaleForStash;
@@ -305,6 +304,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
TaskbarDragLayerController dragLayerController = controllers.taskbarDragLayerController;
mTaskbarBackgroundOffset = dragLayerController.getTaskbarBackgroundOffset();
mTaskbarImeBgAlpha = dragLayerController.getImeBgTaskbar();
+ mTaskbarBackgroundAlphaForStash = dragLayerController.getBackgroundRendererAlphaForStash();
TaskbarViewController taskbarViewController = controllers.taskbarViewController;
mIconAlphaForStash = taskbarViewController.getTaskbarIconAlpha().get(
@@ -324,7 +324,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
// that taskbar unstashes when going to 3 button mode (supportsVisualStashing() false).
boolean isManuallyStashedInApp = supportsVisualStashing()
&& !isTransientTaskbar
- && !FORCE_PERSISTENT_TASKBAR.get()
+ && !ENABLE_TASKBAR_PINNING.get()
&& mPrefs.getBoolean(SHARED_PREFS_STASHED_KEY, DEFAULT_STASHED_PREF);
boolean isInSetup = !mActivity.isUserSetupComplete() || setupUIVisible;
updateStateForFlag(FLAG_STASHED_IN_APP_MANUAL, isManuallyStashedInApp);
@@ -353,7 +353,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
* Returns whether the user can manually stash the taskbar based on the current device state.
*/
protected boolean supportsManualStashing() {
- if (FORCE_PERSISTENT_TASKBAR.get()) {
+ if (ENABLE_TASKBAR_PINNING.get() && mPrefs.getBoolean(TASKBAR_PINNING_KEY, false)) {
return false;
}
return supportsVisualStashing()
@@ -413,13 +413,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
return hasAnyFlag(FLAGS_STASHED_IN_APP);
}
- /**
- * Returns whether the taskbar should be stashed in apps regardless of the IME visibility.
- */
- public boolean isStashedInAppIgnoringIme() {
- return hasAnyFlag(FLAGS_STASHED_IN_APP_IGNORING_IME);
- }
-
/**
* Returns whether the taskbar should be stashed in the current LauncherState.
*/
@@ -510,24 +503,20 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
return mStashedHeight;
}
- /**
- * Stash or unstashes the transient taskbar, using the default TASKBAR_STASH_DURATION.
- */
- public void updateAndAnimateTransientTaskbar(boolean stash) {
- updateAndAnimateTransientTaskbar(stash, TASKBAR_STASH_DURATION);
- }
-
/**
* Stash or unstashes the transient taskbar.
*/
- public void updateAndAnimateTransientTaskbar(boolean stash, long duration) {
+ public void updateAndAnimateTransientTaskbar(boolean stash) {
if (!DisplayController.isTransientTaskbar(mActivity)) {
return;
}
- if (stash && mControllers.taskbarAutohideSuspendController.isSuspended()
+ if (
+ stash
&& !mControllers.taskbarAutohideSuspendController
- .isSuspendedForTransientTaskbarInOverview()) {
+ .isSuspendedForTransientTaskbarInLauncher()
+ && mControllers.taskbarAutohideSuspendController
+ .isTransientTaskbarStashingSuspended()) {
// Avoid stashing if autohide is currently suspended.
return;
}
@@ -585,6 +574,12 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
return false;
}
+ /** Toggles the Taskbar's stash state. */
+ public void toggleTaskbarStash() {
+ if (!DisplayController.isTransientTaskbar(mActivity) || !hasAnyFlag(FLAGS_IN_APP)) return;
+ updateAndAnimateTransientTaskbar(!hasAnyFlag(FLAG_STASHED_IN_APP_AUTO));
+ }
+
/**
* Adds the Taskbar unstash to Hotseat animator to the animator set.
*
@@ -596,10 +591,14 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
* actually be used since this animation tracks a swipe progress.
*/
protected void addUnstashToHotseatAnimation(AnimatorSet animation, int placeholderDuration) {
+ // Defer any UI updates now to avoid the UI becoming stale when the animation plays.
+ mControllers.taskbarViewController.setDeferUpdatesForSUW(true);
createAnimToIsStashed(
/* isStashed= */ false,
placeholderDuration,
TRANSITION_UNSTASH_SUW_MANUAL);
+ animation.addListener(AnimatorListeners.forEndCallback(
+ () -> mControllers.taskbarViewController.setDeferUpdatesForSUW(false)));
animation.play(mAnimator);
}
@@ -753,37 +752,42 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
final float backgroundOffsetTarget = isStashed ? 1 : 0;
final float iconAlphaTarget = isStashed ? 0 : 1;
final float stashedHandleAlphaTarget = isStashed ? 1 : 0;
+ final float backgroundAlphaTarget = isStashed ? 0 : 1;
// Timing for the alpha values depend on the animation played
- long iconAlphaStartDelay = 0, iconAlphaDuration = 0, stashedHandleAlphaDelay = 0,
- stashedHandleAlphaDuration = 0;
+ long iconAlphaStartDelay = 0, iconAlphaDuration = 0, backgroundAndHandleAlphaStartDelay = 0,
+ backgroundAndHandleAlphaDuration = 0;
if (duration > 0) {
if (animationType == TRANSITION_HANDLE_FADE) {
// When fading, the handle fades in/out at the beginning of the transition with
// TASKBAR_STASH_ALPHA_DURATION.
- stashedHandleAlphaDuration = TASKBAR_STASH_ALPHA_DURATION;
+ backgroundAndHandleAlphaDuration = TASKBAR_STASH_ALPHA_DURATION;
// The iconAlphaDuration must be set to duration for the skippable interpolators
// below to work.
iconAlphaDuration = duration;
} else {
iconAlphaStartDelay = TASKBAR_STASH_ALPHA_START_DELAY;
iconAlphaDuration = TASKBAR_STASH_ALPHA_DURATION;
- stashedHandleAlphaDuration = TASKBAR_STASH_ALPHA_DURATION;
+ backgroundAndHandleAlphaDuration = TASKBAR_STASH_ALPHA_DURATION;
if (isStashed) {
if (animationType == TRANSITION_HOME_TO_APP) {
iconAlphaStartDelay = TASKBAR_STASH_ICON_ALPHA_HOME_TO_APP_START_DELAY;
}
- stashedHandleAlphaDelay = iconAlphaStartDelay;
- stashedHandleAlphaDuration = Math.max(0, duration - iconAlphaStartDelay);
+ backgroundAndHandleAlphaStartDelay = iconAlphaStartDelay;
+ backgroundAndHandleAlphaDuration = Math.max(0, duration - iconAlphaStartDelay);
}
}
}
play(as, mTaskbarStashedHandleAlpha.animateToValue(stashedHandleAlphaTarget),
- stashedHandleAlphaDelay,
- stashedHandleAlphaDuration, LINEAR);
+ backgroundAndHandleAlphaStartDelay,
+ backgroundAndHandleAlphaDuration, LINEAR);
+
+ play(as, mTaskbarBackgroundAlphaForStash.animateToValue(backgroundAlphaTarget),
+ backgroundAndHandleAlphaStartDelay,
+ backgroundAndHandleAlphaDuration, LINEAR);
// The rest of the animations might be "skipped" in TRANSITION_HANDLE_FADE transitions.
AnimatorSet skippable = as;
@@ -809,10 +813,13 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
if (isStashed) {
play(skippable, mControllers.taskbarSpringOnStashController.createSpringToStash(),
0, duration, LINEAR);
+ } else {
+ play(skippable, mControllers.taskbarSpringOnStashController.createResetAnimForUnstash(),
+ 0, duration, LINEAR);
}
mControllers.taskbarViewController.addRevealAnimToIsStashed(skippable, isStashed, duration,
- EMPHASIZED);
+ EMPHASIZED, animationType == TRANSITION_UNSTASH_SUW_MANUAL);
play(skippable, mControllers.stashedHandleViewController
.createRevealAnimToIsStashed(isStashed), 0, duration, EMPHASIZED);
@@ -866,15 +873,18 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
/**
* Creates and starts a partial unstash animation, hinting at the new state that will trigger
* when long press is detected.
+ *
* @param animateForward Whether we are going towards the new unstashed state or returning to
* the stashed state.
+ * @param forceUnstash Whether we force the unstash hint to animate.
*/
- public void startUnstashHint(boolean animateForward) {
+ protected void startUnstashHint(boolean animateForward, boolean forceUnstash) {
if (!isStashed()) {
// Already unstashed, no need to hint in that direction.
return;
}
- if (!canCurrentlyManuallyUnstash()) {
+ // TODO(b/270395798): Clean up after removing long-press unstashing code path.
+ if (!canCurrentlyManuallyUnstash() && !forceUnstash) {
// If any other flags are causing us to be stashed, long press won't cause us to
// unstash, so don't hint that it will.
return;
@@ -887,7 +897,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
private void onIsStashedChanged(boolean isStashed) {
mControllers.runAfterInit(() -> {
mControllers.stashedHandleViewController.onIsStashedChanged(isStashed);
- mControllers.taskbarInsetsController.onTaskbarWindowHeightOrInsetsChanged();
+ mControllers.taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
});
}
@@ -940,20 +950,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
}
}
- /**
- * Resets the flag if no system gesture is in progress.
- *
- * Otherwise, the reset should be deferred until after the gesture is finished.
- *
- * @see #setSystemGestureInProgress
- */
- public void resetFlagIfNoGestureInProgress(int flag) {
- if (!mIsSystemGestureInProgress) {
- updateStateForFlag(flag, false);
- applyState(mControllers.taskbarOverlayController.getCloseDuration());
- }
- }
-
/**
* When hiding the IME, delay the unstash animation to align with the end of the transition.
*/
@@ -1021,10 +1017,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
* unstashed.
*/
public void updateStateForFlag(int flag, boolean enabled) {
- if (flag == FLAG_IN_APP && TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.TASKBAR_IN_APP_STATE, String.format(
- "setting flag FLAG_IN_APP to: %b", enabled), new Exception());
- }
if (enabled) {
mState |= flag;
} else {
@@ -1057,17 +1049,15 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
mActivity.getStatsLogManager().logger().log(hasAnyFlag(FLAG_STASHED_IN_APP_AUTO)
? LAUNCHER_TRANSIENT_TASKBAR_HIDE
: LAUNCHER_TRANSIENT_TASKBAR_SHOW);
+ mControllers.taskbarAutohideSuspendController.updateFlag(
+ TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_TRANSIENT_TASKBAR,
+ !hasAnyFlag(FLAG_STASHED_IN_APP_AUTO));
}
}
private void notifyStashChange(boolean visible, boolean stashed) {
mSystemUiProxy.notifyTaskbarStatus(visible, stashed);
setUpTaskbarSystemAction(visible);
- // If stashing taskbar is caused by IME visibility, we could just skip updating rounded
- // corner insets since the rounded corners will be covered by IME during IME is showing and
- // taskbar will be restored back to unstashed when IME is hidden.
- mControllers.taskbarActivityContext.updateInsetRoundedCornerFrame(
- visible && !isStashedInAppIgnoringIme());
mControllers.rotationButtonController.onTaskbarStateChange(visible, stashed);
}
@@ -1154,7 +1144,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
}
private void onTaskbarTimeout(Alarm alarm) {
- if (mControllers.taskbarAutohideSuspendController.isSuspended()) {
+ if (mControllers.taskbarAutohideSuspendController.isTransientTaskbarStashingSuspended()) {
return;
}
updateAndAnimateTransientTaskbar(true);
@@ -1248,17 +1238,16 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
&& mLastStartedTransitionType == TRANSITION_DEFAULT
&& animationType != TRANSITION_DEFAULT;
+ // It is possible for stash=false to be requested by TRANSITION_HOME_TO_APP and
+ // TRANSITION_DEFAULT in quick succession. In this case, we should ignore
+ // transitionTypeChanged because the animations are exactly the same.
+ if (transitionTypeChanged
+ && (!mIsStashed && !isStashed)
+ && animationType == TRANSITION_HOME_TO_APP) {
+ transitionTypeChanged = false;
+ }
+
if (mIsStashed != isStashed || transitionTypeChanged) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.TASKBAR_IN_APP_STATE, String.format(
- "setState: mIsStashed=%b, isStashed=%b, "
- + "mAnimationType=%d, animationType=%d, duration=%d",
- mIsStashed,
- isStashed,
- mLastStartedTransitionType,
- animationType,
- duration));
- }
mIsStashed = isStashed;
mLastStartedTransitionType = animationType;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashViaTouchController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashViaTouchController.kt
index 23add74d91..1cc667211c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashViaTouchController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashViaTouchController.kt
@@ -19,13 +19,13 @@ import android.view.MotionEvent
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.anim.Interpolators.LINEAR
+import com.android.launcher3.testing.shared.ResourceUtils
import com.android.launcher3.touch.SingleAxisSwipeDetector
import com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE
import com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL
import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.TouchController
-import com.android.quickstep.inputconsumers.TaskbarStashInputConsumer
-import com.android.systemui.shared.testing.ResourceUtils
+import com.android.quickstep.inputconsumers.TaskbarUnstashInputConsumer
/**
* A helper [TouchController] for [TaskbarDragLayerController], specifically to handle touch events
@@ -34,7 +34,7 @@ import com.android.systemui.shared.testing.ResourceUtils
* or [MotionEvent.ACTION_OUTSIDE].
* - Touches inside Transient Taskbar bounds will stash if it is detected as a swipe down gesture.
*
- * Note: touches to *unstash* Taskbar are handled by [TaskbarStashInputConsumer].
+ * Note: touches to *unstash* Taskbar are handled by [TaskbarUnstashInputConsumer].
*/
class TaskbarStashViaTouchController(val controllers: TaskbarControllers) : TouchController {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java
index 4b18bb6556..065d1117c8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java
@@ -54,7 +54,6 @@ public class TaskbarTranslationController implements TaskbarControllers.Loggable
private boolean mHasSprungOnceThisGesture;
private @Nullable ValueAnimator mSpringBounce;
private boolean mGestureEnded;
- private boolean mGestureInProgress;
private boolean mAnimationToHomeRunning;
private final boolean mIsTransientTaskbar;
@@ -124,7 +123,6 @@ public class TaskbarTranslationController implements TaskbarControllers.Loggable
private void reset() {
mGestureEnded = false;
- mGestureInProgress = false;
mHasSprungOnceThisGesture = false;
}
@@ -136,24 +134,18 @@ public class TaskbarTranslationController implements TaskbarControllers.Loggable
}
/**
- * Returns {@code true} if we should reset the animation back to zero.
- *
- * Returns {@code false} if there is a gesture in progress, or if we are already animating
- * to 0 within the specified duration.
+ * Returns true if we will animate to zero before the input duration.
*/
- public boolean shouldResetBackToZero(long duration) {
- if (mGestureInProgress) {
- return false;
- }
+ public boolean willAnimateToZeroBefore(long duration) {
if (mSpringBounce != null && mSpringBounce.isRunning()) {
long springDuration = mSpringBounce.getDuration();
long current = mSpringBounce.getCurrentPlayTime();
- return (springDuration - current >= duration);
+ return (springDuration - current < duration);
}
if (mTranslationYForSwipe.isAnimatingToValue(0)) {
- return mTranslationYForSwipe.getRemainingTime() >= duration;
+ return mTranslationYForSwipe.getRemainingTime() < duration;
}
- return true;
+ return false;
}
/**
@@ -196,7 +188,6 @@ public class TaskbarTranslationController implements TaskbarControllers.Loggable
mAnimationToHomeRunning = false;
cancelSpringIfExists();
reset();
- mGestureInProgress = true;
}
/**
* Called when there is movement to move the taskbar.
@@ -220,7 +211,6 @@ public class TaskbarTranslationController implements TaskbarControllers.Loggable
mGestureEnded = true;
startSpring();
}
- mGestureInProgress = false;
}
}
@@ -232,7 +222,6 @@ public class TaskbarTranslationController implements TaskbarControllers.Loggable
pw.println(prefix + "\tmHasSprungOnceThisGesture=" + mHasSprungOnceThisGesture);
pw.println(prefix + "\tmAnimationToHomeRunning=" + mAnimationToHomeRunning);
pw.println(prefix + "\tmGestureEnded=" + mGestureEnded);
- pw.println(prefix + "\tmGestureInProgress=" + mGestureInProgress);
pw.println(prefix + "\tmSpringBounce is running=" + (mSpringBounce != null
&& mSpringBounce.isRunning()));
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 1435cb0996..7154731ee5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -30,9 +30,10 @@ import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.launcher3.Utilities;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
-import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.quickstep.util.GroupTask;
@@ -41,6 +42,7 @@ import com.android.quickstep.views.TaskView;
import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
import java.io.PrintWriter;
+import java.util.stream.Stream;
/**
* Base class for providing different taskbar UI
@@ -127,7 +129,7 @@ public class TaskbarUIController {
/**
* SysUI flags updated, see QuickStepContract.SYSUI_STATE_* values.
*/
- public void updateStateForSysuiFlags(int sysuiFlags, boolean skipAnim){
+ public void updateStateForSysuiFlags(int sysuiFlags) {
}
/**
@@ -202,11 +204,8 @@ public class TaskbarUIController {
return;
}
- ComponentKey componentToBeStaged = new ComponentKey(
- splitSelectSource.itemInfo.getTargetComponent(),
- splitSelectSource.itemInfo.user);
recentsView.getSplitSelectController().findLastActiveTaskAndRunCallback(
- componentToBeStaged,
+ splitSelectSource.itemInfo.getComponentKey(),
foundTask -> {
splitSelectSource.alreadyRunningTaskId = foundTask == null
? INVALID_TASK_ID
@@ -222,9 +221,8 @@ public class TaskbarUIController {
*/
public void triggerSecondAppForSplit(ItemInfoWithIcon info, Intent intent, View startingView) {
RecentsView recents = getRecentsView();
- ComponentKey secondAppComponent = new ComponentKey(info.getTargetComponent(), info.user);
recents.getSplitSelectController().findLastActiveTaskAndRunCallback(
- secondAppComponent,
+ info.getComponentKey(),
foundTask -> {
if (foundTask != null) {
TaskView foundTaskView = recents.getTaskViewByTaskId(foundTask.key.id);
@@ -318,4 +316,19 @@ public class TaskbarUIController {
}
return null;
}
+
+ /**
+ * Refreshes the resumed state of this ui controller.
+ */
+ public void refreshResumedState() {}
+
+ /**
+ * Returns a stream of split screen menu options appropriate to the device.
+ */
+ Stream> getSplitMenuOptions() {
+ return Utilities
+ .getSplitPositionOptions(mControllers.taskbarActivityContext.getDeviceProfile())
+ .stream()
+ .map(mControllers.taskbarPopupController::createSplitShortcutFactory);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
index 280e1debb8..466a05bf02 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
@@ -33,11 +33,14 @@ import java.io.PrintWriter;
public class TaskbarUnfoldAnimationController implements
TaskbarControllers.LoggableTaskbarController {
+ private static final float MAX_WIDTH_INSET_FRACTION = 0.035f;
+
private final ScopedUnfoldTransitionProgressProvider mScopedUnfoldTransitionProgressProvider;
private final NaturalRotationUnfoldProgressProvider mNaturalUnfoldTransitionProgressProvider;
private final UnfoldMoveFromCenterAnimator mMoveFromCenterAnimator;
private final TransitionListener mTransitionListener = new TransitionListener();
private TaskbarViewController mTaskbarViewController;
+ private TaskbarDragLayerController mTaskbarDragLayerController;
public TaskbarUnfoldAnimationController(BaseTaskbarContext context,
ScopedUnfoldTransitionProgressProvider source,
@@ -63,6 +66,7 @@ public class TaskbarUnfoldAnimationController implements
mTaskbarViewController.addOneTimePreDrawListener(() ->
mScopedUnfoldTransitionProgressProvider.setReadyToHandleTransition(true));
mNaturalUnfoldTransitionProgressProvider.addCallback(mTransitionListener);
+ mTaskbarDragLayerController = taskbarControllers.taskbarDragLayerController;
}
/**
@@ -99,11 +103,14 @@ public class TaskbarUnfoldAnimationController implements
public void onTransitionFinished() {
mMoveFromCenterAnimator.onTransitionFinished();
mMoveFromCenterAnimator.clearRegisteredViews();
+ mTaskbarDragLayerController.setBackgroundHorizontalInsets(0f);
}
@Override
public void onTransitionProgress(float progress) {
mMoveFromCenterAnimator.onTransitionProgress(progress);
+ float insetPercentage = (1 - progress) * MAX_WIDTH_INSET_FRACTION;
+ mTaskbarDragLayerController.setBackgroundHorizontalInsets(insetPercentage);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index fedeec88e4..9955b16f9a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -18,6 +18,8 @@ package com.android.launcher3.taskbar;
import static android.content.pm.PackageManager.FEATURE_PC;
import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
+import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
+
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
@@ -120,7 +122,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
mActivityContext = ActivityContext.lookupContext(context);
mIconLayoutBounds = mActivityContext.getTransientTaskbarBounds();
Resources resources = getResources();
- boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivityContext);
+ boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivityContext)
+ && !TaskbarManager.isPhoneMode(mActivityContext.getDeviceProfile());
mIsRtl = Utilities.isRtl(resources);
mTransientTaskbarMinWidth = context.getResources().getDimension(
R.dimen.transient_taskbar_min_width);
@@ -133,12 +136,13 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
int actualMargin = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
int actualIconSize = mActivityContext.getDeviceProfile().taskbarIconSize;
+ int visualIconSize = (int) (actualIconSize * ICON_VISIBLE_AREA_FACTOR);
mIconTouchSize = Math.max(actualIconSize,
resources.getDimensionPixelSize(R.dimen.taskbar_icon_min_touch_size));
// We layout the icons to be of mIconTouchSize in width and height
- mItemMarginLeftRight = actualMargin - (mIconTouchSize - actualIconSize) / 2;
+ mItemMarginLeftRight = actualMargin - (mIconTouchSize - visualIconSize) / 2;
mItemPadding = (mIconTouchSize - actualIconSize) / 2;
mFolderLeaveBehindColor = Themes.getAttrColor(mActivityContext,
@@ -217,6 +221,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
}
protected void init(TaskbarViewController.TaskbarViewCallbacks callbacks) {
+ // set taskbar pane title so that accessibility service know it window and focuses.
+ setAccessibilityPaneTitle(getContext().getString(R.string.taskbar_a11y_title));
mControllerCallbacks = callbacks;
mIconClickListener = mControllerCallbacks.getIconOnClickListener();
mIconLongClickListener = mControllerCallbacks.getIconOnLongClickListener();
@@ -227,7 +233,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
mAllAppsButton.setOnClickListener(mControllerCallbacks.getAllAppsButtonClickListener());
}
if (mTaskbarDivider != null) {
- //TODO(b/265434705): set long press listener
+ mTaskbarDivider.setOnLongClickListener(
+ mControllerCallbacks.getTaskbarDividerLongClickListener());
}
}
@@ -522,6 +529,14 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
return mAllAppsButton;
}
+ /**
+ * Returns the taskbar divider in the taskbar.
+ */
+ @Nullable
+ public View getTaskbarDividerView() {
+ return mTaskbarDivider;
+ }
+
/**
* Returns the QSB in the taskbar.
*/
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 6eb409e35d..4abd9957b2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -30,6 +30,7 @@ import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VAL
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_ALIGNMENT_ANIM;
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_REVEAL_ANIM;
+import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
@@ -286,7 +287,7 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
}
private ValueAnimator createRevealAnimForView(View view, boolean isStashed, float newWidth,
- boolean isQsb) {
+ boolean isQsb, boolean dispatchOnAnimationStart) {
Rect viewBounds = new Rect(0, 0, view.getWidth(), view.getHeight());
int centerY = viewBounds.centerY();
int halfHandleHeight = mStashedHandleHeight / 2;
@@ -318,8 +319,24 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
: 0f;
float stashedRadius = stashedRect.height() / 2f;
- return new RoundedRectRevealOutlineProvider(radius, stashedRadius, viewBounds, stashedRect)
+ ValueAnimator reveal = new RoundedRectRevealOutlineProvider(radius,
+ stashedRadius, viewBounds, stashedRect)
.createRevealAnimator(view, !isStashed, 0);
+ // SUW animation does not dispatch animation start until *after* the animation is complete.
+ // In order to work properly, the reveal animation start needs to be called immediately.
+ if (dispatchOnAnimationStart) {
+ for (Animator.AnimatorListener listener : reveal.getListeners()) {
+ listener.onAnimationStart(reveal);
+ }
+ }
+ return reveal;
+ }
+
+ /**
+ * Defers any updates to the UI for the setup wizard animation.
+ */
+ public void setDeferUpdatesForSUW(boolean defer) {
+ mModelCallbacks.setDeferUpdatesForSUW(defer);
}
/**
@@ -332,7 +349,7 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
* @param interpolator The interpolator to use for all animations.
*/
public void addRevealAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration,
- Interpolator interpolator) {
+ Interpolator interpolator, boolean dispatchOnAnimationStart) {
AnimatorSet reveal = new AnimatorSet();
Rect stashedBounds = new Rect();
@@ -349,8 +366,8 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
boolean isQsb = child == mTaskbarView.getQsb();
// Crop the icons to/from the nav handle shape.
- reveal.play(createRevealAnimForView(child, isStashed, newChildWidth, isQsb)
- .setDuration(duration));
+ reveal.play(createRevealAnimForView(child, isStashed, newChildWidth, isQsb,
+ dispatchOnAnimationStart).setDuration(duration));
// Translate the icons to/from their locations as the "nav handle."
@@ -459,12 +476,14 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
for (int i = 0; i < mTaskbarView.getChildCount(); i++) {
View child = mTaskbarView.getChildAt(i);
boolean isAllAppsButton = child == mTaskbarView.getAllAppsButtonView();
+ boolean isTaskbarDividerView = child == mTaskbarView.getTaskbarDividerView();
if (!mIsHotseatIconOnTopWhenAligned) {
// When going to home, the EMPHASIZED interpolator in TaskbarLauncherStateController
// plays iconAlignment to 1 really fast, therefore moving the fading towards the end
// to avoid icons disappearing rather than fading out visually.
setter.setViewAlpha(child, 0, Interpolators.clampToProgress(LINEAR, 0.8f, 1f));
- } else if ((isAllAppsButton && !FeatureFlags.ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.get())) {
+ } else if ((isAllAppsButton && !FeatureFlags.ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.get())
+ || (isTaskbarDividerView && FeatureFlags.ENABLE_TASKBAR_PINNING.get())) {
if (!isToHome
&& mIsHotseatIconOnTopWhenAligned
&& mControllers.taskbarStashController.isStashed()) {
@@ -637,7 +656,14 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
public View.OnClickListener getAllAppsButtonClickListener() {
return v -> {
mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP);
- mControllers.taskbarAllAppsController.show();
+ mControllers.taskbarAllAppsController.toggle();
+ };
+ }
+
+ public View.OnLongClickListener getTaskbarDividerLongClickListener() {
+ return v -> {
+ mControllers.taskbarPinningController.showPinningView(v);
+ return true;
};
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContainerView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContainerView.java
index eeca329e80..b4b83f6f24 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContainerView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContainerView.java
@@ -18,17 +18,22 @@ package com.android.launcher3.taskbar.allapps;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
-import android.view.WindowInsets;
-import com.android.launcher3.DeviceProfile;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.R;
import com.android.launcher3.allapps.ActivityAllAppsContainerView;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
+import java.util.Optional;
+
/** All apps container accessible from taskbar. */
public class TaskbarAllAppsContainerView extends
ActivityAllAppsContainerView {
+ private @Nullable OnInvalidateHeaderListener mOnInvalidateHeaderListener;
+
public TaskbarAllAppsContainerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
@@ -37,14 +42,16 @@ public class TaskbarAllAppsContainerView extends
super(context, attrs, defStyleAttr);
}
- @Override
- public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- setInsets(insets.getInsets(WindowInsets.Type.systemBars()).toRect());
- return super.onApplyWindowInsets(insets);
+ void setOnInvalidateHeaderListener(OnInvalidateHeaderListener onInvalidateHeaderListener) {
+ mOnInvalidateHeaderListener = onInvalidateHeaderListener;
}
@Override
protected View inflateSearchBox() {
+ if (isSearchSupported()) {
+ return super.inflateSearchBox();
+ }
+
// Remove top padding of header, since we do not have any search
mHeader.setPadding(mHeader.getPaddingLeft(), 0,
mHeader.getPaddingRight(), mHeader.getPaddingBottom());
@@ -57,15 +64,15 @@ public class TaskbarAllAppsContainerView extends
}
@Override
- protected boolean isSearchSupported() {
- return false;
+ public void invalidateHeader() {
+ super.invalidateHeader();
+ Optional.ofNullable(mOnInvalidateHeaderListener).ifPresent(
+ OnInvalidateHeaderListener::onInvalidateHeader);
}
@Override
- protected void updateBackground(DeviceProfile deviceProfile) {
- super.updateBackground(deviceProfile);
- // TODO(b/240670050): Remove this and add header protection for the taskbar entrypoint.
- mBottomSheetBackground.setBackgroundResource(R.drawable.bg_rounded_corner_bottom_sheet);
+ protected boolean isSearchSupported() {
+ return FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get();
}
@Override
@@ -73,4 +80,8 @@ public class TaskbarAllAppsContainerView extends
// All apps is always open
return true;
}
+
+ interface OnInvalidateHeaderListener {
+ void onInvalidateHeader();
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
index 4a95a8f718..e0a502bdb2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
@@ -24,8 +24,12 @@ import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.taskbar.TaskbarControllers;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
+import com.android.launcher3.util.PackageUserKey;
+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.
@@ -51,6 +55,8 @@ public final class TaskbarAllAppsController {
private boolean mDisallowGlobalDrag;
private boolean mDisallowLongClick;
+ private Map mPackageUserKeytoUidMap = Collections.emptyMap();
+
/** Initialize the controller. */
public void init(TaskbarControllers controllers, boolean allAppsVisible) {
mControllers = controllers;
@@ -65,11 +71,12 @@ public final class TaskbarAllAppsController {
}
/** Updates the current {@link AppInfo} instances. */
- public void setApps(AppInfo[] apps, int flags) {
+ public void setApps(AppInfo[] apps, int flags, Map map) {
mApps = apps;
mAppsModelFlags = flags;
+ mPackageUserKeytoUidMap = map;
if (mAppsView != null) {
- mAppsView.getAppsStore().setApps(mApps, mAppsModelFlags);
+ mAppsView.getAppsStore().setApps(mApps, mAppsModelFlags, mPackageUserKeytoUidMap);
}
}
@@ -91,9 +98,20 @@ public final class TaskbarAllAppsController {
}
}
- /** Opens the {@link TaskbarAllAppsContainerView} in a new window. */
- public void show() {
- show(true);
+ /** Updates the current notification dots. */
+ public void updateNotificationDots(Predicate updatedDots) {
+ if (mAppsView != null) {
+ mAppsView.getAppsStore().updateNotificationDots(updatedDots);
+ }
+ }
+
+ /** Toggles visibility of {@link TaskbarAllAppsContainerView} in the overlay window. */
+ public void toggle() {
+ if (isOpen()) {
+ mSlideInView.close(true);
+ } else {
+ show(true);
+ }
}
/** Returns {@code true} if All Apps is open. */
@@ -123,7 +141,7 @@ public final class TaskbarAllAppsController {
viewController.show(animate);
mAppsView = overlayContext.getAppsView();
- mAppsView.getAppsStore().setApps(mApps, mAppsModelFlags);
+ mAppsView.getAppsStore().setApps(mApps, mAppsModelFlags, mPackageUserKeytoUidMap);
mAppsView.getFloatingHeaderView()
.findFixedRowByType(PredictionRowView.class)
.setPredictedApps(mPredictedApps);
@@ -135,10 +153,15 @@ public final class TaskbarAllAppsController {
overlayContext.getDragController().setDisallowLongClick(mDisallowLongClick);
}
-
@VisibleForTesting
public int getTaskbarAllAppsTopPadding() {
// Allow null-pointer since this should only be null if the apps view is not showing.
return mAppsView.getActiveRecyclerView().getClipBounds().top;
}
+
+ @VisibleForTesting
+ public int getTaskbarAllAppsScroll() {
+ // Allow null-pointer since this should only be null if the apps view is not showing.
+ return mAppsView.getActiveRecyclerView().computeVerticalScrollOffset();
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
index c1597b6507..cfa1027dcc 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
@@ -19,14 +19,17 @@ import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
import android.animation.PropertyValuesHolder;
import android.content.Context;
+import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.animation.Interpolator;
+import android.window.OnBackInvokedDispatcher;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.taskbar.allapps.TaskbarAllAppsViewController.TaskbarAllAppsCallbacks;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
import com.android.launcher3.views.AbstractSlideInView;
@@ -97,6 +100,11 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView {
@@ -100,11 +101,6 @@ final class TaskbarAllAppsViewController {
if (DisplayController.isTransientTaskbar(mContext)) {
mTaskbarStashController.updateStateForFlag(FLAG_STASHED_IN_TASKBAR_ALL_APPS, false);
mTaskbarStashController.applyState(mOverlayController.getCloseDuration());
- } else {
- // Post in case view is closing due to gesture navigation. If a gesture is in
- // progress, wait to unstash until after the gesture is finished.
- MAIN_EXECUTOR.post(() -> mTaskbarStashController.resetFlagIfNoGestureInProgress(
- FLAG_STASHED_IN_TASKBAR_ALL_APPS));
}
});
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
new file mode 100644
index 0000000000..7397159f42
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2023 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.bubbles
+
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.ColorFilter
+import android.graphics.Paint
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.ShapeDrawable
+import com.android.launcher3.R
+import com.android.launcher3.Utilities
+import com.android.launcher3.Utilities.mapToRange
+import com.android.launcher3.anim.Interpolators
+import com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound
+import com.android.launcher3.taskbar.TaskbarActivityContext
+import com.android.wm.shell.common.TriangleShape
+
+/** Drawable for the background of the bubble bar. */
+class BubbleBarBackground(context: TaskbarActivityContext, private val backgroundHeight: Float) :
+ Drawable() {
+
+ private val DARK_THEME_SHADOW_ALPHA = 51f
+ private val LIGHT_THEME_SHADOW_ALPHA = 25f
+
+ private val paint: Paint = Paint()
+ private val pointerSize: Float
+
+ private val shadowAlpha: Float
+ private var shadowBlur = 0f
+ private var keyShadowDistance = 0f
+
+ var arrowPositionX: Float = 0f
+ private set
+ private var showingArrow: Boolean = false
+ private var arrowDrawable: ShapeDrawable
+
+ init {
+ paint.color = context.getColor(R.color.taskbar_background)
+ paint.flags = Paint.ANTI_ALIAS_FLAG
+ paint.style = Paint.Style.FILL
+
+ val res = context.resources
+ shadowBlur = res.getDimension(R.dimen.transient_taskbar_shadow_blur)
+ keyShadowDistance = res.getDimension(R.dimen.transient_taskbar_key_shadow_distance)
+ pointerSize = res.getDimension(R.dimen.bubblebar_pointer_size)
+
+ shadowAlpha =
+ if (Utilities.isDarkTheme(context)) DARK_THEME_SHADOW_ALPHA
+ else LIGHT_THEME_SHADOW_ALPHA
+
+ arrowDrawable =
+ ShapeDrawable(TriangleShape.create(pointerSize, pointerSize, /* pointUp= */ true))
+ arrowDrawable.setBounds(0, 0, pointerSize.toInt(), pointerSize.toInt())
+ arrowDrawable.paint.flags = Paint.ANTI_ALIAS_FLAG
+ arrowDrawable.paint.style = Paint.Style.FILL
+ arrowDrawable.paint.color = context.getColor(R.color.taskbar_background)
+ }
+
+ fun showArrow(show: Boolean) {
+ showingArrow = show
+ }
+
+ fun setArrowPosition(x: Float) {
+ arrowPositionX = x
+ }
+
+ /** Draws the background with the given paint and height, on the provided canvas. */
+ override fun draw(canvas: Canvas) {
+ canvas.save()
+
+ // TODO (b/277359345): Should animate the alpha similar to taskbar (see TaskbarDragLayer)
+ // Draw shadows.
+ val newShadowAlpha =
+ mapToRange(paint.alpha.toFloat(), 0f, 255f, 0f, shadowAlpha, Interpolators.LINEAR)
+ paint.setShadowLayer(
+ shadowBlur,
+ 0f,
+ keyShadowDistance,
+ setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha))
+ )
+ arrowDrawable.paint.setShadowLayer(
+ shadowBlur,
+ 0f,
+ keyShadowDistance,
+ setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha))
+ )
+
+ // Draw background.
+ val radius = backgroundHeight / 2f
+ canvas.drawRoundRect(
+ 0f,
+ 0f,
+ canvas.width.toFloat(),
+ canvas.height.toFloat(),
+ radius,
+ radius,
+ paint
+ )
+
+ if (showingArrow) {
+ // Draw arrow.
+ val transX = arrowPositionX - pointerSize / 2f
+ canvas.translate(transX, -pointerSize)
+ arrowDrawable.draw(canvas)
+ }
+
+ canvas.restore()
+ }
+
+ override fun getOpacity(): Int {
+ return paint.alpha
+ }
+
+ override fun setAlpha(alpha: Int) {
+ paint.alpha = alpha
+ }
+
+ override fun setColorFilter(colorFilter: ColorFilter?) {
+ paint.colorFilter = colorFilter
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBubble.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBubble.kt
new file mode 100644
index 0000000000..3cd5f75934
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBubble.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.bubbles
+
+import android.graphics.Bitmap
+import android.graphics.Path
+import com.android.wm.shell.common.bubbles.BubbleInfo
+
+/** Contains state info about a bubble in the bubble bar as well as presentation information. */
+data class BubbleBarBubble(
+ val info: BubbleInfo,
+ val view: BubbleView,
+ val badge: Bitmap,
+ val icon: Bitmap,
+ val dotColor: Int,
+ val dotPath: Path,
+ val appName: String
+) {
+
+ val key: String = info.key
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
new file mode 100644
index 0000000000..6d196924b3
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -0,0 +1,437 @@
+/*
+ * Copyright (C) 2023 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.bubbles;
+
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_PERSONS_DATA;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER;
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+
+import static com.android.launcher3.icons.FastBitmapDrawable.WHITE_SCRIM_ALPHA;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
+
+import android.annotation.BinderThread;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.PathParser;
+import android.view.LayoutInflater;
+
+import com.android.internal.graphics.ColorUtils;
+import com.android.launcher3.R;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.BubbleIconFactory;
+import com.android.launcher3.shortcuts.ShortcutRequest;
+import com.android.launcher3.taskbar.TaskbarControllers;
+import com.android.launcher3.util.Executors.SimpleThreadFactory;
+import com.android.quickstep.SystemUiProxy;
+import com.android.wm.shell.bubbles.IBubblesListener;
+import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
+import com.android.wm.shell.common.bubbles.BubbleInfo;
+import com.android.wm.shell.common.bubbles.RemovedBubble;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+/**
+ * This registers a listener with SysUIProxy to get information about changes to the bubble
+ * stack state from WMShell (SysUI). The controller is also responsible for loading the necessary
+ * information to render each of the bubbles & dispatches changes to
+ * {@link BubbleBarViewController} which will then update {@link BubbleBarView} as needed.
+ *
+ * For details around the behavior of the bubble bar, see {@link BubbleBarView}.
+ */
+public class BubbleBarController extends IBubblesListener.Stub {
+
+ private static final String TAG = BubbleBarController.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ // Whether bubbles are showing in the bubble bar from launcher
+ public static final boolean BUBBLE_BAR_ENABLED =
+ SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
+
+ private static final int MASK_HIDE_BUBBLE_BAR = SYSUI_STATE_BOUNCER_SHOWING
+ | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
+ | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
+ | SYSUI_STATE_IME_SHOWING
+ | SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
+ | SYSUI_STATE_QUICK_SETTINGS_EXPANDED
+ | SYSUI_STATE_IME_SWITCHER_SHOWING;
+
+ private static final int MASK_HIDE_HANDLE_VIEW = SYSUI_STATE_BOUNCER_SHOWING
+ | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
+ | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
+
+ private static final int MASK_SYSUI_LOCKED = SYSUI_STATE_BOUNCER_SHOWING
+ | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
+ | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
+
+ private final Context mContext;
+ private final BubbleBarView mBarView;
+ private final ArrayMap mBubbles = new ArrayMap<>();
+
+ private static final Executor BUBBLE_STATE_EXECUTOR = Executors.newSingleThreadExecutor(
+ new SimpleThreadFactory("BubbleStateUpdates-", THREAD_PRIORITY_BACKGROUND));
+ private final Executor mMainExecutor;
+ private final LauncherApps mLauncherApps;
+ private final BubbleIconFactory mIconFactory;
+
+ private BubbleBarBubble mSelectedBubble;
+
+ private BubbleBarViewController mBubbleBarViewController;
+ private BubbleStashController mBubbleStashController;
+ private BubbleStashedHandleViewController mBubbleStashedHandleViewController;
+
+ /**
+ * Similar to {@link BubbleBarUpdate} but rather than {@link BubbleInfo}s it uses
+ * {@link BubbleBarBubble}s so that it can be used to update the views.
+ */
+ private static class BubbleBarViewUpdate {
+ boolean expandedChanged;
+ boolean expanded;
+ String selectedBubbleKey;
+ String suppressedBubbleKey;
+ String unsuppressedBubbleKey;
+ List removedBubbles;
+ List bubbleKeysInOrder;
+
+ // These need to be loaded in the background
+ BubbleBarBubble addedBubble;
+ BubbleBarBubble updatedBubble;
+ List currentBubbles;
+
+ BubbleBarViewUpdate(BubbleBarUpdate update) {
+ expandedChanged = update.expandedChanged;
+ expanded = update.expanded;
+ selectedBubbleKey = update.selectedBubbleKey;
+ suppressedBubbleKey = update.suppressedBubbleKey;
+ unsuppressedBubbleKey = update.unsupressedBubbleKey;
+ removedBubbles = update.removedBubbles;
+ bubbleKeysInOrder = update.bubbleKeysInOrder;
+ }
+ }
+
+ public BubbleBarController(Context context, BubbleBarView bubbleView) {
+ mContext = context;
+ mBarView = bubbleView; // Need the view for inflating bubble views.
+
+ if (BUBBLE_BAR_ENABLED) {
+ SystemUiProxy.INSTANCE.get(context).setBubblesListener(this);
+ }
+ mMainExecutor = MAIN_EXECUTOR;
+ mLauncherApps = context.getSystemService(LauncherApps.class);
+ mIconFactory = new BubbleIconFactory(context,
+ context.getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size),
+ context.getResources().getDimensionPixelSize(R.dimen.bubblebar_badge_size),
+ context.getResources().getColor(R.color.important_conversation),
+ context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.importance_ring_stroke_width));
+ }
+
+ public void onDestroy() {
+ SystemUiProxy.INSTANCE.get(mContext).setBubblesListener(null);
+ }
+
+ public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
+ mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
+ mBubbleStashController = bubbleControllers.bubbleStashController;
+ mBubbleStashedHandleViewController = bubbleControllers.bubbleStashedHandleViewController;
+
+ bubbleControllers.runAfterInit(() -> {
+ mBubbleBarViewController.setHiddenForBubbles(!BUBBLE_BAR_ENABLED);
+ mBubbleStashedHandleViewController.setHiddenForBubbles(!BUBBLE_BAR_ENABLED);
+ });
+ }
+
+ /**
+ * Updates the bubble bar, handle bar, and stash controllers based on sysui state flags.
+ */
+ public void updateStateForSysuiFlags(int flags) {
+ boolean hideBubbleBar = (flags & MASK_HIDE_BUBBLE_BAR) != 0;
+ mBubbleBarViewController.setHiddenForSysui(hideBubbleBar);
+
+ boolean hideHandleView = (flags & MASK_HIDE_HANDLE_VIEW) != 0;
+ mBubbleStashedHandleViewController.setHiddenForSysui(hideHandleView);
+
+ boolean sysuiLocked = (flags & MASK_SYSUI_LOCKED) != 0;
+ mBubbleStashController.onSysuiLockedStateChange(sysuiLocked);
+ }
+
+ //
+ // Bubble data changes
+ //
+
+ @BinderThread
+ @Override
+ public void onBubbleStateChange(Bundle bundle) {
+ bundle.setClassLoader(BubbleBarUpdate.class.getClassLoader());
+ BubbleBarUpdate update = bundle.getParcelable("update", BubbleBarUpdate.class);
+ BubbleBarViewUpdate viewUpdate = new BubbleBarViewUpdate(update);
+ if (update.addedBubble != null
+ || update.updatedBubble != null
+ || !update.currentBubbleList.isEmpty()) {
+ // We have bubbles to load
+ BUBBLE_STATE_EXECUTOR.execute(() -> {
+ if (update.addedBubble != null) {
+ viewUpdate.addedBubble = populateBubble(update.addedBubble, mContext, mBarView);
+ }
+ if (update.updatedBubble != null) {
+ viewUpdate.updatedBubble =
+ populateBubble(update.updatedBubble, mContext, mBarView);
+ }
+ if (update.currentBubbleList != null && !update.currentBubbleList.isEmpty()) {
+ List currentBubbles = new ArrayList<>();
+ for (int i = 0; i < update.currentBubbleList.size(); i++) {
+ BubbleBarBubble b =
+ populateBubble(update.currentBubbleList.get(i), mContext, mBarView);
+ currentBubbles.add(b);
+ }
+ viewUpdate.currentBubbles = currentBubbles;
+ }
+ mMainExecutor.execute(() -> applyViewChanges(viewUpdate));
+ });
+ } else {
+ // No bubbles to load, immediately apply the changes.
+ BUBBLE_STATE_EXECUTOR.execute(
+ () -> mMainExecutor.execute(() -> applyViewChanges(viewUpdate)));
+ }
+ }
+
+ private void applyViewChanges(BubbleBarViewUpdate update) {
+ final boolean isCollapsed = (update.expandedChanged && !update.expanded)
+ || (!update.expandedChanged && !mBubbleBarViewController.isExpanded());
+ BubbleBarBubble bubbleToSelect = null;
+ if (!update.removedBubbles.isEmpty()) {
+ for (int i = 0; i < update.removedBubbles.size(); i++) {
+ RemovedBubble removedBubble = update.removedBubbles.get(i);
+ BubbleBarBubble bubble = mBubbles.remove(removedBubble.getKey());
+ if (bubble != null) {
+ mBubbleBarViewController.removeBubble(bubble);
+ } else {
+ Log.w(TAG, "trying to remove bubble that doesn't exist: "
+ + removedBubble.getKey());
+ }
+ }
+ }
+ if (update.addedBubble != null) {
+ mBubbles.put(update.addedBubble.getKey(), update.addedBubble);
+ mBubbleBarViewController.addBubble(update.addedBubble);
+ if (isCollapsed) {
+ // If we're collapsed, the most recently added bubble will be selected.
+ bubbleToSelect = update.addedBubble;
+ }
+
+ }
+ if (update.currentBubbles != null && !update.currentBubbles.isEmpty()) {
+ // Iterate in reverse because new bubbles are added in front and the list is in order.
+ for (int i = update.currentBubbles.size() - 1; i >= 0; i--) {
+ BubbleBarBubble bubble = update.currentBubbles.get(i);
+ if (bubble != null) {
+ mBubbles.put(bubble.getKey(), bubble);
+ mBubbleBarViewController.addBubble(bubble);
+ if (isCollapsed) {
+ // If we're collapsed, the most recently added bubble will be selected.
+ bubbleToSelect = bubble;
+ }
+ } else {
+ Log.w(TAG, "trying to add bubble but null after loading! "
+ + update.addedBubble.getKey());
+ }
+ }
+ }
+
+ // Adds and removals have happened, update visibility before any other visual changes.
+ mBubbleBarViewController.setHiddenForBubbles(mBubbles.isEmpty());
+ mBubbleStashedHandleViewController.setHiddenForBubbles(mBubbles.isEmpty());
+
+ if (update.updatedBubble != null) {
+ // TODO: (b/269670235) handle updates:
+ // (1) if content / icons change -- requires reload & add back in place
+ // (2) if showing update dot changes -- tell the view to hide / show the dot
+ }
+ if (update.bubbleKeysInOrder != null && !update.bubbleKeysInOrder.isEmpty()) {
+ // Create the new list
+ List newOrder = update.bubbleKeysInOrder.stream()
+ .map(mBubbles::get).filter(Objects::nonNull).toList();
+ if (!newOrder.isEmpty()) {
+ mBubbleBarViewController.reorderBubbles(newOrder);
+ }
+ }
+ if (update.suppressedBubbleKey != null) {
+ // TODO: (b/273316505) handle suppression
+ }
+ if (update.unsuppressedBubbleKey != null) {
+ // TODO: (b/273316505) handle suppression
+ }
+ if (update.selectedBubbleKey != null) {
+ if (mSelectedBubble != null
+ && !update.selectedBubbleKey.equals(mSelectedBubble.getKey())) {
+ BubbleBarBubble newlySelected = mBubbles.get(update.selectedBubbleKey);
+ if (newlySelected != null) {
+ bubbleToSelect = newlySelected;
+ } else {
+ Log.w(TAG, "trying to select bubble that doesn't exist:"
+ + update.selectedBubbleKey);
+ }
+ }
+ }
+ if (bubbleToSelect != null) {
+ setSelectedBubble(bubbleToSelect);
+ }
+ if (update.expandedChanged) {
+ if (update.expanded != mBubbleBarViewController.isExpanded()) {
+ mBubbleBarViewController.setExpandedFromSysui(update.expanded);
+ } else {
+ Log.w(TAG, "expansion was changed but is the same");
+ }
+ }
+ }
+
+ /**
+ * Sets the bubble that should be selected. This notifies the views, it does not notify
+ * WMShell that the selection has changed, that should go through
+ * {@link SystemUiProxy#showBubble}.
+ */
+ public void setSelectedBubble(BubbleBarBubble b) {
+ if (!Objects.equals(b, mSelectedBubble)) {
+ if (DEBUG) Log.w(TAG, "selectingBubble: " + b.getKey());
+ mSelectedBubble = b;
+ mBubbleBarViewController.updateSelectedBubble(mSelectedBubble);
+ }
+ }
+
+ /**
+ * Returns the selected bubble or null if no bubble is selected.
+ */
+ @Nullable
+ public String getSelectedBubbleKey() {
+ if (mSelectedBubble != null) {
+ return mSelectedBubble.getKey();
+ }
+ return null;
+ }
+
+ //
+ // Loading data for the bubbles
+ //
+
+ @Nullable
+ private BubbleBarBubble populateBubble(BubbleInfo b, Context context, BubbleBarView bbv) {
+ String appName;
+ Bitmap badgeBitmap;
+ Bitmap bubbleBitmap;
+ Path dotPath;
+ int dotColor;
+
+ boolean isImportantConvo = b.isImportantConversation();
+
+ ShortcutRequest.QueryResult result = new ShortcutRequest(context,
+ new UserHandle(b.getUserId()))
+ .forPackage(b.getPackageName(), b.getShortcutId())
+ .query(FLAG_MATCH_DYNAMIC
+ | FLAG_MATCH_PINNED_BY_ANY_LAUNCHER
+ | FLAG_MATCH_CACHED
+ | FLAG_GET_PERSONS_DATA);
+
+ ShortcutInfo shortcutInfo = result.size() > 0 ? result.get(0) : null;
+ if (shortcutInfo == null) {
+ Log.w(TAG, "No shortcutInfo found for bubble: " + b.getKey()
+ + " with shortcutId: " + b.getShortcutId());
+ }
+
+ ApplicationInfo appInfo;
+ try {
+ appInfo = mLauncherApps.getApplicationInfo(
+ b.getPackageName(),
+ 0,
+ new UserHandle(b.getUserId()));
+ } catch (PackageManager.NameNotFoundException e) {
+ // If we can't find package... don't think we should show the bubble.
+ Log.w(TAG, "Unable to find packageName: " + b.getPackageName());
+ return null;
+ }
+ if (appInfo == null) {
+ Log.w(TAG, "Unable to find appInfo: " + b.getPackageName());
+ return null;
+ }
+ PackageManager pm = context.getPackageManager();
+ appName = String.valueOf(appInfo.loadLabel(pm));
+ Drawable appIcon = appInfo.loadUnbadgedIcon(pm);
+ Drawable badgedIcon = pm.getUserBadgedIcon(appIcon, new UserHandle(b.getUserId()));
+
+ // Badged bubble image
+ Drawable bubbleDrawable = mIconFactory.getBubbleDrawable(context, shortcutInfo,
+ b.getIcon());
+ if (bubbleDrawable == null) {
+ // Default to app icon
+ bubbleDrawable = appIcon;
+ }
+
+ BitmapInfo badgeBitmapInfo = mIconFactory.getBadgeBitmap(badgedIcon, isImportantConvo);
+ badgeBitmap = badgeBitmapInfo.icon;
+
+ float[] bubbleBitmapScale = new float[1];
+ bubbleBitmap = mIconFactory.getBubbleBitmap(bubbleDrawable, bubbleBitmapScale);
+
+ // Dot color & placement
+ Path iconPath = PathParser.createPathFromPathData(
+ context.getResources().getString(
+ com.android.internal.R.string.config_icon_mask));
+ Matrix matrix = new Matrix();
+ float scale = bubbleBitmapScale[0];
+ float radius = BubbleView.DEFAULT_PATH_SIZE / 2f;
+ matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
+ radius /* pivot y */);
+ iconPath.transform(matrix);
+ dotPath = iconPath;
+ dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color,
+ Color.WHITE, WHITE_SCRIM_ALPHA);
+
+
+ LayoutInflater inflater = LayoutInflater.from(context);
+ BubbleView bubbleView = (BubbleView) inflater.inflate(
+ R.layout.bubblebar_item_view, bbv, false /* attachToRoot */);
+
+ BubbleBarBubble bubble = new BubbleBarBubble(b, bubbleView,
+ badgeBitmap, bubbleBitmap, dotColor, dotPath, appName);
+ bubbleView.setBubble(bubble);
+ return bubble;
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
new file mode 100644
index 0000000000..0e1e0e1a1b
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2023 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.bubbles;
+
+import android.animation.ValueAnimator;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import com.android.launcher3.R;
+import com.android.launcher3.taskbar.TaskbarActivityContext;
+import com.android.launcher3.views.ActivityContext;
+
+import java.util.List;
+
+/**
+ * The view that holds all the bubble views. Modifying this view should happen through
+ * {@link BubbleBarViewController}. Updates to the bubbles themselves (adds, removes, updates,
+ * selection) should happen through {@link BubbleBarController} which is the source of truth
+ * for state information about the bubbles.
+ *
+ * The bubble bar has a couple of visual states:
+ * - stashed as a handle
+ * - unstashed but collapsed, in this state the bar is showing but the bubbles are stacked within it
+ * - unstashed and expanded, in this state the bar is showing and the bubbles are shown in a row
+ * with one of the bubbles being selected. Additionally, WMShell will display the expanded bubble
+ * view above the bar.
+ *
+ * The bubble bar has some behavior related to taskbar:
+ * - When taskbar is unstashed, bubble bar will also become unstashed (but in its "collapsed"
+ * state)
+ * - When taskbar is stashed, bubble bar will also become stashed (unless bubble bar is in its
+ * "expanded" state)
+ * - When bubble bar is in its "expanded" state, taskbar becomes stashed
+ *
+ * If there are no bubbles, the bubble bar and bubble stashed handle are not shown. Additionally
+ * the bubble bar and stashed handle are not shown on lockscreen.
+ *
+ * When taskbar is in persistent or 3 button nav mode, the bubble bar is not available, and instead
+ * the bubbles are shown fully by WMShell in their floating mode.
+ */
+public class BubbleBarView extends FrameLayout {
+
+ private static final String TAG = BubbleBarView.class.getSimpleName();
+
+ // TODO: (b/273594744) calculate the amount of space we have and base the max on that
+ // if it's smaller than 5.
+ private static final int MAX_BUBBLES = 5;
+ private static final int ARROW_POSITION_ANIMATION_DURATION_MS = 200;
+
+ private final TaskbarActivityContext mActivityContext;
+ private final BubbleBarBackground mBubbleBarBackground;
+
+ // The current bounds of all the bubble bar.
+ private final Rect mBubbleBarBounds = new Rect();
+ // The amount the bubbles overlap when they are stacked in the bubble bar
+ private final float mIconOverlapAmount;
+ // The spacing between the bubbles when they are expanded in the bubble bar
+ private final float mIconSpacing;
+ // The size of a bubble in the bar
+ private final float mIconSize;
+ // The elevation of the bubbles within the bar
+ private final float mBubbleElevation;
+
+ // Whether the bar is expanded (i.e. the bubble activity is being displayed).
+ private boolean mIsBarExpanded = false;
+ // The currently selected bubble view.
+ private BubbleView mSelectedBubbleView;
+ // The click listener when the bubble bar is collapsed.
+ private View.OnClickListener mOnClickListener;
+
+ private final Rect mTempRect = new Rect();
+
+ // We don't reorder the bubbles when they are expanded as it could be jarring for the user
+ // this runnable will be populated with any reordering of the bubbles that should be applied
+ // once they are collapsed.
+ @Nullable
+ private Runnable mReorderRunnable;
+
+ public BubbleBarView(Context context) {
+ this(context, null);
+ }
+
+ public BubbleBarView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public BubbleBarView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public BubbleBarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ mActivityContext = ActivityContext.lookupContext(context);
+
+ mIconOverlapAmount = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_overlap);
+ mIconSpacing = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_spacing);
+ mIconSize = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size);
+ mBubbleElevation = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_elevation);
+ setClipToPadding(false);
+
+ mBubbleBarBackground = new BubbleBarBackground(mActivityContext,
+ getResources().getDimensionPixelSize(R.dimen.bubblebar_size));
+ setBackgroundDrawable(mBubbleBarBackground);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ mBubbleBarBounds.left = left;
+ mBubbleBarBounds.top = top;
+ mBubbleBarBounds.right = right;
+ mBubbleBarBounds.bottom = bottom;
+
+ // The bubble bar handle is aligned to the bottom edge of the screen so scale towards that.
+ setPivotX(getWidth());
+ setPivotY(getHeight());
+
+ // Position the views
+ updateChildrenRenderNodeProperties();
+ }
+
+ /**
+ * Returns the bounds of the bubble bar.
+ */
+ public Rect getBubbleBarBounds() {
+ return mBubbleBarBounds;
+ }
+
+ // TODO: (b/273592694) animate it
+ @Override
+ public void addView(View child, int index, ViewGroup.LayoutParams params) {
+ if (getChildCount() + 1 > MAX_BUBBLES) {
+ removeViewInLayout(getChildAt(getChildCount() - 1));
+ }
+ super.addView(child, index, params);
+ }
+
+ /**
+ * Updates the z order, positions, and badge visibility of the bubble views in the bar based
+ * on the expanded state.
+ */
+ // TODO: (b/273592694) animate it
+ private void updateChildrenRenderNodeProperties() {
+ int bubbleCount = getChildCount();
+ final float ty = (mBubbleBarBounds.height() - mIconSize) / 2f;
+ for (int i = 0; i < bubbleCount; i++) {
+ BubbleView bv = (BubbleView) getChildAt(i);
+ bv.setTranslationY(ty);
+ if (mIsBarExpanded) {
+ final float tx = i * (mIconSize + mIconSpacing);
+ bv.setTranslationX(tx);
+ bv.setZ(0);
+ bv.showBadge();
+ } else {
+ bv.setZ((MAX_BUBBLES * mBubbleElevation) - i);
+ bv.setTranslationX(i * mIconOverlapAmount);
+ if (i > 0) {
+ bv.hideBadge();
+ } else {
+ bv.showBadge();
+ }
+ }
+ }
+ }
+
+ /**
+ * Reorders the views to match the provided list.
+ */
+ public void reorder(List viewOrder) {
+ if (isExpanded()) {
+ mReorderRunnable = () -> doReorder(viewOrder);
+ } else {
+ doReorder(viewOrder);
+ }
+ }
+
+ // TODO: (b/273592694) animate it
+ private void doReorder(List viewOrder) {
+ if (!isExpanded()) {
+ for (int i = 0; i < viewOrder.size(); i++) {
+ View child = viewOrder.get(i);
+ if (child != null) {
+ removeViewInLayout(child);
+ addViewInLayout(child, i, child.getLayoutParams());
+ }
+ }
+ updateChildrenRenderNodeProperties();
+ }
+ }
+
+ /**
+ * Sets which bubble view should be shown as selected.
+ */
+ public void setSelectedBubble(BubbleView view) {
+ mSelectedBubbleView = view;
+ updateArrowForSelected(/* shouldAnimate= */ true);
+ }
+
+ /**
+ * Update the arrow position to match the selected bubble.
+ *
+ * @param shouldAnimate whether or not to animate the arrow. If the bar was just expanded, this
+ * should be set to {@code false}. Otherwise set this to {@code true}.
+ */
+ private void updateArrowForSelected(boolean shouldAnimate) {
+ if (mSelectedBubbleView == null) {
+ Log.w(TAG, "trying to update selection arrow without a selected view!");
+ return;
+ }
+ final int index = indexOfChild(mSelectedBubbleView);
+ // Find the center of the bubble when it's expanded, set the arrow position to it.
+ final float tx = getPaddingStart() + index * (mIconSize + mIconSpacing) + mIconSize / 2f;
+
+ if (shouldAnimate) {
+ final float currentArrowPosition = mBubbleBarBackground.getArrowPositionX();
+ ValueAnimator animator = ValueAnimator.ofFloat(currentArrowPosition, tx);
+ animator.setDuration(ARROW_POSITION_ANIMATION_DURATION_MS);
+ animator.addUpdateListener(animation -> {
+ float x = (float) animation.getAnimatedValue();
+ mBubbleBarBackground.setArrowPosition(x);
+ invalidate();
+ });
+ animator.start();
+ } else {
+ mBubbleBarBackground.setArrowPosition(tx);
+ invalidate();
+ }
+ }
+
+ @Override
+ public void setOnClickListener(View.OnClickListener listener) {
+ mOnClickListener = listener;
+ setOrUnsetClickListener();
+ }
+
+ /**
+ * The click listener used for the bubble view gets added / removed depending on whether
+ * the bar is expanded or collapsed, this updates whether the listener is set based on state.
+ */
+ private void setOrUnsetClickListener() {
+ super.setOnClickListener(mIsBarExpanded ? null : mOnClickListener);
+ }
+
+ /**
+ * Sets whether the bubble bar is expanded or collapsed.
+ */
+ // TODO: (b/273592694) animate it
+ public void setExpanded(boolean isBarExpanded) {
+ if (mIsBarExpanded != isBarExpanded) {
+ mIsBarExpanded = isBarExpanded;
+ updateArrowForSelected(/* shouldAnimate= */ false);
+ setOrUnsetClickListener();
+ if (!isBarExpanded && mReorderRunnable != null) {
+ mReorderRunnable.run();
+ mReorderRunnable = null;
+ }
+ mBubbleBarBackground.showArrow(mIsBarExpanded);
+ requestLayout(); // trigger layout to reposition views & update size for expansion
+ }
+ }
+
+ /**
+ * Returns whether the bubble bar is expanded.
+ */
+ public boolean isExpanded() {
+ return mIsBarExpanded;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int childCount = getChildCount();
+ final float iconWidth = mIsBarExpanded
+ ? (childCount * (mIconSize + mIconSpacing))
+ : mIconSize + ((childCount - 1) * mIconOverlapAmount);
+ final int totalWidth = (int) iconWidth + getPaddingStart() + getPaddingEnd();
+ setMeasuredDimension(totalWidth, MeasureSpec.getSize(heightMeasureSpec));
+
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ measureChild(child, (int) mIconSize, (int) mIconSize);
+ }
+ }
+
+ /**
+ * Returns whether the given MotionEvent, *in screen coordinates*, is within bubble bar
+ * touch bounds.
+ */
+ public boolean isEventOverAnyItem(MotionEvent ev) {
+ if (getVisibility() == View.VISIBLE) {
+ getBoundsOnScreen(mTempRect);
+ return mTempRect.contains((int) ev.getX(), (int) ev.getY());
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (!mIsBarExpanded) {
+ // When the bar is collapsed, all taps on it should expand it.
+ return true;
+ }
+ return super.onInterceptTouchEvent(ev);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
new file mode 100644
index 0000000000..82494c6fda
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2023 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.bubbles;
+
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+
+import android.graphics.Rect;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.launcher3.R;
+import com.android.launcher3.anim.AnimatedFloat;
+import com.android.launcher3.taskbar.TaskbarActivityContext;
+import com.android.launcher3.taskbar.TaskbarControllers;
+import com.android.launcher3.util.MultiPropertyFactory;
+import com.android.launcher3.util.MultiValueAlpha;
+import com.android.quickstep.SystemUiProxy;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Controller for {@link BubbleBarView}. Manages the visibility of the bubble bar as well as
+ * responding to changes in bubble state provided by BubbleBarController.
+ */
+public class BubbleBarViewController {
+
+ private static final String TAG = BubbleBarViewController.class.getSimpleName();
+
+ private final SystemUiProxy mSystemUiProxy;
+ private final TaskbarActivityContext mActivity;
+ private final BubbleBarView mBarView;
+ private final int mIconSize;
+
+ // Initialized in init.
+ private BubbleStashController mBubbleStashController;
+ private BubbleBarController mBubbleBarController;
+ private View.OnClickListener mBubbleClickListener;
+ private View.OnClickListener mBubbleBarClickListener;
+
+ // These are exposed to {@link BubbleStashController} to animate for stashing/un-stashing
+ private final MultiValueAlpha mBubbleBarAlpha;
+ private final AnimatedFloat mBubbleBarScale = new AnimatedFloat(this::updateScale);
+ private final AnimatedFloat mBubbleBarTranslationY = new AnimatedFloat(
+ this::updateTranslationY);
+
+ // Modified when swipe up is happening on the bubble bar or task bar.
+ private float mBubbleBarSwipeUpTranslationY;
+
+ // Whether the bar is hidden for a sysui state.
+ private boolean mHiddenForSysui;
+ // Whether the bar is hidden because there are no bubbles.
+ private boolean mHiddenForNoBubbles;
+
+ public BubbleBarViewController(TaskbarActivityContext activity, BubbleBarView barView) {
+ mActivity = activity;
+ mBarView = barView;
+ mSystemUiProxy = SystemUiProxy.INSTANCE.get(mActivity);
+ mBubbleBarAlpha = new MultiValueAlpha(mBarView, 1 /* num alpha channels */);
+ mBubbleBarAlpha.setUpdateVisibility(true);
+ mIconSize = activity.getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size);
+ }
+
+ public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
+ mBubbleStashController = bubbleControllers.bubbleStashController;
+ mBubbleBarController = bubbleControllers.bubbleBarController;
+
+ mActivity.addOnDeviceProfileChangeListener(dp ->
+ mBarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarHeight
+ );
+ mBarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarHeight;
+ mBubbleBarScale.updateValue(1f);
+ mBubbleClickListener = v -> onBubbleClicked(v);
+ mBubbleBarClickListener = v -> setExpanded(true);
+ mBarView.setOnClickListener(mBubbleBarClickListener);
+ // TODO: when barView layout changes tell taskbarInsetsController the insets have changed.
+ }
+
+ private void onBubbleClicked(View v) {
+ BubbleBarBubble bubble = ((BubbleView) v).getBubble();
+ if (bubble == null) {
+ Log.e(TAG, "bubble click listener, bubble was null");
+ }
+ final String currentlySelected = mBubbleBarController.getSelectedBubbleKey();
+ if (mBarView.isExpanded() && Objects.equals(bubble.getKey(), currentlySelected)) {
+ // Tapping the currently selected bubble while expanded collapses the view.
+ setExpanded(false);
+ mBubbleStashController.stashBubbleBar();
+ } else {
+ mBubbleBarController.setSelectedBubble(bubble);
+ mSystemUiProxy.showBubble(bubble.getKey(),
+ mBubbleStashController.isBubblesShowingOnHome());
+ }
+ }
+
+ //
+ // The below animators are exposed to BubbleStashController so it can manage the stashing
+ // animation.
+ //
+
+ public MultiPropertyFactory getBubbleBarAlpha() {
+ return mBubbleBarAlpha;
+ }
+
+ public AnimatedFloat getBubbleBarScale() {
+ return mBubbleBarScale;
+ }
+
+ public AnimatedFloat getBubbleBarTranslationY() {
+ return mBubbleBarTranslationY;
+ }
+
+ /**
+ * Whether the bubble bar is visible or not.
+ */
+ public boolean isBubbleBarVisible() {
+ return mBarView.getVisibility() == VISIBLE;
+ }
+
+ /**
+ * The bounds of the bubble bar.
+ */
+ public Rect getBubbleBarBounds() {
+ return mBarView.getBubbleBarBounds();
+ }
+
+ /**
+ * When the bubble bar is not stashed, it can be collapsed (the icons are in a stack) or
+ * expanded (the icons are in a row). This indicates whether the bubble bar is expanded.
+ */
+ public boolean isExpanded() {
+ return mBarView.isExpanded();
+ }
+
+ /**
+ * Whether the motion event is within the bounds of the bubble bar.
+ */
+ public boolean isEventOverAnyItem(MotionEvent ev) {
+ return mBarView.isEventOverAnyItem(ev);
+ }
+
+ //
+ // Visibility of the bubble bar
+ //
+
+ /**
+ * Returns whether the bubble bar is hidden because there are no bubbles.
+ */
+ public boolean isHiddenForNoBubbles() {
+ return mHiddenForNoBubbles;
+ }
+
+ /**
+ * Sets whether the bubble bar should be hidden because there are no bubbles.
+ */
+ public void setHiddenForBubbles(boolean hidden) {
+ if (mHiddenForNoBubbles != hidden) {
+ mHiddenForNoBubbles = hidden;
+ updateVisibilityForStateChange();
+ }
+ }
+
+ /**
+ * Sets whether the bubble bar should be hidden due to SysUI state (e.g. on lockscreen).
+ */
+ public void setHiddenForSysui(boolean hidden) {
+ if (mHiddenForSysui != hidden) {
+ mHiddenForSysui = hidden;
+ updateVisibilityForStateChange();
+ }
+ }
+
+ // TODO: (b/273592694) animate it
+ private void updateVisibilityForStateChange() {
+ if (!mHiddenForSysui && !mBubbleStashController.isStashed() && !mHiddenForNoBubbles) {
+ mBarView.setVisibility(VISIBLE);
+ } else {
+ mBarView.setVisibility(INVISIBLE);
+ }
+ }
+
+ //
+ // Modifying view related properties.
+ //
+
+ /**
+ * Sets the translation of the bubble bar during the swipe up gesture.
+ */
+ public void setTranslationYForSwipe(float transY) {
+ mBubbleBarSwipeUpTranslationY = transY;
+ updateTranslationY();
+ }
+
+ private void updateTranslationY() {
+ mBarView.setTranslationY(mBubbleBarTranslationY.value
+ + mBubbleBarSwipeUpTranslationY);
+ }
+
+ /**
+ * Applies scale properties for the entire bubble bar.
+ */
+ private void updateScale() {
+ float scale = mBubbleBarScale.value;
+ mBarView.setScaleX(scale);
+ mBarView.setScaleY(scale);
+ }
+
+ //
+ // Manipulating the specific bubble views in the bar
+ //
+
+ /**
+ * Removes the provided bubble from the bubble bar.
+ */
+ public void removeBubble(BubbleBarBubble b) {
+ if (b != null) {
+ mBarView.removeView(b.getView());
+ } else {
+ Log.w(TAG, "removeBubble, bubble was null!");
+ }
+ }
+
+ /**
+ * Adds the provided bubble to the bubble bar.
+ */
+ public void addBubble(BubbleBarBubble b) {
+ if (b != null) {
+ mBarView.addView(b.getView(), 0, new FrameLayout.LayoutParams(mIconSize, mIconSize));
+ b.getView().setOnClickListener(mBubbleClickListener);
+ } else {
+ Log.w(TAG, "addBubble, bubble was null!");
+ }
+ }
+
+ /**
+ * Reorders the bubbles based on the provided list.
+ */
+ public void reorderBubbles(List newOrder) {
+ List viewList = newOrder.stream().filter(Objects::nonNull)
+ .map(BubbleBarBubble::getView).toList();
+ mBarView.reorder(viewList);
+ }
+
+ /**
+ * Updates the selected bubble.
+ */
+ public void updateSelectedBubble(BubbleBarBubble newlySelected) {
+ mBarView.setSelectedBubble(newlySelected.getView());
+ }
+
+ /**
+ * Sets whether the bubble bar should be expanded (not unstashed, but have the contents
+ * within it expanded). This method notifies SystemUI that the bubble bar is expanded and
+ * showing a selected bubble. This method should ONLY be called from UI events originating
+ * from Launcher.
+ */
+ public void setExpanded(boolean isExpanded) {
+ if (isExpanded != mBarView.isExpanded()) {
+ mBarView.setExpanded(isExpanded);
+ if (!isExpanded) {
+ mSystemUiProxy.collapseBubbles();
+ } else {
+ final String selectedKey = mBubbleBarController.getSelectedBubbleKey();
+ if (selectedKey != null) {
+ mSystemUiProxy.showBubble(selectedKey,
+ mBubbleStashController.isBubblesShowingOnHome());
+ } else {
+ Log.w(TAG, "trying to expand bubbles when there isn't one selected");
+ }
+ // TODO: Tell taskbar stash controller to stash without bubbles following
+ }
+ }
+ }
+
+ /**
+ * Sets whether the bubble bar should be expanded. This method is used in response to UI events
+ * from SystemUI.
+ */
+ public void setExpandedFromSysui(boolean isExpanded) {
+ if (!isExpanded) {
+ mBubbleStashController.stashBubbleBar();
+ } else {
+ mBubbleStashController.showBubbleBar(true /* expand the bubbles */);
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
new file mode 100644
index 0000000000..6417f3c585
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 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.bubbles;
+
+import com.android.launcher3.taskbar.TaskbarControllers;
+import com.android.launcher3.util.RunnableList;
+
+/**
+ * Hosts various bubble controllers to facilitate passing between one another.
+ */
+public class BubbleControllers {
+
+ public final BubbleBarController bubbleBarController;
+ public final BubbleBarViewController bubbleBarViewController;
+ public final BubbleStashController bubbleStashController;
+ public final BubbleStashedHandleViewController bubbleStashedHandleViewController;
+
+ private final RunnableList mPostInitRunnables = new RunnableList();
+
+ /**
+ * Want to add a new controller? Don't forget to:
+ * * Call init
+ * * Call onDestroy
+ */
+ public BubbleControllers(
+ BubbleBarController bubbleBarController,
+ BubbleBarViewController bubbleBarViewController,
+ BubbleStashController bubbleStashController,
+ BubbleStashedHandleViewController bubbleStashedHandleViewController) {
+ this.bubbleBarController = bubbleBarController;
+ this.bubbleBarViewController = bubbleBarViewController;
+ this.bubbleStashController = bubbleStashController;
+ this.bubbleStashedHandleViewController = bubbleStashedHandleViewController;
+ }
+
+ /**
+ * Initializes all controllers. Note that controllers can now reference each other through this
+ * BubbleControllers 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(TaskbarControllers taskbarControllers) {
+ bubbleBarController.init(taskbarControllers, this);
+ bubbleBarViewController.init(taskbarControllers, this);
+ bubbleStashedHandleViewController.init(taskbarControllers, this);
+ bubbleStashController.init(taskbarControllers, this);
+
+ mPostInitRunnables.executeAllAndDestroy();
+ }
+
+ /**
+ * If all controllers are already initialized, runs the given callback immediately. Otherwise,
+ * queues it to run after calling init() on all controllers. This should likely be used in any
+ * case where one controller is telling another controller to do something inside init().
+ */
+ public void runAfterInit(Runnable runnable) {
+ // If this has been executed in init, it automatically runs adds to it.
+ mPostInitRunnables.add(runnable);
+ }
+
+ /**
+ * Cleans up all controllers.
+ */
+ public void onDestroy() {
+ bubbleStashedHandleViewController.onDestroy();
+ bubbleBarController.onDestroy();
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
new file mode 100644
index 0000000000..0ab53b0c20
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2023 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.bubbles;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.annotation.Nullable;
+import android.view.InsetsController;
+
+import com.android.launcher3.anim.AnimatedFloat;
+import com.android.launcher3.taskbar.StashedHandleViewController;
+import com.android.launcher3.taskbar.TaskbarActivityContext;
+import com.android.launcher3.taskbar.TaskbarControllers;
+import com.android.launcher3.taskbar.TaskbarStashController;
+import com.android.launcher3.util.MultiPropertyFactory;
+
+/**
+ * Coordinates between controllers such as BubbleBarView and BubbleHandleViewController to
+ * create a cohesive animation between stashed/unstashed states.
+ */
+public class BubbleStashController {
+
+ private static final String TAG = BubbleStashController.class.getSimpleName();
+
+ /**
+ * How long to stash/unstash.
+ */
+ public static final long BAR_STASH_DURATION = InsetsController.ANIMATION_DURATION_RESIZE;
+
+ /**
+ * The scale bubble bar animates to when being stashed.
+ */
+ private static final float STASHED_BAR_SCALE = 0.5f;
+
+ protected final TaskbarActivityContext mActivity;
+
+ // Initialized in init.
+ private TaskbarControllers mControllers;
+ private BubbleBarViewController mBarViewController;
+ private BubbleStashedHandleViewController mHandleViewController;
+ private TaskbarStashController mTaskbarStashController;
+
+ private MultiPropertyFactory.MultiProperty mIconAlphaForStash;
+ private AnimatedFloat mIconScaleForStash;
+ private AnimatedFloat mIconTranslationYForStash;
+ private MultiPropertyFactory.MultiProperty mBubbleStashedHandleAlpha;
+
+ private boolean mRequestedStashState;
+ private boolean mRequestedExpandedState;
+
+ private boolean mIsStashed;
+ private int mStashedHeight;
+ private int mUnstashedHeight;
+ private boolean mBubblesShowingOnHome;
+ private boolean mBubblesShowingOnOverview;
+
+ @Nullable
+ private AnimatorSet mAnimator;
+
+ public BubbleStashController(TaskbarActivityContext activity) {
+ mActivity = activity;
+ }
+
+ public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
+ mControllers = controllers;
+ mBarViewController = bubbleControllers.bubbleBarViewController;
+ mHandleViewController = bubbleControllers.bubbleStashedHandleViewController;
+ mTaskbarStashController = controllers.taskbarStashController;
+
+ mIconAlphaForStash = mBarViewController.getBubbleBarAlpha().get(0);
+ mIconScaleForStash = mBarViewController.getBubbleBarScale();
+ mIconTranslationYForStash = mBarViewController.getBubbleBarTranslationY();
+
+ mBubbleStashedHandleAlpha = mHandleViewController.getStashedHandleAlpha().get(
+ StashedHandleViewController.ALPHA_INDEX_STASHED);
+
+ mStashedHeight = mHandleViewController.getStashedHeight();
+ mUnstashedHeight = mHandleViewController.getUnstashedHeight();
+
+ bubbleControllers.runAfterInit(() -> {
+ if (mTaskbarStashController.isStashed()) {
+ stashBubbleBar();
+ } else {
+ showBubbleBar(false /* expandBubbles */);
+ }
+ });
+ }
+
+ /**
+ * Returns the touchable height of the bubble bar based on it's stashed state.
+ */
+ public int getTouchableHeight() {
+ return mIsStashed ? mStashedHeight : mUnstashedHeight;
+ }
+
+ /**
+ * Returns whether the bubble bar is currently stashed.
+ */
+ public boolean isStashed() {
+ return mIsStashed;
+ }
+
+ /**
+ * Called when launcher enters or exits the home page. Bubbles are unstashed on home.
+ */
+ public void setBubblesShowingOnHome(boolean onHome) {
+ if (mBubblesShowingOnHome != onHome) {
+ mBubblesShowingOnHome = onHome;
+ if (mBubblesShowingOnHome) {
+ showBubbleBar(/* expanded= */ false);
+ } else if (!mBarViewController.isExpanded()) {
+ stashBubbleBar();
+ }
+ }
+ }
+
+ /** Whether bubbles are showing on the launcher home page. */
+ public boolean isBubblesShowingOnHome() {
+ return mBubblesShowingOnHome;
+ }
+
+ // TODO: when tapping on an app in overview, this is a bit delayed compared to taskbar stashing
+ /** Called when launcher enters or exits overview. Bubbles are unstashed on overview. */
+ public void setBubblesShowingOnOverview(boolean onOverview) {
+ if (mBubblesShowingOnOverview != onOverview) {
+ mBubblesShowingOnOverview = onOverview;
+ if (!mBubblesShowingOnOverview && !mBarViewController.isExpanded()) {
+ stashBubbleBar();
+ }
+ }
+ }
+
+ /** Called when sysui locked state changes, when locked, bubble bar is stashed. */
+ public void onSysuiLockedStateChange(boolean isSysuiLocked) {
+ if (isSysuiLocked) {
+ // TODO: should the normal path flip mBubblesOnHome / check if this is needed
+ // If we're locked, we're no longer showing on home.
+ mBubblesShowingOnHome = false;
+ mBubblesShowingOnOverview = false;
+ stashBubbleBar();
+ }
+ }
+
+ /**
+ * Stashes the bubble bar if allowed based on other state (e.g. on home and overview the
+ * bar does not stash).
+ */
+ public void stashBubbleBar() {
+ mRequestedStashState = true;
+ mRequestedExpandedState = false;
+ updateStashedAndExpandedState();
+ }
+
+ /**
+ * Shows the bubble bar, and expands bubbles depending on {@param expandBubbles}.
+ */
+ public void showBubbleBar(boolean expandBubbles) {
+ mRequestedStashState = false;
+ mRequestedExpandedState = expandBubbles;
+ updateStashedAndExpandedState();
+ }
+
+ private void updateStashedAndExpandedState() {
+ if (mBarViewController.isHiddenForNoBubbles()) {
+ // If there are no bubbles the bar and handle are invisible, nothing to do here.
+ return;
+ }
+ boolean isStashed = mRequestedStashState
+ && !mBubblesShowingOnHome
+ && !mBubblesShowingOnOverview;
+ if (mIsStashed != isStashed) {
+ mIsStashed = isStashed;
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ }
+ mAnimator = createStashAnimator(mIsStashed, BAR_STASH_DURATION);
+ mAnimator.start();
+ onIsStashedChanged();
+ }
+ if (mBarViewController.isExpanded() != mRequestedExpandedState) {
+ mBarViewController.setExpanded(mRequestedExpandedState);
+ }
+ }
+
+ /**
+ * Create a stash animation.
+ *
+ * @param isStashed whether it's a stash animation or an unstash animation
+ * @param duration duration of the animation
+ * @return the animation
+ */
+ private AnimatorSet createStashAnimator(boolean isStashed, long duration) {
+ AnimatorSet animatorSet = new AnimatorSet();
+ final float stashTranslation = (mUnstashedHeight - mStashedHeight) / 2f;
+
+ AnimatorSet fullLengthAnimatorSet = new AnimatorSet();
+ // Not exactly half and may overlap. See [first|second]HalfDurationScale below.
+ AnimatorSet firstHalfAnimatorSet = new AnimatorSet();
+ AnimatorSet secondHalfAnimatorSet = new AnimatorSet();
+
+ final float firstHalfDurationScale;
+ final float secondHalfDurationScale;
+
+ if (isStashed) {
+ firstHalfDurationScale = 0.75f;
+ secondHalfDurationScale = 0.5f;
+
+ fullLengthAnimatorSet.play(mIconTranslationYForStash.animateToValue(stashTranslation));
+
+ firstHalfAnimatorSet.playTogether(
+ mIconAlphaForStash.animateToValue(0),
+ mIconScaleForStash.animateToValue(STASHED_BAR_SCALE));
+ secondHalfAnimatorSet.playTogether(
+ mBubbleStashedHandleAlpha.animateToValue(1));
+ } else {
+ firstHalfDurationScale = 0.5f;
+ secondHalfDurationScale = 0.75f;
+
+ // If we're on home, adjust the translation so the bubble bar aligns with hotseat.
+ final float hotseatTransY = mActivity.getDeviceProfile().getTaskbarOffsetY();
+ final float translationY = mBubblesShowingOnHome ? hotseatTransY : 0;
+ fullLengthAnimatorSet.playTogether(
+ mIconScaleForStash.animateToValue(1),
+ mIconTranslationYForStash.animateToValue(translationY));
+
+ firstHalfAnimatorSet.playTogether(
+ mBubbleStashedHandleAlpha.animateToValue(0)
+ );
+ secondHalfAnimatorSet.playTogether(
+ mIconAlphaForStash.animateToValue(1)
+ );
+ }
+
+ fullLengthAnimatorSet.play(mHandleViewController.createRevealAnimToIsStashed(isStashed));
+
+ fullLengthAnimatorSet.setDuration(duration);
+ firstHalfAnimatorSet.setDuration((long) (duration * firstHalfDurationScale));
+ secondHalfAnimatorSet.setDuration((long) (duration * secondHalfDurationScale));
+ secondHalfAnimatorSet.setStartDelay((long) (duration * (1 - secondHalfDurationScale)));
+
+ animatorSet.playTogether(fullLengthAnimatorSet, firstHalfAnimatorSet,
+ secondHalfAnimatorSet);
+ animatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimator = null;
+ mControllers.runAfterInit(() -> {
+ if (isStashed) {
+ mBarViewController.setExpanded(false);
+ }
+ });
+ }
+ });
+ return animatorSet;
+ }
+
+ private void onIsStashedChanged() {
+ mControllers.runAfterInit(() -> {
+ mHandleViewController.onIsStashedChanged();
+ // TODO: when stash changes tell taskbarInsetsController the insets have changed.
+ });
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
new file mode 100644
index 0000000000..2170a5dc5b
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2023 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.bubbles;
+
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.res.Resources;
+import android.graphics.Outline;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+
+import com.android.launcher3.R;
+import com.android.launcher3.anim.RevealOutlineAnimation;
+import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
+import com.android.launcher3.taskbar.StashedHandleView;
+import com.android.launcher3.taskbar.TaskbarActivityContext;
+import com.android.launcher3.taskbar.TaskbarControllers;
+import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.MultiPropertyFactory;
+import com.android.launcher3.util.MultiValueAlpha;
+import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
+
+/**
+ * Handles properties/data collection, then passes the results to our stashed handle View to render.
+ */
+public class BubbleStashedHandleViewController {
+
+ private final TaskbarActivityContext mActivity;
+ private final StashedHandleView mStashedHandleView;
+ private final MultiValueAlpha mTaskbarStashedHandleAlpha;
+
+ // Initialized in init.
+ private BubbleBarViewController mBarViewController;
+ private BubbleStashController mBubbleStashController;
+ private RegionSamplingHelper mRegionSamplingHelper;
+ private int mBarSize;
+ private int mStashedHandleWidth;
+ private int mStashedHandleHeight;
+
+ // The bounds we want to clip to in the settled state when showing the stashed handle.
+ private final Rect mStashedHandleBounds = new Rect();
+
+ // When the reveal animation is cancelled, we can assume it's about to create a new animation,
+ // which should start off at the same point the cancelled one left off.
+ private float mStartProgressForNextRevealAnim;
+ private boolean mWasLastRevealAnimReversed;
+
+ // XXX: if there are more of these maybe do state flags instead
+ private boolean mHiddenForSysui;
+ private boolean mHiddenForNoBubbles;
+ private boolean mHiddenForHomeButtonDisabled;
+
+ public BubbleStashedHandleViewController(TaskbarActivityContext activity,
+ StashedHandleView stashedHandleView) {
+ mActivity = activity;
+ mStashedHandleView = stashedHandleView;
+ mTaskbarStashedHandleAlpha = new MultiValueAlpha(mStashedHandleView, 1);
+ }
+
+ public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
+ mBarViewController = bubbleControllers.bubbleBarViewController;
+ mBubbleStashController = bubbleControllers.bubbleStashController;
+
+ Resources resources = mActivity.getResources();
+ mStashedHandleHeight = resources.getDimensionPixelSize(
+ R.dimen.bubblebar_stashed_handle_height);
+ mStashedHandleWidth = resources.getDimensionPixelSize(
+ R.dimen.bubblebar_stashed_handle_width);
+ mBarSize = resources.getDimensionPixelSize(R.dimen.bubblebar_size);
+
+ final int bottomMargin = resources.getDimensionPixelSize(
+ R.dimen.transient_taskbar_bottom_margin);
+ mStashedHandleView.getLayoutParams().height = mBarSize + bottomMargin;
+
+ mTaskbarStashedHandleAlpha.get(0).setValue(0);
+
+ final int stashedTaskbarHeight = resources.getDimensionPixelSize(
+ R.dimen.bubblebar_stashed_size);
+ mStashedHandleView.setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ float stashedHandleRadius = view.getHeight() / 2f;
+ outline.setRoundRect(mStashedHandleBounds, stashedHandleRadius);
+ }
+ });
+
+ mRegionSamplingHelper = new RegionSamplingHelper(mStashedHandleView,
+ new RegionSamplingHelper.SamplingCallback() {
+ @Override
+ public void onRegionDarknessChanged(boolean isRegionDark) {
+ mStashedHandleView.updateHandleColor(isRegionDark, true /* animate */);
+ }
+
+ @Override
+ public Rect getSampledRegion(View sampledView) {
+ return mStashedHandleView.getSampledRegion();
+ }
+ }, Executors.UI_HELPER_EXECUTOR);
+
+ mStashedHandleView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) -> {
+ // As more bubbles get added, the icon bounds become larger. To ensure a consistent
+ // handle bar position, we pin it to the edge of the screen.
+ Rect bubblebarRect = mBarViewController.getBubbleBarBounds();
+ final int stashedCenterY = view.getHeight() - stashedTaskbarHeight / 2;
+
+ mStashedHandleBounds.set(
+ bubblebarRect.right - mStashedHandleWidth,
+ stashedCenterY - mStashedHandleHeight / 2,
+ bubblebarRect.right,
+ stashedCenterY + mStashedHandleHeight / 2);
+ mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
+
+ view.setPivotX(view.getWidth());
+ view.setPivotY(view.getHeight() - stashedTaskbarHeight / 2f);
+ });
+ }
+
+ public void onDestroy() {
+ mRegionSamplingHelper.stopAndDestroy();
+ mRegionSamplingHelper = null;
+ }
+
+ /**
+ * Returns the height of the stashed handle.
+ */
+ public int getStashedHeight() {
+ return mStashedHandleHeight;
+ }
+
+ /**
+ * Returns the height when the bubble bar is unstashed (so the height of the bubble bar).
+ */
+ public int getUnstashedHeight() {
+ return mBarSize;
+ }
+
+ /**
+ * Called when system ui state changes. Bubbles don't show when the device is locked.
+ */
+ public void setHiddenForSysui(boolean hidden) {
+ if (mHiddenForSysui != hidden) {
+ mHiddenForSysui = hidden;
+ updateVisibilityForStateChange();
+ }
+ }
+
+ /**
+ * Called when the handle should be hidden (or shown) because there are no bubbles
+ * (or 1+ bubbles).
+ */
+ public void setHiddenForBubbles(boolean hidden) {
+ if (mHiddenForNoBubbles != hidden) {
+ mHiddenForNoBubbles = hidden;
+ updateVisibilityForStateChange();
+ }
+ }
+
+ /**
+ * Called when the home button is enabled / disabled. Bubbles don't show if home is disabled.
+ */
+ // TODO: is this needed for bubbles?
+ public void setIsHomeButtonDisabled(boolean homeDisabled) {
+ mHiddenForHomeButtonDisabled = homeDisabled;
+ updateVisibilityForStateChange();
+ }
+
+ // TODO: (b/273592694) animate it?
+ private void updateVisibilityForStateChange() {
+ if (!mHiddenForSysui && !mHiddenForHomeButtonDisabled && !mHiddenForNoBubbles) {
+ mStashedHandleView.setVisibility(VISIBLE);
+ } else {
+ mStashedHandleView.setVisibility(INVISIBLE);
+ }
+ updateRegionSampling();
+ }
+
+ /**
+ * Called when bubble bar is stash state changes so that updates to the stashed handle color
+ * can be started or stopped.
+ */
+ public void onIsStashedChanged() {
+ updateRegionSampling();
+ }
+
+ private void updateRegionSampling() {
+ boolean handleVisible = mStashedHandleView.getVisibility() == VISIBLE
+ && mBubbleStashController.isStashed();
+ mRegionSamplingHelper.setWindowVisible(handleVisible);
+ if (handleVisible) {
+ mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
+ mRegionSamplingHelper.start(mStashedHandleView.getSampledRegion());
+ } else {
+ mRegionSamplingHelper.stop();
+ }
+ }
+
+ /**
+ * Sets the translation of the stashed handle during the swipe up gesture.
+ */
+ public void setTranslationYForSwipe(float transY) {
+ mStashedHandleView.setTranslationY(transY);
+ }
+
+ /**
+ * Used by {@link BubbleStashController} to animate the handle when stashing or un stashing.
+ */
+ public MultiPropertyFactory getStashedHandleAlpha() {
+ return mTaskbarStashedHandleAlpha;
+ }
+
+ /**
+ * Creates and returns an Animator that updates the stashed handle shape and size.
+ * When stashed, the shape is a thin rounded pill. When unstashed, the shape morphs into
+ * the size of where the bubble bar icons will be.
+ */
+ public Animator createRevealAnimToIsStashed(boolean isStashed) {
+ Rect bubbleBarBounds = new Rect(mBarViewController.getBubbleBarBounds());
+
+ // Account for the full visual height of the bubble bar
+ int heightDiff = (mBarSize - bubbleBarBounds.height()) / 2;
+ bubbleBarBounds.top -= heightDiff;
+ bubbleBarBounds.bottom += heightDiff;
+ float stashedHandleRadius = mStashedHandleView.getHeight() / 2f;
+ final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider(
+ stashedHandleRadius, stashedHandleRadius, bubbleBarBounds, mStashedHandleBounds);
+
+ boolean isReversed = !isStashed;
+ boolean changingDirection = mWasLastRevealAnimReversed != isReversed;
+ mWasLastRevealAnimReversed = isReversed;
+ if (changingDirection) {
+ mStartProgressForNextRevealAnim = 1f - mStartProgressForNextRevealAnim;
+ }
+
+ ValueAnimator revealAnim = handleRevealProvider.createRevealAnimator(mStashedHandleView,
+ isReversed, mStartProgressForNextRevealAnim);
+ revealAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mStartProgressForNextRevealAnim = ((ValueAnimator) animation).getAnimatedFraction();
+ }
+ });
+ return revealAnim;
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
new file mode 100644
index 0000000000..e22e63aa36
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2023 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.bubbles;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Outline;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+import android.widget.ImageView;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+
+import com.android.launcher3.R;
+import com.android.launcher3.icons.IconNormalizer;
+
+// TODO: (b/276978250) This is will be similar to WMShell's BadgedImageView, it'd be nice to share.
+// TODO: (b/269670235) currently this doesn't show the 'update dot'
+/**
+ * View that displays a bubble icon, along with an app badge on either the left or
+ * right side of the view.
+ */
+public class BubbleView extends ConstraintLayout {
+
+ // TODO: (b/269670235) currently we don't render the 'update dot', this will be used for that.
+ public static final int DEFAULT_PATH_SIZE = 100;
+
+ private final ImageView mBubbleIcon;
+ private final ImageView mAppIcon;
+ private final int mBubbleSize;
+
+ // TODO: (b/273310265) handle RTL
+ private boolean mOnLeft = false;
+
+ private BubbleBarBubble mBubble;
+
+ public BubbleView(Context context) {
+ this(context, null);
+ }
+
+ public BubbleView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public BubbleView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public BubbleView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ // We manage positioning the badge ourselves
+ setLayoutDirection(LAYOUT_DIRECTION_LTR);
+
+ LayoutInflater.from(context).inflate(R.layout.bubble_view, this);
+
+ mBubbleSize = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size);
+ mBubbleIcon = findViewById(R.id.icon_view);
+ mAppIcon = findViewById(R.id.app_icon_view);
+
+ setFocusable(true);
+ setClickable(true);
+ setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ BubbleView.this.getOutline(outline);
+ }
+ });
+ }
+
+ private void getOutline(Outline outline) {
+ final int normalizedSize = IconNormalizer.getNormalizedCircleSize(mBubbleSize);
+ final int inset = (mBubbleSize - normalizedSize) / 2;
+ outline.setOval(inset, inset, inset + normalizedSize, inset + normalizedSize);
+ }
+
+ /** Sets the bubble being rendered in this view. */
+ void setBubble(BubbleBarBubble bubble) {
+ mBubble = bubble;
+ mBubbleIcon.setImageBitmap(bubble.getIcon());
+ mAppIcon.setImageBitmap(bubble.getBadge());
+ }
+
+ /** Returns the bubble being rendered in this view. */
+ @Nullable
+ BubbleBarBubble getBubble() {
+ return mBubble;
+ }
+
+ /** Shows the app badge on this bubble. */
+ void showBadge() {
+ Bitmap appBadgeBitmap = mBubble.getBadge();
+ if (appBadgeBitmap == null) {
+ mAppIcon.setVisibility(GONE);
+ return;
+ }
+
+ int translationX;
+ if (mOnLeft) {
+ translationX = -(mBubble.getIcon().getWidth() - appBadgeBitmap.getWidth());
+ } else {
+ translationX = 0;
+ }
+
+ mAppIcon.setTranslationX(translationX);
+ mAppIcon.setVisibility(VISIBLE);
+ }
+
+ /** Hides the app badge on this bubble. */
+ void hideBadge() {
+ mAppIcon.setVisibility(GONE);
+ }
+
+ @Override
+ public String toString() {
+ return "BubbleView{" + mBubble + "}";
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
index c093c9240a..468a1a7093 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
@@ -25,14 +25,7 @@ import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import com.android.launcher3.DeviceProfile
-import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.DIMEN_TASKBAR_BACK_BUTTON_LEFT_MARGIN_KIDS
-import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.DIMEN_TASKBAR_HOME_BUTTON_LEFT_MARGIN_KIDS
-import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.DIMEN_TASKBAR_ICON_SIZE_KIDS
-import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.DIMEN_TASKBAR_NAV_BUTTONS_CORNER_RADIUS_KIDS
-import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.DIMEN_TASKBAR_NAV_BUTTONS_HEIGHT_KIDS
-import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.DIMEN_TASKBAR_NAV_BUTTONS_WIDTH_KIDS
-import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.DRAWABLE_SYSBAR_BACK_KIDS
-import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.DRAWABLE_SYSBAR_HOME_KIDS
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.*
class KidsNavLayoutter(
resources: Resources,
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
index b730b215cd..000778d9df 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
@@ -21,9 +21,7 @@ import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.LinearLayout
import com.android.launcher3.DeviceProfile
-import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.ID_END_CONTEXTUAL_BUTTONS
-import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.ID_END_NAV_BUTTONS
-import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.ID_START_CONTEXTUAL_BUTTONS
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.*
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.Companion
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter
diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
index 66d591889b..a642693137 100644
--- a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
@@ -27,7 +27,6 @@ import com.android.launcher3.taskbar.BaseTaskbarContext;
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.taskbar.TaskbarControllers;
import com.android.launcher3.taskbar.TaskbarDragController;
-import com.android.launcher3.taskbar.TaskbarStashController;
import com.android.launcher3.taskbar.TaskbarUIController;
import com.android.launcher3.taskbar.allapps.TaskbarAllAppsContainerView;
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
@@ -45,8 +44,6 @@ public class TaskbarOverlayContext extends BaseTaskbarContext {
private final TaskbarDragController mDragController;
private final TaskbarOverlayDragLayer mDragLayer;
- // We automatically stash taskbar when All Apps is opened in gesture navigation mode.
- private final boolean mWillTaskbarBeVisuallyStashed;
private final int mStashedTaskbarHeight;
private final TaskbarUIController mUiController;
@@ -60,18 +57,11 @@ public class TaskbarOverlayContext extends BaseTaskbarContext {
mDragController = new TaskbarDragController(this);
mDragController.init(controllers);
mDragLayer = new TaskbarOverlayDragLayer(this);
-
- TaskbarStashController taskbarStashController = controllers.taskbarStashController;
- mWillTaskbarBeVisuallyStashed = taskbarStashController.supportsVisualStashing();
- mStashedTaskbarHeight = taskbarStashController.getStashedHeight();
+ mStashedTaskbarHeight = controllers.taskbarStashController.getStashedHeight();
mUiController = controllers.uiController;
}
- boolean willTaskbarBeVisuallyStashed() {
- return mWillTaskbarBeVisuallyStashed;
- }
-
int getStashedTaskbarHeight() {
return mStashedTaskbarHeight;
}
@@ -80,11 +70,22 @@ public class TaskbarOverlayContext extends BaseTaskbarContext {
return mOverlayController;
}
+ /** Returns {@code true} if overlay or Taskbar windows are handling a system drag. */
+ boolean isAnySystemDragInProgress() {
+ return mDragController.isSystemDragInProgress()
+ || mTaskbarContext.getDragController().isSystemDragInProgress();
+ }
+
@Override
public DeviceProfile getDeviceProfile() {
return mOverlayController.getLauncherDeviceProfile();
}
+ @Override
+ public View.AccessibilityDelegate getAccessibilityDelegate() {
+ return mTaskbarContext.getAccessibilityDelegate();
+ }
+
@Override
public TaskbarDragController getDragController() {
return mDragController;
diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java
index 476e0a8bab..d4e2be9b49 100644
--- a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java
@@ -23,6 +23,7 @@ import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
import static com.android.launcher3.LauncherState.ALL_APPS;
import android.annotation.SuppressLint;
+import android.content.ComponentName;
import android.content.Context;
import android.graphics.PixelFormat;
import android.view.Gravity;
@@ -59,13 +60,15 @@ public final class TaskbarOverlayController {
private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
@Override
- public void onTaskStackChanged() {
- mProxyView.close(false);
+ public void onTaskCreated(int taskId, ComponentName componentName) {
+ // Created task will be below existing overlay, so move out of the way.
+ hideWindow();
}
@Override
public void onTaskMovedToFront(int taskId) {
- mProxyView.close(false);
+ // New front task will be below existing overlay, so move out of the way.
+ hideWindow();
}
};
diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java
index ec64128c91..add72791d4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java
@@ -32,6 +32,7 @@ import androidx.annotation.NonNull;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.BaseDragLayer;
@@ -112,7 +113,7 @@ public class TaskbarOverlayDragLayer extends
@Override
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
- if (mActivity.getDragController().isSystemDragInProgress()) {
+ if (mActivity.isAnySystemDragInProgress()) {
inoutInfo.touchableRegion.setEmpty();
inoutInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
}
@@ -120,7 +121,9 @@ public class TaskbarOverlayDragLayer extends
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- return updateInsetsDueToStashing(insets);
+ insets = updateInsetsDueToStashing(insets);
+ setInsets(insets.getInsets(WindowInsets.Type.systemBars()).toRect());
+ return insets;
}
@Override
@@ -183,7 +186,7 @@ public class TaskbarOverlayDragLayer extends
* 2) Sets tappableInsets bottom inset to 0.
*/
private WindowInsets updateInsetsDueToStashing(WindowInsets oldInsets) {
- if (!mActivity.willTaskbarBeVisuallyStashed()) {
+ if (!DisplayController.isTransientTaskbar(mActivity)) {
return oldInsets;
}
WindowInsets.Builder updatedInsetsBuilder = new WindowInsets.Builder(oldInsets);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
index d22b32a66a..62a8e05a24 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -17,10 +17,15 @@
package com.android.launcher3.uioverrides;
import android.app.Person;
+import android.content.Context;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
import com.android.launcher3.Utilities;
+import java.util.Map;
+
/**
* A wrapper for the hidden API calls
*/
@@ -34,4 +39,8 @@ public class ApiWrapper {
Person[] persons = si.getPersons();
return persons == null ? Utilities.EMPTY_PERSON_ARRAY : persons;
}
+
+ public static Map getActivityOverrides(Context context) {
+ return context.getSystemService(LauncherApps.class).getActivityOverrides();
+ }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index f3663cd19c..470830a805 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -56,7 +56,6 @@ import com.android.launcher3.icons.IconNormalizer;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.views.ActivityContext;
@@ -184,7 +183,16 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
: null;
super.applyFromWorkspaceItem(info, animate, staggerIndex);
int oldPlateColor = mPlateColor;
- int newPlateColor = ColorUtils.setAlphaComponent(mDotParams.appColor, 200);
+
+ int newPlateColor;
+ if (getIcon().isThemed()) {
+ newPlateColor = getResources().getColor(android.R.color.system_accent1_300);
+ } else {
+ float[] hctPlateColor = new float[3];
+ ColorUtils.colorToM3HCT(mDotParams.appColor, hctPlateColor);
+ newPlateColor = ColorUtils.M3HCTToColor(hctPlateColor[0], 36, 85);
+ }
+
if (!animate) {
mPlateColor = newPlateColor;
}
@@ -403,8 +411,9 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
PredictedAppIcon icon = (PredictedAppIcon) LayoutInflater.from(parent.getContext())
.inflate(R.layout.predicted_app_icon, parent, false);
icon.applyFromWorkspaceItem(info);
- icon.setOnClickListener(ItemClickHandler.INSTANCE);
- icon.setOnFocusChangeListener(Launcher.getLauncher(parent.getContext()).getFocusHandler());
+ Launcher launcher = Launcher.getLauncher(parent.getContext());
+ icon.setOnClickListener(launcher.getItemOnClickListener());
+ icon.setOnFocusChangeListener(launcher.getFocusHandler());
return icon;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
index df0f204135..3fcf324093 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
@@ -39,7 +39,10 @@ import com.android.launcher3.widget.LauncherAppWidgetHostView;
import app.lawnchair.LawnchairApp;
import dev.rikka.tools.refine.Refine;
-/** Provides a Quickstep specific animation when launching an activity from an app widget. */
+/**
+ * Provides a Quickstep specific animation when launching an activity from an
+ * app widget.
+ */
class QuickstepInteractionHandler implements RemoteViews.InteractionHandler {
private static final String TAG = "QuickstepInteractionHandler";
@@ -60,6 +63,10 @@ class QuickstepInteractionHandler implements RemoteViews.InteractionHandler {
return RemoteViews.startPendingIntent(hostView, pendingIntent,
remoteResponse.getLaunchOptions(view));
}
+ if (mLauncher.getSplitToWorkspaceController().handleSecondWidgetSelectionForSplit(view,
+ pendingIntent)) {
+ return true;
+ }
Pair options = remoteResponse.getLaunchOptions(view);
ActivityOptionsWrapper activityOptions = mLauncher.getAppTransitionManager()
.getActivityLaunchOptions(hostView);
@@ -69,8 +76,9 @@ class QuickstepInteractionHandler implements RemoteViews.InteractionHandler {
launchCookie = mLauncher.getLaunchCookie((ItemInfo) itemInfo);
activityOptions.options.setLaunchCookie(launchCookie);
}
- if (Utilities.ATLEAST_S && !pendingIntent.isActivity() && LawnchairApp.isRecentsEnabled ()) {
- // In the event this pending intent eventually launches an activity, i.e. a trampoline,
+ if (Utilities.ATLEAST_S && !pendingIntent.isActivity() && LawnchairApp.isRecentsEnabled()) {
+ // In the event this pending intent eventually launches an activity, i.e. a
+ // trampoline,
// use the Quickstep transition animation.
try {
IActivityTaskManagerHidden atm = Refine.unsafeCast(ActivityTaskManager.getService());
@@ -89,8 +97,9 @@ class QuickstepInteractionHandler implements RemoteViews.InteractionHandler {
}
activityOptions.options.setPendingIntentLaunchFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
activityOptions.options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR);
+ activityOptions.options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
}
-
options = Pair.create(options.first, activityOptions.options);
if (pendingIntent.isActivity()) {
logAppLaunch(itemInfo);
@@ -100,6 +109,7 @@ class QuickstepInteractionHandler implements RemoteViews.InteractionHandler {
/**
* Logs that the app was launched from the widget.
+ *
* @param itemInfo the widget info.
*/
private void logAppLaunch(Object itemInfo) {
@@ -112,7 +122,8 @@ class QuickstepInteractionHandler implements RemoteViews.InteractionHandler {
private LauncherAppWidgetHostView findHostViewAncestor(View v) {
while (v != null) {
- if (v instanceof LauncherAppWidgetHostView) return (LauncherAppWidgetHostView) v;
+ if (v instanceof LauncherAppWidgetHostView)
+ return (LauncherAppWidgetHostView) v;
v = (View) v.getParent();
}
return null;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 056de79b83..d7b1977693 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -14,6 +14,7 @@
* limitations under the License.
*/
package com.android.launcher3.uioverrides;
+
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.os.Trace.TRACE_TAG_APP;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
@@ -65,6 +66,7 @@ import android.content.Intent;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.content.res.Configuration;
+import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.RectF;
import android.hardware.SensorManager;
@@ -76,7 +78,6 @@ import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.IBinder;
import android.os.SystemProperties;
-import android.os.Trace;
import android.view.Display;
import android.view.HapticFeedbackConstants;
import android.view.RemoteAnimationTarget;
@@ -88,13 +89,13 @@ import android.window.SplashScreen;
import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import com.android.app.viewcapture.SettingsAwareViewCapture;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.OnBackPressedHandler;
import com.android.launcher3.QuickstepAccessibilityDelegate;
import com.android.launcher3.QuickstepTransitionManager;
import com.android.launcher3.R;
@@ -136,8 +137,8 @@ import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchControlle
import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarTouchController;
import com.android.launcher3.util.ActivityOptionsWrapper;
-import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.Executors;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.NavigationMode;
import com.android.launcher3.util.ObjectWrapper;
@@ -189,9 +190,10 @@ import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Stream;
+
public class QuickstepLauncher extends Launcher {
- public static final boolean ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
- SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", true);
+ public static final boolean ENABLE_PIP_KEEP_CLEAR_ALGORITHM = SystemProperties
+ .getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", true);
public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
private FixedContainerItems mAllAppsPredictions;
private HotseatPredictionController mHotseatPredictionController;
@@ -211,21 +213,22 @@ public class QuickstepLauncher extends Launcher {
private SplitWithKeyboardShortcutController mSplitWithKeyboardShortcutController;
private SplitToWorkspaceController mSplitToWorkspaceController;
/**
- * If Launcher restarted while in the middle of an Overview split select, it needs this data to
+ * If Launcher restarted while in the middle of an Overview split select, it
+ * needs this data to
* recover. In all other cases this will remain null.
*/
private PendingSplitSelectInfo mPendingSplitSelectInfo = null;
private SafeCloseable mViewCapture;
private boolean mEnableWidgetDepth;
+
@Override
protected void setupViews() {
super.setupViews();
mActionsView = findViewById(R.id.overview_actions_view);
RecentsView overviewPanel = (RecentsView) getOverviewPanel();
- mSplitSelectStateController =
- new SplitSelectStateController(this, mHandler, getStateManager(),
- getDepthController(), getStatsLogManager(),
- SystemUiProxy.INSTANCE.get(this), RecentsModel.INSTANCE.get(this));
+ mSplitSelectStateController = new SplitSelectStateController(this, mHandler, getStateManager(),
+ getDepthController(), getStatsLogManager(),
+ SystemUiProxy.INSTANCE.get(this), RecentsModel.INSTANCE.get(this));
overviewPanel.init(mActionsView, mSplitSelectStateController);
mSplitWithKeyboardShortcutController = new SplitWithKeyboardShortcutController(this,
mSplitSelectStateController);
@@ -241,13 +244,15 @@ public class QuickstepLauncher extends Launcher {
mDesktopVisibilityController = new DesktopVisibilityController(this);
mHotseatPredictionController = new HotseatPredictionController(this);
mEnableWidgetDepth = SystemProperties.getBoolean("ro.launcher.depth.widget", true);
- getWorkspace().addOverlayCallback(progress ->
- onTaskbarInAppDisplayProgressUpdate(progress, MINUS_ONE_PAGE_PROGRESS_INDEX));
+ getWorkspace().addOverlayCallback(
+ progress -> onTaskbarInAppDisplayProgressUpdate(progress, MINUS_ONE_PAGE_PROGRESS_INDEX));
}
+
@Override
public void logAppLaunch(StatsLogManager statsLogManager, ItemInfo info,
- InstanceId instanceId) {
- // If the app launch is from any of the surfaces in AllApps then add the InstanceId from
+ InstanceId instanceId) {
+ // If the app launch is from any of the surfaces in AllApps then add the
+ // InstanceId from
// LiveSearchManager to recreate the AllApps session on the server side.
if (mAllAppsSessionLogId != null && ALL_APPS.equals(
getStateManager().getCurrentStableState())) {
@@ -256,8 +261,8 @@ public class QuickstepLauncher extends Launcher {
StatsLogger logger = statsLogManager.logger().withItemInfo(info).withInstanceId(instanceId);
if (mAllAppsPredictions != null
&& (info.itemType == ITEM_TYPE_APPLICATION
- || info.itemType == ITEM_TYPE_SHORTCUT
- || info.itemType == ITEM_TYPE_DEEP_SHORTCUT)) {
+ || info.itemType == ITEM_TYPE_SHORTCUT
+ || info.itemType == ITEM_TYPE_DEEP_SHORTCUT)) {
int count = mAllAppsPredictions.items.size();
for (int i = 0; i < count; i++) {
ItemInfo targetInfo = mAllAppsPredictions.items.get(i);
@@ -272,54 +277,69 @@ public class QuickstepLauncher extends Launcher {
logger.log(LAUNCHER_APP_LAUNCH_TAP);
mHotseatPredictionController.logLaunchedAppRankingInfo(info, instanceId);
}
+
@Override
protected void completeAddShortcut(Intent data, int container, int screenId, int cellX,
- int cellY, PendingRequestArgs args) {
+ int cellY, PendingRequestArgs args) {
if (container == CONTAINER_HOTSEAT) {
mHotseatPredictionController.onDeferredDrop(cellX, cellY);
}
super.completeAddShortcut(data, container, screenId, cellX, cellY, args);
}
+
@Override
protected LauncherAccessibilityDelegate createAccessibilityDelegate() {
return new QuickstepAccessibilityDelegate(this);
}
+
/**
* Returns Prediction controller for hybrid hotseat
*/
public HotseatPredictionController getHotseatPredictionController() {
return mHotseatPredictionController;
}
+
@Override
public void enableHotseatEdu(boolean enable) {
super.enableHotseatEdu(enable);
mHotseatPredictionController.enableHotseatEdu(enable);
}
+
/**
- * Builds the {@link QuickstepTransitionManager} instance to use for managing transitions.
+ * Builds the {@link QuickstepTransitionManager} instance to use for managing
+ * transitions.
*/
protected QuickstepTransitionManager buildAppTransitionManager() {
return new QuickstepTransitionManager(this);
}
+
@Override
protected QuickstepOnboardingPrefs createOnboardingPrefs(SharedPreferences sharedPrefs) {
return new QuickstepOnboardingPrefs(this, sharedPrefs);
}
+
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
onStateOrResumeChanging(false /* inTransition */);
}
+
@Override
- public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
- // Only pause is taskbar controller is not present
+ public RunnableList startActivitySafely(View v, Intent intent, ItemInfo item) {
+ // Only pause is taskbar controller is not present until the transition (if it
+ // exists) ends
mHotseatPredictionController.setPauseUIUpdate(getTaskbarUIController() == null);
- boolean started = super.startActivitySafely(v, intent, item);
- if (getTaskbarUIController() == null && !started) {
- mHotseatPredictionController.setPauseUIUpdate(false);
+ RunnableList result = super.startActivitySafely(v, intent, item);
+ if (result == null) {
+ if (getTaskbarUIController() == null) {
+ mHotseatPredictionController.setPauseUIUpdate(false);
+ }
+ } else {
+ result.add(() -> mHotseatPredictionController.setPauseUIUpdate(false));
}
- return started;
+ return result;
}
+
@Override
protected void onActivityFlagsChanged(int changeBits) {
if ((changeBits & ACTIVITY_STATE_STARTED) != 0) {
@@ -335,25 +355,25 @@ public class QuickstepLauncher extends Launcher {
| ACTIVITY_STATE_USER_ACTIVE | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0) {
onStateOrResumeChanging((getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0);
}
- if (((changeBits & ACTIVITY_STATE_STARTED) != 0
- || (changeBits & getActivityFlags() & ACTIVITY_STATE_DEFERRED_RESUMED) != 0)) {
- mHotseatPredictionController.setPauseUIUpdate(false);
- }
}
+
@Override
protected void showAllAppsFromIntent(boolean alreadyOnHome) {
TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
super.showAllAppsFromIntent(alreadyOnHome);
}
+
protected void onItemClicked(View view) {
if (!mSplitToWorkspaceController.handleSecondAppSelectionForSplit(view)) {
QuickstepLauncher.super.getItemOnClickListener().onClick(view);
}
}
+
@Override
public View.OnClickListener getItemOnClickListener() {
return this::onItemClicked;
}
+
@Override
public Stream getSupportedShortcuts() {
// Order matters as it affects order of appearance in popup container
@@ -364,35 +384,37 @@ public class QuickstepLauncher extends Launcher {
shortcuts.add(INSTALL);
return shortcuts.stream();
}
+
private List> getSplitShortcuts() {
- if (!ENABLE_SPLIT_FROM_WORKSPACE.get() || !mDeviceProfile.isTablet) {
+ if (!mDeviceProfile.isTablet) {
return Collections.emptyList();
}
RecentsView recentsView = (RecentsView) getOverviewPanel();
- // TODO(b/266482558): Pull it out of PagedOrentationHandler for split from workspace.
- List positions =
- recentsView.getPagedOrientationHandler().getSplitPositionOptions(
- mDeviceProfile);
+ // TODO(b/266482558): Pull it out of PagedOrentationHandler for split from
+ // workspace.
+ List positions = recentsView.getPagedOrientationHandler().getSplitPositionOptions(
+ mDeviceProfile);
List> splitShortcuts = new ArrayList<>();
for (SplitPositionOption position : positions) {
splitShortcuts.add(getSplitSelectShortcutByPosition(position));
}
return splitShortcuts;
}
+
/**
- * Recents logic that triggers when launcher state changes or launcher activity stops/resumes.
+ * Recents logic that triggers when launcher state changes or launcher activity
+ * stops/resumes.
*/
private void onStateOrResumeChanging(boolean inTransition) {
LauncherState state = getStateManager().getState();
boolean started = ((getActivityFlags() & ACTIVITY_STATE_STARTED)) != 0;
if (started) {
DeviceProfile profile = getDeviceProfile();
- boolean willUserBeActive =
- (getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0;
+ boolean willUserBeActive = (getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0;
boolean visible = (state == NORMAL || state == OVERVIEW)
&& (willUserBeActive || isUserActive())
&& !profile.isVerticalBarLayout();
- if (ENABLE_PIP_KEEP_CLEAR_ALGORITHM) {
+ if (ENABLE_PIP_KEEP_CLEAR_ALGORITHM) {
SystemUiProxy.INSTANCE.get(this)
.setLauncherKeepClearAreaHeight(visible, profile.hotseatBarSizePx);
} else {
@@ -403,13 +425,13 @@ public class QuickstepLauncher extends Launcher {
((RecentsView) getOverviewPanel()).setSwipeDownShouldLaunchApp(false);
}
}
+
@Override
public void bindExtraContainerItems(FixedContainerItems item) {
if (item.containerId == Favorites.CONTAINER_PREDICTION) {
mAllAppsPredictions = item;
- PredictionRowView> predictionRowView =
- getAppsView().getFloatingHeaderView().findFixedRowByType(
- PredictionRowView.class);
+ PredictionRowView> predictionRowView = getAppsView().getFloatingHeaderView().findFixedRowByType(
+ PredictionRowView.class);
predictionRowView.setPredictedApps(item.items);
} else if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) {
mHotseatPredictionController.setPredictedItems(item);
@@ -417,15 +439,19 @@ public class QuickstepLauncher extends Launcher {
getPopupDataProvider().setRecommendedWidgets(item.items);
}
}
+
@Override
public void bindWorkspaceComponentsRemoved(Predicate matcher) {
super.bindWorkspaceComponentsRemoved(matcher);
mHotseatPredictionController.onModelItemsRemoved(matcher);
}
+
@Override
public void onDestroy() {
mAppTransitionManager.onActivityDestroyed();
if (mUnfoldTransitionProgressProvider != null) {
+ SystemUiProxy.INSTANCE.get(this).setUnfoldAnimationListener(null);
+
mUnfoldTransitionProgressProvider.destroy();
}
mTISBindHelper.onDestroy();
@@ -438,8 +464,10 @@ public class QuickstepLauncher extends Launcher {
super.onDestroy();
mHotseatPredictionController.destroy();
mSplitWithKeyboardShortcutController.onDestroy();
- if (mViewCapture != null) mViewCapture.close();
+ if (mViewCapture != null)
+ mViewCapture.close();
}
+
@Override
public void onStateSetEnd(LauncherState state) {
super.onStateSetEnd(state);
@@ -482,6 +510,7 @@ public class QuickstepLauncher extends Launcher {
}
}
}
+
@Override
public TouchController[] createTouchControllers() {
NavigationMode mode = DisplayController.getNavigationMode(this);
@@ -510,21 +539,27 @@ public class QuickstepLauncher extends Launcher {
list.add(new LauncherTaskViewController(this));
return list.toArray(new TouchController[list.size()]);
}
+
@Override
public AtomicAnimationFactory createAtomicAnimationFactory() {
return new QuickstepAtomicAnimationFactory(this);
}
+
@Override
protected LauncherWidgetHolder createAppWidgetHolder() {
- final QuickstepHolderFactory factory =
- (QuickstepHolderFactory) LauncherWidgetHolder.HolderFactory.newFactory(this);
+ final QuickstepHolderFactory factory = (QuickstepHolderFactory) LauncherWidgetHolder.HolderFactory
+ .newFactory(this);
return factory.newInstance(this,
appWidgetId -> getWorkspace().removeWidget(appWidgetId),
new QuickstepInteractionHandler(this));
}
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ if (Utilities.ATLEAST_U && FeatureFlags.ENABLE_BACK_SWIPE_LAUNCHER_ANIMATION.get()) {
+ getApplicationInfo().setEnableOnBackInvokedCallback(true);
+ }
if (savedInstanceState != null) {
mPendingSplitSelectInfo = ObjectWrapper.unwrap(
savedInstanceState.getIBinder(PENDING_SPLIT_SELECT_INFO));
@@ -536,16 +571,15 @@ public class QuickstepLauncher extends Launcher {
}
getWindow().addPrivateFlags(PRIVATE_FLAG_OPTIMIZE_MEASURE);
}
+
@Override
public void startSplitSelection(SplitSelectSource splitSelectSource) {
RecentsView recentsView = getOverviewPanel();
- ComponentKey componentToBeStaged = new ComponentKey(
- splitSelectSource.itemInfo.getTargetComponent(),
- splitSelectSource.itemInfo.user);
- // Check if there is already an instance of this app running, if so, initiate the split
+ // Check if there is already an instance of this app running, if so, initiate
+ // the split
// using that.
mSplitSelectStateController.findLastActiveTaskAndRunCallback(
- componentToBeStaged,
+ splitSelectSource.itemInfo.getComponentKey(),
foundTask -> {
splitSelectSource.alreadyRunningTaskId = foundTask == null
? INVALID_TASK_ID
@@ -555,10 +589,13 @@ public class QuickstepLauncher extends Launcher {
} else {
recentsView.initiateSplitSelect(splitSelectSource);
}
- }
- );
+ });
}
- /** TODO(b/266482558) Migrate into SplitSelectStateController or someplace split specific. */
+
+ /**
+ * TODO(b/266482558) Migrate into SplitSelectStateController or someplace split
+ * specific.
+ */
private void startSplitToHome(SplitSelectSource source) {
AbstractFloatingView.closeAllOpenViews(this);
int splitPlaceholderSize = getResources().getDimensionPixelSize(
@@ -590,6 +627,7 @@ public class QuickstepLauncher extends Launcher {
});
anim.buildAnim().start();
}
+
@Override
protected void onResume() {
super.onResume();
@@ -597,6 +635,7 @@ public class QuickstepLauncher extends Launcher {
mLauncherUnfoldAnimationController.onResume();
}
}
+
@Override
protected void onPause() {
if (mLauncherUnfoldAnimationController != null) {
@@ -604,6 +643,7 @@ public class QuickstepLauncher extends Launcher {
}
super.onPause();
}
+
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
@@ -611,35 +651,43 @@ public class QuickstepLauncher extends Launcher {
mOverviewCommandHelper.clearPendingCommands();
}
}
+
public QuickstepTransitionManager getAppTransitionManager() {
return mAppTransitionManager;
}
+
@Override
public void onEnterAnimationComplete() {
super.onEnterAnimationComplete();
- // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled
- // as a part of quickstep, so that high-res thumbnails can load the next time we enter
+ // After the transition to home, enable the high-res thumbnail loader if it
+ // wasn't enabled
+ // as a part of quickstep, so that high-res thumbnails can load the next time we
+ // enter
// overview
RecentsModel.INSTANCE.get(this).getThumbnailCache()
.getHighResLoadingState().setVisible(true);
}
+
@Override
protected void handleGestureContract(Intent intent) {
if (FeatureFlags.SEPARATE_RECENTS_ACTIVITY.get()) {
super.handleGestureContract(intent);
}
}
+
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
RecentsModel.INSTANCE.get(this).onTrimMemory(level);
}
+
@Override
public void onUiChangedWhileSleeping() {
// Remove the snapshot because the content view may have obvious changes.
UI_HELPER_EXECUTOR.execute(
() -> ActivityManagerWrapper.getInstance().invalidateHomeTaskSnapshot(this));
}
+
@Override
protected void onScreenOnChanged(boolean isOn) {
super.onScreenOnChanged(isOn);
@@ -648,11 +696,13 @@ public class QuickstepLauncher extends Launcher {
recentsView.finishRecentsAnimation(true /* toRecents */, null);
}
}
+
@Override
public void onAllAppsTransition(float progress) {
super.onAllAppsTransition(progress);
onTaskbarInAppDisplayProgressUpdate(progress, ALL_APPS_PAGE_PROGRESS_INDEX);
}
+
@Override
public void onWidgetsTransition(float progress) {
super.onWidgetsTransition(progress);
@@ -662,52 +712,67 @@ public class QuickstepLauncher extends Launcher {
progress, 0f, 1f, 0f, getDeviceProfile().bottomSheetDepth, EMPHASIZED));
}
}
+
@Override
protected void registerBackDispatcher() {
+ if (!FeatureFlags.ENABLE_BACK_SWIPE_LAUNCHER_ANIMATION.get()) {
+ super.registerBackDispatcher();
+ return;
+ }
getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
new OnBackAnimationCallback() {
- @Nullable OnBackPressedHandler mActiveOnBackPressedHandler;
+
+ @Nullable
+ OnBackAnimationCallback mActiveOnBackAnimationCallback;
+
@Override
public void onBackStarted(@NonNull BackEvent backEvent) {
- if (mActiveOnBackPressedHandler != null) {
- mActiveOnBackPressedHandler.onBackCancelled();
+ if (mActiveOnBackAnimationCallback != null) {
+ mActiveOnBackAnimationCallback.onBackCancelled();
}
- mActiveOnBackPressedHandler = getOnBackPressedHandler();
- mActiveOnBackPressedHandler.onBackStarted();
+ mActiveOnBackAnimationCallback = getOnBackAnimationCallback();
+ mActiveOnBackAnimationCallback.onBackStarted(backEvent);
}
+
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Override
public void onBackInvoked() {
- // Recreate mActiveOnBackPressedHandler if necessary to avoid NPE because:
+ // Recreate mActiveOnBackAnimationCallback if necessary to avoid NPE
+ // because:
// 1. b/260636433: In 3-button-navigation mode, onBackStarted() is not
// called on ACTION_DOWN before onBackInvoked() is called in ACTION_UP.
// 2. Launcher#onBackPressed() will call onBackInvoked() without calling
// onBackInvoked() beforehand.
- if (mActiveOnBackPressedHandler == null) {
- mActiveOnBackPressedHandler = getOnBackPressedHandler();
+ if (mActiveOnBackAnimationCallback == null) {
+ mActiveOnBackAnimationCallback = getOnBackAnimationCallback();
}
- mActiveOnBackPressedHandler.onBackInvoked();
- mActiveOnBackPressedHandler = null;
+ mActiveOnBackAnimationCallback.onBackInvoked();
+ mActiveOnBackAnimationCallback = null;
TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onBackInvoked");
}
+
@Override
public void onBackProgressed(@NonNull BackEvent backEvent) {
- if (!FeatureFlags.IS_STUDIO_BUILD && mActiveOnBackPressedHandler == null) {
+ if (!FeatureFlags.IS_STUDIO_BUILD
+ && mActiveOnBackAnimationCallback == null) {
return;
}
- mActiveOnBackPressedHandler
- .onBackProgressed(backEvent.getProgress());
+ mActiveOnBackAnimationCallback.onBackProgressed(backEvent);
}
+
@Override
public void onBackCancelled() {
- if (!FeatureFlags.IS_STUDIO_BUILD && mActiveOnBackPressedHandler == null) {
+ if (!FeatureFlags.IS_STUDIO_BUILD
+ && mActiveOnBackAnimationCallback == null) {
return;
}
- mActiveOnBackPressedHandler.onBackCancelled();
- mActiveOnBackPressedHandler = null;
+ mActiveOnBackAnimationCallback.onBackCancelled();
+ mActiveOnBackAnimationCallback = null;
}
});
}
+
private void onTaskbarInAppDisplayProgressUpdate(float progress, int flag) {
if (mTaskbarManager == null
|| mTaskbarManager.getCurrentActivityContext() == null
@@ -716,9 +781,10 @@ public class QuickstepLauncher extends Launcher {
}
mTaskbarUIController.onTaskbarInAppDisplayProgressUpdate(progress, flag);
}
+
@Override
public void startIntentSenderForResult(IntentSender intent, int requestCode,
- Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) {
+ Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) {
if (requestCode != -1) {
mPendingActivityRequestCode = requestCode;
StartActivityParams params = new StartActivityParams(this, requestCode);
@@ -734,6 +800,7 @@ public class QuickstepLauncher extends Launcher {
flagsValues, extraFlags, options);
}
}
+
@Override
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
if (requestCode != -1) {
@@ -746,6 +813,7 @@ public class QuickstepLauncher extends Launcher {
super.startActivityForResult(intent, requestCode, options);
}
}
+
@Override
public void setResumed() {
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
@@ -754,36 +822,45 @@ public class QuickstepLauncher extends Launcher {
&& !controller.isGestureInProgress()) {
// Return early to skip setting activity to appear as resumed
// TODO(b/255649902): shouldn't be needed when we have a separate launcher state
- // for desktop that we can use to control other parts of launcher
+ // for desktop that we can use to control other parts of launcher
return;
}
}
super.setResumed();
}
+
@Override
protected void onDeferredResumed() {
super.onDeferredResumed();
handlePendingActivityRequest();
}
+
private void handlePendingActivityRequest() {
if (mPendingActivityRequestCode != -1 && isInState(NORMAL)
&& ((getActivityFlags() & ACTIVITY_STATE_DEFERRED_RESUMED) != 0)) {
- // Remove any active ProxyActivityStarter task and send RESULT_CANCELED to Launcher.
+ // Remove any active ProxyActivityStarter task and send RESULT_CANCELED to
+ // Launcher.
onActivityResult(mPendingActivityRequestCode, RESULT_CANCELED, null);
- // ProxyActivityStarter is started with clear task to reset the task after which it
+ // ProxyActivityStarter is started with clear task to reset the task after which
+ // it
// removes the task itself.
startActivity(ProxyActivityStarter.getLaunchIntent(this, null));
}
}
+
private void onTISConnected(TISBinder binder) {
mTaskbarManager = binder.getTaskbarManager();
- mTaskbarManager.setActivity(this);
+ if (mTaskbarManager != null) {
+ mTaskbarManager.setActivity(this);
+ }
mOverviewCommandHelper = binder.getOverviewCommandHelper();
}
+
@Override
public void runOnBindToTouchInteractionService(Runnable r) {
mTISBindHelper.runOnBindToTouchInteractionService(r);
}
+
private void initUnfoldTransitionProgressProvider() {
final UnfoldTransitionConfig config = new ResourceUnfoldTransitionConfig();
if (config.isEnabled()) {
@@ -794,24 +871,26 @@ public class QuickstepLauncher extends Launcher {
}
}
}
- /** Registers hinge angle listener and calculates the animation progress in this process. */
+
+ /**
+ * Registers hinge angle listener and calculates the animation progress in this
+ * process.
+ */
private void initLocallyCalculatedUnfoldAnimation(UnfoldTransitionConfig config) {
- UnfoldSharedComponent unfoldComponent =
- UnfoldTransitionFactory.createUnfoldSharedComponent(
- /* context= */ this,
- config,
- ProxyScreenStatusProvider.INSTANCE,
- new DeviceStateManagerFoldProvider(
- getSystemService(DeviceStateManager.class), /* context= */ this),
- new ActivityManagerActivityTypeProvider(
- getSystemService(ActivityManager.class)),
- getSystemService(SensorManager.class),
- getMainThreadHandler(),
- getMainExecutor(),
- /* backgroundExecutor= */ UI_HELPER_EXECUTOR,
- /* tracingTagPrefix= */ "launcher",
- getSystemService(DisplayManager.class)
- );
+ UnfoldSharedComponent unfoldComponent = UnfoldTransitionFactory.createUnfoldSharedComponent(
+ /* context= */ this,
+ config,
+ ProxyScreenStatusProvider.INSTANCE,
+ new DeviceStateManagerFoldProvider(
+ getSystemService(DeviceStateManager.class), /* context= */ this),
+ new ActivityManagerActivityTypeProvider(
+ getSystemService(ActivityManager.class)),
+ getSystemService(SensorManager.class),
+ getMainThreadHandler(),
+ getMainExecutor(),
+ /* backgroundExecutor= */ UI_HELPER_EXECUTOR,
+ /* tracingTagPrefix= */ "launcher",
+ getSystemService(DisplayManager.class));
mUnfoldTransitionProgressProvider = unfoldComponent.getUnfoldTransitionProvider()
.orElseThrow(() -> new IllegalStateException(
"Trying to create UnfoldTransitionProgressProvider when the "
@@ -819,20 +898,19 @@ public class QuickstepLauncher extends Launcher {
initUnfoldAnimationController(mUnfoldTransitionProgressProvider,
unfoldComponent.getRotationChangeProvider());
}
+
/** Receives animation progress from sysui process. */
private void initRemotelyCalculatedUnfoldAnimation(UnfoldTransitionConfig config) {
- RemoteUnfoldSharedComponent unfoldComponent =
- UnfoldTransitionFactory.createRemoteUnfoldSharedComponent(
- /* context= */ this,
- config,
- getMainExecutor(),
- getMainThreadHandler(),
- /* backgroundExecutor= */ UI_HELPER_EXECUTOR,
- /* tracingTagPrefix= */ "launcher",
- getSystemService(DisplayManager.class)
- );
- final RemoteUnfoldTransitionReceiver remoteUnfoldTransitionProgressProvider =
- unfoldComponent.getRemoteTransitionProgress().orElseThrow(
+ RemoteUnfoldSharedComponent unfoldComponent = UnfoldTransitionFactory.createRemoteUnfoldSharedComponent(
+ /* context= */ this,
+ config,
+ getMainExecutor(),
+ getMainThreadHandler(),
+ /* backgroundExecutor= */ UI_HELPER_EXECUTOR,
+ /* tracingTagPrefix= */ "launcher",
+ getSystemService(DisplayManager.class));
+ final RemoteUnfoldTransitionReceiver remoteUnfoldTransitionProgressProvider = unfoldComponent
+ .getRemoteTransitionProgress().orElseThrow(
() -> new IllegalStateException(
"Trying to create getRemoteTransitionProgress when the transition "
+ "is disabled"));
@@ -842,52 +920,63 @@ public class QuickstepLauncher extends Launcher {
initUnfoldAnimationController(mUnfoldTransitionProgressProvider,
unfoldComponent.getRotationChangeProvider());
}
+
private void initUnfoldAnimationController(UnfoldTransitionProgressProvider progressProvider,
- RotationChangeProvider rotationChangeProvider) {
+ RotationChangeProvider rotationChangeProvider) {
mLauncherUnfoldAnimationController = new LauncherUnfoldAnimationController(
/* launcher= */ this,
getWindowManager(),
progressProvider,
- rotationChangeProvider
- );
+ rotationChangeProvider);
}
+
public void setTaskbarUIController(LauncherTaskbarUIController taskbarUIController) {
mTaskbarUIController = taskbarUIController;
}
+
public @Nullable LauncherTaskbarUIController getTaskbarUIController() {
return mTaskbarUIController;
}
- public SplitSelectStateController getSplitSelectStateController() {
- return mSplitSelectStateController;
+
+ public SplitToWorkspaceController getSplitToWorkspaceController() {
+ return mSplitToWorkspaceController;
}
+
public T getActionsView() {
return (T) mActionsView;
}
+
@Override
protected void closeOpenViews(boolean animate) {
super.closeOpenViews(animate);
TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
}
+
@Override
protected void collectStateHandlers(List out) {
super.collectStateHandlers(out);
out.add(getDepthController());
out.add(new RecentsViewStateController(this));
}
+
public DepthController getDepthController() {
return mDepthController;
}
+
public DesktopVisibilityController getDesktopVisibilityController() {
return mDesktopVisibilityController;
}
+
@Nullable
public UnfoldTransitionProgressProvider getUnfoldTransitionProgressProvider() {
return mUnfoldTransitionProgressProvider;
}
+
@Override
public boolean supportsAdaptiveIconAnimation(View clickedView) {
return mAppTransitionManager.hasControlRemoteAppTransitionPermission();
}
+
@Override
public DragOptions getDefaultWorkspaceDragOptions() {
if (mNextWorkspaceDragOptions != null) {
@@ -897,16 +986,18 @@ public class QuickstepLauncher extends Launcher {
}
return super.getDefaultWorkspaceDragOptions();
}
+
public void setNextWorkspaceDragOptions(DragOptions dragOptions) {
mNextWorkspaceDragOptions = dragOptions;
}
+
@Override
public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) {
QuickstepTransitionManager appTransitionManager = getAppTransitionManager();
appTransitionManager.setRemoteAnimationProvider(new RemoteAnimationProvider() {
@Override
public AnimatorSet createWindowAnimation(RemoteAnimationTarget[] appTargets,
- RemoteAnimationTarget[] wallpaperTargets) {
+ RemoteAnimationTarget[] wallpaperTargets) {
// On the first call clear the reference.
signal.cancel();
ValueAnimator fadeAnimation = ValueAnimator.ofFloat(1, 0);
@@ -918,21 +1009,27 @@ public class QuickstepLauncher extends Launcher {
}
}, signal);
}
+
@Override
public float[] getNormalOverviewScaleAndOffset() {
return DisplayController.getNavigationMode(this).hasGestures
- ? new float[] {1, 1} : new float[] {1.1f, NO_OFFSET};
+ ? new float[] { 1, 1 }
+ : new float[] { 1.1f, NO_OFFSET };
}
+
@Override
public void finishBindingItems(IntSet pagesBoundFirst) {
super.finishBindingItems(pagesBoundFirst);
- // Instantiate and initialize WellbeingModel now that its loading won't interfere with
+ // Instantiate and initialize WellbeingModel now that its loading won't
+ // interfere with
// populating workspace.
// TODO: Find a better place for this
WellbeingModel.INSTANCE.get(this);
}
+
@Override
- public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) {
+ public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks,
+ int workspaceItemCount, boolean isBindSync) {
pendingTasks.add(() -> {
// This is added in pending task as we need to wait for views to be positioned
// correctly before registering them for the animation.
@@ -942,14 +1039,14 @@ public class QuickstepLauncher extends Launcher {
mLauncherUnfoldAnimationController.updateRegisteredViewsIfNeeded();
}
});
- super.onInitialBindComplete(boundPages, pendingTasks);
+ super.onInitialBindComplete(boundPages, pendingTasks, workspaceItemCount, isBindSync);
}
+
@Override
public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
- ActivityOptionsWrapper activityOptions =
- mAppTransitionManager.hasControlRemoteAppTransitionPermission()
- ? mAppTransitionManager.getActivityLaunchOptions(v)
- : super.getActivityLaunchOptions(v, item);
+ ActivityOptionsWrapper activityOptions = mAppTransitionManager.hasControlRemoteAppTransitionPermission()
+ ? mAppTransitionManager.getActivityLaunchOptions(v)
+ : super.getActivityLaunchOptions(v, item);
if (mLastTouchUpTime > 0 && app.lawnchair.LawnchairApp.isAtleastT()) {
activityOptions.options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_LAUNCHER,
mLastTouchUpTime);
@@ -966,16 +1063,29 @@ public class QuickstepLauncher extends Launcher {
activityOptions.options.setLaunchDisplayId(
(v != null && v.getDisplay() != null) ? v.getDisplay().getDisplayId()
: Display.DEFAULT_DISPLAY);
- if(app.lawnchair.LawnchairApp.isAtleastT()){
+ if (app.lawnchair.LawnchairApp.isAtleastT()) {
addLaunchCookie(item, activityOptions.options);
}
return activityOptions;
}
+
+ @Override
+ public ActivityOptionsWrapper makeDefaultActivityOptions(int splashScreenStyle) {
+ RunnableList callbacks = new RunnableList();
+ ActivityOptions options = ActivityOptions.makeCustomAnimation(
+ this, 0, 0, Color.TRANSPARENT,
+ Executors.MAIN_EXECUTOR.getHandler(), null,
+ elapsedRealTime -> callbacks.executeAllAndDestroy());
+ options.setSplashScreenStyle(splashScreenStyle);
+ return new ActivityOptionsWrapper(options, callbacks);
+ }
+
@Override
@BinderThread
public void enterStageSplitFromRunningApp(boolean leftOrTop) {
mSplitWithKeyboardShortcutController.enterStageSplit(leftOrTop);
}
+
/**
* Adds a new launch cookie for the activity launch if supported.
*
@@ -988,6 +1098,7 @@ public class QuickstepLauncher extends Launcher {
opts.setLaunchCookie(launchCookie);
}
}
+
/**
* Return a new launch cookie for the activity launch if supported.
*
@@ -1000,8 +1111,10 @@ public class QuickstepLauncher extends Launcher {
switch (info.container) {
case Favorites.CONTAINER_DESKTOP:
case Favorites.CONTAINER_HOTSEAT:
- // Fall through and continue it's on the workspace (we don't support swiping back
- // to other containers like all apps or the hotseat predictions (which can change)
+ // Fall through and continue it's on the workspace (we don't support swiping
+ // back
+ // to other containers like all apps or the hotseat predictions (which can
+ // change)
break;
default:
if (info.container >= 0) {
@@ -1024,18 +1137,24 @@ public class QuickstepLauncher extends Launcher {
}
return ObjectWrapper.wrap(new Integer(info.id));
}
+
public void setHintUserWillBeActive() {
addActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE);
}
+
@Override
public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
super.onDisplayInfoChanged(context, info, flags);
- // When changing screens, force moving to rest state similar to StatefulActivity.onStop, as
+ // When changing screens, force moving to rest state similar to
+ // StatefulActivity.onStop, as
// StatefulActivity isn't called consistently.
if ((flags & CHANGE_ACTIVE_SCREEN) != 0) {
- // Do not animate moving to rest state, as it can clash with Launcher#onIdpChanged
- // where reapplyUi calls StateManager's reapplyState during the state change animation,
- // and cancel the state change unexpectedly. The screen will be off during screen
+ // Do not animate moving to rest state, as it can clash with
+ // Launcher#onIdpChanged
+ // where reapplyUi calls StateManager's reapplyState during the state change
+ // animation,
+ // and cancel the state change unexpectedly. The screen will be off during
+ // screen
// transition, hiding the unanimated transition.
getStateManager().moveToRestState(/* isAnimated = */false);
}
@@ -1046,14 +1165,25 @@ public class QuickstepLauncher extends Launcher {
}
}
}
+
+ @Override
+ public void tryClearAccessibilityFocus(View view) {
+ view.clearAccessibilityFocus();
+ }
+
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
- // If Launcher shuts downs during split select, we save some extra data in the recovery
- // bundle to allow graceful recovery. The normal LauncherState restore mechanism doesn't
- // work in this case because restoring straight to OverviewSplitSelect without staging data,
- // or before the tasks themselves have loaded into Overview, causes a crash. So we tell
- // Launcher to first restore into Overview state, wait for the relevant tasks and icons to
+ // If Launcher shuts downs during split select, we save some extra data in the
+ // recovery
+ // bundle to allow graceful recovery. The normal LauncherState restore mechanism
+ // doesn't
+ // work in this case because restoring straight to OverviewSplitSelect without
+ // staging data,
+ // or before the tasks themselves have loaded into Overview, causes a crash. So
+ // we tell
+ // Launcher to first restore into Overview state, wait for the relevant tasks
+ // and icons to
// load in, and then proceed to OverviewSplitSelect.
if (isInState(OVERVIEW_SPLIT_SELECT)) {
// Launcher will restart in Overview and then transition to OverviewSplitSelect.
@@ -1061,33 +1191,39 @@ public class QuickstepLauncher extends Launcher {
new PendingSplitSelectInfo(
mSplitSelectStateController.getInitialTaskId(),
mSplitSelectStateController.getActiveSplitStagePosition(),
- mSplitSelectStateController.getSplitEvent())
- ));
+ mSplitSelectStateController.getSplitEvent())));
outState.putInt(RUNTIME_STATE, OVERVIEW.ordinal);
}
}
+
/**
- * When Launcher restarts, it sometimes needs to recover to a split selection state.
+ * When Launcher restarts, it sometimes needs to recover to a split selection
+ * state.
* This function checks if such a recovery is needed.
+ *
* @return a boolean representing whether the launcher is waiting to recover to
- * OverviewSplitSelect state.
+ * OverviewSplitSelect state.
*/
public boolean hasPendingSplitSelectInfo() {
return mPendingSplitSelectInfo != null;
}
+
/**
* See {@link #hasPendingSplitSelectInfo()}
*/
public @Nullable PendingSplitSelectInfo getPendingSplitSelectInfo() {
return mPendingSplitSelectInfo;
}
+
/**
- * When the launcher has successfully recovered to OverviewSplitSelect state, this function
+ * When the launcher has successfully recovered to OverviewSplitSelect state,
+ * this function
* deletes the recovery data, returning it to a null state.
*/
public void finishSplitSelectRecovery() {
mPendingSplitSelectInfo = null;
}
+
@Override
public boolean areFreeformTasksVisible() {
if (mDesktopVisibilityController != null) {
@@ -1095,11 +1231,13 @@ public class QuickstepLauncher extends Launcher {
}
return false;
}
+
@Override
protected void onDeviceProfileInitiated() {
super.onDeviceProfileInitiated();
SystemUiProxy.INSTANCE.get(this).setLauncherAppIconSize(mDeviceProfile.iconSizePx);
}
+
@Override
public void dispatchDeviceProfileChanged() {
super.dispatchDeviceProfileChanged();
@@ -1108,6 +1246,7 @@ public class QuickstepLauncher extends Launcher {
mTaskbarManager.debugWhyTaskbarNotDestroyed("QuickstepLauncher#onDeviceProfileChanged");
}
}
+
/**
* Launches the given {@link GroupTask} in splitscreen.
*
@@ -1115,42 +1254,48 @@ public class QuickstepLauncher extends Launcher {
*/
public void launchSplitTasks(@NonNull View taskView, @NonNull GroupTask groupTask) {
if (groupTask.task2 == null) {
- UI_HELPER_EXECUTOR.execute(() ->
- ActivityManagerWrapper.getInstance().startActivityFromRecents(
- groupTask.task1.key,
- getActivityLaunchOptions(taskView, null).options));
+ UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance().startActivityFromRecents(
+ groupTask.task1.key,
+ getActivityLaunchOptions(taskView, null).options));
return;
}
- mSplitSelectStateController.launchTasks(
+ mSplitSelectStateController.launchExistingSplitPair(
+ null /* launchingTaskView */,
groupTask.task1.key.id,
groupTask.task2.key.id,
SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
- /* callback= */ success -> {},
+ /* callback= */ success -> {
+ },
/* freezeTaskList= */ true,
groupTask.mSplitBounds == null
? DEFAULT_SPLIT_RATIO
: groupTask.mSplitBounds.appsStackedVertically
- ? groupTask.mSplitBounds.topTaskPercent
- : groupTask.mSplitBounds.leftTaskPercent);
+ ? groupTask.mSplitBounds.topTaskPercent
+ : groupTask.mSplitBounds.leftTaskPercent);
}
+
private static final class LauncherTaskViewController extends
TaskViewTouchController {
LauncherTaskViewController(Launcher activity) {
super(activity);
}
+
@Override
protected boolean isRecentsInteractive() {
return mActivity.isInState(OVERVIEW) || mActivity.isInState(OVERVIEW_MODAL_TASK);
}
+
@Override
protected boolean isRecentsModal() {
return mActivity.isInState(OVERVIEW_MODAL_TASK);
}
+
@Override
protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
mActivity.getStateManager().setCurrentUserControlledAnimation(animController);
}
}
+
@Override
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
super.dump(prefix, fd, writer, args);
@@ -1159,7 +1304,10 @@ public class QuickstepLauncher extends Launcher {
}
RecentsView recentsView = getOverviewPanel();
writer.println("\nQuickstepLauncher:");
- writer.println(prefix + "\tmOrientationState: " + (recentsView == null ? "recentsNull" :
- recentsView.getPagedViewOrientedState()));
+ writer.println(prefix + "\tmOrientationState: "
+ + (recentsView == null ? "recentsNull" : recentsView.getPagedViewOrientedState()));
+ if (recentsView != null) {
+ recentsView.getSplitSelectController().dump(prefix, writer);
+ }
}
}
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
index b318100205..39543b0d7d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
@@ -95,7 +95,11 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
i -> MAIN_EXECUTOR.execute(() ->
sHolders.forEach(h -> h.mAppWidgetRemovedCallback.accept(i))),
() -> MAIN_EXECUTOR.execute(() ->
- sHolders.forEach(h -> h.mProviderChangedListeners.forEach(
+ sHolders.forEach(h ->
+ // Listeners might remove themselves from the list during the
+ // iteration. Creating a copy of the list to avoid exceptions
+ // for concurrent modification.
+ new ArrayList<>(h.mProviderChangedListeners).forEach(
ProviderChangedListener::notifyWidgetProvidersChanged))),
UI_HELPER_EXECUTOR.getLooper());
if (!WidgetsModel.GO_DISABLE_WIDGETS) {
@@ -155,7 +159,6 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
} else if (KEY_VIEWS_UPDATE.equals(key)) {
// For views update, remove all previous updates, except the provider
pendingUpdate.remoteViews = (RemoteViews) data;
- pendingUpdate.changedViews.clear();
} else if (KEY_VIEW_DATA_CHANGED.equals(key)) {
pendingUpdate.changedViews.add((Integer) data);
}
@@ -197,7 +200,7 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
@Override
public void addProviderChangeListener(
@NonNull LauncherWidgetHolder.ProviderChangedListener listener) {
- mProviderChangedListeners.add(listener);
+ MAIN_EXECUTOR.execute(() -> mProviderChangedListeners.add(listener));
}
/**
@@ -207,7 +210,7 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
@Override
public void removeProviderChangeListener(
LauncherWidgetHolder.ProviderChangedListener listener) {
- mProviderChangedListeners.remove(listener);
+ MAIN_EXECUTOR.execute(() -> mProviderChangedListeners.remove(listener));
}
/**
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DebugFlag.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DebugFlag.java
index d4944d0315..481e20007e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/DebugFlag.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DebugFlag.java
@@ -15,16 +15,20 @@
*/
package com.android.launcher3.uioverrides.flags;
+import androidx.annotation.NonNull;
+
import com.android.launcher3.config.FeatureFlags.BooleanFlag;
+import com.android.launcher3.config.FeatureFlags.FlagState;
class DebugFlag extends BooleanFlag {
public final String key;
public final String description;
- public final boolean defaultValue;
+ @NonNull
+ public final FlagState defaultValue;
- public DebugFlag(String key, String description, boolean defaultValue, boolean currentValue) {
+ DebugFlag(String key, String description, FlagState defaultValue, boolean currentValue) {
super(currentValue);
this.key = key;
this.defaultValue = defaultValue;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsFragment.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsFragment.java
index 67ea1af056..b901a87753 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsFragment.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsFragment.java
@@ -39,6 +39,7 @@ import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.text.Editable;
+import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.ArrayMap;
import android.util.Pair;
@@ -182,9 +183,16 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
}
private PreferenceCategory newCategory(String title) {
+ return newCategory(title, null);
+ }
+
+ private PreferenceCategory newCategory(String title, @Nullable String summary) {
PreferenceCategory category = new PreferenceCategory(getContext());
category.setOrder(Preference.DEFAULT_ORDER);
category.setTitle(title);
+ if (!TextUtils.isEmpty(summary)) {
+ category.setSummary(summary);
+ }
mPreferenceScreen.addPreference(category);
return category;
}
@@ -195,7 +203,7 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
}
mFlagTogglerPrefUi = new FlagTogglerPrefUi(this);
- mFlagTogglerPrefUi.applyTo(newCategory("Feature flags"));
+ mFlagTogglerPrefUi.applyTo(newCategory("Feature flags", "Long press to reset"));
}
@Override
@@ -351,29 +359,6 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
return true;
});
sandboxCategory.addPreference(launchOverviewTutorialPreference);
- Preference launchAssistantTutorialPreference = new Preference(context);
- launchAssistantTutorialPreference.setKey("launchAssistantTutorial");
- launchAssistantTutorialPreference.setTitle("Launch Assistant Tutorial");
- launchAssistantTutorialPreference.setSummary("Learn how to use the Assistant gesture");
- launchAssistantTutorialPreference.setOnPreferenceClickListener(preference -> {
- startActivity(launchSandboxIntent
- .putExtra("use_tutorial_menu", false)
- .putExtra("tutorial_steps", new String[] {"ASSISTANT"}));
- return true;
- });
- sandboxCategory.addPreference(launchAssistantTutorialPreference);
- Preference launchSandboxModeTutorialPreference = new Preference(context);
- launchSandboxModeTutorialPreference.setKey("launchSandboxMode");
- launchSandboxModeTutorialPreference.setTitle("Launch Sandbox Mode");
- launchSandboxModeTutorialPreference.setSummary("Practice navigation gestures");
- launchSandboxModeTutorialPreference.setOnPreferenceClickListener(preference -> {
- startActivity(launchSandboxIntent
- .putExtra("use_tutorial_menu", false)
- .putExtra("tutorial_steps", new String[] {"SANDBOX_MODE"}));
- return true;
- });
- sandboxCategory.addPreference(launchSandboxModeTutorialPreference);
-
Preference launchSecondaryDisplayPreference = new Preference(context);
launchSecondaryDisplayPreference.setKey("launchSecondaryDisplay");
launchSecondaryDisplayPreference.setTitle("Launch Secondary Display");
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DeviceFlag.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DeviceFlag.java
index 3900ebb549..035beb4df1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/DeviceFlag.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DeviceFlag.java
@@ -16,11 +16,13 @@
package com.android.launcher3.uioverrides.flags;
+import com.android.launcher3.config.FeatureFlags.FlagState;
+
class DeviceFlag extends DebugFlag {
private final boolean mDefaultValueInCode;
- public DeviceFlag(String key, String description, boolean defaultValue,
+ DeviceFlag(String key, String description, FlagState defaultValue,
boolean currentValue, boolean defaultValueInCode) {
super(key, description, defaultValue, currentValue);
mDefaultValueInCode = defaultValueInCode;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java
index b7fb2ed827..87c836bbac 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java
@@ -16,12 +16,14 @@
package com.android.launcher3.uioverrides.flags;
-import static com.android.launcher3.config.FeatureFlags.FLAGS_PREF_NAME;
+import static com.android.launcher3.config.FeatureFlags.FlagState.TEAMFOOD;
+import static com.android.launcher3.uioverrides.flags.FlagsFactory.TEAMFOOD_FLAG;
import android.content.Context;
-import android.content.SharedPreferences;
+import android.os.Handler;
import android.os.Process;
import android.text.Html;
+import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
@@ -30,6 +32,7 @@ import android.widget.Toast;
import androidx.preference.PreferenceDataStore;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceGroup;
+import androidx.preference.PreferenceViewHolder;
import androidx.preference.SwitchPreference;
import com.android.launcher3.R;
@@ -47,36 +50,27 @@ public final class FlagTogglerPrefUi {
private final PreferenceFragmentCompat mFragment;
private final Context mContext;
- private final SharedPreferences mSharedPreferences;
-
private final PreferenceDataStore mDataStore = new PreferenceDataStore() {
@Override
public void putBoolean(String key, boolean value) {
- mSharedPreferences.edit().putBoolean(key, value).apply();
+ FlagsFactory.getSharedPreferences().edit().putBoolean(key, value).apply();
updateMenu();
}
@Override
public boolean getBoolean(String key, boolean defaultValue) {
- for (DebugFlag flag : FlagsFactory.getDebugFlags()) {
- if (flag.key.equals(key)) {
- return mSharedPreferences.getBoolean(key, flag.defaultValue);
- }
- }
- return defaultValue;
+ return FlagsFactory.getSharedPreferences().getBoolean(key, defaultValue);
}
};
public FlagTogglerPrefUi(PreferenceFragmentCompat fragment) {
mFragment = fragment;
mContext = fragment.getActivity();
- mSharedPreferences = mContext.getSharedPreferences(
- FLAGS_PREF_NAME, Context.MODE_PRIVATE);
}
public void applyTo(PreferenceGroup parent) {
- Set modifiedPrefs = mSharedPreferences.getAll().keySet();
+ Set modifiedPrefs = FlagsFactory.getSharedPreferences().getAll().keySet();
List flags = FlagsFactory.getDebugFlags();
flags.sort((f1, f2) -> {
// Sort first by any prefs that the user has changed, then alphabetically.
@@ -87,18 +81,41 @@ public final class FlagTogglerPrefUi {
: f1.key.compareToIgnoreCase(f2.key);
});
+ // Ensure that teamfood flag comes on the top
+ if (flags.remove(TEAMFOOD_FLAG)) {
+ flags.add(0, (DebugFlag) TEAMFOOD_FLAG);
+ }
+
// For flag overrides we only want to store when the engineer chose to override the
// flag with a different value than the default. That way, when we flip flags in
// future, engineers will pick up the new value immediately. To accomplish this, we use a
// custom preference data store.
for (DebugFlag flag : flags) {
- SwitchPreference switchPreference = new SwitchPreference(mContext);
+ SwitchPreference switchPreference = new SwitchPreference(mContext) {
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+ holder.itemView.setOnLongClickListener(v -> {
+ FlagsFactory.getSharedPreferences().edit().remove(flag.key).apply();
+ setChecked(getFlagStateFromSharedPrefs(flag));
+ updateSummary(this, flag);
+ updateMenu();
+ return true;
+ });
+ }
+ };
switchPreference.setKey(flag.key);
- switchPreference.setDefaultValue(flag.defaultValue);
+ switchPreference.setDefaultValue(FlagsFactory.getEnabledValue(flag.defaultValue));
switchPreference.setChecked(getFlagStateFromSharedPrefs(flag));
switchPreference.setTitle(flag.key);
updateSummary(switchPreference, flag);
switchPreference.setPreferenceDataStore(mDataStore);
+ switchPreference.setOnPreferenceChangeListener((p, v) -> {
+ new Handler().post(() -> updateSummary(switchPreference, flag));
+ return true;
+ });
+
+
parent.addPreference(switchPreference);
}
updateMenu();
@@ -108,10 +125,15 @@ public final class FlagTogglerPrefUi {
* Updates the summary to show the description and whether the flag overrides the default value.
*/
private void updateSummary(SwitchPreference switchPreference, DebugFlag flag) {
- String onWarning = flag.defaultValue ? "" : "OVERRIDDEN
";
- String offWarning = flag.defaultValue ? "OVERRIDDEN
" : "";
- switchPreference.setSummaryOn(Html.fromHtml(onWarning + flag.description));
- switchPreference.setSummaryOff(Html.fromHtml(offWarning + flag.description));
+ String summary = flag.defaultValue == TEAMFOOD
+ ? "[TEAMFOOD] " : "";
+ if (FlagsFactory.getSharedPreferences().contains(flag.key)) {
+ summary += "[OVERRIDDEN] ";
+ }
+ if (!TextUtils.isEmpty(summary)) {
+ summary += "
";
+ }
+ switchPreference.setSummary(Html.fromHtml(summary + flag.description));
}
private void updateMenu() {
@@ -128,7 +150,7 @@ public final class FlagTogglerPrefUi {
public void onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.menu_apply_flags) {
- mSharedPreferences.edit().commit();
+ FlagsFactory.getSharedPreferences().edit().commit();
Log.e(TAG,
"Killing launcher process " + Process.myPid() + " to apply new flag values");
System.exit(0);
@@ -143,7 +165,8 @@ public final class FlagTogglerPrefUi {
}
private boolean getFlagStateFromSharedPrefs(DebugFlag flag) {
- return mDataStore.getBoolean(flag.key, flag.defaultValue);
+ boolean defaultValue = FlagsFactory.getEnabledValue(flag.defaultValue);
+ return mDataStore.getBoolean(flag.key, defaultValue);
}
private boolean anyChanged() {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
index 2758f032e0..3f30100454 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
@@ -18,7 +18,9 @@ package com.android.launcher3.uioverrides.flags;
import static android.app.ActivityThread.currentApplication;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.launcher3.Utilities.IS_DEBUG_DEVICE;
+import static com.android.launcher3.config.FeatureFlags.FlagState.DISABLED;
+import static com.android.launcher3.config.FeatureFlags.FlagState.ENABLED;
import android.content.Context;
import android.content.SharedPreferences;
@@ -26,8 +28,11 @@ import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
import android.util.Log;
+import androidx.annotation.NonNull;
+
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags.BooleanFlag;
+import com.android.launcher3.config.FeatureFlags.FlagState;
import com.android.launcher3.config.FeatureFlags.IntFlag;
import com.android.launcher3.util.ScreenOnTracker;
@@ -46,12 +51,16 @@ public class FlagsFactory {
private static final String TAG = "FlagsFactory";
private static final FlagsFactory INSTANCE = new FlagsFactory();
- private static final boolean FLAG_AUTO_APPLY_ENABLED = false;
+ private static final boolean FLAG_AUTO_APPLY_ENABLED = true;
- public static final String FLAGS_PREF_NAME = "featureFlags";
+ private static final String FLAGS_PREF_NAME = "featureFlags";
public static final String NAMESPACE_LAUNCHER = "launcher";
private static final List sDebugFlags = new ArrayList<>();
+ private static SharedPreferences sSharedPreferences;
+
+ static final BooleanFlag TEAMFOOD_FLAG = getReleaseFlag(
+ 0, "LAUNCHER_TEAMFOOD", DISABLED, "Enable this flag to opt-in all team food flags");
private final Set mKeySet = new HashSet<>();
private boolean mRestartRequested = false;
@@ -60,44 +69,56 @@ public class FlagsFactory {
if (!FLAG_AUTO_APPLY_ENABLED) {
return;
}
-// DeviceConfig.addOnPropertiesChangedListener(
-// NAMESPACE_LAUNCHER, UI_HELPER_EXECUTOR, this::onPropertiesChanged);
}
- /**
- * Creates a new debug flag
- */
- public static BooleanFlag getDebugFlag(
- int bugId, String key, boolean defaultValue, String description) {
- if (Utilities.IS_DEBUG_DEVICE) {
- SharedPreferences prefs = currentApplication()
- .getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE);
- boolean currentValue = prefs.getBoolean(key, defaultValue);
- DebugFlag flag = new DebugFlag(key, description, defaultValue, currentValue);
- sDebugFlags.add(flag);
- return flag;
+ static boolean getEnabledValue(FlagState flagState) {
+ if (IS_DEBUG_DEVICE) {
+ switch (flagState) {
+ case ENABLED:
+ return true;
+ case TEAMFOOD:
+ return TEAMFOOD_FLAG.get();
+ default:
+ return false;
+ }
} else {
- return new BooleanFlag(defaultValue);
+ return flagState == ENABLED;
}
}
/**
- * Creates a new release flag
+ * Creates a new debug flag. Debug flags always take their default value in
+ * release builds. On
+ * dogfood builds, they can be manually turned on using the flag toggle UI.
+ */
+ public static BooleanFlag getDebugFlag(
+ int bugId, String key, FlagState flagState, String description) {
+ if (IS_DEBUG_DEVICE) {
+ boolean defaultValue = getEnabledValue(flagState);
+ boolean currentValue = getSharedPreferences().getBoolean(key, defaultValue);
+ DebugFlag flag = new DebugFlag(key, description, flagState, currentValue);
+ sDebugFlags.add(flag);
+ return flag;
+ } else {
+ return new BooleanFlag(getEnabledValue(flagState));
+ }
+ }
+
+ /**
+ * Creates a new release flag. Release flags can be rolled out using server
+ * configurations and
+ * also allow manual overrides on debug builds.
*/
public static BooleanFlag getReleaseFlag(
- int bugId, String key, boolean defaultValueInCode, String description) {
+ int bugId, String key, FlagState flagState, String description) {
INSTANCE.mKeySet.add(key);
-// boolean defaultValue = DeviceConfig.getBoolean(NAMESPACE_LAUNCHER, key, defaultValueInCode);
- if (Utilities.IS_DEBUG_DEVICE) {
+ if (IS_DEBUG_DEVICE) {
SharedPreferences prefs = currentApplication()
.getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE);
- boolean currentValue = prefs.getBoolean(key, defaultValueInCode);
-// DebugFlag flag = new DeviceFlag(key, description, defaultValue, currentValue,
-// defaultValueInCode);
-// sDebugFlags.add(flag);
- return new BooleanFlag(defaultValueInCode);
+ boolean currentValue = prefs.getBoolean(key, flagState.equals(ENABLED));
+ return new BooleanFlag(currentValue);
} else {
- return new BooleanFlag(defaultValueInCode);
+ return new BooleanFlag(flagState.equals(ENABLED));
}
}
@@ -111,7 +132,7 @@ public class FlagsFactory {
}
static List getDebugFlags() {
- if (!Utilities.IS_DEBUG_DEVICE) {
+ if (!IS_DEBUG_DEVICE) {
return Collections.emptyList();
}
synchronized (sDebugFlags) {
@@ -119,11 +140,22 @@ public class FlagsFactory {
}
}
+ /** Returns the SharedPreferences instance backing Debug FeatureFlags. */
+ @NonNull
+ static SharedPreferences getSharedPreferences() {
+ if (sSharedPreferences == null) {
+ sSharedPreferences = currentApplication()
+ .createDeviceProtectedStorageContext()
+ .getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE);
+ }
+ return sSharedPreferences;
+ }
+
/**
* Dumps the current flags state to the print writer
*/
public static void dump(PrintWriter pw) {
- if (!Utilities.IS_DEBUG_DEVICE) {
+ if (!IS_DEBUG_DEVICE) {
return;
}
pw.println("DeviceFlags:");
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
index d26df6acf8..b177cff22c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
@@ -63,7 +63,7 @@ public class PluginManagerWrapper {
List privilegedPlugins = Collections.emptyList();
PluginInstance.Factory instanceFactory = new PluginInstance.Factory(
getClass().getClassLoader(), new PluginInstance.InstanceFactory<>(),
- new PluginInstance.VersionChecker(), privilegedPlugins,
+ new PluginInstance.VersionCheckerImpl(), privilegedPlugins,
Utilities.IS_DEBUG_DEVICE);
PluginActionManager.Factory instanceManagerFactory = new PluginActionManager.Factory(
c, c.getPackageManager(), ContextCompat.getMainExecutor(c), MODEL_EXECUTOR,
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index c7cd39c100..a8d7538687 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -94,8 +94,12 @@ public class QuickstepAtomicAnimationFactory extends
@Override
public void prepareForAtomicAnimation(LauncherState fromState, LauncherState toState,
StateAnimationConfig config) {
+
RecentsView overview = mActivity.getOverviewPanel();
if ((fromState == OVERVIEW || fromState == OVERVIEW_SPLIT_SELECT) && toState == NORMAL) {
+ overview.switchToScreenshot(() ->
+ overview.finishRecentsAnimation(true /* toRecents */, null));
+
if (fromState == OVERVIEW_SPLIT_SELECT) {
config.setInterpolator(ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN,
clampToProgress(EMPHASIZED_ACCELERATE, 0, 0.4f));
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index 40dfd82f5d..8cbd6e8b47 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -41,11 +41,9 @@ import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.util.TouchController;
import com.android.quickstep.TaskUtils;
-import com.android.quickstep.TopTaskTracker;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.OverviewToHomeAnim;
import com.android.quickstep.views.RecentsView;
@@ -108,11 +106,6 @@ public class NavBarToHomeTouchController implements TouchController,
if (AbstractFloatingView.getTopOpenViewWithType(mLauncher, typeToClose) != null) {
return true;
}
- if (FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS.get()
- && TopTaskTracker.INSTANCE.get(mLauncher).getCachedTopTask(false)
- .isExcludedAssistant()) {
- return true;
- }
return false;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 847114a960..80f5558315 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -15,11 +15,16 @@
*/
package com.android.launcher3.uioverrides.touchcontrollers;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+
import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW_ACTIONS;
import static com.android.launcher3.LauncherState.QUICK_SWITCH_FROM_HOME;
+import static com.android.launcher3.MotionEventsUtils.isTrackpadFourFingerSwipe;
+import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
import static com.android.launcher3.anim.AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD;
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
@@ -105,6 +110,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
newCancelListener(this::clearState);
private boolean mNoIntercept;
+ private Boolean mIsTrackpadFourFingerSwipe;
private LauncherState mStartState;
private boolean mIsHomeScreenVisible = true;
@@ -130,7 +136,9 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ int action = ev.getActionMasked();
+ if (action == ACTION_DOWN) {
+ mIsTrackpadFourFingerSwipe = null;
mNoIntercept = !canInterceptTouch(ev);
if (mNoIntercept) {
return false;
@@ -139,6 +147,13 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
// Only detect horizontal swipe for intercept, then we will allow swipe up as well.
mSwipeDetector.setDetectableScrollConditions(DIRECTION_RIGHT,
false /* ignoreSlopWhenSettling */);
+ } else if (isTrackpadMultiFingerSwipe(ev) && mIsTrackpadFourFingerSwipe == null
+ && action == ACTION_MOVE) {
+ mIsTrackpadFourFingerSwipe = isTrackpadFourFingerSwipe(ev);
+ mNoIntercept = !mIsTrackpadFourFingerSwipe;
+ if (mNoIntercept) {
+ return false;
+ }
}
if (mNoIntercept) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
index fd99b97004..5da0188312 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
@@ -48,8 +48,10 @@ import app.lawnchair.LawnchairAppKt;
import app.lawnchair.util.CompatibilityKt;
/**
- * TouchController for handling touch events that get sent to the StatusBar. Once the
- * Once the event delta mDownY passes the touch slop, the events start getting forwarded.
+ * TouchController for handling touch events that get sent to the StatusBar.
+ * Once the
+ * Once the event delta mDownY passes the touch slop, the events start getting
+ * forwarded.
* All events are offset by initial Y value of the pointer.
*/
public class StatusBarTouchController implements TouchController {
@@ -62,7 +64,10 @@ public class StatusBarTouchController implements TouchController {
private int mLastAction;
private final SparseArray mDownEvents;
- /* If {@code false}, this controller should not handle the input {@link MotionEvent}.*/
+ /*
+ * If {@code false}, this controller should not handle the input {@link
+ * MotionEvent}.
+ */
private boolean mCanIntercept;
private boolean mExpanded;
@@ -98,13 +103,14 @@ public class StatusBarTouchController implements TouchController {
}
}
- @SuppressLint({"WrongConstant", "PrivateApi"})
+ @SuppressLint({ "WrongConstant", "PrivateApi" })
private void expand() {
try {
Class.forName("android.app.StatusBarManager")
.getMethod("expandNotificationsPanel")
.invoke(mLauncher.getSystemService("statusbar"));
- } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException e) {
+ } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException
+ | ClassNotFoundException e) {
e.printStackTrace();
}
}
@@ -127,10 +133,11 @@ public class StatusBarTouchController implements TouchController {
}
mExpanded = false;
mVibrated = false;
+ mDownEvents.clear();
mDownEvents.put(pid, new PointF(ev.getX(), ev.getY()));
} else if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
- // Check!! should only set it only when threshold is not entered.
- mDownEvents.put(pid, new PointF(ev.getX(idx), ev.getY(idx)));
+ // Check!! should only set it only when threshold is not entered.
+ mDownEvents.put(pid, new PointF(ev.getX(idx), ev.getY(idx)));
}
if (!mCanIntercept) {
return false;
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 8f7652a5f8..918eedb10f 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -39,6 +39,7 @@ import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
+import static com.android.quickstep.GestureState.GestureEndTarget.ALL_APPS;
import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
@@ -62,6 +63,7 @@ import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.content.Context;
import android.content.Intent;
@@ -105,6 +107,7 @@ import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.taskbar.TaskbarUIController;
import com.android.launcher3.tracing.InputConsumerProto;
import com.android.launcher3.tracing.SwipeHandlerProto;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.TraceHelper;
@@ -136,6 +139,7 @@ import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
+import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.wm.shell.common.TransactionPool;
@@ -144,6 +148,7 @@ import com.android.wm.shell.startingsurface.SplashScreenExitAnimationUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
@@ -160,6 +165,11 @@ public abstract class AbsSwipeUpHandler, Q extends
private static final ArrayList STATE_NAMES = new ArrayList<>();
+ /**
+ * Shift distance to transition to All Apps if ENABLE_ALL_APPS_FROM_OVERVIEW.
+ */
+ public static final float ALL_APPS_SHIFT_THRESHOLD = 2f;
+
protected final BaseActivityInterface mActivityInterface;
protected final InputConsumerProxy mInputConsumerProxy;
protected final ActivityInitListener mActivityInitListener;
@@ -173,7 +183,7 @@ public abstract class AbsSwipeUpHandler, Q extends
protected @Nullable RecentsAnimationController mDeferredCleanupRecentsAnimationController;
protected RecentsAnimationTargets mRecentsAnimationTargets;
protected T mActivity;
- protected Q mRecentsView;
+ protected @Nullable Q mRecentsView;
protected Runnable mGestureEndCallback;
protected MultiStateCallback mStateCallback;
protected boolean mCanceled;
@@ -230,6 +240,7 @@ public abstract class AbsSwipeUpHandler, Q extends
private static final int STATE_START_NEW_TASK = getNextStateFlag("STATE_START_NEW_TASK");
private static final int STATE_CURRENT_TASK_FINISHED = getNextStateFlag("STATE_CURRENT_TASK_FINISHED");
private static final int STATE_FINISH_WITH_NO_END = getNextStateFlag("STATE_FINISH_WITH_NO_END");
+ private static final int STATE_SETTLED_ON_ALL_APPS = getNextStateFlag("STATE_SETTLED_ON_ALL_APPS");
private static final int LAUNCHER_UI_STATES = STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED
|
@@ -286,8 +297,8 @@ public abstract class AbsSwipeUpHandler, Q extends
private boolean mGestureStarted;
private boolean mLogDirectionUpOrLeft = true;
- private PointF mDownPos;
private boolean mIsLikelyToStartNewTask;
+ private boolean mIsInAllAppsRegion;
private final long mTouchTimeMs;
private long mLauncherFrameDrawnTime;
@@ -317,14 +328,15 @@ public abstract class AbsSwipeUpHandler, Q extends
// Indicates whether the divider is shown, only used when split screen is
// activated.
private boolean mIsDividerShown = true;
+ private boolean mStartMovingTasks;
@Nullable
private RemoteAnimationTargets.ReleaseCheck mSwipePipToHomeReleaseCheck = null;
public AbsSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
- TaskAnimationManager taskAnimationManager, GestureState gestureState,
- long touchTimeMs, boolean continuingLastGesture,
- InputConsumerController inputConsumer) {
+ TaskAnimationManager taskAnimationManager, GestureState gestureState,
+ long touchTimeMs, boolean continuingLastGesture,
+ InputConsumerController inputConsumer) {
super(context, deviceState, gestureState);
mActivityInterface = gestureState.getActivityInterface();
mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
@@ -358,7 +370,7 @@ public abstract class AbsSwipeUpHandler, Q extends
mTaskbarAlreadyOpen = controller != null && !controller.isTaskbarStashed();
mIsTaskbarAllAppsOpen = controller != null && controller.isTaskbarAllAppsOpen();
mTaskbarAppWindowThreshold = res.getDimensionPixelSize(R.dimen.taskbar_app_window_threshold);
- boolean swipeWillNotShowTaskbar = mTaskbarAlreadyOpen;
+ boolean swipeWillNotShowTaskbar = mTaskbarAlreadyOpen || mGestureState.isTrackpadGesture();
mTaskbarHomeOverviewThreshold = swipeWillNotShowTaskbar
? 0
: res.getDimensionPixelSize(R.dimen.taskbar_home_overview_threshold);
@@ -399,7 +411,7 @@ public abstract class AbsSwipeUpHandler, Q extends
this::launcherFrameDrawn);
mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED
- | STATE_GESTURE_CANCELLED,
+ | STATE_GESTURE_CANCELLED,
this::resetStateForAnimationCancel);
mStateCallback.runOnceAtState(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED,
@@ -408,29 +420,32 @@ public abstract class AbsSwipeUpHandler, Q extends
this::startNewTask);
mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
- | STATE_LAUNCHER_DRAWN | STATE_CAPTURE_SCREENSHOT,
+ | STATE_LAUNCHER_DRAWN | STATE_CAPTURE_SCREENSHOT,
this::switchToScreenshot);
mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
- | STATE_SCALED_CONTROLLER_RECENTS,
+ | STATE_SCALED_CONTROLLER_RECENTS,
this::finishCurrentTransitionToRecents);
mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
- | STATE_SCALED_CONTROLLER_HOME,
+ | STATE_SCALED_CONTROLLER_HOME,
this::finishCurrentTransitionToHome);
mStateCallback.runOnceAtState(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED,
this::reset);
+ mStateCallback.runOnceAtState(STATE_SETTLED_ON_ALL_APPS | STATE_SCREENSHOT_CAPTURED
+ | STATE_GESTURE_COMPLETED,
+ this::finishCurrentTransitionToAllApps);
mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
- | STATE_LAUNCHER_DRAWN | STATE_SCALED_CONTROLLER_RECENTS
- | STATE_CURRENT_TASK_FINISHED | STATE_GESTURE_COMPLETED
- | STATE_GESTURE_STARTED,
+ | STATE_LAUNCHER_DRAWN | STATE_SCALED_CONTROLLER_RECENTS
+ | STATE_CURRENT_TASK_FINISHED | STATE_GESTURE_COMPLETED
+ | STATE_GESTURE_STARTED,
this::setupLauncherUiAfterSwipeUpToRecentsAnimation);
mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED,
this::continueComputingRecentsScrollIfNecessary);
mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED
- | STATE_RECENTS_SCROLLING_FINISHED,
+ | STATE_RECENTS_SCROLLING_FINISHED,
this::onSettledOnEndTarget);
mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED, this::invalidateHandler);
@@ -622,7 +637,7 @@ public abstract class AbsSwipeUpHandler, Q extends
mActivityInterface);
});
- notifyGestureStartedAsync();
+ notifyGestureStarted();
}
private void onDeferredActivityLaunch() {
@@ -648,6 +663,16 @@ public abstract class AbsSwipeUpHandler, Q extends
} else {
runningTasks = mGestureState.getRunningTask().getPlaceholderTasks();
}
+
+ // Safeguard against any null tasks being sent to recents view, happens when
+ // quickswitching
+ // very quickly w/ split tasks because TopTaskTracker provides stale information
+ // compared to
+ // actual running tasks in the recents animation.
+ // TODO(b/236226779), Proper fix (ag/22237143)
+ if (Arrays.stream(runningTasks).anyMatch(Objects::isNull)) {
+ return;
+ }
mRecentsView.onGestureAnimationStart(runningTasks, mDeviceState.getRotationTouchHelper());
}
@@ -683,7 +708,9 @@ public abstract class AbsSwipeUpHandler, Q extends
maybeUpdateRecentsAttachedState(true/* animate */, true/* moveRunningTask */);
Optional.ofNullable(mActivityInterface.getTaskbarController())
.ifPresent(TaskbarUIController::startTranslationSpring);
- performHapticFeedback();
+ if (!mIsInAllAppsRegion) {
+ performHapticFeedback();
+ }
}
@Override
@@ -697,7 +724,7 @@ public abstract class AbsSwipeUpHandler, Q extends
maybeUpdateRecentsAttachedState(true /* animate */);
}
- private void maybeUpdateRecentsAttachedState(boolean animate) {
+ protected void maybeUpdateRecentsAttachedState(boolean animate) {
maybeUpdateRecentsAttachedState(animate, false /* moveRunningTask */);
}
@@ -719,7 +746,9 @@ public abstract class AbsSwipeUpHandler, Q extends
? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId())
: null;
final boolean recentsAttachedToAppWindow;
- if (mGestureState.getEndTarget() != null) {
+ if (mIsInAllAppsRegion) {
+ recentsAttachedToAppWindow = false;
+ } else if (mGestureState.getEndTarget() != null) {
recentsAttachedToAppWindow = mGestureState.getEndTarget().recentsAttachedToAppWindow;
} else if (mContinuingLastGesture
&& mRecentsView.getRunningTaskIndex() != mRecentsView.getNextPage()) {
@@ -778,6 +807,26 @@ public abstract class AbsSwipeUpHandler, Q extends
}
}
+ /**
+ * Update whether user is currently dragging in a region that will trigger all
+ * apps.
+ */
+ private void setIsInAllAppsRegion(boolean isInAllAppsRegion) {
+ if (mIsInAllAppsRegion == isInAllAppsRegion
+ || !mActivityInterface.allowAllAppsFromOverview()) {
+ return;
+ }
+ mIsInAllAppsRegion = isInAllAppsRegion;
+
+ // Newly entering or exiting the zone - do haptic and animate recent tasks.
+ VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
+ maybeUpdateRecentsAttachedState(true);
+
+ // Draw active task below Launcher so that All Apps can appear over it.
+ runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
+ .setDrawsBelowRecents(isInAllAppsRegion));
+ }
+
private void buildAnimationController() {
if (!canCreateNewOrUpdateExistingLauncherTransitionController()) {
return;
@@ -794,17 +843,25 @@ public abstract class AbsSwipeUpHandler, Q extends
* @return Whether we can create the launcher controller or update its progress.
*/
private boolean canCreateNewOrUpdateExistingLauncherTransitionController() {
- return mGestureState.getEndTarget() != HOME && !mHasEndedLauncherTransition;
+ return mGestureState.getEndTarget() != HOME
+ && !mHasEndedLauncherTransition && mActivity != null;
}
@Override
public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
WindowInsets result = view.onApplyWindowInsets(windowInsets);
+ // Don't rebuild animation when we are animating the IME, because it will cause
+ // a loop
+ // where the insets change -> animation changes (updating ime) -> insets change
+ // -> ...
+ if (windowInsets.isVisible(WindowInsets.Type.ime())) {
+ return result;
+ }
buildAnimationController();
// Reapply the current shift to ensure it takes new insets into account, e.g.
// when long
// pressing to stash taskbar without moving the finger.
- updateFinalShift();
+ onCurrentShiftUpdated();
return result;
}
@@ -833,7 +890,8 @@ public abstract class AbsSwipeUpHandler, Q extends
*/
@UiThread
@Override
- public void updateFinalShift() {
+ public void onCurrentShiftUpdated() {
+ setIsInAllAppsRegion(mCurrentShift.value >= ALL_APPS_SHIFT_THRESHOLD);
updateSysUiFlags(mCurrentShift.value);
applyScrollAndTransform();
@@ -881,12 +939,17 @@ public abstract class AbsSwipeUpHandler, Q extends
@Override
public void onRecentsAnimationStart(RecentsAnimationController controller,
- RecentsAnimationTargets targets) {
+ RecentsAnimationTargets targets) {
super.onRecentsAnimationStart(controller, targets);
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED && targets.hasDesktopTasks()) {
mRemoteTargetHandles = mTargetGluer.assignTargetsForDesktop(targets);
} else {
+ int untrimmedAppCount = mRemoteTargetHandles.length;
mRemoteTargetHandles = mTargetGluer.assignTargetsForSplitScreen(targets);
+ if (mRemoteTargetHandles.length < untrimmedAppCount && mIsSwipeForSplit) {
+ updateIsGestureForSplit(mRemoteTargetHandles.length);
+ setupRecentsViewUi();
+ }
}
mRecentsAnimationController = controller;
mRecentsAnimationTargets = targets;
@@ -981,7 +1044,7 @@ public abstract class AbsSwipeUpHandler, Q extends
}
});
}
- notifyGestureStartedAsync();
+ notifyGestureStarted();
setIsLikelyToStartNewTask(isLikelyToStartNewTask, false /* animate */);
if (mIsTransientTaskbar && !mTaskbarAlreadyOpen && !isLikelyToStartNewTask) {
@@ -1016,7 +1079,7 @@ public abstract class AbsSwipeUpHandler, Q extends
* multiple times.
*/
@UiThread
- private void notifyGestureStartedAsync() {
+ private void notifyGestureStarted() {
final T curActivity = mActivity;
if (curActivity != null) {
// Once the gesture starts, we can no longer transition home through the button,
@@ -1042,10 +1105,9 @@ public abstract class AbsSwipeUpHandler, Q extends
* screen.
* @param velocityPxPerMs The x and y components of the velocity when the
* gesture ends.
- * @param downPos The x and y value of where the gesture started.
*/
@UiThread
- public void onGestureEnded(float endVelocityPxPerMs, PointF velocityPxPerMs, PointF downPos) {
+ public void onGestureEnded(float endVelocityPxPerMs, PointF velocityPxPerMs) {
float flingThreshold = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_threshold_speed);
boolean isFling = mGestureStarted && !mIsMotionPaused
@@ -1057,7 +1119,6 @@ public abstract class AbsSwipeUpHandler, Q extends
} else {
mLogDirectionUpOrLeft = velocityPxPerMs.x < 0;
}
- mDownPos = downPos;
Runnable handleNormalGestureEndCallback = () -> handleNormalGestureEnd(
endVelocityPxPerMs, isFling, velocityPxPerMs, /* isCancel= */ false);
if (mRecentsView != null) {
@@ -1112,6 +1173,9 @@ public abstract class AbsSwipeUpHandler, Q extends
}
switch (endTarget) {
+ case ALL_APPS:
+ mStateCallback.setState(STATE_SETTLED_ON_ALL_APPS | STATE_CAPTURE_SCREENSHOT);
+ break;
case HOME:
mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
// Notify the SysUI to use fade-in animation when entering PiP
@@ -1202,6 +1266,9 @@ public abstract class AbsSwipeUpHandler, Q extends
// If swiping at a diagonal, base end target on the faster velocity direction.
final boolean willGoToNewTask = isScrollingToNewTask() && Math.abs(velocity.x) > Math.abs(endVelocity);
final boolean isSwipeUp = endVelocity < 0;
+ if (mIsInAllAppsRegion) {
+ return isSwipeUp ? ALL_APPS : LAST_TASK;
+ }
if (!isSwipeUp) {
final boolean isCenteredOnNewTask = mRecentsView.getDestinationPage() != mRecentsView.getRunningTaskIndex();
return willGoToNewTask || isCenteredOnNewTask ? NEW_TASK : LAST_TASK;
@@ -1216,7 +1283,9 @@ public abstract class AbsSwipeUpHandler, Q extends
// Fully gestural mode.
final boolean isFlingX = Math.abs(velocity.x) > mContext.getResources()
.getDimension(R.dimen.quickstep_fling_threshold_speed);
- if (isScrollingToNewTask && isFlingX) {
+ if (mIsInAllAppsRegion) {
+ return ALL_APPS;
+ } else if (isScrollingToNewTask && isFlingX) {
// Flinging towards new task takes precedence over mIsMotionPaused (which only
// checks y-velocity).
return NEW_TASK;
@@ -1267,7 +1336,8 @@ public abstract class AbsSwipeUpHandler, Q extends
mGestureState.setEndTarget(endTarget, false /* isAtomic */);
mAnimationFactory.setEndTarget(endTarget);
- float endShift = endTarget.isLauncher ? 1 : 0;
+ float endShift = endTarget == ALL_APPS ? mDragLengthFactor
+ : endTarget.isLauncher ? 1 : 0;
final float startShift;
if (!isFling) {
long expectedDuration = Math.abs(Math.round((endShift - currentShift)
@@ -1357,7 +1427,7 @@ public abstract class AbsSwipeUpHandler, Q extends
}
private void doLogGesture(GestureEndTarget endTarget, @Nullable TaskView targetTask) {
- if (mDp == null || !mDp.isGestureMode || mDownPos == null) {
+ if (mDp == null || !mDp.isGestureMode) {
// We probably never received an animation controller, skip logging.
return;
}
@@ -1380,7 +1450,10 @@ public abstract class AbsSwipeUpHandler, Q extends
}
StatsLogger logger = StatsLogManager.newInstance(mContext).logger()
.withSrcState(LAUNCHER_STATE_BACKGROUND)
- .withDstState(endTarget.containerType);
+ .withDstState(endTarget.containerType)
+ .withInputType(mGestureState.isTrackpadGesture()
+ ? SysUiStatsLog.LAUNCHER_UICHANGED__INPUT_TYPE__TRACKPAD
+ : SysUiStatsLog.LAUNCHER_UICHANGED__INPUT_TYPE__TOUCH);
if (targetTask != null) {
logger.withItemInfo(targetTask.getItemInfo());
}
@@ -1397,7 +1470,7 @@ public abstract class AbsSwipeUpHandler, Q extends
*/
@UiThread
private void animateToProgress(float start, float end, long duration, Interpolator interpolator,
- GestureEndTarget target, PointF velocityPxPerMs) {
+ GestureEndTarget target, PointF velocityPxPerMs) {
runOnRecentsAnimationAndLauncherBound(() -> animateToProgressInternal(start, end, duration,
interpolator, target, velocityPxPerMs));
}
@@ -1409,7 +1482,7 @@ public abstract class AbsSwipeUpHandler, Q extends
private final TaskStackChangeListener mActivityRestartListener = new TaskStackChangeListener() {
@Override
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
- boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
+ boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
if (task.taskId == mGestureState.getRunningTaskId()
&& task.configuration.windowConfiguration.getActivityType() != ACTIVITY_TYPE_HOME) {
// Since this is an edge case, just cancel and relaunch with default activity
@@ -1425,7 +1498,7 @@ public abstract class AbsSwipeUpHandler, Q extends
@UiThread
private void animateToProgressInternal(float start, float end, long duration,
- Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {
+ Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {
maybeUpdateRecentsAttachedState();
// If we are transitioning to launcher, then listen for the activity to be
@@ -1461,8 +1534,8 @@ public abstract class AbsSwipeUpHandler, Q extends
: null;
final ArrayList cookies = runningTaskTarget != null
&& runningTaskTarget.taskInfo != null
- ? runningTaskTarget.taskInfo.launchCookies
- : new ArrayList<>();
+ ? runningTaskTarget.taskInfo.launchCookies
+ : new ArrayList<>();
boolean isTranslucent = runningTaskTarget != null && runningTaskTarget.isTranslucent;
boolean hasValidLeash = runningTaskTarget != null
&& runningTaskTarget.leash != null
@@ -1484,6 +1557,21 @@ public abstract class AbsSwipeUpHandler, Q extends
if (mSwipePipToHomeReleaseCheck != null) {
mSwipePipToHomeReleaseCheck.setCanRelease(false);
}
+
+ // grab a screenshot before the PipContentOverlay gets parented on top of the
+ // task
+ UI_HELPER_EXECUTOR.execute(() -> {
+ mTaskSnapshot = mRecentsAnimationController.screenshotTask(
+ mGestureState.getRunningTaskId());
+ });
+
+ // let SystemUi reparent the overlay leash as soon as possible
+ SystemUiProxy.INSTANCE.get(mContext).stopSwipePipToHome(
+ mSwipePipToHomeAnimator.getTaskId(),
+ mSwipePipToHomeAnimator.getComponentName(),
+ mSwipePipToHomeAnimator.getDestinationBounds(),
+ mSwipePipToHomeAnimator.getContentOverlay());
+
windowAnim = mSwipePipToHomeAnimators;
} else {
mSwipePipToHomeAnimator = null;
@@ -1575,13 +1663,13 @@ public abstract class AbsSwipeUpHandler, Q extends
}
private int calculateWindowRotation(RemoteAnimationTarget runningTaskTarget,
- RecentsOrientedState orientationState) {
+ RecentsOrientedState orientationState) {
try {
if (runningTaskTarget.rotationChange != 0
- && TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
+ && TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
return Math.abs(runningTaskTarget.rotationChange) == ROTATION_90
- ? ROTATION_270
- : ROTATION_90;
+ ? ROTATION_270
+ : ROTATION_90;
} else {
return orientationState.getDisplayRotation();
}
@@ -1592,7 +1680,7 @@ public abstract class AbsSwipeUpHandler, Q extends
@Nullable
private SwipePipToHomeAnimator createWindowAnimationToPip(HomeAnimationFactory homeAnimFactory,
- RemoteAnimationTarget runningTaskTarget, float startProgress) {
+ RemoteAnimationTarget runningTaskTarget, float startProgress) {
// Directly animate the app to PiP (picture-in-picture) mode
final ActivityManager.RunningTaskInfo taskInfo = runningTaskTarget.taskInfo;
final RecentsOrientedState orientationState = mRemoteTargetHandles[0].getTaskViewSimulator()
@@ -1717,12 +1805,21 @@ public abstract class AbsSwipeUpHandler, Q extends
return keepClearArea;
}
+ /**
+ * Notifies to start intercepting touches in the app window and hide the divider
+ * bar if needed.
+ *
+ * @see RecentsAnimationController#enableInputConsumer()
+ */
private void startInterceptingTouchesForGesture() {
- if (mRecentsAnimationController == null) {
+ if (mRecentsAnimationController == null || !mStartMovingTasks) {
return;
}
mRecentsAnimationController.enableInputConsumer();
+
+ // Hide the divider as it starts intercepting touches in the app window.
+ setDividerShown(false);
}
private void computeRecentsScrollIfInvisible() {
@@ -1752,7 +1849,7 @@ public abstract class AbsSwipeUpHandler, Q extends
*/
@Override
protected RectFSpringAnim[] createWindowAnimationToHome(float startProgress,
- HomeAnimationFactory homeAnimationFactory) {
+ HomeAnimationFactory homeAnimationFactory) {
RectFSpringAnim[] anim = super.createWindowAnimationToHome(startProgress, homeAnimationFactory);
setupWindowAnimation(anim);
return anim;
@@ -1855,6 +1952,12 @@ public abstract class AbsSwipeUpHandler, Q extends
reset();
}
+ @UiThread
+ private void finishCurrentTransitionToAllApps() {
+ finishCurrentTransitionToHome();
+ reset();
+ }
+
private void reset() {
mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
if (mActivity != null) {
@@ -1903,7 +2006,9 @@ public abstract class AbsSwipeUpHandler, Q extends
private void invalidateHandlerWithLauncher() {
endLauncherTransitionController();
- mRecentsView.onGestureAnimationEnd();
+ if (mRecentsView != null) {
+ mRecentsView.onGestureAnimationEnd();
+ }
resetLauncherListeners();
}
@@ -1932,7 +2037,9 @@ public abstract class AbsSwipeUpHandler, Q extends
private void resetLauncherListeners() {
mActivity.getRootView().setOnApplyWindowInsetsListener(null);
- mRecentsView.removeOnScrollChangedListener(mOnRecentsScrollListener);
+ if (mRecentsView != null) {
+ mRecentsView.removeOnScrollChangedListener(mOnRecentsScrollListener);
+ }
}
private void resetStateForAnimationCancel() {
@@ -1993,7 +2100,10 @@ public abstract class AbsSwipeUpHandler, Q extends
private boolean updateThumbnail(int runningTaskId, boolean refreshView) {
boolean finishTransitionPosted = false;
final TaskView taskView;
- if (mGestureState.getEndTarget() == HOME || mGestureState.getEndTarget() == NEW_TASK) {
+ if (mGestureState.getEndTarget() == HOME
+ || mGestureState.getEndTarget() == NEW_TASK
+ || mGestureState.getEndTarget() == ALL_APPS
+ || mRecentsView == null) {
// Capture the screenshot before finishing the transition to home or
// quickswitching to
// ensure it's taken in the correct orientation, but no need to update the
@@ -2041,8 +2151,9 @@ public abstract class AbsSwipeUpHandler, Q extends
// If there are no targets or the animation not started, then there is nothing
// to finish
mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
+ maybeAbortSwipePipToHome();
} else {
- maybeFinishSwipeToHome();
+ maybeFinishSwipePipToHome();
finishRecentsControllerToHome(
() -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
}
@@ -2053,19 +2164,28 @@ public abstract class AbsSwipeUpHandler, Q extends
doLogGesture(HOME, mRecentsView == null ? null : mRecentsView.getCurrentPageTaskView());
}
+ /**
+ * Notifies SysUI that transition is aborted if applicable and also pass leash
+ * transactions
+ * from Launcher to WM.
+ */
+ private void maybeAbortSwipePipToHome() {
+ if (mIsSwipingPipToHome && mSwipePipToHomeAnimators[0] != null) {
+ SystemUiProxy.INSTANCE.get(mContext).abortSwipePipToHome(
+ mSwipePipToHomeAnimator.getTaskId(),
+ mSwipePipToHomeAnimator.getComponentName());
+ mIsSwipingPipToHome = false;
+ }
+ }
+
/**
* Notifies SysUI that transition is finished if applicable and also pass leash
* transactions
* from Launcher to WM.
* This should happen before {@link #finishRecentsControllerToHome(Runnable)}.
*/
- private void maybeFinishSwipeToHome() {
+ private void maybeFinishSwipePipToHome() {
if (mIsSwipingPipToHome && mSwipePipToHomeAnimators[0] != null) {
- SystemUiProxy.INSTANCE.get(mContext).stopSwipePipToHome(
- mSwipePipToHomeAnimator.getTaskId(),
- mSwipePipToHomeAnimator.getComponentName(),
- mSwipePipToHomeAnimator.getDestinationBounds(),
- mSwipePipToHomeAnimator.getContentOverlay());
mRecentsAnimationController.setFinishTaskTransaction(
mSwipePipToHomeAnimator.getTaskId(),
mSwipePipToHomeAnimator.getFinishTransaction(),
@@ -2123,36 +2243,42 @@ public abstract class AbsSwipeUpHandler, Q extends
}
protected void linkRecentsViewScroll() {
- SurfaceTransactionApplier.create(mRecentsView, applier -> {
- runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
- .setSyncTransactionApplier(applier));
- runOnRecentsAnimationAndLauncherBound(() -> mRecentsAnimationTargets.addReleaseCheck(applier));
- });
+ SurfaceTransactionApplier applier = new SurfaceTransactionApplier(mRecentsView);
+ runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
+ .setSyncTransactionApplier(applier));
+ runOnRecentsAnimationAndLauncherBound(() -> mRecentsAnimationTargets.addReleaseCheck(applier));
mRecentsView.addOnScrollChangedListener(mOnRecentsScrollListener);
runOnRecentsAnimationAndLauncherBound(() -> mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController,
mRecentsAnimationTargets));
- mRecentsViewScrollLinked = true;
+
+ // Disable scrolling in RecentsView for trackpad 3-finger swipe up gesture.
+ if (!mGestureState.isThreeFingerTrackpadGesture()) {
+ mRecentsViewScrollLinked = true;
+ }
}
private void onRecentsViewScroll() {
if (moveWindowWithRecentsScroll()) {
- updateFinalShift();
+ onCurrentShiftUpdated();
}
}
protected void startNewTask(Consumer resultCallback) {
// Launch the task user scrolled to (mRecentsView.getNextPage()).
if (!mCanceled) {
- TaskView nextTask = mRecentsView.getNextPageTaskView();
+ TaskView nextTask = mRecentsView == null ? null : mRecentsView.getNextPageTaskView();
if (nextTask != null) {
- int taskId = nextTask.getTask().key.id;
+ Task.TaskKey nextTaskKey = nextTask.getTask().key;
+ int taskId = nextTaskKey.id;
mGestureState.updateLastStartedTaskId(taskId);
boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds()
.contains(taskId);
if (!hasTaskPreviouslyAppeared) {
ActiveGestureLog.INSTANCE.trackEvent(EXPECTING_TASK_APPEARED);
}
+ ActiveGestureLog.INSTANCE.addLog("Launching task: id=" + taskId
+ + " pkg=" + nextTaskKey.getPackageName());
nextTask.launchTask(success -> {
resultCallback.accept(success);
if (success) {
@@ -2226,52 +2352,83 @@ public abstract class AbsSwipeUpHandler, Q extends
@Override
public void onTasksAppeared(RemoteAnimationTarget[] appearedTaskTargets) {
if (mRecentsAnimationController != null) {
- if (handleTaskAppeared(appearedTaskTargets)) {
+ boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTargets).anyMatch(
+ targetCompat -> targetCompat.taskId == mGestureState.getLastStartedTaskId());
+ if (!mStateCallback.hasStates(STATE_GESTURE_COMPLETED) && !hasStartedTaskBefore) {
+ // This is a special case, if a task is started mid-gesture that wasn't a part
+ // of a
+ // previous quickswitch task launch, then cancel the animation back to the app
+ RemoteAnimationTarget appearedTaskTarget = appearedTaskTargets[0];
+ TaskInfo taskInfo = appearedTaskTarget.taskInfo;
+ ActiveGestureLog.INSTANCE.addLog("Unexpected task appeared"
+ + " id=" + taskInfo.taskId
+ + " pkg=" + taskInfo.baseIntent.getComponent().getPackageName());
+ finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
+ } else if (handleTaskAppeared(appearedTaskTargets)) {
Optional taskTargetOptional = Arrays.stream(appearedTaskTargets)
.filter(targetCompat -> targetCompat.taskId == mGestureState.getLastStartedTaskId())
.findFirst();
if (!taskTargetOptional.isPresent()) {
- finishRecentsAnimationOnTasksAppeared();
+ ActiveGestureLog.INSTANCE.addLog("No appeared task matching started task id");
+ finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
return;
}
RemoteAnimationTarget taskTarget = taskTargetOptional.get();
- TaskView taskView = mRecentsView.getTaskViewByTaskId(taskTarget.taskId);
+ TaskView taskView = mRecentsView == null
+ ? null
+ : mRecentsView.getTaskViewByTaskId(taskTarget.taskId);
if (taskView == null || !taskView.getThumbnail().shouldShowSplashView()) {
- finishRecentsAnimationOnTasksAppeared();
+ ActiveGestureLog.INSTANCE.addLog("Invalid task view splash state");
+ finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
return;
}
ViewGroup splashView = mActivity.getDragLayer();
+ final QuickstepLauncher quickstepLauncher = mActivity instanceof QuickstepLauncher
+ ? (QuickstepLauncher) mActivity
+ : null;
+ if (quickstepLauncher != null) {
+ quickstepLauncher.getDepthController().pauseBlursOnWindows(true);
+ }
// When revealing the app with launcher splash screen, make the app visible
// and behind the splash view before the splash is animated away.
SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(splashView);
SurfaceTransaction transaction = new SurfaceTransaction();
for (RemoteAnimationTarget target : appearedTaskTargets) {
- transaction.forSurface(target.leash).setAlpha(1).setLayer(-1);
+ transaction.forSurface(target.leash).setAlpha(1).setLayer(-1).setShow();
}
surfaceApplier.scheduleApply(transaction);
-// SplashScreenExitAnimationUtils.startAnimations(splashView, taskTarget.leash,
-// mSplashMainWindowShiftLength, new TransactionPool(), new Rect(),
-// SPLASH_ANIMATION_DURATION, SPLASH_FADE_OUT_DURATION,
-// /* iconStartAlpha= */ 0, /* brandingStartAlpha= */ 0,
-// SPLASH_APP_REVEAL_DELAY, SPLASH_APP_REVEAL_DURATION,
-// new AnimatorListenerAdapter() {
-// @Override
-// public void onAnimationEnd(Animator animation) {
-// finishRecentsAnimationOnTasksAppeared();
-// }
-// });
+ SplashScreenExitAnimationUtils.startAnimations(splashView, taskTarget.leash,
+ mSplashMainWindowShiftLength, new TransactionPool(), new Rect(),
+ SPLASH_ANIMATION_DURATION, SPLASH_FADE_OUT_DURATION,
+ /* iconStartAlpha= */ 0, /* brandingStartAlpha= */ 0,
+ SPLASH_APP_REVEAL_DELAY, SPLASH_APP_REVEAL_DURATION,
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // Hiding launcher which shows the app surface behind, then
+ // finishing recents to the app. After transition finish, showing
+ // the views on launcher again, so it can be visible when next
+ // animation starts.
+ splashView.setAlpha(0);
+ if (quickstepLauncher != null) {
+ quickstepLauncher.getDepthController()
+ .pauseBlursOnWindows(false);
+ }
+ finishRecentsAnimationOnTasksAppeared(() -> splashView.setAlpha(1));
+ }
+ });
}
}
}
- private void finishRecentsAnimationOnTasksAppeared() {
+ private void finishRecentsAnimationOnTasksAppeared(Runnable onFinishComplete) {
if (mRecentsAnimationController != null) {
- mRecentsAnimationController.finish(false /* toRecents */, null /* onFinishComplete */);
+ mRecentsAnimationController.finish(false /* toRecents */, onFinishComplete);
}
- ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
+ ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimationOnTasksAppeared");
}
/**
@@ -2280,9 +2437,11 @@ public abstract class AbsSwipeUpHandler, Q extends
* resume if we finish the controller.
*/
protected int getLastAppearedTaskIndex() {
- return mGestureState.getLastAppearedTaskId() != -1
- ? mRecentsView.getTaskIndexForId(mGestureState.getLastAppearedTaskId())
- : mRecentsView.getRunningTaskIndex();
+ return mRecentsView == null
+ ? -1
+ : mGestureState.getLastAppearedTaskId() != -1
+ ? mRecentsView.getTaskIndexForId(mGestureState.getLastAppearedTaskId())
+ : mRecentsView.getRunningTaskIndex();
}
/**
@@ -2316,9 +2475,9 @@ public abstract class AbsSwipeUpHandler, Q extends
boolean setRecentsScroll = mRecentsViewScrollLinked && mRecentsView != null;
float progress = Math.max(mCurrentShift.value, getScaleProgressDueToScroll());
int scrollOffset = setRecentsScroll ? mRecentsView.getScrollOffset() : 0;
- if (progress > 0 || scrollOffset != 0) {
- // Hide the divider as the tasks start moving.
- setDividerShown(false);
+ if (!mStartMovingTasks && (progress > 0 || scrollOffset != 0)) {
+ mStartMovingTasks = true;
+ startInterceptingTouchesForGesture();
}
for (RemoteTargetHandle remoteHandle : mRemoteTargetHandles) {
AnimatorControllerWithResistance playbackController = remoteHandle.getPlaybackController();
@@ -2379,7 +2538,7 @@ public abstract class AbsSwipeUpHandler, Q extends
return displacement;
}
- if (mTaskbarAlreadyOpen || mIsTaskbarAllAppsOpen) {
+ if (mTaskbarAlreadyOpen || mIsTaskbarAllAppsOpen || mGestureState.isTrackpadGesture()) {
return displacement;
}
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index ce41c60e89..60083c67e7 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -16,6 +16,7 @@
package com.android.quickstep;
import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR;
+import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -186,8 +187,12 @@ public abstract class BaseActivityInterface TYPE_NO_OP;
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 38cf3a1bc3..a2e709b80b 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -15,6 +15,7 @@
*/
package com.android.quickstep;
+import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
@@ -39,6 +40,7 @@ import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.LauncherInitListener;
import com.android.launcher3.LauncherState;
import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.statemanager.StateManager;
@@ -267,6 +269,11 @@ public final class LauncherActivityInterface extends
return true;
}
+ @Override
+ public boolean allowAllAppsFromOverview() {
+ return FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get();
+ }
+
@Override
public boolean isInLiveTileMode() {
Launcher launcher = getCreatedActivity();
@@ -351,6 +358,8 @@ public final class LauncherActivityInterface extends
case NEW_TASK:
case LAST_TASK:
return BACKGROUND_APP;
+ case ALL_APPS:
+ return ALL_APPS;
case HOME:
default:
return NORMAL;
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index c6cab2637e..f1f7acbae3 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -16,6 +16,9 @@
package com.android.quickstep;
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+
import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
import static com.android.launcher3.BaseActivity.INVISIBLE_ALL;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
@@ -30,15 +33,23 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
import android.util.Pair;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewRootImpl;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.window.BackEvent;
+import android.window.BackMotionEvent;
import android.window.BackProgressAnimator;
import android.window.IOnBackInvokedCallback;
+import com.android.internal.view.AppearanceRegion;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.QuickstepTransitionManager;
import com.android.launcher3.R;
@@ -50,22 +61,31 @@ import com.android.systemui.shared.system.QuickStepContract;
/**
* Controls the animation of swiping back and returning to launcher.
*
- * This is a two part animation. The first part is an animation that tracks gesture location to
- * scale and move the leaving app window. Once the gesture is committed, the second part takes over
+ * This is a two part animation. The first part is an animation that tracks
+ * gesture location to
+ * scale and move the leaving app window. Once the gesture is committed, the
+ * second part takes over
* the app window and plays the rest of app close transitions in one go.
*
* This animation is used only for apps that enable back dispatching via
* {@link android.window.OnBackInvokedDispatcher}. The controller registers
- * an {@link IOnBackInvokedCallback} with WM Shell and receives back dispatches when a back
+ * an {@link IOnBackInvokedCallback} with WM Shell and receives back dispatches
+ * when a back
* navigation to launcher starts.
*
- * Apps using the legacy back dispatching will keep triggering the WALLPAPER_OPEN remote
+ * Apps using the legacy back dispatching will keep triggering the
+ * WALLPAPER_OPEN remote
* transition registered in {@link QuickstepTransitionManager}.
*
*/
public class LauncherBackAnimationController {
private static final int CANCEL_TRANSITION_DURATION = 233;
- private static final float MIN_WINDOW_SCALE = 0.7f;
+ private static final int SCRIM_FADE_DURATION = 233;
+ private static final float MIN_WINDOW_SCALE = 0.85f;
+ private static final float MAX_SCRIM_ALPHA_DARK = 0.8f;
+ private static final float MAX_SCRIM_ALPHA_LIGHT = 0.2f;
+ private static final float UPDATE_SYSUI_FLAGS_THRESHOLD = 0.20f;
+
private final QuickstepTransitionManager mQuickstepTransitionManager;
private final Matrix mTransformMatrix = new Matrix();
/** The window position at the beginning of the back animation. */
@@ -90,7 +110,12 @@ public class LauncherBackAnimationController {
private float mBackProgress = 0;
private boolean mBackInProgress = false;
private IOnBackInvokedCallback mBackCallback;
- private BackProgressAnimator mProgressAnimator;
+ private IRemoteAnimationFinishedCallback mAnimationFinishedCallback;
+ private BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+ private SurfaceControl mScrimLayer;
+ private ValueAnimator mScrimAlphaAnimator;
+ private float mScrimAlpha;
+ private boolean mOverridingStatusBarFlags;
public LauncherBackAnimationController(
QuickstepLauncher launcher,
@@ -99,16 +124,15 @@ public class LauncherBackAnimationController {
mQuickstepTransitionManager = quickstepTransitionManager;
mWindowScaleEndCornerRadius = QuickStepContract.supportsRoundedCornersOnWindows(
mLauncher.getResources())
- ? mLauncher.getResources().getDimensionPixelSize(
- R.dimen.swipe_back_window_corner_radius)
- : 0;
+ ? mLauncher.getResources().getDimensionPixelSize(
+ R.dimen.swipe_back_window_corner_radius)
+ : 0;
mWindowScaleStartCornerRadius = QuickStepContract.getWindowCornerRadius(mLauncher);
mWindowScaleMarginX = mLauncher.getResources().getDimensionPixelSize(
R.dimen.swipe_back_window_scale_x_margin);
mWindowMaxDeltaY = mLauncher.getResources().getDimensionPixelSize(
R.dimen.swipe_back_window_max_delta_y);
- mCancelInterpolator =
- AnimationUtils.loadInterpolator(mLauncher, R.interpolator.back_cancel);
+ mCancelInterpolator = AnimationUtils.loadInterpolator(mLauncher, R.interpolator.back_cancel);
try {
mProgressAnimator = new BackProgressAnimator();
} catch (Throwable throwable) {
@@ -117,19 +141,45 @@ public class LauncherBackAnimationController {
}
/**
- * Registers {@link IOnBackInvokedCallback} to receive back dispatches from shell.
+ * Registers {@link IOnBackInvokedCallback} to receive back dispatches from
+ * shell.
+ *
* @param handler Handler to the thread to run the animations on.
*/
public void registerBackCallbacks(Handler handler) {
mBackCallback = new IOnBackInvokedCallback.Stub() {
@Override
- public void onBackCancelled() {
+ public void onBackStarted(BackMotionEvent backMotionEvent) {
+ startBack (backMotionEvent);
handler.post(() -> {
- resetPositionAnimated();
if (mProgressAnimator == null) {
return;
}
- mProgressAnimator.reset();
+ mProgressAnimator.onBackStarted(backMotionEvent, event -> {
+ mBackProgress = event.getProgress();
+ // TODO: Update once the interpolation curve spec is finalized.
+ mBackProgress = 1 - (1 - mBackProgress) * (1 - mBackProgress) * (1
+ - mBackProgress);
+ updateBackProgress(mBackProgress, event);
+ });
+ });
+ }
+
+ @Override
+ public void onBackProgressed(BackMotionEvent backMotionEvent) {
+ handler.post(() -> {
+ if (mProgressAnimator == null) {
+ return;
+ }
+ mProgressAnimator.onBackProgressed(backMotionEvent);
+ });
+ }
+
+ @Override
+ public void onBackCancelled() {
+ handler.post(() -> {
+ mProgressAnimator.onBackCancelled(
+ LauncherBackAnimationController.this::resetPositionAnimated);
});
}
@@ -143,36 +193,28 @@ public class LauncherBackAnimationController {
mProgressAnimator.reset();
});
}
+ };
+ final IRemoteAnimationRunner runner = new IRemoteAnimationRunner.Stub() {
@Override
- public void onBackProgressed(BackEvent backEvent) {
- handler.post(() -> {
- if (mProgressAnimator == null) {
- return;
+ public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback) {
+ for (final RemoteAnimationTarget target : apps) {
+ if (MODE_CLOSING == target.mode) {
+ mBackTarget = target;
+ break;
}
- mProgressAnimator.onBackProgressed(backEvent);
- });
+ }
+ mAnimationFinishedCallback = finishedCallback;
}
@Override
- public void onBackStarted(BackEvent backEvent) {
- handler.post(() -> {
- startBack(backEvent);
- if (mProgressAnimator == null) {
- return;
- }
- mProgressAnimator.onBackStarted(backEvent, event -> {
- mBackProgress = event.getProgress();
- // TODO: Update once the interpolation curve spec is finalized.
- mBackProgress =
- 1 - (1 - mBackProgress) * (1 - mBackProgress) * (1
- - mBackProgress);
- updateBackProgress(mBackProgress, event);
- });
- });
+ public void onAnimationCancelled() {
}
};
- SystemUiProxy.INSTANCE.get(mLauncher).setBackToLauncherCallback(mBackCallback);
+
+ SystemUiProxy.INSTANCE.get(mLauncher).setBackToLauncherCallback(mBackCallback, runner);
}
private void resetPositionAnimated() {
@@ -187,6 +229,8 @@ public class LauncherBackAnimationController {
cancelAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
+ // Refresh the status bar appearance to the original one.
+ customizeStatusBarAppearance(false);
finishAnimation();
}
});
@@ -204,7 +248,7 @@ public class LauncherBackAnimationController {
mBackCallback = null;
}
- private void startBack(BackEvent backEvent) {
+ private void startBack(BackMotionEvent backEvent) {
mBackInProgress = true;
RemoteAnimationTarget appTarget = backEvent.getDepartingAnimationTarget();
@@ -212,14 +256,51 @@ public class LauncherBackAnimationController {
return;
}
- mTransaction.show(appTarget.leash).apply();
- mTransaction.setAnimationTransaction();
+ mTransaction
+ .show(appTarget.leash)
+ .setAnimationTransaction();
mBackTarget = appTarget;
mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
// TODO(b/218916755): Offset start rectangle in multiwindow mode.
- mStartRect.set(mBackTarget.windowConfiguration.getMaxBounds());
+ mStartRect.set(appTarget.windowConfiguration.getMaxBounds());
mCurrentRect.set(mStartRect);
+ addScrimLayer();
+ mTransaction.apply();
+ }
+
+ void addScrimLayer() {
+ ViewRootImpl viewRootImpl = mLauncher.getDragLayer().getViewRootImpl();
+ SurfaceControl parent = viewRootImpl != null
+ ? viewRootImpl.getSurfaceControl()
+ : null;
+ boolean isDarkTheme = Utilities.isDarkTheme(mLauncher);
+ mScrimLayer = new SurfaceControl.Builder()
+ .setName("Back to launcher background scrim")
+ .setCallsite("LauncherBackAnimationController")
+ .setColorLayer()
+ .setParent(parent)
+ .setOpaque(false)
+ .setHidden(false)
+ .build();
+ final float[] colorComponents = new float[] { 0f, 0f, 0f };
+ mScrimAlpha = (isDarkTheme)
+ ? MAX_SCRIM_ALPHA_DARK
+ : MAX_SCRIM_ALPHA_LIGHT;
+ mTransaction
+ .setColor(mScrimLayer, colorComponents)
+ .setAlpha(mScrimLayer, mScrimAlpha)
+ .show(mScrimLayer);
+ }
+
+ void removeScrimLayer() {
+ if (mScrimLayer == null) {
+ return;
+ }
+ if (mScrimLayer.isValid()) {
+ mTransaction.remove(mScrimLayer).apply();
+ }
+ mScrimLayer = null;
}
private void updateBackProgress(float progress, BackEvent event) {
@@ -244,6 +325,8 @@ public class LauncherBackAnimationController {
float cornerRadius = Utilities.mapRange(
progress, mWindowScaleStartCornerRadius, mWindowScaleEndCornerRadius);
applyTransform(mCurrentRect, cornerRadius);
+
+ customizeStatusBarAppearance(progress > UPDATE_SYSUI_FLAGS_THRESHOLD);
}
private void updateCancelProgress(float progress) {
@@ -288,28 +371,30 @@ public class LauncherBackAnimationController {
if (mLauncher.isDestroyed()) {
return;
}
- // TODO: Catch the moment when launcher becomes visible after the top app un-occludes
- // launcher and start animating afterwards. Currently we occasionally get a flicker from
- // animating when launcher is still invisible.
+ // TODO: Catch the moment when launcher becomes visible after the top app
+ // un-occludes
+ // launcher and start animating afterwards. Currently we occasionally get a
+ // flicker from
+ // animating when launcher is still invisible.
if (mLauncher.hasSomeInvisibleFlag(PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION)) {
mLauncher.addForceInvisibleFlag(INVISIBLE_BY_PENDING_FLAGS);
mLauncher.getStateManager().moveToRestState();
}
// Explicitly close opened floating views (which is typically called from
- // Launcher#onResumed, but in the predictive back flow launcher is not resumed until
+ // Launcher#onResumed, but in the predictive back flow launcher is not resumed
+ // until
// the transition is fully finished.)
AbstractFloatingView.closeAllOpenViewsExcept(mLauncher, false, TYPE_REBIND_SAFE);
float cornerRadius = Utilities.mapRange(
mBackProgress, mWindowScaleStartCornerRadius, mWindowScaleEndCornerRadius);
- Pair pair =
- mQuickstepTransitionManager.createWallpaperOpenAnimations(
- new RemoteAnimationTarget[]{mBackTarget},
- new RemoteAnimationTarget[0],
- false /* fromUnlock */,
- mCurrentRect,
- cornerRadius,
- mBackInProgress /* fromPredictiveBack */);
+ Pair pair = mQuickstepTransitionManager.createWallpaperOpenAnimations(
+ new RemoteAnimationTarget[] { mBackTarget },
+ new RemoteAnimationTarget[0],
+ false /* fromUnlock */,
+ mCurrentRect,
+ cornerRadius,
+ mBackInProgress /* fromPredictiveBack */);
startTransitionAnimations(pair.first, pair.second);
mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
}
@@ -325,7 +410,22 @@ public class LauncherBackAnimationController {
mInitialTouchPos.set(0, 0);
mAnimatorSetInProgress = false;
mSpringAnimationInProgress = false;
- SystemUiProxy.INSTANCE.get(mLauncher).onBackToLauncherAnimationFinished();
+ // We don't call customizeStatusBarAppearance here to prevent the status bar
+ // update with
+ // the legacy appearance. It should be refreshed after the transition done.
+ mOverridingStatusBarFlags = false;
+ if (mAnimationFinishedCallback != null) {
+ try {
+ mAnimationFinishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ Log.w("ShellBackPreview", "Failed call onBackAnimationFinished", e);
+ }
+ mAnimationFinishedCallback = null;
+ }
+ if (mScrimAlphaAnimator != null && mScrimAlphaAnimator.isRunning()) {
+ mScrimAlphaAnimator.cancel();
+ mScrimAlphaAnimator = null;
+ }
}
private void startTransitionAnimations(RectFSpringAnim springAnim, AnimatorSet anim) {
@@ -339,8 +439,7 @@ public class LauncherBackAnimationController {
mSpringAnimationInProgress = false;
tryFinishBackAnimation();
}
- }
- );
+ });
}
anim.addListener(new AnimatorListenerAdapter() {
@Override
@@ -349,12 +448,47 @@ public class LauncherBackAnimationController {
tryFinishBackAnimation();
}
});
+ mScrimAlphaAnimator = new ValueAnimator().ofFloat(1, 0);
+ mScrimAlphaAnimator.addUpdateListener(animation -> {
+ float value = (Float) animation.getAnimatedValue();
+ if (mScrimLayer.isValid()) {
+ mTransaction.setAlpha(mScrimLayer, value * mScrimAlpha);
+ mTransaction.apply();
+ }
+ });
+ mScrimAlphaAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ resetScrim();
+ }
+ });
+ mScrimAlphaAnimator.setDuration(SCRIM_FADE_DURATION).start();
anim.start();
}
+ private void resetScrim() {
+ removeScrimLayer();
+ mScrimAlpha = 0;
+ }
+
private void tryFinishBackAnimation() {
if (!mSpringAnimationInProgress && !mAnimatorSetInProgress) {
finishAnimation();
}
}
+
+ private void customizeStatusBarAppearance(boolean overridingStatusBarFlags) {
+ if (mOverridingStatusBarFlags == overridingStatusBarFlags) {
+ return;
+ }
+
+ mOverridingStatusBarFlags = overridingStatusBarFlags;
+ final boolean isBackgroundDark = (mLauncher.getWindow().getDecorView().getSystemUiVisibility()
+ & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) == 0;
+ final AppearanceRegion region = mOverridingStatusBarFlags
+ ? new AppearanceRegion(!isBackgroundDark ? APPEARANCE_LIGHT_STATUS_BARS : 0,
+ mBackTarget.windowConfiguration.getBounds())
+ : null;
+ SystemUiProxy.INSTANCE.get(mLauncher).customizeStatusBarAppearance(region);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 43ad175b6f..fd5c1a7a78 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -47,6 +47,7 @@ import com.android.launcher3.views.FloatingView;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.StaggeredWorkspaceAnim;
+import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.views.FloatingWidgetView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -160,13 +161,16 @@ public class LauncherSwipeHandlerV2 extends
Rect crop = new Rect();
// We can assume there is only one remote target here because staged split never animates
// into the app icon, only into the homescreen
- mRemoteTargetHandles[0].getTaskViewSimulator().getCurrentCropRect().roundOut(crop);
+ RemoteTargetGluer.RemoteTargetHandle remoteTargetHandle = mRemoteTargetHandles[0];
+ TaskViewSimulator tvs = remoteTargetHandle.getTaskViewSimulator();
+ // This is to set up the inverse matrix in the simulator
+ tvs.apply(remoteTargetHandle.getTransformParams());
+ tvs.getCurrentCropRect().roundOut(crop);
Size windowSize = new Size(crop.width(), crop.height());
int fallbackBackgroundColor =
FloatingWidgetView.getDefaultBackgroundColor(mContext, runningTaskTarget);
FloatingWidgetView floatingWidgetView = FloatingWidgetView.getFloatingWidgetView(mActivity,
- hostView, backgroundLocation, windowSize,
- mRemoteTargetHandles[0].getTaskViewSimulator().getCurrentCornerRadius(),
+ hostView, backgroundLocation, windowSize, tvs.getCurrentCornerRadius(),
isTargetTranslucent, fallbackBackgroundColor);
return new FloatingViewHomeAnimationFactory(floatingWidgetView) {
diff --git a/quickstep/src/com/android/quickstep/MultiStateCallback.java b/quickstep/src/com/android/quickstep/MultiStateCallback.java
index a68bea2cc0..df42efcc4e 100644
--- a/quickstep/src/com/android/quickstep/MultiStateCallback.java
+++ b/quickstep/src/com/android/quickstep/MultiStateCallback.java
@@ -86,9 +86,7 @@ public class MultiStateCallback {
Log.d(TAG, "[" + System.identityHashCode(this) + "] Adding "
+ convertToFlagNames(stateFlag) + " to " + convertToFlagNames(mState));
}
- if (FeatureFlags.ENABLE_GESTURE_ERROR_DETECTION.get()) {
- trackGestureEvents(stateFlag);
- }
+ trackGestureEvents(stateFlag);
final int oldState = mState;
mState = mState | stateFlag;
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 5b85249dfa..07db194ff4 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -248,7 +248,8 @@ public class OverviewCommandHelper {
InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
}
- GestureState gestureState = mService.createGestureState(GestureState.DEFAULT_STATE);
+ GestureState gestureState = mService.createGestureState(GestureState.DEFAULT_STATE,
+ GestureState.TrackpadGestureType.NONE);
gestureState.setHandlingAtomicEvent(true);
AbsSwipeUpHandler interactionHandler = mService.getSwipeUpHandlerFactory()
.newHandler(gestureState, cmd.createTime);
@@ -261,7 +262,7 @@ public class OverviewCommandHelper {
public void onRecentsAnimationStart(RecentsAnimationController controller,
RecentsAnimationTargets targets) {
activityInterface.runOnInitBackgroundStateUI(() ->
- interactionHandler.onGestureEnded(0, new PointF(), new PointF()));
+ interactionHandler.onGestureEnded(0, new PointF()));
cmd.removeListener(this);
}
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
deleted file mode 100644
index 5391f4d46c..0000000000
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ /dev/null
@@ -1,223 +0,0 @@
-package com.android.quickstep;
-
-import static com.android.launcher3.testing.shared.TestProtocol.NPE_TRANSIENT_TASKBAR;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.R;
-import com.android.launcher3.taskbar.TaskbarActivityContext;
-import com.android.launcher3.testing.TestInformationHandler;
-import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.util.DisplayController;
-import com.android.quickstep.util.LayoutUtils;
-import com.android.quickstep.util.TISBindHelper;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
-import java.util.function.Consumer;
-import java.util.function.Function;
-
-public class QuickstepTestInformationHandler extends TestInformationHandler {
-
- protected final Context mContext;
-
- public QuickstepTestInformationHandler(Context context) {
- mContext = context;
- }
-
- @Override
- public Bundle call(String method, String arg, @Nullable Bundle extras) {
- final Bundle response = new Bundle();
- switch (method) {
- case TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT: {
- final float swipeHeight =
- LayoutUtils.getDefaultSwipeHeight(mContext, mDeviceProfile);
- response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
- return response;
- }
-
- case TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT: {
- final float swipeHeight =
- LayoutUtils.getShelfTrackingDistance(mContext, mDeviceProfile,
- PagedOrientationHandler.PORTRAIT);
- response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
- return response;
- }
-
- case TestProtocol.REQUEST_GET_FOCUSED_TASK_HEIGHT_FOR_TABLET: {
- if (!mDeviceProfile.isTablet) {
- return null;
- }
- Rect focusedTaskRect = new Rect();
- LauncherActivityInterface.INSTANCE.calculateTaskSize(mContext, mDeviceProfile,
- focusedTaskRect, PagedOrientationHandler.PORTRAIT);
- response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, focusedTaskRect.height());
- return response;
- }
-
- case TestProtocol.REQUEST_GET_GRID_TASK_SIZE_RECT_FOR_TABLET: {
- if (!mDeviceProfile.isTablet) {
- return null;
- }
- Rect gridTaskRect = new Rect();
- LauncherActivityInterface.INSTANCE.calculateGridTaskSize(mContext, mDeviceProfile,
- gridTaskRect, PagedOrientationHandler.PORTRAIT);
- response.putParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD, gridTaskRect);
- return response;
- }
-
- case TestProtocol.REQUEST_GET_OVERVIEW_PAGE_SPACING: {
- response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
- mDeviceProfile.overviewPageSpacing);
- return response;
- }
-
- case TestProtocol.REQUEST_HAS_TIS: {
- response.putBoolean(
- TestProtocol.REQUEST_HAS_TIS, true);
- return response;
- }
-
- case TestProtocol.REQUEST_ENABLE_MANUAL_TASKBAR_STASHING:
- runOnTISBinder(tisBinder -> {
- enableManualTaskbarStashing(tisBinder, true);
- });
- return response;
-
- case TestProtocol.REQUEST_DISABLE_MANUAL_TASKBAR_STASHING:
- runOnTISBinder(tisBinder -> {
- enableManualTaskbarStashing(tisBinder, false);
- });
- return response;
-
- case TestProtocol.REQUEST_UNSTASH_TASKBAR_IF_STASHED:
- runOnTISBinder(tisBinder -> {
- enableManualTaskbarStashing(tisBinder, true);
-
- // Allow null-pointer to catch illegal states.
- tisBinder.getTaskbarManager().getCurrentActivityContext()
- .unstashTaskbarIfStashed();
-
- enableManualTaskbarStashing(tisBinder, false);
- });
- return response;
-
- case TestProtocol.REQUEST_STASHED_TASKBAR_HEIGHT: {
- final Resources resources = mContext.getResources();
- response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
- resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size));
- return response;
- }
-
- case TestProtocol.REQUEST_TASKBAR_ALL_APPS_TOP_PADDING: {
- return getTISBinderUIProperty(Bundle::putInt, tisBinder ->
- tisBinder.getTaskbarManager()
- .getCurrentActivityContext()
- .getTaskbarAllAppsTopPadding());
- }
-
- case TestProtocol.REQUEST_ENABLE_BLOCK_TIMEOUT:
- runOnTISBinder(tisBinder -> {
- enableBlockingTimeout(tisBinder, true);
- });
- return response;
-
- case TestProtocol.REQUEST_DISABLE_BLOCK_TIMEOUT:
- runOnTISBinder(tisBinder -> {
- enableBlockingTimeout(tisBinder, false);
- });
- return response;
-
- case TestProtocol.REQUEST_ENABLE_TRANSIENT_TASKBAR:
- enableTransientTaskbar(true);
- return response;
-
- case TestProtocol.REQUEST_DISABLE_TRANSIENT_TASKBAR:
- enableTransientTaskbar(false);
- return response;
- }
-
- return super.call(method, arg, extras);
- }
-
- @Override
- protected Activity getCurrentActivity() {
- RecentsAnimationDeviceState rads = new RecentsAnimationDeviceState(mContext);
- OverviewComponentObserver observer = new OverviewComponentObserver(mContext, rads);
- try {
- return observer.getActivityInterface().getCreatedActivity();
- } finally {
- observer.onDestroy();
- rads.destroy();
- }
- }
-
- @Override
- protected boolean isLauncherInitialized() {
- return super.isLauncherInitialized() && TouchInteractionService.isInitialized();
- }
-
- private void enableManualTaskbarStashing(
- TouchInteractionService.TISBinder tisBinder, boolean enable) {
- // Allow null-pointer to catch illegal states.
- tisBinder.getTaskbarManager().getCurrentActivityContext().enableManualStashingDuringTests(
- enable);
- }
-
- private void enableBlockingTimeout(
- TouchInteractionService.TISBinder tisBinder, boolean enable) {
- TaskbarActivityContext context = tisBinder.getTaskbarManager().getCurrentActivityContext();
- if (context == null) {
- if (TestProtocol.sDebugTracing) {
- Log.d(NPE_TRANSIENT_TASKBAR, "enableBlockingTimeout: enable=" + enable,
- new Exception());
- }
- } else {
- context.enableBlockingTimeoutDuringTests(enable);
- }
- }
-
- private void enableTransientTaskbar(boolean enable) {
- DisplayController.INSTANCE.get(mContext).enableTransientTaskbarForTests(enable);
- }
-
- /**
- * Runs the given command on the UI thread, after ensuring we are connected to
- * TouchInteractionService.
- */
- protected void runOnTISBinder(Consumer connectionCallback) {
- try {
- CountDownLatch countDownLatch = new CountDownLatch(1);
- TISBindHelper helper = MAIN_EXECUTOR.submit(() ->
- new TISBindHelper(mContext, tisBinder -> {
- connectionCallback.accept(tisBinder);
- countDownLatch.countDown();
- })).get();
- countDownLatch.await();
- MAIN_EXECUTOR.execute(helper::onDestroy);
- } catch (ExecutionException | InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
-
- private Bundle getTISBinderUIProperty(
- BundleSetter bundleSetter, Function provider) {
- Bundle response = new Bundle();
-
- runOnTISBinder(tisBinder -> bundleSetter.set(
- response,
- TestProtocol.TEST_INFO_RESPONSE_FIELD,
- provider.apply(tisBinder)));
-
- return response;
- }
-}
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 70c5ee05ba..9d29e7e155 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -145,7 +145,9 @@ public final class RecentsActivity extends StatefulActivity {
private void onTISConnected(TouchInteractionService.TISBinder binder) {
mTaskbarManager = binder.getTaskbarManager();
- mTaskbarManager.setActivity(this);
+ if (mTaskbarManager != null) {
+ mTaskbarManager.setActivity(this);
+ }
}
@Override
@@ -271,7 +273,8 @@ public final class RecentsActivity extends StatefulActivity {
new RemoteAnimationAdapter(wrapper, RECENTS_LAUNCH_DURATION,
RECENTS_LAUNCH_DURATION - STATUS_BAR_TRANSITION_DURATION
- STATUS_BAR_TRANSITION_PRE_DELAY),
- new RemoteTransition(wrapper.toRemoteTransition(), getIApplicationThread()));
+ new RemoteTransition(wrapper.toRemoteTransition(), getIApplicationThread(),
+ "LaunchFromRecents"));
final ActivityOptionsWrapper activityOptions = new ActivityOptionsWrapper(options,
onEndCallback);
if (Utilities.ATLEAST_S) {
@@ -414,7 +417,8 @@ public final class RecentsActivity extends StatefulActivity {
getMainThreadHandler(), mAnimationToHomeFactory, true);
ActivityOptions options = ActivityOptions.makeRemoteAnimation(
new RemoteAnimationAdapter(runner, HOME_APPEAR_DURATION, 0),
- new RemoteTransition(runner.toRemoteTransition(), getIApplicationThread()));
+ new RemoteTransition(runner.toRemoteTransition(), getIApplicationThread(),
+ "StartHomeFromRecents"));
startHomeIntentSafely(this, options.toBundle());
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index b82ff03b34..2256cbf14b 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -15,6 +15,9 @@
*/
package com.android.quickstep;
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.CANCEL_RECENTS_ANIMATION;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.START_RECENTS_ANIMATION;
@@ -34,6 +37,8 @@ import com.android.quickstep.util.ActiveGestureLog;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Set;
@@ -98,15 +103,36 @@ public class RecentsAnimationCallbacks implements
RemoteAnimationTarget[] appTargets,
RemoteAnimationTarget[] wallpaperTargets,
Rect homeContentInsets, Rect minimizedHomeBounds) {
+ long appCount = Arrays.stream(appTargets)
+ .filter(app -> app.mode == MODE_CLOSING)
+ .count();
+ if (appCount == 0) {
+ // Edge case, if there are no closing app targets, then Launcher has nothing to handle
+ ActiveGestureLog.INSTANCE.addLog(
+ /* event= */ "RecentsAnimationCallbacks.onAnimationStart (canceled)",
+ /* extras= */ 0,
+ /* gestureEvent= */ START_RECENTS_ANIMATION);
+ notifyAnimationCanceled();
+ animationController.finish(false /* toHome */, false /* sendUserLeaveHint */);
+ return;
+ }
+
mController = new RecentsAnimationController(animationController,
mAllowMinimizeSplitScreen, this::onAnimationFinished);
-
if (mCancelled) {
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(),
mController::finishAnimationToApp);
} else {
- RemoteAnimationTarget[] nonAppTargets =
- mSystemUiProxy.onGoingToRecentsLegacy(appTargets);
+ RemoteAnimationTarget[] nonAppTargets;
+ if (!TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
+ nonAppTargets = mSystemUiProxy.onGoingToRecentsLegacy(appTargets);
+ } else {
+ final ArrayList apps = new ArrayList<>();
+ final ArrayList nonApps = new ArrayList<>();
+ classifyTargets(appTargets, apps, nonApps);
+ appTargets = apps.toArray(new RemoteAnimationTarget[apps.size()]);
+ nonAppTargets = nonApps.toArray(new RemoteAnimationTarget[nonApps.size()]);
+ }
if (nonAppTargets == null) {
nonAppTargets = new RemoteAnimationTarget[0];
}
@@ -176,6 +202,18 @@ public class RecentsAnimationCallbacks implements
return mListeners.toArray(new RecentsAnimationListener[mListeners.size()]);
}
+ private void classifyTargets(RemoteAnimationTarget[] appTargets,
+ ArrayList apps, ArrayList nonApps) {
+ for (int i = 0; i < appTargets.length; i++) {
+ RemoteAnimationTarget target = appTargets[i];
+ if (target.windowType == TYPE_DOCK_DIVIDER) {
+ nonApps.add(target);
+ } else {
+ apps.add(target);
+ }
+ }
+ }
+
/**
* Listener for the recents animation callbacks.
*/
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 4adfae5ee8..f8e09e1e88 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -150,10 +150,17 @@ public class RecentsAnimationController {
@UiThread
public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) {
- if (mFinishRequested) {
- // If finishing, add to pending finish callbacks, otherwise, if finished, adding to the
- // destroyed RunnableList will just trigger the callback to be called immediately
- mPendingFinishCallbacks.add(callback);
+ finishController(toRecents, callback, sendUserLeaveHint, false /* forceFinish */);
+ }
+
+ @UiThread
+ public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint,
+ boolean forceFinish) {
+ mPendingFinishCallbacks.add(callback);
+ if (!forceFinish && mFinishRequested) {
+ // If finish has already been requested, then add the callback to the pending list.
+ // If already finished, then adding it to the destroyed RunnableList will just
+ // trigger the callback to be called immediately
return;
}
ActiveGestureLog.INSTANCE.addLog(
@@ -165,15 +172,19 @@ public class RecentsAnimationController {
mFinishRequested = true;
mFinishTargetIsLauncher = toRecents;
mOnFinishedListener.accept(this);
- mPendingFinishCallbacks.add(callback);
- UI_HELPER_EXECUTOR.execute(() -> {
+ Runnable finishCb = () -> {
mController.finish(toRecents, sendUserLeaveHint);
InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
InteractionJankMonitorWrapper.end(
InteractionJankMonitorWrapper.CUJ_APP_SWIPE_TO_RECENTS);
MAIN_EXECUTOR.execute(mPendingFinishCallbacks::executeAllAndDestroy);
- });
+ };
+ if (forceFinish) {
+ finishCb.run();
+ } else {
+ UI_HELPER_EXECUTOR.execute(finishCb);
+ }
}
/**
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index be810c61ad..adbf3ea67b 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -33,6 +33,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
@@ -57,6 +58,7 @@ import android.os.SystemProperties;
import android.os.UserManager;
import android.provider.Settings;
import android.view.MotionEvent;
+import android.view.ViewConfiguration;
import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
@@ -89,17 +91,23 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode";
+ // TODO: Move to quickstep contract
+ private static final float QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON = 9;
+ private static final float QUICKSTEP_TOUCH_SLOP_RATIO_GESTURAL = 2;
+
private final Context mContext;
private final DisplayController mDisplayController;
private final int mDisplayId;
private final RotationTouchHelper mRotationTouchHelper;
private final TaskStackChangeListener mPipListener;
// Cache for better performance since it doesn't change at runtime.
- private final boolean mCanImeRenderGesturalNavButtons = LawnchairApp.isAtleastT() ? InputMethodService.canImeRenderGesturalNavButtons() : isImeRenderingNavButtons();
+ private final boolean mCanImeRenderGesturalNavButtons = LawnchairApp.isAtleastT()
+ ? InputMethodService.canImeRenderGesturalNavButtons()
+ : isImeRenderingNavButtons();
private final ArrayList mOnDestroyActions = new ArrayList<>();
- private @SystemUiStateFlags int mSystemUiStateFlags;
+ private @SystemUiStateFlags int mSystemUiStateFlags = QuickStepContract.SYSUI_STATE_AWAKE;
private NavigationMode mMode = THREE_BUTTONS;
private NavBarPosition mNavBarPosition;
@@ -130,8 +138,9 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
}
/**
- * @param isInstanceForTouches {@code true} if this is the persistent instance being used for
- * gesture touch handling
+ * @param isInstanceForTouches {@code true} if this is the persistent instance
+ * being used for
+ * gesture touch handling
*/
public RecentsAnimationDeviceState(Context context, boolean isInstanceForTouches) {
mContext = context;
@@ -140,7 +149,8 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
mIsOneHandedModeSupported = SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false);
mRotationTouchHelper = RotationTouchHelper.INSTANCE.get(context);
if (isInstanceForTouches) {
- // rotationTouchHelper doesn't get initialized after being destroyed, so only destroy
+ // rotationTouchHelper doesn't get initialized after being destroyed, so only
+ // destroy
// if primary TouchInteractionService instance needs to be destroyed.
mRotationTouchHelper.init();
runOnDestroy(mRotationTouchHelper::destroy);
@@ -179,25 +189,23 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
SettingsCache settingsCache = SettingsCache.INSTANCE.get(mContext);
if (mIsOneHandedModeSupported) {
Uri oneHandedUri = Settings.Secure.getUriFor(ONE_HANDED_ENABLED);
- SettingsCache.OnChangeListener onChangeListener =
- enabled -> mIsOneHandedModeEnabled = enabled;
+ SettingsCache.OnChangeListener onChangeListener = enabled -> mIsOneHandedModeEnabled = enabled;
settingsCache.register(oneHandedUri, onChangeListener);
- mIsOneHandedModeEnabled = LawnchairApp.isRecentsEnabled () && settingsCache.getValue(oneHandedUri);
+ mIsOneHandedModeEnabled = LawnchairApp.isRecentsEnabled() && settingsCache.getValue(oneHandedUri);
runOnDestroy(() -> settingsCache.unregister(oneHandedUri, onChangeListener));
} else {
mIsOneHandedModeEnabled = false;
}
- Uri swipeBottomNotificationUri =
- Settings.Secure.getUriFor(ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED);
- SettingsCache.OnChangeListener onChangeListener =
- enabled -> mIsSwipeToNotificationEnabled = enabled;
+ Uri swipeBottomNotificationUri = Settings.Secure.getUriFor(ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED);
+ SettingsCache.OnChangeListener onChangeListener = enabled -> mIsSwipeToNotificationEnabled = enabled;
settingsCache.register(swipeBottomNotificationUri, onChangeListener);
- mIsSwipeToNotificationEnabled = LawnchairApp.isRecentsEnabled () && settingsCache.getValue(swipeBottomNotificationUri);
+ mIsSwipeToNotificationEnabled = LawnchairApp.isRecentsEnabled()
+ && settingsCache.getValue(swipeBottomNotificationUri);
runOnDestroy(() -> settingsCache.unregister(swipeBottomNotificationUri, onChangeListener));
Uri setupCompleteUri = Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE);
- mIsUserSetupComplete = LawnchairApp.isRecentsEnabled () && settingsCache.getValue(setupCompleteUri, 0);
+ mIsUserSetupComplete = LawnchairApp.isRecentsEnabled() && settingsCache.getValue(setupCompleteUri, 0);
if (!mIsUserSetupComplete) {
SettingsCache.OnChangeListener userSetupChangeListener = e -> mIsUserSetupComplete = e;
settingsCache.register(setupCompleteUri, userSetupChangeListener);
@@ -205,8 +213,9 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
}
try {
- mPipIsActive = LawnchairApp.isRecentsEnabled () && Utilities.ATLEAST_S && ActivityTaskManager.getService().getRootTaskInfo(
- WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED) != null;
+ mPipIsActive = LawnchairApp.isRecentsEnabled() && Utilities.ATLEAST_S
+ && ActivityTaskManager.getService().getRootTaskInfo(
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED) != null;
} catch (RemoteException e) {
// Do nothing
}
@@ -223,8 +232,7 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
};
if (Utilities.ATLEAST_Q) {
TaskStackChangeListeners.getInstance().registerTaskStackListener(mPipListener);
- runOnDestroy(() ->
- TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mPipListener));
+ runOnDestroy(() -> TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mPipListener));
}
}
@@ -242,7 +250,8 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
}
/**
- * Adds a listener for the nav mode change, guaranteed to be called after the device state's
+ * Adds a listener for the nav mode change, guaranteed to be called after the
+ * device state's
* mode has changed.
*/
public void addNavigationModeChangedCallback(Runnable callback) {
@@ -262,7 +271,8 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
mMode = info.navigationMode;
mNavBarPosition = new NavBarPosition(mMode, info);
- if (mExclusionListener == null) return;
+ if (mExclusionListener == null)
+ return;
if (mMode == NO_BUTTON) {
mExclusionListener.register();
@@ -277,7 +287,8 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
}
/**
- * @return the nav bar position for the current nav bar mode and display rotation.
+ * @return the nav bar position for the current nav bar mode and display
+ * rotation.
*/
public NavBarPosition getNavBarPosition() {
return mNavBarPosition;
@@ -291,7 +302,8 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
}
/**
- * @return whether the current nav mode has some gestures (either 2 or 0 button mode).
+ * @return whether the current nav mode has some gestures (either 2 or 0 button
+ * mode).
*/
public boolean isGesturalNavMode() {
return mMode.hasGestures;
@@ -319,7 +331,8 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
}
/**
- * Adds a callback for when a user is unlocked. If the user is already unlocked, this listener
+ * Adds a callback for when a user is unlocked. If the user is already unlocked,
+ * this listener
* will be called back immediately.
*/
public void runOnUserUnlocked(Runnable action) {
@@ -394,11 +407,13 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
&& (mSystemUiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0
&& (mSystemUiStateFlags & SYSUI_STATE_MAGNIFICATION_OVERLAP) == 0
&& ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
- || (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0);
+ || (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0)
+ && (mSystemUiStateFlags & SYSUI_STATE_DEVICE_DREAMING) == 0;
}
/**
- * @return whether the keyguard is showing and is occluded by an app showing above the keyguard
+ * @return whether the keyguard is showing and is occluded by an app showing
+ * above the keyguard
* (like camera or maps)
*/
public boolean isKeyguardShowingOccluded() {
@@ -483,7 +498,8 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
}
/**
- * Sets the region in screen space where the gestures should be deferred (ie. due to specific
+ * Sets the region in screen space where the gestures should be deferred (ie.
+ * due to specific
* nav bar ui).
*/
public void setDeferredGestureRegion(Region deferredGestureRegion) {
@@ -491,8 +507,10 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
}
/**
- * @return whether the given {@param event} is in the deferred gesture region indicating that
- * the Launcher should not immediately start the recents animation until the gesture
+ * @return whether the given {@param event} is in the deferred gesture region
+ * indicating that
+ * the Launcher should not immediately start the recents animation until
+ * the gesture
* passes a certain threshold.
*/
public boolean isInDeferredGestureRegion(MotionEvent event) {
@@ -500,7 +518,8 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
}
/**
- * @return whether the given {@param event} is in the app-requested gesture-exclusion region.
+ * @return whether the given {@param event} is in the app-requested
+ * gesture-exclusion region.
* This is only used for quickswitch, and not swipe up.
*/
public boolean isInExclusionRegion(MotionEvent event) {
@@ -533,7 +552,8 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
/**
* @param ev An ACTION_DOWN motion event
- * @return whether the given motion event can trigger the assistant over the current task.
+ * @return whether the given motion event can trigger the assistant over the
+ * current task.
*/
public boolean canTriggerAssistantAction(MotionEvent ev) {
return mAssistantAvailable
@@ -543,7 +563,8 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
}
/**
- * One handed gestural in quickstep only active on NO_BUTTON, TWO_BUTTONS, and portrait mode
+ * One handed gestural in quickstep only active on NO_BUTTON, TWO_BUTTONS, and
+ * portrait mode
*
* @param ev The touch screen motion event.
* @return whether the given motion event can trigger the one handed mode.
@@ -577,14 +598,31 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
return mRotationTouchHelper;
}
- /** Returns whether IME is rendering nav buttons, and IME is currently showing. */
+ /**
+ * Returns whether IME is rendering nav buttons, and IME is currently showing.
+ */
public boolean isImeRenderingNavButtons() {
return mCanImeRenderGesturalNavButtons && mMode == NO_BUTTON
&& ((mSystemUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0);
}
+ /**
+ * Returns the touch slop for {@link InputConsumer}s to compare against before
+ * pilfering
+ * pointers. Note that this is squared because it expects to be compared against
+ * {@link com.android.launcher3.Utilities#squaredHypot} (to avoid square root on
+ * each event).
+ */
+ public float getSquaredTouchSlop() {
+ float slopMultiplier = isFullyGesturalNavMode()
+ ? QUICKSTEP_TOUCH_SLOP_RATIO_GESTURAL
+ : QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON;
+ float touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
+ return slopMultiplier * touchSlop * touchSlop;
+ }
+
public String getSystemUiStateString() {
- return QuickStepContract.getSystemUiStateString(mSystemUiStateFlags);
+ return QuickStepContract.getSystemUiStateString(mSystemUiStateFlags);
}
public void dump(PrintWriter pw) {
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index b9064563a0..157971e6d3 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -150,6 +150,9 @@ public class RecentsModel implements IconChangeListener, TaskStackChangeListener
* @param filter Returns true if GroupTask should be in the list of considerations
*/
public void isTaskRemoved(int taskId, Consumer callback, Predicate filter) {
+ // Invalidate the existing list before checking to ensure this reflects the current state in
+ // the system
+ mTaskList.onRecentTasksChanged();
mTaskList.getTasks(true /* loadKeysOnly */, (taskGroups) -> {
for (GroupTask group : taskGroups) {
if (group.containsTask(taskId)) {
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
index f30d3f14cc..84246e93c0 100644
--- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -16,8 +16,10 @@
package com.android.quickstep;
+import android.app.WindowConfiguration;
import android.content.Context;
import android.graphics.Rect;
+import android.util.Log;
import android.view.RemoteAnimationTarget;
import androidx.annotation.Nullable;
@@ -29,12 +31,17 @@ import com.android.quickstep.util.TransformParams;
import com.android.quickstep.views.DesktopTaskView;
import java.util.ArrayList;
+import java.util.Arrays;
/**
* Glues together the necessary components to animate a remote target using a
* {@link TaskViewSimulator}
*/
public class RemoteTargetGluer {
+ private static final String TAG = "RemoteTargetGluer";
+
+ private static final int DEFAULT_NUM_HANDLES = 2;
+
private RemoteTargetHandle[] mRemoteTargetHandles;
private SplitBounds mSplitBounds;
@@ -52,17 +59,19 @@ public class RemoteTargetGluer {
*/
public RemoteTargetGluer(Context context, BaseActivityInterface sizingStrategy) {
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
- // TODO: binder call, only for prototyping. Creating the gluer should be postponed so
- // we can create it when we have the remote animation targets ready.
- int desktopTasks = SystemUiProxy.INSTANCE.get(context).getVisibleDesktopTaskCount();
+ // TODO(279931899): binder call, only for prototyping. Creating the gluer should be
+ // postponed so we can create it when we have the remote animation targets ready.
+ int desktopTasks = SystemUiProxy.INSTANCE.get(context).getVisibleDesktopTaskCount(
+ context.getDisplayId());
if (desktopTasks > 0) {
init(context, sizingStrategy, desktopTasks, true /* forDesktop */);
return;
}
}
- int[] splitIds = TopTaskTracker.INSTANCE.get(context).getRunningSplitTaskIds();
- init(context, sizingStrategy, splitIds.length == 2 ? 2 : 1, false /* forDesktop */);
+ // Assume 2 handles needed for split, scale down as needed later on when we actually
+ // get remote targets
+ init(context, sizingStrategy, DEFAULT_NUM_HANDLES, false /* forDesktop */);
}
private void init(Context context, BaseActivityInterface sizingStrategy, int numHandles,
@@ -107,7 +116,29 @@ public class RemoteTargetGluer {
* the left/top task, index 1 right/bottom.
*/
public RemoteTargetHandle[] assignTargetsForSplitScreen(RemoteAnimationTargets targets) {
+ // Resize the mRemoteTargetHandles array since we started assuming split screen, but
+ // targets.apps is the ultimate source of truth here
+ long appCount = Arrays.stream(targets.apps)
+ .filter(app -> app.mode == targets.targetMode)
+ .count();
+ Log.d(TAG, "appCount: " + appCount + " handleLength: " + mRemoteTargetHandles.length);
+ if (appCount < mRemoteTargetHandles.length) {
+ Log.d(TAG, "resizing handles");
+ RemoteTargetHandle[] newHandles = new RemoteTargetHandle[(int) appCount];
+ System.arraycopy(mRemoteTargetHandles, 0/*src*/, newHandles, 0/*dst*/, (int) appCount);
+ mRemoteTargetHandles = newHandles;
+ }
+
+ boolean containsSplitTargets = Arrays.stream(targets.apps)
+ .anyMatch(remoteAnimationTarget ->
+ remoteAnimationTarget.windowConfiguration.getWindowingMode()
+ == WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
+ Log.d(TAG, "containsSplitTargets? " + containsSplitTargets + " handleLength: " +
+ mRemoteTargetHandles.length + " appsLength: " + targets.apps.length);
+
if (mRemoteTargetHandles.length == 1) {
+ // Single fullscreen app
+
// If we're not in split screen, the splitIds count doesn't really matter since we
// should always hit this case.
mRemoteTargetHandles[0].mTransformParams.setTargetSet(targets);
@@ -115,13 +146,29 @@ public class RemoteTargetGluer {
// Unclear why/when target.apps length == 0, but it sure does happen :(
mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(targets.apps[0], null);
}
+ } else if (!containsSplitTargets) {
+ // Single App + Assistant
+ for (int i = 0; i < mRemoteTargetHandles.length; i++) {
+ mRemoteTargetHandles[i].mTransformParams.setTargetSet(targets);
+ mRemoteTargetHandles[i].mTaskViewSimulator.setPreview(targets.apps[i], null);
+ }
} else {
- RemoteAnimationTarget topLeftTarget = targets.apps[0];
+ // Split apps (+ maybe assistant)
+ RemoteAnimationTarget topLeftTarget = Arrays.stream(targets.apps)
+ .filter(remoteAnimationTarget ->
+ remoteAnimationTarget.windowConfiguration.getWindowingMode()
+ == WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW)
+ .findFirst().get();
// Fetch the adjacent target for split screen.
RemoteAnimationTarget bottomRightTarget = null;
- for (int i = 1; i < targets.apps.length; i++) {
+ for (int i = 0; i < targets.apps.length; i++) {
final RemoteAnimationTarget target = targets.apps[i];
+ if (target.windowConfiguration.getWindowingMode() !=
+ WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW ||
+ target == topLeftTarget) {
+ continue;
+ }
Rect topLeftBounds = getStartBounds(topLeftTarget);
Rect bounds = getStartBounds(target);
if (topLeftBounds.left > bounds.right || topLeftBounds.top > bounds.bottom) {
@@ -232,6 +279,14 @@ public class RemoteTargetGluer {
targets.targetMode);
}
+ /**
+ * The object returned by this is may be modified in
+ * {@link #assignTargetsForSplitScreen(RemoteAnimationTargets)}, specifically the length of the
+ * array may be shortened based on the number of RemoteAnimationTargets present.
+ *
+ * This can be accessed at any time, however the count will be more accurate if accessed after
+ * calling one of the respective assignTargets*() methods
+ */
public RemoteTargetHandle[] getRemoteTargetHandles() {
return mRemoteTargetHandles;
}
diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
index f5db2444f8..13e999928d 100644
--- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -18,6 +18,7 @@ package com.android.quickstep;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Surface.ROTATION_0;
+import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
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_NAVIGATION_MODE;
@@ -254,8 +255,8 @@ public class RotationTouchHelper implements DisplayInfoChangeListener {
* @return whether the coordinates of the {@param event} is in the swipe up
* gesture region.
*/
- public boolean isInSwipeUpTouchRegion(MotionEvent event) {
- return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY());
+ public boolean isInSwipeUpTouchRegion(MotionEvent event, BaseActivityInterface activity) {
+ return isInSwipeUpTouchRegion(event, 0, activity);
}
/**
@@ -263,7 +264,11 @@ public class RotationTouchHelper implements DisplayInfoChangeListener {
* {@param pointerIndex}
* is in the swipe up gesture region.
*/
- public boolean isInSwipeUpTouchRegion(MotionEvent event, int pointerIndex) {
+ public boolean isInSwipeUpTouchRegion(MotionEvent event, int pointerIndex,
+ BaseActivityInterface activity) {
+ if (isTrackpadMultiFingerSwipe(event)) {
+ return true;
+ }
return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(pointerIndex),
event.getY(pointerIndex));
}
@@ -371,7 +376,8 @@ public class RotationTouchHelper implements DisplayInfoChangeListener {
enableMultipleRegions(true);
}
activityInterface.onExitOverview(this, mExitOverviewRunnable);
- } else if (endTarget == GestureState.GestureEndTarget.HOME) {
+ } else if (endTarget == GestureState.GestureEndTarget.HOME
+ || endTarget == GestureState.GestureEndTarget.ALL_APPS) {
enableMultipleRegions(false);
} else if (endTarget == GestureState.GestureEndTarget.NEW_TASK) {
if (mOrientationTouchTransformer.getQuickStepStartingRotation() == -1) {
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index f913aff167..25ac47a45f 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -67,7 +67,7 @@ public abstract class SwipeUpAnimationLogic implements
// 0 => preview snapShot is completely visible, and hotseat is completely translated down
// 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
// visible.
- protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
+ protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::onCurrentShiftUpdated);
protected float mCurrentDisplacement;
// The distance needed to drag to reach the task size in recents.
@@ -82,7 +82,8 @@ public abstract class SwipeUpAnimationLogic implements
mContext = context;
mDeviceState = deviceState;
mGestureState = gestureState;
- mIsSwipeForSplit = TopTaskTracker.INSTANCE.get(context).getRunningSplitTaskIds().length > 1;
+ updateIsGestureForSplit(TopTaskTracker.INSTANCE.get(context)
+ .getRunningSplitTaskIds().length);
mTargetGluer = new RemoteTargetGluer(mContext, mGestureState.getActivityInterface());
mRemoteTargetHandles = mTargetGluer.getRemoteTargetHandles();
@@ -148,7 +149,7 @@ public abstract class SwipeUpAnimationLogic implements
* Called when the value of {@link #mCurrentShift} changes
*/
@UiThread
- public abstract void updateFinalShift();
+ public abstract void onCurrentShiftUpdated();
protected PagedOrientationHandler getOrientationHandler() {
// OrientationHandler should be independent of remote target, can directly take one
@@ -280,6 +281,10 @@ public abstract class SwipeUpAnimationLogic implements
return out;
}
+ protected void updateIsGestureForSplit(int targetCount) {
+ mIsSwipeForSplit = targetCount > 1;
+ }
+
private RectFSpringAnim getWindowAnimationToHomeInternal(
HomeAnimationFactory homeAnimationFactory, RectF targetRect,
TransformParams transformParams, TaskViewSimulator taskViewSimulator,
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index b2ee376a75..3e2e660c5b 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -21,6 +21,7 @@ import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.app.ActivityManager;
+import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.app.PictureInPictureParams;
import android.content.ComponentName;
@@ -38,29 +39,42 @@ import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
+import android.view.IRecentsAnimationController;
+import android.view.IRecentsAnimationRunner;
+import android.view.IRemoteAnimationRunner;
import android.view.MotionEvent;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.IOnBackInvokedCallback;
import android.window.RemoteTransition;
+import android.window.TaskSnapshot;
import android.window.TransitionFilter;
+import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.android.internal.logging.InstanceId;
import com.android.internal.util.ScreenshotRequest;
+import com.android.internal.view.AppearanceRegion;
import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.systemui.shared.recents.ISystemUiProxy;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
+import com.android.systemui.shared.system.RecentsAnimationListener;
import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController;
import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController;
import com.android.systemui.shared.system.smartspace.SmartspaceState;
import com.android.systemui.unfold.progress.IUnfoldAnimation;
import com.android.systemui.unfold.progress.IUnfoldTransitionListener;
import com.android.wm.shell.back.IBackAnimation;
+import com.android.wm.shell.bubbles.IBubbles;
+import com.android.wm.shell.bubbles.IBubblesListener;
import com.android.wm.shell.desktopmode.IDesktopMode;
+import com.android.wm.shell.draganddrop.IDragAndDrop;
import com.android.wm.shell.onehanded.IOneHanded;
import com.android.wm.shell.pip.IPip;
import com.android.wm.shell.pip.IPipAnimationListener;
@@ -91,6 +105,7 @@ public class SystemUiProxy implements ISystemUiProxy {
private ISystemUiProxy mSystemUiProxy;
private IPip mPip;
+ private IBubbles mBubbles;
private ISysuiUnlockAnimationController mSysuiUnlockAnimationController;
private ISplitScreen mSplitScreen;
private IOneHanded mOneHanded;
@@ -112,13 +127,17 @@ public class SystemUiProxy implements ISystemUiProxy {
// indefinitely
// in case SysUI needs to rebind.
private IPipAnimationListener mPipAnimationListener;
+ private IBubblesListener mBubblesListener;
private ISplitScreenListener mSplitScreenListener;
private IStartingWindowListener mStartingWindowListener;
private ILauncherUnlockAnimationController mLauncherUnlockAnimationController;
private IRecentTasksListener mRecentTasksListener;
private IUnfoldTransitionListener mUnfoldAnimationListener;
private final LinkedHashMap mRemoteTransitions = new LinkedHashMap<>();
+ private IBinder mOriginalTransactionToken = null;
private IOnBackInvokedCallback mBackToLauncherCallback;
+ private IRemoteAnimationRunner mBackToLauncherRunner;
+ private IDragAndDrop mDragAndDrop;
// Used to dedupe calls to SystemUI
private int mLastShelfHeight;
@@ -134,9 +153,22 @@ public class SystemUiProxy implements ISystemUiProxy {
// TODO(141886704): Find a way to remove this
private int mLastSystemUiStateFlags;
+ /**
+ * This is a singleton pending intent that is used to start recents via Shell
+ * (which is a
+ * different process). It is bare-bones, so it's expected that the component and
+ * options will
+ * be provided via fill-in intent.
+ */
+ private final PendingIntent mRecentsPendingIntent;
+
public SystemUiProxy(Context context) {
mContext = context;
mAsyncHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleMessageAsync);
+ final Intent baseIntent = new Intent().setPackage(mContext.getPackageName());
+ mRecentsPendingIntent = PendingIntent.getActivity(mContext, 0, baseIntent,
+ PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT
+ | Intent.FILL_IN_COMPONENT);
}
@Override
@@ -178,15 +210,22 @@ public class SystemUiProxy implements ISystemUiProxy {
return null;
}
- public void setProxy(ISystemUiProxy proxy, IPip pip, ISplitScreen splitScreen,
+ /**
+ * Sets proxy state, including death linkage, various listeners, and other
+ * configuration objects
+ */
+ @MainThread
+ public void setProxy(ISystemUiProxy proxy, IPip pip, IBubbles bubbles, ISplitScreen splitScreen,
IOneHanded oneHanded, IShellTransitions shellTransitions,
IStartingWindow startingWindow, IRecentTasks recentTasks,
ISysuiUnlockAnimationController sysuiUnlockAnimationController,
IBackAnimation backAnimation, IDesktopMode desktopMode,
- IUnfoldAnimation unfoldAnimation) {
+ IUnfoldAnimation unfoldAnimation, IDragAndDrop dragAndDrop) {
+ Preconditions.assertUIThread();
unlinkToDeath();
mSystemUiProxy = proxy;
mPip = pip;
+ mBubbles = bubbles;
mSplitScreen = splitScreen;
mOneHanded = oneHanded;
mShellTransitions = shellTransitions;
@@ -196,35 +235,29 @@ public class SystemUiProxy implements ISystemUiProxy {
mBackAnimation = backAnimation;
mDesktopMode = desktopMode;
mUnfoldAnimation = unfoldAnimation;
+ mDragAndDrop = dragAndDrop;
linkToDeath();
// re-attach the listeners once missing due to setProxy has not been initialized
// yet.
- if (mPipAnimationListener != null && mPip != null) {
- setPipAnimationListener(mPipAnimationListener);
- }
- if (mSplitScreenListener != null && mSplitScreen != null) {
- registerSplitScreenListener(mSplitScreenListener);
- }
- if (mStartingWindowListener != null && mStartingWindow != null) {
- setStartingWindowListener(mStartingWindowListener);
- }
- if (mSysuiUnlockAnimationController != null && mLauncherUnlockAnimationController != null) {
- setLauncherUnlockAnimationController(mLauncherUnlockAnimationController);
- }
+ setPipAnimationListener(mPipAnimationListener);
+ setBubblesListener(mBubblesListener);
+ registerSplitScreenListener(mSplitScreenListener);
+ setStartingWindowListener(mStartingWindowListener);
+ setLauncherUnlockAnimationController(mLauncherUnlockAnimationController);
new LinkedHashMap<>(mRemoteTransitions).forEach(this::registerRemoteTransition);
- if (mRecentTasksListener != null && mRecentTasks != null) {
- registerRecentTasksListener(mRecentTasksListener);
- }
- if (mBackAnimation != null && mBackToLauncherCallback != null) {
- setBackToLauncherCallback(mBackToLauncherCallback);
- }
- if (unfoldAnimation != null && mUnfoldAnimationListener != null) {
- setUnfoldAnimationListener(mUnfoldAnimationListener);
- }
+ setupTransactionQueue();
+ registerRecentTasksListener(mRecentTasksListener);
+ setBackToLauncherCallback(mBackToLauncherCallback, mBackToLauncherRunner);
+ setUnfoldAnimationListener(mUnfoldAnimationListener);
}
+ /**
+ * Clear the proxy to release held resources and turn the majority of its
+ * operations into no-ops
+ */
+ @MainThread
public void clearProxy() {
- setProxy(null, null, null, null, null, null, null, null, null, null, null);
+ setProxy(null, null, null, null, null, null, null, null, null, null, null, null, null);
}
// TODO(141886704): Find a way to remove this
@@ -273,6 +306,11 @@ public class SystemUiProxy implements ISystemUiProxy {
onOverviewShown(fromHome, TAG);
}
+ @Override
+ public void onStatusBarTouchEvent(MotionEvent event) throws RemoteException {
+
+ }
+
public void onOverviewShown(boolean fromHome, String tag) {
if (mSystemUiProxy != null) {
try {
@@ -283,15 +321,16 @@ public class SystemUiProxy implements ISystemUiProxy {
}
}
- @Override
+ @MainThread
public void onStatusBarMotionEvent(MotionEvent event) {
- if (mSystemUiProxy != null) {
- try {
- mSystemUiProxy.onStatusBarMotionEvent(event);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call onStatusBarMotionEvent", e);
- }
- }
+// Preconditions.assertUIThread();
+// if (mSystemUiProxy != null) {
+// try {
+// mSystemUiProxy.onStatusBarMotionEvent(event);
+// } catch (RemoteException e) {
+// Log.w(TAG, "Failed call onStatusBarMotionEvent", e);
+// }
+// }
}
@Override
@@ -327,6 +366,10 @@ public class SystemUiProxy implements ISystemUiProxy {
}
}
+ @Override
+ public void setAssistantOverridesRequested(int[] invocationTypes) throws RemoteException {
+
+ }
@Override
public void notifyAccessibilityButtonClicked(int displayId) {
@@ -372,16 +415,16 @@ public class SystemUiProxy implements ISystemUiProxy {
}
}
- public void setTaskbarEnabled(boolean enabled) {
- if (mSystemUiProxy != null) {
- try {
- mSystemUiProxy.setTaskbarEnabled(enabled);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call setTaskbarEnabled with arg: " +
- enabled, e);
- }
- }
- }
+// public void setTaskbarEnabled(boolean enabled) {
+// if (mSystemUiProxy != null) {
+// try {
+// mSystemUiProxy.setTaskbarEnabled(enabled);
+// } catch (RemoteException e) {
+// Log.w(TAG, "Failed call setTaskbarEnabled with arg: " +
+// enabled, e);
+// }
+// }
+// }
public void notifyTaskbarStatus(boolean visible, boolean stashed) {
if (mSystemUiProxy != null) {
@@ -423,6 +466,11 @@ public class SystemUiProxy implements ISystemUiProxy {
}
}
+ @Override
+ public void onStatusBarTrackpadEvent(MotionEvent event) throws RemoteException {
+
+ }
+
@Override
public void expandNotificationPanel() {
if (mSystemUiProxy != null) {
@@ -535,11 +583,11 @@ public class SystemUiProxy implements ISystemUiProxy {
}
/**
- * Notifies WM Shell that launcher has finished all the animation for swipe to
- * home. WM Shell
- * can choose to fade out the overlay when entering PIP is finished, and WM
- * Shell should be
- * responsible for cleaning up the overlay.
+ * Notifies WM Shell that launcher has finished the preparation of the animation
+ * for swipe to
+ * home. WM Shell can choose to fade out the overlay when entering PIP is
+ * finished, and WM Shell
+ * should be responsible for cleaning up the overlay.
*/
public void stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds,
SurfaceControl overlay) {
@@ -552,6 +600,21 @@ public class SystemUiProxy implements ISystemUiProxy {
}
}
+ /**
+ * Notifies WM Shell that launcher has aborted all the animation for swipe to
+ * home. WM Shell
+ * can use this callback to clean up its internal states.
+ */
+ public void abortSwipePipToHome(int taskId, ComponentName componentName) {
+ if (mPip != null) {
+ try {
+ mPip.abortSwipePipToHome(taskId, componentName);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call abortSwipePipToHome");
+ }
+ }
+ }
+
/**
* Sets the next pip animation type to be the alpha animation.
*/
@@ -578,6 +641,61 @@ public class SystemUiProxy implements ISystemUiProxy {
}
}
+ //
+ // Bubbles
+ //
+
+ /**
+ * Sets the listener to be notified of bubble state changes.
+ */
+ public void setBubblesListener(IBubblesListener listener) {
+ if (mBubbles != null) {
+ try {
+ if (mBubblesListener != null) {
+ // Clear out any previous listener
+ mBubbles.unregisterBubbleListener(mBubblesListener);
+ }
+ if (listener != null) {
+ mBubbles.registerBubbleListener(listener);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call registerBubblesListener");
+ }
+ }
+ mBubblesListener = listener;
+ }
+
+ /**
+ * Tells SysUI to show the bubble with the provided key.
+ *
+ * @param key the key of the bubble to show.
+ * @param onLauncherHome whether the bubble is showing on launcher home or not
+ * (modifies where
+ * the expanded bubble view is placed).
+ */
+ public void showBubble(String key, boolean onLauncherHome) {
+// if (mBubbles != null) {
+// try {
+// mBubbles.showBubble(key, onLauncherHome);
+// } catch (RemoteException e) {
+// Log.w(TAG, "Failed call showBubble");
+// }
+// }
+ }
+
+ /**
+ * Tells SysUI to collapse the bubbles.
+ */
+ public void collapseBubbles() {
+ if (mBubbles != null) {
+ try {
+ mBubbles.collapseBubbles();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call collapseBubbles");
+ }
+ }
+ }
+
//
// Splitscreen
//
@@ -618,12 +736,12 @@ public class SystemUiProxy implements ISystemUiProxy {
}
}
- public void startIntentAndTask(PendingIntent pendingIntent, Bundle options1, int taskId,
- Bundle options2, @SplitConfigurationOptions.StagePosition int splitPosition,
+ public void startIntentAndTask(PendingIntent pendingIntent, int userId1, Bundle options1,
+ int taskId, Bundle options2, @SplitConfigurationOptions.StagePosition int splitPosition,
float splitRatio, RemoteTransition remoteTransition, InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
- mSplitScreen.startIntentAndTask(pendingIntent, options1, taskId, options2,
+ mSplitScreen.startIntentAndTask(pendingIntent, userId1, options1, taskId, options2,
splitPosition, splitRatio, remoteTransition, instanceId);
} catch (RemoteException e) {
Log.w(TAG, "Failed call startIntentAndTask");
@@ -631,14 +749,16 @@ public class SystemUiProxy implements ISystemUiProxy {
}
}
- public void startIntents(PendingIntent pendingIntent1, Bundle options1,
- PendingIntent pendingIntent2, Bundle options2,
- @SplitConfigurationOptions.StagePosition int splitPosition,
- float splitRatio, RemoteTransition remoteTransition, InstanceId instanceId) {
+ public void startIntents(PendingIntent pendingIntent1, int userId1,
+ @Nullable ShortcutInfo shortcutInfo1, Bundle options1, PendingIntent pendingIntent2,
+ int userId2, @Nullable ShortcutInfo shortcutInfo2, Bundle options2,
+ @SplitConfigurationOptions.StagePosition int splitPosition, float splitRatio,
+ RemoteTransition remoteTransition, InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
- mSplitScreen.startIntents(pendingIntent1, options1, pendingIntent2, options2,
- splitPosition, splitRatio, remoteTransition, instanceId);
+ mSplitScreen.startIntents(pendingIntent1, userId1, shortcutInfo1, options1,
+ pendingIntent2, userId2, shortcutInfo2, options2, splitPosition, splitRatio,
+ remoteTransition, instanceId);
} catch (RemoteException e) {
Log.w(TAG, "Failed call startIntents");
}
@@ -674,14 +794,14 @@ public class SystemUiProxy implements ISystemUiProxy {
}
}
- public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
+ public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1,
Bundle options1, int taskId, Bundle options2,
@SplitConfigurationOptions.StagePosition int splitPosition, float splitRatio,
RemoteAnimationAdapter adapter, InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
- mSplitScreen.startIntentAndTaskWithLegacyTransition(pendingIntent, options1, taskId,
- options2, splitPosition, splitRatio, adapter, instanceId);
+ mSplitScreen.startIntentAndTaskWithLegacyTransition(pendingIntent, userId1,
+ options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId);
} catch (RemoteException e) {
Log.w(TAG, "Failed call startIntentAndTaskWithLegacyTransition");
}
@@ -706,16 +826,16 @@ public class SystemUiProxy implements ISystemUiProxy {
* transition. Passing a
* non-null shortcut info means to start the app as a shortcut.
*/
- public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1,
+ public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, int userId1,
@Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
- PendingIntent pendingIntent2, @Nullable ShortcutInfo shortcutInfo2,
+ PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
@Nullable Bundle options2, @SplitConfigurationOptions.StagePosition int sidePosition,
float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
- mSplitScreen.startIntentsWithLegacyTransition(pendingIntent1, shortcutInfo1,
- options1, pendingIntent2, shortcutInfo2, options2, sidePosition, splitRatio,
- adapter, instanceId);
+ mSplitScreen.startIntentsWithLegacyTransition(pendingIntent1, userId1,
+ shortcutInfo1, options1, pendingIntent2, userId2, shortcutInfo2, options2,
+ sidePosition, splitRatio, adapter, instanceId);
} catch (RemoteException e) {
Log.w(TAG, "Failed call startIntentsWithLegacyTransition");
}
@@ -734,11 +854,12 @@ public class SystemUiProxy implements ISystemUiProxy {
}
}
- public void startIntent(PendingIntent intent, Intent fillInIntent, int position,
+ public void startIntent(PendingIntent intent, int userId, Intent fillInIntent, int position,
Bundle options, InstanceId instanceId) {
if (mSplitScreen != null) {
try {
- mSplitScreen.startIntent(intent, fillInIntent, position, options, instanceId);
+ mSplitScreen.startIntent(intent, userId, fillInIntent, position, options,
+ instanceId);
} catch (RemoteException e) {
Log.w(TAG, "Failed call startIntent");
}
@@ -765,7 +886,7 @@ public class SystemUiProxy implements ISystemUiProxy {
*/
@Nullable
public RemoteAnimationTarget[] onGoingToRecentsLegacy(RemoteAnimationTarget[] apps) {
- if (mSplitScreen != null) {
+ if (!TaskAnimationManager.ENABLE_SHELL_TRANSITIONS && mSplitScreen != null) {
try {
return mSplitScreen.onGoingToRecentsLegacy(apps);
} catch (RemoteException e) {
@@ -840,6 +961,52 @@ public class SystemUiProxy implements ISystemUiProxy {
mRemoteTransitions.remove(remoteTransition);
}
+ /**
+ * Use SystemUI's transaction-queue instead of Launcher's independent one. This
+ * is necessary
+ * if Launcher and SystemUI need to coordinate transactions (eg. for shell
+ * transitions).
+ */
+ public void shareTransactionQueue() {
+ if (mOriginalTransactionToken == null) {
+ mOriginalTransactionToken = SurfaceControl.Transaction.getDefaultApplyToken();
+ }
+ setupTransactionQueue();
+ }
+
+ /**
+ * Switch back to using Launcher's independent transaction queue.
+ */
+ public void unshareTransactionQueue() {
+ if (mOriginalTransactionToken == null) {
+ return;
+ }
+ SurfaceControl.Transaction.setDefaultApplyToken(mOriginalTransactionToken);
+ mOriginalTransactionToken = null;
+ }
+
+ private void setupTransactionQueue() {
+ if (mOriginalTransactionToken == null) {
+ return;
+ }
+ if (mShellTransitions == null) {
+ SurfaceControl.Transaction.setDefaultApplyToken(mOriginalTransactionToken);
+ return;
+ }
+ final IBinder shellApplyToken;
+ try {
+ shellApplyToken = mShellTransitions.getShellApplyToken();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error getting Shell's apply token", e);
+ return;
+ }
+ if (shellApplyToken == null) {
+ Log.e(TAG, "Didn't receive apply token from Shell");
+ return;
+ }
+ SurfaceControl.Transaction.setDefaultApplyToken(shellApplyToken);
+ }
+
//
// Starting window
//
@@ -935,13 +1102,15 @@ public class SystemUiProxy implements ISystemUiProxy {
//
/** Sets the launcher {@link android.window.IOnBackInvokedCallback} to shell */
- public void setBackToLauncherCallback(IOnBackInvokedCallback callback) {
+ public void setBackToLauncherCallback(IOnBackInvokedCallback callback,
+ IRemoteAnimationRunner runner) {
mBackToLauncherCallback = callback;
- if (mBackAnimation == null) {
+ mBackToLauncherRunner = runner;
+ if (mBackAnimation == null || mBackToLauncherCallback == null) {
return;
}
try {
- mBackAnimation.setBackToLauncherCallback(callback);
+ mBackAnimation.setBackToLauncherCallback(callback, runner);
} catch (RemoteException e) {
Log.e(TAG, "Failed call setBackToLauncherCallback", e);
}
@@ -957,6 +1126,7 @@ public class SystemUiProxy implements ISystemUiProxy {
return;
}
mBackToLauncherCallback = null;
+ mBackToLauncherRunner = null;
if (mBackAnimation == null) {
return;
}
@@ -968,17 +1138,16 @@ public class SystemUiProxy implements ISystemUiProxy {
}
/**
- * Notifies shell that all back to launcher animations have finished (including
- * the transition
- * that plays after the gesture is committed and before the app is closed.
+ * Called when the status bar color needs to be customized when back navigation.
*/
- public void onBackToLauncherAnimationFinished() {
- if (mBackAnimation != null) {
- try {
- mBackAnimation.onBackToLauncherAnimationFinished();
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call onBackAnimationFinished", e);
- }
+ public void customizeStatusBarAppearance(AppearanceRegion appearance) {
+ if (mBackAnimation == null) {
+ return;
+ }
+ try {
+ mBackAnimation.customizeStatusBarAppearance(appearance);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed call useLauncherSysBarFlags", e);
}
}
@@ -1031,10 +1200,10 @@ public class SystemUiProxy implements ISystemUiProxy {
//
/** Call shell to show all apps active on the desktop */
- public void showDesktopApps() {
+ public void showDesktopApps(int displayId) {
if (mDesktopMode != null) {
try {
- mDesktopMode.showDesktopApps();
+ mDesktopMode.showDesktopApps(displayId);
} catch (RemoteException e) {
Log.w(TAG, "Failed call showDesktopApps", e);
}
@@ -1042,10 +1211,10 @@ public class SystemUiProxy implements ISystemUiProxy {
}
/** Call shell to get number of visible freeform tasks */
- public int getVisibleDesktopTaskCount() {
+ public int getVisibleDesktopTaskCount(int displayId) {
if (mDesktopMode != null) {
try {
- return mDesktopMode.getVisibleTaskCount();
+ return mDesktopMode.getVisibleTaskCount(displayId);
} catch (RemoteException e) {
Log.w(TAG, "Failed call getVisibleDesktopTaskCount", e);
}
@@ -1070,4 +1239,69 @@ public class SystemUiProxy implements ISystemUiProxy {
Log.e(TAG, "Failed call setUnfoldAnimationListener", e);
}
}
+
+ //
+ // Recents
+ //
+
+ /**
+ * Starts the recents activity. The caller should manage the thread on which
+ * this is called.
+ */
+ public boolean startRecentsActivity(Intent intent, ActivityOptions options,
+ RecentsAnimationListener listener) {
+ if (mRecentTasks == null) {
+ return false;
+ }
+ final IRecentsAnimationRunner runner = new IRecentsAnimationRunner.Stub() {
+ @Override
+ public void onAnimationStart(IRecentsAnimationController controller,
+ RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
+ Rect homeContentInsets, Rect minimizedHomeBounds) {
+ listener.onAnimationStart(new RecentsAnimationControllerCompat(controller), apps,
+ wallpapers, homeContentInsets, minimizedHomeBounds);
+ }
+
+ @Override
+ public void onAnimationCanceled(int[] taskIds, TaskSnapshot[] taskSnapshots) {
+ listener.onAnimationCanceled(
+ ThumbnailData.wrap(taskIds, taskSnapshots));
+ }
+
+ @Override
+ public void onTasksAppeared(RemoteAnimationTarget[] apps) {
+ listener.onTasksAppeared(apps);
+ }
+ };
+ final Bundle optsBundle = options.toBundle();
+ try {
+ mRecentTasks.startRecentsTransition(mRecentsPendingIntent, intent, optsBundle,
+ mContext.getIApplicationThread(), runner);
+ return true;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error starting recents via shell", e);
+ return false;
+ }
+ }
+
+ //
+ // Drag and drop
+ //
+
+ /**
+ * For testing purposes. Returns `true` only if the shell drop target has shown
+ * and
+ * drawn and is ready to handle drag events and the subsequent drop.
+ */
+ public boolean isDragAndDropReady() {
+ if (mDragAndDrop == null) {
+ return false;
+ }
+ try {
+ return mDragAndDrop.isReadyToHandleDrag();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error querying drag state", e);
+ return false;
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 23f43ee223..fc26bf329e 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -19,10 +19,11 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.launcher3.util.NavigationMode.NO_BUTTON;
+import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_INITIALIZED;
import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_STARTED;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.START_RECENTS_ANIMATION;
-import static com.android.systemui.shared.system.RemoteTransitionCompat.newRemoteTransition;
import android.app.ActivityManager;
import android.app.ActivityOptions;
@@ -31,13 +32,13 @@ import android.content.Intent;
import android.os.SystemProperties;
import android.util.Log;
import android.view.RemoteAnimationTarget;
-import android.window.RemoteTransition;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.DisplayController;
import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.views.DesktopTaskView;
@@ -50,8 +51,8 @@ import com.android.systemui.shared.system.TaskStackChangeListeners;
import java.util.HashMap;
public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener {
- public static final boolean ENABLE_SHELL_TRANSITIONS =
- SystemProperties.getBoolean("persist.wm.debug.shell_transit", false);
+ public static final boolean ENABLE_SHELL_TRANSITIONS = SystemProperties.getBoolean("persist.wm.debug.shell_transit",
+ true);
public static final boolean SHELL_TRANSITIONS_ROTATION = ENABLE_SHELL_TRANSITIONS
&& SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false);
@@ -89,6 +90,7 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
TaskAnimationManager(Context ctx) {
mCtx = ctx;
}
+
/**
* Preloads the recents animation.
*/
@@ -99,7 +101,8 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
}
/**
- * Starts a new recents animation for the activity with the given {@param intent}.
+ * Starts a new recents animation for the activity with the given
+ * {@param intent}.
*/
@UiThread
public RecentsAnimationCallbacks startRecentsAnimation(GestureState gestureState,
@@ -117,12 +120,15 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
}
}
// But force-finish it anyways
- finishRunningRecentsAnimation(false /* toHome */);
+ finishRunningRecentsAnimation(false /* toHome */, true /* forceFinish */);
if (mCallbacks != null) {
- // If mCallbacks still != null, that means we are getting this startRecentsAnimation()
- // before the previous one got onRecentsAnimationStart(). In that case, cleanup the
- // previous animation so it doesn't mess up/listen to state changes in this animation.
+ // If mCallbacks still != null, that means we are getting this
+ // startRecentsAnimation()
+ // before the previous one got onRecentsAnimationStart(). In that case, cleanup
+ // the
+ // previous animation so it doesn't mess up/listen to state changes in this
+ // animation.
cleanUpRecentsAnimation();
}
@@ -163,10 +169,15 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
for (RemoteAnimationTarget compat : appearedTaskTargets) {
if (compat.windowConfiguration.getActivityType() == ACTIVITY_TYPE_HOME
- && activityInterface.getCreatedActivity() instanceof RecentsActivity) {
- // When receive opening home activity while recents is running, enter home
- // and dismiss recents.
- ((RecentsActivity) activityInterface.getCreatedActivity()).startHome();
+ && activityInterface.getCreatedActivity() instanceof RecentsActivity
+ && DisplayController.getNavigationMode(mCtx) != NO_BUTTON) {
+ // The only time we get onTasksAppeared() in button navigation with a
+ // 3p launcher is if the user goes to overview first, and in this case we
+ // can immediately finish the transition
+ RecentsView recentsView = activityInterface.getCreatedActivity().getOverviewPanel();
+ if (recentsView != null) {
+ recentsView.finishRecentsAnimation(true, null);
+ }
return;
}
}
@@ -176,20 +187,24 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
if (nonAppTargets == null) {
nonAppTargets = new RemoteAnimationTarget[0];
}
- if (activityInterface.isInLiveTileMode()
+ if ((activityInterface.isInLiveTileMode()
+ || mLastGestureState.getEndTarget() == RECENTS)
&& activityInterface.getCreatedActivity() != null) {
- RecentsView recentsView =
- activityInterface.getCreatedActivity().getOverviewPanel();
+ RecentsView recentsView = activityInterface.getCreatedActivity().getOverviewPanel();
if (recentsView != null) {
+ ActiveGestureLog.INSTANCE.addLog("Launching side task id="
+ + appearedTaskTarget.taskId);
recentsView.launchSideTaskInLiveTileMode(appearedTaskTarget.taskId,
appearedTaskTargets,
new RemoteAnimationTarget[0] /* wallpaper */,
nonAppTargets /* nonApps */);
return;
+ } else {
+ ActiveGestureLog.INSTANCE.addLog("Unable to launch side task (no recents)");
}
} else if (nonAppTargets.length > 0) {
TaskViewUtils.createSplitAuxiliarySurfacesAnimator(nonAppTargets /* nonApps */,
- true /*shown*/, null /* animatorHandler */);
+ true /* shown */, null /* animatorHandler */);
}
if (mController != null) {
if (mLastAppearedTaskTarget == null
@@ -210,8 +225,7 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
// No need to switch since tile is already a screenshot.
onFinished.run();
} else {
- final RecentsView recentsView =
- activityInterface.getCreatedActivity().getOverviewPanel();
+ final RecentsView recentsView = activityInterface.getCreatedActivity().getOverviewPanel();
if (recentsView != null) {
recentsView.switchToScreenshot(onFinished);
} else {
@@ -226,12 +240,11 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
mCallbacks.addListener(listener);
if (ENABLE_SHELL_TRANSITIONS) {
- RemoteTransition transition = newRemoteTransition(mCallbacks,
- mController != null ? mController.getController() : null,
- mCtx.getIApplicationThread());
- final ActivityOptions options = ActivityOptions.makeRemoteTransition(transition);
- // Allowing to pause Home if Home is top activity and Recents is not Home. So when user
- // start home when recents animation is playing, the home activity can be resumed again
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ // Allowing to pause Home if Home is top activity and Recents is not Home. So
+ // when user
+ // start home when recents animation is playing, the home activity can be
+ // resumed again
// to let the transition controller collect Home activity.
CachedTaskInfo cti = gestureState.getRunningTask();
boolean homeIsOnTop = cti != null && cti.isHomeTask();
@@ -241,13 +254,17 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
homeIsOnTop = true;
}
}
+ if (activityInterface.allowAllAppsFromOverview()) {
+ homeIsOnTop = true;
+ }
if (!homeIsOnTop) {
options.setTransientLaunch();
}
- if(app.lawnchair.LawnchairApp.isAtleastT()){
+ if (app.lawnchair.LawnchairApp.isAtleastT()) {
options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_RECENTS_ANIMATION, eventTime);
}
UI_HELPER_EXECUTOR.execute(() -> mCtx.startActivity(intent, options.toBundle()));
+ SystemUiProxy.INSTANCE.getNoCreate().startRecentsActivity(intent, options, mCallbacks);
} else {
UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance()
.startRecentsActivity(intent, eventTime, mCallbacks, null, null));
@@ -298,20 +315,36 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
* Finishes the running recents animation.
*/
public void finishRunningRecentsAnimation(boolean toHome) {
+ finishRunningRecentsAnimation(toHome, false /* forceFinish */);
+ }
+
+ /**
+ * Finishes the running recents animation.
+ *
+ * @param forceFinish will synchronously finish the controller
+ */
+ private void finishRunningRecentsAnimation(boolean toHome, boolean forceFinish) {
if (mController != null) {
ActiveGestureLog.INSTANCE.addLog(
/* event= */ "finishRunningRecentsAnimation", toHome);
mCallbacks.notifyAnimationCanceled();
- Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), toHome
- ? mController::finishAnimationToHome
- : mController::finishAnimationToApp);
+ if (forceFinish) {
+ mController.finishController(toHome, null, false /* sendUserLeaveHint */,
+ true /* forceFinish */);
+ } else {
+ Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), toHome
+ ? mController::finishAnimationToHome
+ : mController::finishAnimationToApp);
+ }
cleanUpRecentsAnimation();
}
}
/**
- * Used to notify a listener of the current recents animation state (used if the listener was
- * not yet added to the callbacks at the point that the listener callbacks would have been
+ * Used to notify a listener of the current recents animation state (used if the
+ * listener was
+ * not yet added to the callbacks at the point that the listener callbacks would
+ * have been
* made).
*/
public void notifyRecentsAnimationState(
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 7aa358dfd0..3df0e9bfe4 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -28,7 +28,6 @@ import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Build;
import android.view.View;
-import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
@@ -42,6 +41,7 @@ import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.util.ResourceBasedOverride;
import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.Snackbar;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
@@ -136,7 +136,8 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
TaskShortcutFactory.PIN,
TaskShortcutFactory.INSTALL,
TaskShortcutFactory.FREE_FORM,
- TaskShortcutFactory.WELLBEING
+ TaskShortcutFactory.WELLBEING,
+ TaskShortcutFactory.SAVE_APP_PAIR
};
/**
@@ -279,10 +280,8 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
String message = activityContext.getStringCache() != null
? activityContext.getStringCache().disabledByAdminMessage
: mThumbnailView.getContext().getString(R.string.blocked_by_policy);
- Toast.makeText(
- mThumbnailView.getContext(),
- message,
- Toast.LENGTH_LONG).show();
+
+ Snackbar.show(BaseActivity.fromContext(mThumbnailView.getContext()), message, null);
}
/** Called when the snapshot has updated its full screen drawing parameters. */
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 24f9e8a86f..411325b519 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -17,6 +17,7 @@
package com.android.quickstep;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP;
@@ -41,6 +42,7 @@ import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
import com.android.launcher3.model.WellbeingModel;
import com.android.launcher3.popup.SystemShortcut;
@@ -65,7 +67,9 @@ import java.util.function.Function;
import java.util.stream.Collectors;
/**
- * Represents a system shortcut that can be shown for a recent task.
+ * Represents a system shortcut that can be shown for a recent task. Appears as
+ * a single entry in
+ * the dropdown menu that shows up when you tap an app icon in Overview.
*/
public interface TaskShortcutFactory {
@Nullable
@@ -125,6 +129,28 @@ public interface TaskShortcutFactory {
}
}
+ /**
+ * A menu item, "Save app pair", that allows the user to preserve the current
+ * app combination as
+ * a single persistent icon on the Home screen, allowing for quick split screen
+ * initialization.
+ */
+ class SaveAppPairSystemShortcut extends SystemShortcut {
+ private final TaskView mTaskView;
+
+ public SaveAppPairSystemShortcut(BaseDraggingActivity activity, TaskView taskView) {
+ super(R.drawable.ic_save_app_pair, R.string.save_app_pair, activity,
+ taskView.getItemInfo(), taskView);
+ mTaskView = taskView;
+ }
+
+ @Override
+ public void onClick(View view) {
+ ((RecentsView) mTarget.getOverviewPanel())
+ .getSplitSelectController().getAppPairsController().saveAppPair(mTaskView);
+ }
+ }
+
class FreeformSystemShortcut extends SystemShortcut {
private static final String TAG = "FreeformSystemShortcut";
@@ -257,25 +283,24 @@ public interface TaskShortcutFactory {
TaskIdAttributeContainer taskContainer) {
DeviceProfile deviceProfile = activity.getDeviceProfile();
final Task task = taskContainer.getTask();
+ final int intentFlags = task.key.baseIntent.getFlags();
final TaskView taskView = taskContainer.getTaskView();
final RecentsView recentsView = taskView.getRecentsView();
final PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler();
- int[] taskViewTaskIds = taskView.getTaskIds();
- boolean taskViewHasMultipleTasks = taskViewTaskIds[0] != -1 &&
- taskViewTaskIds[1] != -1;
boolean notEnoughTasksToSplit = recentsView.getTaskViewCount() < 2;
boolean isFocusedTask = deviceProfile.isTablet && taskView.isFocusedTask();
boolean isTaskInExpectedScrollPosition = recentsView
.isTaskInExpectedScrollPosition(recentsView.indexOfChild(taskView));
- boolean isTaskSplitNotSupported = !task.isDockable;
+ boolean isTaskSplitNotSupported = !task.isDockable ||
+ (intentFlags & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
boolean hideForExistingMultiWindow = activity.getDeviceProfile().isMultiWindowMode;
- if (taskViewHasMultipleTasks ||
- notEnoughTasksToSplit ||
- isTaskSplitNotSupported ||
- hideForExistingMultiWindow ||
- (isFocusedTask && isTaskInExpectedScrollPosition)) {
+ if (taskView.containsMultipleTasks()
+ || notEnoughTasksToSplit
+ || isTaskSplitNotSupported
+ || hideForExistingMultiWindow
+ || (isFocusedTask && isTaskInExpectedScrollPosition)) {
return null;
}
@@ -287,6 +312,26 @@ public interface TaskShortcutFactory {
}
};
+ TaskShortcutFactory SAVE_APP_PAIR = new TaskShortcutFactory() {
+ @Nullable
+ @Override
+ public List getShortcuts(BaseDraggingActivity activity,
+ TaskIdAttributeContainer taskContainer) {
+ final TaskView taskView = taskContainer.getTaskView();
+
+ if (!FeatureFlags.ENABLE_APP_PAIRS.get() || !taskView.containsMultipleTasks()) {
+ return null;
+ }
+
+ return Collections.singletonList(new SaveAppPairSystemShortcut(activity, taskView));
+ }
+
+ @Override
+ public boolean showForSplitscreen() {
+ return true;
+ }
+ };
+
TaskShortcutFactory FREE_FORM = new TaskShortcutFactory() {
@Override
public List getShortcuts(BaseDraggingActivity activity,
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index f0d0bdb0ed..1238819acd 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -243,7 +243,17 @@ public final class TaskViewUtils {
TOUCH_RESPONSE_INTERPOLATOR);
out.setFloat(tvsLocal.recentsViewScroll, AnimatedFloat.VALUE, 0,
TOUCH_RESPONSE_INTERPOLATOR);
-
+ out.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ final SurfaceTransaction showTransaction = new SurfaceTransaction();
+ for (int i = targets.apps.length - 1; i >= 0; --i) {
+ showTransaction.getTransaction().show(targets.apps[i].leash);
+ }
+ applier.scheduleApply(showTransaction);
+ }
+ });
out.addOnFrameCallback(() -> {
for (RemoteTargetHandle handle : remoteTargetHandles) {
handle.getTaskViewSimulator().apply(handle.getTransformParams());
@@ -463,16 +473,14 @@ public final class TaskViewUtils {
throw new IllegalStateException(
"Expected task to be showing, but it is " + mode);
}
- if (change.getParent() == null) {
- throw new IllegalStateException("Initiating multi-split launch but the split"
- + "root of " + taskId + " is already visible or has broken hierarchy.");
- }
}
if (taskId == initialTaskId) {
- splitRoot1 = transitionInfo.getChange(change.getParent());
+ splitRoot1 = change.getParent() == null ? change :
+ transitionInfo.getChange(change.getParent());
}
if (taskId == secondTaskId) {
- splitRoot2 = transitionInfo.getChange(change.getParent());
+ splitRoot2 = change.getParent() == null ? change :
+ transitionInfo.getChange(change.getParent());
}
}
diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java
index 6f502d0ece..d34cddf584 100644
--- a/quickstep/src/com/android/quickstep/TopTaskTracker.java
+++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java
@@ -18,6 +18,7 @@ package com.android.quickstep;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.content.Intent.ACTION_CHOOSER;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
@@ -127,23 +128,16 @@ public class TopTaskTracker extends ISplitScreenListener.Stub implements TaskSta
@Override
public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
- // If task is not visible but we are tracking it, stop tracking it
- if (!visible) {
+ // If a task is not visible anymore or has been moved to undefined, stop tracking it.
+ if (!visible || stage == SplitConfigurationOptions.STAGE_TYPE_UNDEFINED) {
if (mMainStagePosition.taskId == taskId) {
- resetTaskId(mMainStagePosition);
+ mMainStagePosition.taskId = INVALID_TASK_ID;
} else if (mSideStagePosition.taskId == taskId) {
- resetTaskId(mSideStagePosition);
+ mSideStagePosition.taskId = INVALID_TASK_ID;
} // else it's an un-tracked child
return;
}
- // If stage has moved to undefined, stop tracking the task
- if (stage == SplitConfigurationOptions.STAGE_TYPE_UNDEFINED) {
- resetTaskId(taskId == mMainStagePosition.taskId
- ? mMainStagePosition : mSideStagePosition);
- return;
- }
-
if (stage == SplitConfigurationOptions.STAGE_TYPE_MAIN) {
mMainStagePosition.taskId = taskId;
} else {
@@ -161,10 +155,6 @@ public class TopTaskTracker extends ISplitScreenListener.Stub implements TaskSta
mPinnedTaskId = INVALID_TASK_ID;
}
- private void resetTaskId(SplitStageInfo taskPosition) {
- taskPosition.taskId = -1;
- }
-
/**
* @return index 0 will be task in left/top position, index 1 in right/bottom position.
* Will return empty array if device is not in staged split
@@ -255,6 +245,11 @@ public class TopTaskTracker extends ISplitScreenListener.Stub implements TaskSta
.getActivityType() == ACTIVITY_TYPE_HOME;
}
+ public boolean isRecentsTask() {
+ return mTopTask != null && mTopTask.configuration.windowConfiguration
+ .getActivityType() == ACTIVITY_TYPE_RECENTS;
+ }
+
/**
* Returns {@code true} if this task windowing mode is set to {@link
* android.app.WindowConfiguration#WINDOWING_MODE_FREEFORM}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 0113dd40de..08288446af 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -18,13 +18,20 @@ package com.android.quickstep;
import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_POINTER_DOWN;
+import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
-import static com.android.launcher3.config.FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS;
+import static com.android.launcher3.Launcher.INTENT_ACTION_ALL_APPS_TOGGLE;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_TRACKPAD_GESTURE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.quickstep.GestureState.DEFAULT_STATE;
+import static com.android.quickstep.GestureState.TrackpadGestureType.getTrackpadGestureType;
+import static com.android.quickstep.InputConsumer.TYPE_CURSOR_HOVER;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_DOWN;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_MOVE;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_UP;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
@@ -35,7 +42,9 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_N
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BUBBLES;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DRAG_AND_DROP;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS;
@@ -56,6 +65,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import android.view.Choreographer;
@@ -69,8 +79,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
-import com.android.app.viewcapture.SettingsAwareViewCapture;
import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -82,6 +92,7 @@ import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.taskbar.TaskbarManager;
import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.shared.ResourceUtils;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.tracing.LauncherTraceProto;
import com.android.launcher3.tracing.TouchInteractionServiceProto;
@@ -100,8 +111,9 @@ import com.android.quickstep.inputconsumers.OverviewWithoutFocusInputConsumer;
import com.android.quickstep.inputconsumers.ProgressDelegateInputConsumer;
import com.android.quickstep.inputconsumers.ResetGestureInputConsumer;
import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer;
+import com.android.quickstep.inputconsumers.StatusBarInputConsumer;
import com.android.quickstep.inputconsumers.SysUiOverlayInputConsumer;
-import com.android.quickstep.inputconsumers.TaskbarStashInputConsumer;
+import com.android.quickstep.inputconsumers.TaskbarUnstashInputConsumer;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.ActiveGestureLog.CompoundString;
import com.android.quickstep.util.ProtoTracer;
@@ -113,11 +125,12 @@ import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController;
-import com.android.systemui.shared.testing.ResourceUtils;
import com.android.systemui.shared.tracing.ProtoTraceable;
import com.android.systemui.unfold.progress.IUnfoldAnimation;
import com.android.wm.shell.back.IBackAnimation;
+import com.android.wm.shell.bubbles.IBubbles;
import com.android.wm.shell.desktopmode.IDesktopMode;
+import com.android.wm.shell.draganddrop.IDragAndDrop;
import com.android.wm.shell.onehanded.IOneHanded;
import com.android.wm.shell.pip.IPip;
import com.android.wm.shell.recents.IRecentTasks;
@@ -127,8 +140,10 @@ import com.android.wm.shell.transition.IShellTransitions;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.LinkedList;
+import java.util.function.Consumer;
import java.util.function.Function;
import app.lawnchair.LawnchairApp;
@@ -147,21 +162,28 @@ public class TouchInteractionService extends Service
private static final String HAS_ENABLED_QUICKSTEP_ONCE = "launcher.has_enabled_quickstep_once";
- private final TISBinder mTISBinder = LawnchairApp.isRecentsEnabled() ? new TISBinder() : null;
+ private final TISBinder mTISBinder = LawnchairApp.isRecentsEnabled() ? new TISBinder(this) : null;
/**
* Local IOverviewProxy implementation with some methods for local components
*/
- public class TISBinder extends IOverviewProxy.Stub {
+ public static class TISBinder extends IOverviewProxy.Stub {
+
+ private final WeakReference mTis;
@Nullable
private Runnable mOnOverviewTargetChangeListener = null;
+ private TISBinder(TouchInteractionService tis) {
+ mTis = new WeakReference<>(tis);
+ }
+
@BinderThread
public void onInitialize(Bundle bundle) {
ISystemUiProxy proxy = ISystemUiProxy.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
IPip pip = IPip.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_PIP));
+ IBubbles bubbles = IBubbles.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_BUBBLES));
ISplitScreen splitscreen = ISplitScreen.Stub.asInterface(bundle.getBinder(
KEY_EXTRA_SHELL_SPLIT_SCREEN));
IOneHanded onehanded = IOneHanded.Stub.asInterface(
@@ -181,66 +203,94 @@ public class TouchInteractionService extends Service
bundle.getBinder(KEY_EXTRA_SHELL_DESKTOP_MODE));
IUnfoldAnimation unfoldTransition = IUnfoldAnimation.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER));
- MAIN_EXECUTOR.execute(() -> {
- SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy, pip,
- splitscreen, onehanded, shellTransitions, startingWindow,
+ IDragAndDrop dragAndDrop = IDragAndDrop.Stub.asInterface(
+ bundle.getBinder(KEY_EXTRA_SHELL_DRAG_AND_DROP));
+ MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
+ SystemUiProxy.INSTANCE.get(tis).setProxy(proxy, pip,
+ bubbles, splitscreen, onehanded, shellTransitions, startingWindow,
recentTasks, launcherUnlockAnimationController, backAnimation, desktopMode,
- unfoldTransition);
- TouchInteractionService.this.initInputMonitor("TISBinder#onInitialize()");
- preloadOverview(true /* fromInit */);
- });
+ unfoldTransition, dragAndDrop);
+ tis.initInputMonitor("TISBinder#onInitialize()");
+ tis.preloadOverview(true /* fromInit */);
+ }));
sIsInitialized = true;
}
+ @BinderThread
+ @Override
+ public void onTaskbarToggled() {
+ if (!FeatureFlags.ENABLE_KEYBOARD_TASKBAR_TOGGLE.get())
+ return;
+ MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
+ TaskbarActivityContext activityContext = tis.mTaskbarManager.getCurrentActivityContext();
+
+ if (activityContext != null) {
+ activityContext.toggleTaskbarStash();
+ }
+ }));
+ }
+
@BinderThread
public void onOverviewToggle() {
TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle");
- // If currently screen pinning, do not enter overview
- if (mDeviceState.isScreenPinningActive()) {
- return;
- }
- TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
- mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_TOGGLE);
+ executeForTouchInteractionService(tis -> {
+ // If currently screen pinning, do not enter overview
+ if (tis.mDeviceState.isScreenPinningActive()) {
+ return;
+ }
+ TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+ tis.mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_TOGGLE);
+ });
}
@BinderThread
@Override
public void onOverviewShown(boolean triggeredFromAltTab) {
- if (triggeredFromAltTab) {
- TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
- mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_KEYBOARD_INPUT);
- } else {
- mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_SHOW);
- }
+ executeForTouchInteractionService(tis -> {
+ if (triggeredFromAltTab) {
+ TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+ tis.mOverviewCommandHelper.addCommand(
+ OverviewCommandHelper.TYPE_KEYBOARD_INPUT);
+ } else {
+ tis.mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_SHOW);
+ }
+ });
}
@BinderThread
@Override
public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
- if (triggeredFromAltTab && !triggeredFromHomeKey) {
- // onOverviewShownFromAltTab hides the overview and ends at the target app
- mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_HIDE);
- }
+ executeForTouchInteractionService(tis -> {
+ if (triggeredFromAltTab && !triggeredFromHomeKey) {
+ // onOverviewShownFromAltTab hides the overview and ends at the target app
+ tis.mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_HIDE);
+ }
+ });
}
@BinderThread
@Override
public void onAssistantAvailable(boolean available, boolean longPressHomeEnabled) {
- MAIN_EXECUTOR.execute(() -> {
- mDeviceState.setAssistantAvailable(available);
- TouchInteractionService.this.onAssistantVisibilityChanged();
- executeForTaskbarManager(() -> mTaskbarManager
+ MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
+ tis.mDeviceState.setAssistantAvailable(available);
+ tis.onAssistantVisibilityChanged();
+ executeForTaskbarManager(taskbarManager -> taskbarManager
.onLongPressHomeEnabled(longPressHomeEnabled));
- });
+ }));
}
@BinderThread
@Override
public void onAssistantVisibilityChanged(float visibility) {
- MAIN_EXECUTOR.execute(() -> {
- mDeviceState.setAssistantVisibility(visibility);
- TouchInteractionService.this.onAssistantVisibilityChanged();
- });
+ MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
+ tis.mDeviceState.setAssistantVisibility(visibility);
+ tis.onAssistantVisibilityChanged();
+ }));
+ }
+
+ @Override
+ public void onAssistantOverrideInvoked(int invocationType) throws RemoteException {
+
}
@Override
@@ -250,32 +300,30 @@ public class TouchInteractionService extends Service
@BinderThread
public void onSystemUiStateChanged(int stateFlags) {
- MAIN_EXECUTOR.execute(() -> {
- int lastFlags = mDeviceState.getSystemUiStateFlags();
- mDeviceState.setSystemUiFlags(stateFlags);
- TouchInteractionService.this.onSystemUiFlagsChanged(lastFlags);
- });
+ MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
+ int lastFlags = tis.mDeviceState.getSystemUiStateFlags();
+ tis.mDeviceState.setSystemUiFlags(stateFlags);
+ tis.onSystemUiFlagsChanged(lastFlags);
+ }));
}
@BinderThread
public void onActiveNavBarRegionChanges(Region region) {
- MAIN_EXECUTOR.execute(() -> mDeviceState.setDeferredGestureRegion(region));
+ MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
+ tis -> tis.mDeviceState.setDeferredGestureRegion(region)));
}
@BinderThread
- @Override
public void onScreenTurnedOn() {
MAIN_EXECUTOR.execute(ProxyScreenStatusProvider.INSTANCE::onScreenTurnedOn);
}
@BinderThread
- @Override
public void onScreenTurningOn() {
MAIN_EXECUTOR.execute(ProxyScreenStatusProvider.INSTANCE::onScreenTurningOn);
}
@BinderThread
- @Override
public void onScreenTurningOff() {
MAIN_EXECUTOR.execute(ProxyScreenStatusProvider.INSTANCE::onScreenTurningOff);
}
@@ -283,75 +331,109 @@ public class TouchInteractionService extends Service
@BinderThread
@Override
public void enterStageSplitFromRunningApp(boolean leftOrTop) {
- StatefulActivity activity = mOverviewComponentObserver.getActivityInterface().getCreatedActivity();
- if (activity != null) {
- activity.enterStageSplitFromRunningApp(leftOrTop);
- }
+ executeForTouchInteractionService(tis -> {
+ StatefulActivity activity = tis.mOverviewComponentObserver.getActivityInterface().getCreatedActivity();
+ if (activity != null) {
+ activity.enterStageSplitFromRunningApp(leftOrTop);
+ }
+ });
}
/**
* Preloads the Overview activity.
- *
+ *
* This method should only be used when the All Set page of the SUW is reached
* to safely
* preload the Launcher for the SUW first reveal.
*/
public void preloadOverviewForSUWAllSet() {
- preloadOverview(false, true);
+ executeForTouchInteractionService(tis -> tis.preloadOverview(false, true));
}
@Override
public void onRotationProposal(int rotation, boolean isValid) {
- executeForTaskbarManager(() -> mTaskbarManager.onRotationProposal(rotation, isValid));
+ executeForTaskbarManager(taskbarManager -> taskbarManager.onRotationProposal(rotation, isValid));
}
@Override
public void disable(int displayId, int state1, int state2, boolean animate) {
- executeForTaskbarManager(() -> mTaskbarManager
- .disableNavBarElements(displayId, state1, state2, animate));
+ executeForTaskbarManager(
+ taskbarManager -> taskbarManager.disableNavBarElements(displayId, state1, state2, animate));
}
@Override
public void onSystemBarAttributesChanged(int displayId, int behavior) {
- executeForTaskbarManager(() -> mTaskbarManager
- .onSystemBarAttributesChanged(displayId, behavior));
+ executeForTaskbarManager(
+ taskbarManager -> taskbarManager.onSystemBarAttributesChanged(displayId, behavior));
}
@Override
public void onNavButtonsDarkIntensityChanged(float darkIntensity) {
- executeForTaskbarManager(() -> mTaskbarManager
- .onNavButtonsDarkIntensityChanged(darkIntensity));
+ executeForTaskbarManager(taskbarManager -> taskbarManager.onNavButtonsDarkIntensityChanged(darkIntensity));
}
- private void executeForTaskbarManager(final Runnable r) {
- MAIN_EXECUTOR.execute(() -> {
- if (mTaskbarManager == null) {
+ private void executeForTouchInteractionService(
+ @NonNull Consumer tisConsumer) {
+ TouchInteractionService tis = mTis.get();
+ if (tis == null)
+ return;
+ tisConsumer.accept(tis);
+ }
+
+ private void executeForTaskbarManager(
+ @NonNull Consumer taskbarManagerConsumer) {
+ MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
+ TaskbarManager taskbarManager = tis.mTaskbarManager;
+ if (taskbarManager == null)
return;
- }
- r.run();
- });
+ taskbarManagerConsumer.accept(taskbarManager);
+ }));
}
+ /**
+ * Returns the {@link TaskbarManager}.
+ *