() {
- @Override
- public void onStateTransitionStart(LauncherState toState) {
- if (toState != mLauncherState) {
- // Treat FLAG_LAUNCHER_IN_STATE_TRANSITION as a changed flag even if a
- // previous state transition was already running, so we update the new
- // target.
- mPrevState &= ~FLAG_LAUNCHER_IN_STATE_TRANSITION;
- mLauncherState = toState;
- }
- updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, true);
- if (!mShouldDelayLauncherStateAnim) {
- if (toState == LauncherState.NORMAL) {
- applyState(QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION);
- } else {
- applyState();
- }
- }
- }
-
- @Override
- public void onStateTransitionComplete(LauncherState finalState) {
- mLauncherState = finalState;
- updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, false);
- // TODO(b/279514548) Cleans up bad state that can occur when user interacts with
- // taskbar on top of transparent activity.
- if (finalState == LauncherState.NORMAL && mLauncher.isResumed()) {
- updateStateForFlag(FLAG_RESUMED, true);
- }
+ @Override
+ public void onStateTransitionStart(LauncherState toState) {
+ if (toState != mLauncherState) {
+ // Treat FLAG_LAUNCHER_IN_STATE_TRANSITION as a changed flag even if a
+ // previous state transition was already running, so we update the new
+ // target.
+ mPrevState &= ~FLAG_LAUNCHER_IN_STATE_TRANSITION;
+ mLauncherState = toState;
+ }
+ updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, true);
+ if (!mShouldDelayLauncherStateAnim) {
+ if (toState == LauncherState.NORMAL) {
+ applyState(QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION);
+ } else {
applyState();
- boolean disallowLongClick = finalState == LauncherState.OVERVIEW_SPLIT_SELECT;
- com.android.launcher3.taskbar.Utilities.setOverviewDragState(
- mControllers, finalState.disallowTaskbarGlobalDrag(),
- disallowLongClick, finalState.allowTaskbarInitialSplitSelection());
}
- };
+ }
+ }
- /** Initializes the controller instance, and applies the initial state immediately. */
+ @Override
+ public void onStateTransitionComplete(LauncherState finalState) {
+ mLauncherState = finalState;
+ updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, false);
+ // TODO(b/279514548) Cleans up bad state that can occur when user interacts with
+ // taskbar on top of transparent activity.
+ if (finalState == LauncherState.NORMAL && mLauncher.hasBeenResumed()) {
+ updateStateForFlag(FLAG_RESUMED, true);
+ }
+ applyState();
+ boolean disallowLongClick = finalState == LauncherState.OVERVIEW_SPLIT_SELECT;
+ com.android.launcher3.taskbar.Utilities.setOverviewDragState(
+ mControllers, finalState.disallowTaskbarGlobalDrag(),
+ disallowLongClick, finalState.allowTaskbarInitialSplitSelection());
+ }
+ };
+
+ /**
+ * Initializes the controller instance, and applies the initial state
+ * immediately.
+ */
public void init(TaskbarControllers controllers, QuickstepLauncher launcher,
int sysuiStateFlags) {
mCanSyncViews = false;
@@ -236,7 +250,7 @@ public class TaskbarLauncherStateController {
mLauncher.getStateManager().addStateListener(mStateListener);
mLauncherState = launcher.getStateManager().getState();
- updateStateForSysuiFlags(sysuiStateFlags, /*applyState*/ false);
+ updateStateForSysuiFlags(sysuiStateFlags, /* applyState */ false);
applyState(0);
@@ -249,7 +263,6 @@ public class TaskbarLauncherStateController {
mIconAlignment.finishAnimation();
- Log.d("b/260135164", "onDestroy - updateIconAlphaForHome(1)");
mLauncher.getHotseat().setIconsAlpha(1f);
mLauncher.getStateManager().removeStateListener(mStateListener);
@@ -260,7 +273,8 @@ public class TaskbarLauncherStateController {
/**
* Creates a transition animation to the launcher activity.
*
- * Warning: the resulting animation must be played, since this method has side effects on this
+ * Warning: the resulting animation must be played, since this method has side
+ * effects on this
* controller's state.
*/
public Animator createAnimToLauncher(@NonNull LauncherState toState,
@@ -269,7 +283,8 @@ public class TaskbarLauncherStateController {
// If going home, align the icons to hotseat
AnimatorSet animatorSet = new AnimatorSet();
- // Update stashed flags first to ensure goingToUnstashedLauncherState() returns correctly.
+ // Update stashed flags first to ensure goingToUnstashedLauncherState() returns
+ // correctly.
TaskbarStashController stashController = mControllers.taskbarStashController;
stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE,
toState.isTaskbarStashed(mLauncher));
@@ -288,8 +303,8 @@ public class TaskbarLauncherStateController {
}
mTaskBarRecentsAnimationListener = new TaskBarRecentsAnimationListener(callbacks);
callbacks.addListener(mTaskBarRecentsAnimationListener);
- ((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(() ->
- mTaskBarRecentsAnimationListener.endGestureStateOverride(true));
+ ((RecentsView) mLauncher.getOverviewPanel())
+ .setTaskLaunchListener(() -> mTaskBarRecentsAnimationListener.endGestureStateOverride(true));
return animatorSet;
}
@@ -299,7 +314,8 @@ public class TaskbarLauncherStateController {
public void setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim) {
if (!shouldDelayLauncherStateAnim && mShouldDelayLauncherStateAnim) {
- // Animate the animation we have delayed immediately. This is usually triggered when
+ // Animate the animation we have delayed immediately. This is usually triggered
+ // when
// the user has released their finger.
applyState();
}
@@ -311,7 +327,7 @@ public class TaskbarLauncherStateController {
updateStateForSysuiFlags(systemUiStateFlags, /* applyState */ true);
}
- private void updateStateForSysuiFlags(int systemUiStateFlags, boolean applyState) {
+ private void updateStateForSysuiFlags(int systemUiStateFlags, boolean applyState) {
final boolean prevIsAwake = hasAnyFlag(FLAG_AWAKE);
final boolean currIsAwake = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_AWAKE);
@@ -326,10 +342,14 @@ public class TaskbarLauncherStateController {
boolean isDeviceLocked = hasAnyFlag(systemUiStateFlags, MASK_ANY_SYSUI_LOCKED);
updateStateForFlag(FLAG_DEVICE_LOCKED, isDeviceLocked);
- // Taskbar is hidden whenever the device is dreaming. The dreaming state includes the
- // interactive dreams, AoD, screen off. Since the SYSUI_STATE_DEVICE_DREAMING only kicks in
- // when the device is asleep, the second condition extends ensures that the transition from
- // and to the WAKEFULNESS_ASLEEP state also hide the taskbar, and improves the taskbar
+ // Taskbar is hidden whenever the device is dreaming. The dreaming state
+ // includes the
+ // interactive dreams, AoD, screen off. Since the SYSUI_STATE_DEVICE_DREAMING
+ // only kicks in
+ // when the device is asleep, the second condition extends ensures that the
+ // transition from
+ // and to the WAKEFULNESS_ASLEEP state also hide the taskbar, and improves the
+ // taskbar
// hide/reveal animation timings.
boolean isTaskbarHidden = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_DEVICE_DREAMING)
|| (systemUiStateFlags & SYSUI_STATE_WAKEFULNESS_MASK) != WAKEFULNESS_AWAKE;
@@ -343,7 +363,8 @@ public class TaskbarLauncherStateController {
/**
* Updates the proper flag to change the state of the task bar.
*
- * Note that this only updates the flag. {@link #applyState()} needs to be called separately.
+ * Note that this only updates the flag. {@link #applyState()} needs to be
+ * called separately.
*
* @param flag The flag to update.
* @param enabled Whether to enable the flag
@@ -409,6 +430,14 @@ public class TaskbarLauncherStateController {
+ ", mLauncherState: " + mLauncherState
+ ", toAlignment: " + toAlignment);
}
+ mControllers.bubbleControllers.ifPresent(controllers -> {
+ // Show the bubble bar when on launcher home or in overview.
+ boolean onHome = isInLauncher && mLauncherState == LauncherState.NORMAL;
+ boolean onOverview = mLauncherState == LauncherState.OVERVIEW;
+ controllers.bubbleStashController.setBubblesShowingOnHome(onHome);
+ controllers.bubbleStashController.setBubblesShowingOnOverview(onOverview);
+ });
+
AnimatorSet animatorSet = new AnimatorSet();
if (hasAnyFlag(changedFlags, FLAG_LAUNCHER_IN_STATE_TRANSITION)) {
@@ -426,7 +455,8 @@ public class TaskbarLauncherStateController {
handleOpenFloatingViews = true;
}
if (mLauncherState == LauncherState.OVERVIEW) {
- // Calling to update the insets in TaskbarInsetController#updateInsetsTouchability
+ // Calling to update the insets in
+ // TaskbarInsetController#updateInsetsTouchability
mControllers.taskbarActivityContext.notifyUpdateLayoutParams();
}
}
@@ -437,8 +467,7 @@ public class TaskbarLauncherStateController {
public void onAnimationStart(Animator animation) {
mIsAnimatingToLauncher = isInLauncher;
- TaskbarStashController stashController =
- mControllers.taskbarStashController;
+ TaskbarStashController stashController = mControllers.taskbarStashController;
if (DEBUG) {
Log.d(TAG, "onAnimationStart - FLAG_IN_APP: " + !isInLauncher);
}
@@ -477,13 +506,14 @@ public class TaskbarLauncherStateController {
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- TaskbarStashController stashController =
- mControllers.taskbarStashController;
- stashController.updateAndAnimateTransientTaskbar(/* stash */ true);
+ TaskbarStashController stashController = mControllers.taskbarStashController;
+ stashController.updateAndAnimateTransientTaskbar(
+ /* stash */ true, /* bubblesShouldFollow */ true);
}
});
} else {
- // delay the fade in animation a bit to reduce visual noise when waking up a device
+ // delay the fade in animation a bit to reduce visual noise when waking up a
+ // device
// with a fingerprint reader. This should only be done when the device was woken
// up via fingerprint reader, however since this information is currently not
// available, opting to always delay the fade-in a bit.
@@ -511,7 +541,8 @@ public class TaskbarLauncherStateController {
boolean isInLauncherIconIsAligned = isInLauncher && isIconAlignedWithHotseat;
float startDelay = 0;
- // We want to delay the background from fading in so that the icons have time to move
+ // We want to delay the background from fading in so that the icons have time to
+ // move
// into the bounds of the background before it appears.
if (isInLauncherIconNotAligned) {
startDelay = duration * TASKBAR_BG_ALPHA_LAUNCHER_NOT_ALIGNED_DELAY_MULT;
@@ -547,13 +578,14 @@ public class TaskbarLauncherStateController {
// Keep isUnlockTransition in sync with its counterpart in
// TaskbarStashController#createAnimToIsStashed.
- boolean isUnlockTransition =
- hasAnyFlag(changedFlags, FLAG_DEVICE_LOCKED) && !hasAnyFlag(FLAG_DEVICE_LOCKED);
+ boolean isUnlockTransition = hasAnyFlag(changedFlags, FLAG_DEVICE_LOCKED) && !hasAnyFlag(FLAG_DEVICE_LOCKED);
if (isUnlockTransition) {
// When transitioning to unlocked, ensure the hotseat is fully visible from the
- // beginning. The hotseat itself is animated by LauncherUnlockAnimationController.
+ // beginning. The hotseat itself is animated by
+ // LauncherUnlockAnimationController.
mIconAlignment.cancelAnimation();
- // updateValue ensures onIconAlignmentRatioChanged will be called if there is an actual
+ // updateValue ensures onIconAlignmentRatioChanged will be called if there is an
+ // actual
// change in value
mIconAlignment.updateValue(toAlignment);
@@ -589,9 +621,11 @@ public class TaskbarLauncherStateController {
}
/**
- * Whether the taskbar is aligned with the hotseat in the current/target launcher state.
+ * Whether the taskbar is aligned with the hotseat in the current/target
+ * launcher state.
*
- * This refers to the intended state - a transition to this state might be in progress.
+ * This refers to the intended state - a transition to this state might be in
+ * progress.
*/
public boolean isTaskbarAlignedWithHotseat() {
return mLauncherState.isTaskbarAlignedWithHotseat(mLauncher);
@@ -605,8 +639,7 @@ public class TaskbarLauncherStateController {
boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher);
boolean willStashVisually = isInStashedState
&& mControllers.taskbarStashController.supportsVisualStashing();
- boolean isTaskbarAlignedWithHotseat =
- mLauncherState.isTaskbarAlignedWithHotseat(mLauncher);
+ boolean isTaskbarAlignedWithHotseat = mLauncherState.isTaskbarAlignedWithHotseat(mLauncher);
return isTaskbarAlignedWithHotseat && !willStashVisually;
} else {
return false;
@@ -636,8 +669,6 @@ public class TaskbarLauncherStateController {
public void onAnimationEnd(Animator animation) {
if (isInStashedState && committed) {
// Reset hotseat alpha to default
- Log.d("b/260135164",
- "playStateTransitionAnim#onAnimationEnd - setIconsAlpha(1)");
mLauncher.getHotseat().setIconsAlpha(1);
}
}
@@ -652,9 +683,12 @@ public class TaskbarLauncherStateController {
animatorSet.play(stashAnimator);
}
- // Translate back to 0 at a shorter or same duration as the icon alignment animation.
- // This ensures there is no jump after switching to hotseat, e.g. when swiping up from
- // overview to home. When not in app, we do duration / 2 just to make it feel snappier.
+ // Translate back to 0 at a shorter or same duration as the icon alignment
+ // animation.
+ // This ensures there is no jump after switching to hotseat, e.g. when swiping
+ // up from
+ // overview to home. When not in app, we do duration / 2 just to make it feel
+ // snappier.
long resetDuration = mControllers.taskbarStashController.isInApp()
? duration
: duration / 2;
@@ -705,21 +739,16 @@ public class TaskbarLauncherStateController {
private void updateIconAlphaForHome(float alpha) {
if (mControllers.taskbarActivityContext.isDestroyed()) {
- Log.e("b/260135164", "updateIconAlphaForHome is called after Taskbar is destroyed",
- new Exception());
return;
}
mIconAlphaForHome.setValue(alpha);
boolean hotseatVisible = alpha == 0
|| (!mControllers.uiController.isHotseatIconOnTopWhenAligned()
- && mIconAlignment.value > 0);
+ && mIconAlignment.value > 0);
/*
* Hide Launcher Hotseat icons when Taskbar icons have opacity. Both icon sets
* should not be visible at the same time.
*/
- Log.d("b/260135164",
- "updateIconAlphaForHome - setIconsAlpha(" + (hotseatVisible ? 1 : 0)
- + "), isTaskbarPresent: " + mLauncher.getDeviceProfile().isTaskbarPresent);
mLauncher.getHotseat().setIconsAlpha(hotseatVisible ? 1 : 0);
if (mIsQsbInline) {
mLauncher.getHotseat().setQsbAlpha(hotseatVisible ? 1 : 0);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index bdd2e3a410..6d1e0b586a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -22,13 +22,13 @@ import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING;
import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_KEY;
-import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
-import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
+import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.util.DisplayController.TASKBAR_NOT_DESTROYED_TAG;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
import android.annotation.SuppressLint;
+import android.app.Activity;
import android.app.PendingIntent;
import android.content.ComponentCallbacks;
import android.content.Context;
@@ -41,6 +41,7 @@ import android.hardware.display.DisplayManager;
import android.net.Uri;
import android.os.Handler;
import android.os.SystemProperties;
+import android.os.Trace;
import android.provider.Settings;
import android.util.Log;
import android.view.Display;
@@ -50,21 +51,20 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.taskbar.unfold.NonDestroyableScopedUnfoldTransitionProgressProvider;
import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.NavigationMode;
+import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.quickstep.RecentsActivity;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TouchInteractionService;
+import com.android.quickstep.util.AssistUtils;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
@@ -79,6 +79,22 @@ public class TaskbarManager {
private static final String TAG = "TaskbarManager";
private static final boolean DEBUG = false;
+ /**
+ * All the configurations which do not initiate taskbar recreation.
+ * This includes all the configurations defined in Launcher's manifest entry and
+ * ActivityController#filterConfigChanges
+ */
+ private static final int SKIP_RECREATE_CONFIG_CHANGES = ActivityInfo.CONFIG_WINDOW_CONFIGURATION
+ | ActivityInfo.CONFIG_KEYBOARD
+ | ActivityInfo.CONFIG_KEYBOARD_HIDDEN
+ | ActivityInfo.CONFIG_MCC
+ | ActivityInfo.CONFIG_MNC
+ | ActivityInfo.CONFIG_NAVIGATION
+ | ActivityInfo.CONFIG_ORIENTATION
+ | ActivityInfo.CONFIG_SCREEN_SIZE
+ | ActivityInfo.CONFIG_SCREEN_LAYOUT
+ | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
+
public static final boolean FLAG_HIDE_NAVBAR_WINDOW = SystemProperties
.getBoolean("persist.wm.debug.hide_navbar_window", false);
@@ -89,12 +105,11 @@ public class TaskbarManager {
Settings.Secure.NAV_BAR_KIDS_MODE);
private final Context mContext;
- private final DisplayController mDisplayController;
private final TaskbarNavButtonController mNavButtonController;
- private final SettingsCache.OnChangeListener mUserSetupCompleteListener;
- private final SettingsCache.OnChangeListener mNavBarKidsModeListener;
private final ComponentCallbacks mComponentCallbacks;
- private final SimpleBroadcastReceiver mShutdownReceiver;
+
+ private final SimpleBroadcastReceiver mShutdownReceiver = new SimpleBroadcastReceiver(
+ i -> destroyExistingTaskbar());
// The source for this provider is set when Launcher is available
// We use 'non-destroyable' version here so the original provider won't be
@@ -102,7 +117,6 @@ public class TaskbarManager {
// as it is tied to the activity lifecycle, not the taskbar lifecycle.
// It's destruction/creation will be managed by the activity.
private final ScopedUnfoldTransitionProgressProvider mUnfoldProgressProvider = new NonDestroyableScopedUnfoldTransitionProgressProvider();
- private NavigationMode mNavMode;
private TaskbarActivityContext mTaskbarActivityContext;
private StatefulActivity mActivity;
@@ -114,24 +128,12 @@ public class TaskbarManager {
private final TaskbarSharedState mSharedState = new TaskbarSharedState();
/**
- * We use WindowManager's ComponentCallbacks() for most of the config changes,
- * however for
- * navigation mode, that callback gets called too soon, before it's internal
- * navigation mode
- * reflects the current one.
- * DisplayController's callback is delayed enough to get the correct nav mode
- * value
- *
- * We also use density change here because DeviceProfile has had a chance to
- * update it's state
- * whereas density for component callbacks registered in this class don't update
- * DeviceProfile.
- * Confused? Me too. Make it less confusing (TODO: b/227669780)
- *
- * Flags used with {@link #mDispInfoChangeListener}
+ * We use WindowManager's ComponentCallbacks() for internal UI changes (similar
+ * to an Activity)
+ * which comes via a different channel
*/
- private static final int CHANGE_FLAGS = CHANGE_NAVIGATION_MODE | CHANGE_DENSITY;
- private final DisplayController.DisplayInfoChangeListener mDispInfoChangeListener;
+ private final OnIDPChangeListener mIdpChangeListener = c -> recreateTaskbar();
+ private final SettingsCache.OnChangeListener mOnSettingsChangeListener = c -> recreateTaskbar();
private boolean mUserUnlocked = false;
@@ -152,101 +154,120 @@ public class TaskbarManager {
}
};
+ private final ActivityLifecycleCallbacksAdapter mLifecycleCallbacks = new ActivityLifecycleCallbacksAdapter() {
+ @Override
+ public void onActivityDestroyed(Activity activity) {
+ if (mActivity != activity)
+ return;
+ if (mActivity != null) {
+ mActivity.removeOnDeviceProfileChangeListener(
+ mDebugActivityDeviceProfileChanged);
+ Log.d(TASKBAR_NOT_DESTROYED_TAG,
+ "unregistering activity lifecycle callbacks from "
+ + "onActivityDestroyed.");
+ mActivity.unregisterActivityLifecycleCallbacks(this);
+ }
+ mActivity = null;
+ debugWhyTaskbarNotDestroyed("clearActivity");
+ if (mTaskbarActivityContext != null) {
+ mTaskbarActivityContext.setUIController(TaskbarUIController.DEFAULT);
+ }
+ mUnfoldProgressProvider.setSourceProvider(null);
+ }
+ };
+
+ UnfoldTransitionProgressProvider.TransitionProgressListener mUnfoldTransitionProgressListener = new UnfoldTransitionProgressProvider.TransitionProgressListener() {
+ @Override
+ public void onTransitionStarted() {
+ Log.d(TASKBAR_NOT_DESTROYED_TAG,
+ "fold/unfold transition started getting called.");
+ }
+
+ @Override
+ public void onTransitionProgress(float progress) {
+ Log.d(TASKBAR_NOT_DESTROYED_TAG,
+ "fold/unfold transition progress : " + progress);
+ }
+
+ @Override
+ public void onTransitionFinishing() {
+ Log.d(TASKBAR_NOT_DESTROYED_TAG,
+ "fold/unfold transition finishing getting called.");
+
+ }
+
+ @Override
+ public void onTransitionFinished() {
+ Log.d(TASKBAR_NOT_DESTROYED_TAG,
+ "fold/unfold transition finished getting called.");
+
+ }
+ };
+
@SuppressLint("WrongConstant")
public TaskbarManager(TouchInteractionService service) {
- mDisplayController = DisplayController.INSTANCE.get(service);
Display display = service.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
- mContext = Utilities.ATLEAST_S
- ? service.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null)
- : null;
+ mContext = service.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null);
mNavButtonController = new TaskbarNavButtonController(service,
- SystemUiProxy.INSTANCE.get(mContext), new Handler());
- mUserSetupCompleteListener = isUserSetupComplete -> recreateTaskbar();
- mNavBarKidsModeListener = isNavBarKidsMode -> recreateTaskbar();
- // TODO(b/227669780): Consolidate this w/ DisplayController callbacks
+ SystemUiProxy.INSTANCE.get(mContext), new Handler(),
+ AssistUtils.newInstance(mContext));
mComponentCallbacks = new ComponentCallbacks() {
private Configuration mOldConfig = mContext.getResources().getConfiguration();
@Override
public void onConfigurationChanged(Configuration newConfig) {
+ Trace.instantForTrack(Trace.TRACE_TAG_APP, "TaskbarManager",
+ "onConfigurationChanged: " + newConfig);
debugWhyTaskbarNotDestroyed(
"TaskbarManager#mComponentCallbacks.onConfigurationChanged: " + newConfig);
DeviceProfile dp = mUserUnlocked
? LauncherAppState.getIDP(mContext).getDeviceProfile(mContext)
: null;
- int configDiff = mOldConfig.diff(newConfig);
- int configDiffForRecreate = configDiff;
- int configsRequiringRecreate = ActivityInfo.CONFIG_ASSETS_PATHS
- | ActivityInfo.CONFIG_LAYOUT_DIRECTION | ActivityInfo.CONFIG_UI_MODE
- | ActivityInfo.CONFIG_SCREEN_SIZE;
- if ((configDiff & ActivityInfo.CONFIG_SCREEN_SIZE) != 0
- && mTaskbarActivityContext != null && dp != null
- && !isPhoneMode(dp)) {
- // Additional check since this callback gets fired multiple times w/o
- // screen size changing, or when simply rotating the device.
- // In the case of phone device rotation, we do want to call recreateTaskbar()
- DeviceProfile oldDp = mTaskbarActivityContext.getDeviceProfile();
- boolean isOrientationChange = (configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0;
+ int configDiff = mOldConfig.diff(newConfig) & ~SKIP_RECREATE_CONFIG_CHANGES;
- int newOrientation = newConfig.windowConfiguration.getRotation();
- int oldOrientation = mOldConfig.windowConfiguration.getRotation();
- int oldWidth = isOrientationChange ? oldDp.heightPx : oldDp.widthPx;
- int oldHeight = isOrientationChange ? oldDp.widthPx : oldDp.heightPx;
-
- if ((dp.widthPx == oldWidth && dp.heightPx == oldHeight)
- || (newOrientation == oldOrientation)) {
- configDiffForRecreate &= ~ActivityInfo.CONFIG_SCREEN_SIZE;
- }
- }
if ((configDiff & ActivityInfo.CONFIG_UI_MODE) != 0) {
// Only recreate for theme changes, not other UI mode changes such as docking.
int oldUiNightMode = (mOldConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK);
int newUiNightMode = (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK);
if (oldUiNightMode == newUiNightMode) {
- configDiffForRecreate &= ~ActivityInfo.CONFIG_UI_MODE;
+ configDiff &= ~ActivityInfo.CONFIG_UI_MODE;
}
}
debugWhyTaskbarNotDestroyed("ComponentCallbacks#onConfigurationChanged() "
- + "configDiffForRecreate="
- + Configuration.configurationDiffToString(configDiffForRecreate));
- if ((configDiffForRecreate & configsRequiringRecreate) != 0) {
+ + "configDiff=" + Configuration.configurationDiffToString(configDiff));
+ if (configDiff != 0 || mTaskbarActivityContext == null) {
recreateTaskbar();
} else {
// Config change might be handled without re-creating the taskbar
- if (mTaskbarActivityContext != null) {
- if (dp != null && !isTaskbarPresent(dp)) {
- destroyExistingTaskbar();
- } else {
- if (dp != null && isTaskbarPresent(dp)) {
- mTaskbarActivityContext.updateDeviceProfile(dp, mNavMode);
+ if (dp != null && !isTaskbarPresent(dp)) {
+ destroyExistingTaskbar();
+ } else {
+ if (dp != null && isTaskbarPresent(dp)) {
+ if (FLAG_HIDE_NAVBAR_WINDOW) {
+ // Re-initialize for screen size change? Should this be done
+ // by looking at screen-size change flag in configDiff in the
+ // block above?
+ recreateTaskbar();
+ } else {
+ mTaskbarActivityContext.updateDeviceProfile(dp);
}
- mTaskbarActivityContext.onConfigurationChanged(configDiff);
}
+ mTaskbarActivityContext.onConfigurationChanged(configDiff);
}
}
- mOldConfig = newConfig;
+ mOldConfig = new Configuration(newConfig);
}
@Override
public void onLowMemory() {
}
};
- mShutdownReceiver = new SimpleBroadcastReceiver(i -> destroyExistingTaskbar());
- mDispInfoChangeListener = (context, info, flags) -> {
- if ((flags & CHANGE_FLAGS) != 0) {
- mNavMode = info.navigationMode;
- recreateTaskbar();
- }
- debugWhyTaskbarNotDestroyed("DisplayInfoChangeListener#"
- + mDisplayController.getChangeFlagsString(flags));
- };
- mNavMode = mDisplayController.getInfo().navigationMode;
- mDisplayController.addChangeListener(mDispInfoChangeListener);
- SettingsCache.INSTANCE.get(mContext).register(USER_SETUP_COMPLETE_URI,
- mUserSetupCompleteListener);
- SettingsCache.INSTANCE.get(mContext).register(NAV_BAR_KIDS_MODE,
- mNavBarKidsModeListener);
+ SettingsCache.INSTANCE.get(mContext)
+ .register(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
+ SettingsCache.INSTANCE.get(mContext)
+ .register(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
+ Log.d(TASKBAR_NOT_DESTROYED_TAG, "registering component callbacks from constructor.");
mContext.registerComponentCallbacks(mComponentCallbacks);
mShutdownReceiver.register(mContext, Intent.ACTION_SHUTDOWN);
UI_HELPER_EXECUTOR.execute(() -> {
@@ -286,6 +307,26 @@ public class TaskbarManager {
}
}
+ /**
+ * Toggles All Apps for Taskbar or Launcher depending on the current state.
+ *
+ * @param homeAllAppsIntent Intent used if Taskbar is not enabled or Launcher is
+ * resumed.
+ */
+ public void toggleAllApps(Intent homeAllAppsIntent) {
+ if (mTaskbarActivityContext == null) {
+ mContext.startActivity(homeAllAppsIntent);
+ return;
+ }
+
+ if (mActivity != null && mActivity.isResumed() && !mActivity.isInState(OVERVIEW)) {
+ mContext.startActivity(homeAllAppsIntent);
+ return;
+ }
+
+ mTaskbarActivityContext.toggleAllApps();
+ }
+
/**
* Displays a frame of the first Launcher reveal animation.
*
@@ -304,6 +345,7 @@ public class TaskbarManager {
*/
public void onUserUnlocked() {
mUserUnlocked = true;
+ LauncherAppState.getIDP(mContext).addOnChangeListener(mIdpChangeListener);
recreateTaskbar();
}
@@ -318,14 +360,18 @@ public class TaskbarManager {
if (mActivity == activity) {
return;
}
- if (mActivity != null) {
- mActivity.removeOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
- }
+ removeActivityCallbacksAndListeners();
mActivity = activity;
debugWhyTaskbarNotDestroyed("Set mActivity=" + mActivity);
mActivity.addOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
+ Log.d(TASKBAR_NOT_DESTROYED_TAG,
+ "registering activity lifecycle callbacks from setActivity().");
+ mActivity.registerActivityLifecycleCallbacks(mLifecycleCallbacks);
UnfoldTransitionProgressProvider unfoldTransitionProgressProvider = getUnfoldTransitionProgressProviderForActivity(
activity);
+ if (unfoldTransitionProgressProvider != null) {
+ unfoldTransitionProgressProvider.addCallback(mUnfoldTransitionProgressListener);
+ }
mUnfoldProgressProvider.setSourceProvider(unfoldTransitionProgressProvider);
if (mTaskbarActivityContext != null) {
@@ -364,21 +410,6 @@ public class TaskbarManager {
return TaskbarUIController.DEFAULT;
}
- /**
- * Clears a previously set {@link StatefulActivity}
- */
- public void clearActivity(@NonNull StatefulActivity activity) {
- if (mActivity == activity) {
- mActivity.removeOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
- mActivity = null;
- debugWhyTaskbarNotDestroyed("clearActivity");
- if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.setUIController(TaskbarUIController.DEFAULT);
- }
- mUnfoldProgressProvider.setSourceProvider(null);
- }
- }
-
/**
* This method is called multiple times (ex. initial init, then when user
* unlocks) in which case
@@ -387,32 +418,42 @@ public class TaskbarManager {
*/
@VisibleForTesting
public void recreateTaskbar() {
- DeviceProfile dp = mUserUnlocked ? LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
+ Trace.beginSection("recreateTaskbar");
+ try {
+ DeviceProfile dp = mUserUnlocked ? LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
- destroyExistingTaskbar();
+ destroyExistingTaskbar();
- boolean isTaskbarEnabled = dp != null && isTaskbarPresent(dp);
- debugWhyTaskbarNotDestroyed("recreateTaskbar: isTaskbarEnabled=" + isTaskbarEnabled
- + " [dp != null (i.e. mUserUnlocked)]=" + (dp != null)
- + " FLAG_HIDE_NAVBAR_WINDOW=" + FLAG_HIDE_NAVBAR_WINDOW
- + " dp.isTaskbarPresent=" + (dp == null ? "null" : dp.isTaskbarPresent));
- if (!isTaskbarEnabled) {
- SystemUiProxy.INSTANCE.get(mContext)
- .notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
- return;
- }
+ boolean isTaskbarEnabled = dp != null && isTaskbarPresent(dp);
+ debugWhyTaskbarNotDestroyed("recreateTaskbar: isTaskbarEnabled=" + isTaskbarEnabled
+ + " [dp != null (i.e. mUserUnlocked)]=" + (dp != null)
+ + " FLAG_HIDE_NAVBAR_WINDOW=" + FLAG_HIDE_NAVBAR_WINDOW
+ + " dp.isTaskbarPresent=" + (dp == null ? "null" : dp.isTaskbarPresent));
+ if (!isTaskbarEnabled) {
+ SystemUiProxy.INSTANCE.get(mContext)
+ .notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
+ return;
+ }
- if (mTaskbarActivityContext == null) {
- mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp, mNavButtonController,
- mUnfoldProgressProvider);
- } else {
- mTaskbarActivityContext.updateDeviceProfile(dp, mNavMode);
- }
- mTaskbarActivityContext.init(mSharedState);
+ if (mTaskbarActivityContext == null) {
+ mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp,
+ mNavButtonController,
+ mUnfoldProgressProvider);
+ } else {
+ mTaskbarActivityContext.updateDeviceProfile(dp);
+ }
+ mTaskbarActivityContext.init(mSharedState);
- if (mActivity != null) {
- mTaskbarActivityContext.setUIController(
- createTaskbarUIControllerForActivity(mActivity));
+ if (mActivity != null) {
+ mTaskbarActivityContext.setUIController(
+ createTaskbarUIControllerForActivity(mActivity));
+ }
+
+ // We to wait until user unlocks the device to attach listener.
+ LauncherPrefs.get(mContext).addListener(mTaskbarPinningPreferenceChangeListener,
+ TASKBAR_PINNING);
+ } finally {
+ Trace.endSection();
}
// We to wait until user unlocks the device to attach listener.
@@ -498,23 +539,38 @@ public class TaskbarManager {
}
}
+ private void removeActivityCallbacksAndListeners() {
+ if (mActivity != null) {
+ mActivity.removeOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
+ Log.d(TASKBAR_NOT_DESTROYED_TAG,
+ "unregistering activity lifecycle callbacks from "
+ + "removeActivityCallbackAndListeners().");
+ mActivity.unregisterActivityLifecycleCallbacks(mLifecycleCallbacks);
+ UnfoldTransitionProgressProvider unfoldTransitionProgressProvider = getUnfoldTransitionProgressProviderForActivity(
+ mActivity);
+ if (unfoldTransitionProgressProvider != null) {
+ unfoldTransitionProgressProvider.removeCallback(mUnfoldTransitionProgressListener);
+ }
+ }
+ }
+
/**
* Called when the manager is no longer needed
*/
public void destroy() {
debugWhyTaskbarNotDestroyed("TaskbarManager#destroy()");
- if (mActivity != null) {
- mActivity.removeOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
- }
-
+ removeActivityCallbacksAndListeners();
UI_HELPER_EXECUTOR.execute(
() -> mTaskbarBroadcastReceiver.unregisterReceiverSafely(mContext));
destroyExistingTaskbar();
- mDisplayController.removeChangeListener(mDispInfoChangeListener);
- SettingsCache.INSTANCE.get(mContext).unregister(USER_SETUP_COMPLETE_URI,
- mUserSetupCompleteListener);
- SettingsCache.INSTANCE.get(mContext).unregister(NAV_BAR_KIDS_MODE,
- mNavBarKidsModeListener);
+ if (mUserUnlocked) {
+ LauncherAppState.getIDP(mContext).removeOnChangeListener(mIdpChangeListener);
+ }
+ SettingsCache.INSTANCE.get(mContext)
+ .unregister(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
+ SettingsCache.INSTANCE.get(mContext)
+ .unregister(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
+ Log.d(TASKBAR_NOT_DESTROYED_TAG, "unregistering component callbacks from destroy().");
mContext.unregisterComponentCallbacks(mComponentCallbacks);
mContext.unregisterReceiver(mShutdownReceiver);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
index 6cf63a9193..7692760834 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -57,7 +57,7 @@ public class TaskbarModelCallbacks implements
private final TaskbarView mContainer;
// Initialized in init.
- private TaskbarControllers mControllers;
+ protected TaskbarControllers mControllers;
// Used to defer any UI updates during the SUW unstash animation.
private boolean mDeferUpdatesForSUW;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacksFactory.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacksFactory.kt
new file mode 100644
index 0000000000..eb03b4abc5
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacksFactory.kt
@@ -0,0 +1,42 @@
+/*
+ * 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
+
+import android.content.Context
+import com.android.launcher3.R
+import com.android.launcher3.util.ResourceBasedOverride
+import com.android.launcher3.util.ResourceBasedOverride.Overrides
+
+/** Creates [TaskbarModelCallbacks] instances. */
+open class TaskbarModelCallbacksFactory : ResourceBasedOverride {
+
+ open fun create(
+ activityContext: TaskbarActivityContext,
+ container: TaskbarView,
+ ): TaskbarModelCallbacks = TaskbarModelCallbacks(activityContext, container)
+
+ companion object {
+ @JvmStatic
+ fun newInstance(context: Context): TaskbarModelCallbacksFactory {
+ return Overrides.getObject(
+ TaskbarModelCallbacksFactory::class.java,
+ context,
+ R.string.taskbar_model_callbacks_factory_class,
+ )
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index 610efebfcb..03df0365bb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -43,12 +43,16 @@ import androidx.annotation.StringRes;
import com.android.launcher3.R;
import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.quickstep.LauncherActivityInterface;
import com.android.quickstep.OverviewCommandHelper;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.TouchInteractionService;
+import com.android.quickstep.util.AssistUtils;
+import com.android.quickstep.views.DesktopTaskView;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -105,15 +109,17 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
private final TouchInteractionService mService;
private final SystemUiProxy mSystemUiProxy;
private final Handler mHandler;
+ private final AssistUtils mAssistUtils;
@Nullable private StatsLogManager mStatsLogManager;
private final Runnable mResetLongPress = this::resetScreenUnpin;
public TaskbarNavButtonController(TouchInteractionService service,
- SystemUiProxy systemUiProxy, Handler handler) {
+ SystemUiProxy systemUiProxy, Handler handler, AssistUtils assistUtils) {
mService = service;
mSystemUiProxy = systemUiProxy;
mHandler = handler;
+ mAssistUtils = assistUtils;
}
public void onButtonClick(@TaskbarButton int buttonType, View view) {
@@ -155,7 +161,7 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
switch (buttonType) {
case BUTTON_HOME:
logEvent(LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS);
- startAssistant();
+ onLongPressHome();
return true;
case BUTTON_A11Y:
logEvent(LAUNCHER_TASKBAR_A11Y_BUTTON_LONGPRESS);
@@ -267,6 +273,15 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
private void navigateHome() {
TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
+
+ if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
+ DesktopVisibilityController desktopVisibilityController =
+ LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
+ if (desktopVisibilityController != null) {
+ desktopVisibilityController.onHomeActionTriggered();
+ }
+ }
+
mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_HOME);
}
@@ -295,13 +310,16 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
}
}
- private void startAssistant() {
+ private void onLongPressHome() {
if (mScreenPinned || !mAssistantLongPressEnabled) {
return;
}
- Bundle args = new Bundle();
- args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS);
- mSystemUiProxy.startAssistant(args);
+ // Attempt to start Assist with AssistUtils, otherwise fall back to SysUi's implementation.
+ if (!mAssistUtils.tryStartAssistOverride(INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS)) {
+ Bundle args = new Bundle();
+ args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS);
+ mSystemUiProxy.startAssistant(args);
+ }
}
private void showQuickSettings() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
index 5ea00cf408..1c250bf377 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.taskbar;
+import static com.android.launcher3.taskbar.bubbles.BubbleBarController.BUBBLE_BAR_ENABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
@@ -23,6 +24,7 @@ import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import com.android.launcher3.anim.AnimatedFloat;
+import com.android.launcher3.util.DisplayController;
import com.android.quickstep.SystemUiProxy;
import java.io.PrintWriter;
@@ -63,6 +65,10 @@ public class TaskbarScrimViewController implements TaskbarControllers.LoggableTa
* Updates the scrim state based on the flags.
*/
public void updateStateForSysuiFlags(int stateFlags, boolean skipAnim) {
+ if (BUBBLE_BAR_ENABLED && DisplayController.isTransientTaskbar(mActivity)) {
+ // These scrims aren't used if bubble bar & transient taskbar are active.
+ return;
+ }
final boolean bubblesExpanded = (stateFlags & SYSUI_STATE_BUBBLES_EXPANDED) != 0;
final boolean manageMenuExpanded =
(stateFlags & SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index c51d9d30f8..bca78055d0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -18,11 +18,11 @@ 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.app.animation.Interpolators.EMPHASIZED;
+import static com.android.app.animation.Interpolators.FINAL_FRAME;
+import static com.android.app.animation.Interpolators.INSTANT;
+import static com.android.app.animation.Interpolators.LINEAR;
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.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;
@@ -77,7 +77,8 @@ import java.util.StringJoiner;
import java.util.function.IntPredicate;
/**
- * Coordinates between controllers such as TaskbarViewController and StashedHandleViewController to
+ * Coordinates between controllers such as TaskbarViewController and
+ * StashedHandleViewController to
* create a cohesive animation between stashed/unstashed states.
*/
public class TaskbarStashController implements TaskbarControllers.LoggableTaskbarController {
@@ -94,22 +95,27 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
public static final int FLAG_IN_SETUP = 1 << 7; // In the Setup Wizard
public static final int FLAG_STASHED_SMALL_SCREEN = 1 << 8; // phone screen gesture nav, stashed
public static final int FLAG_STASHED_IN_APP_AUTO = 1 << 9; // Autohide (transient taskbar).
- public static final int FLAG_STASHED_SYSUI = 1 << 10; // app pinning,...
+ public static final int FLAG_STASHED_SYSUI = 1 << 10; // app pinning,...
public static final int FLAG_STASHED_DEVICE_LOCKED = 1 << 11; // device is locked: keyguard, ...
// If any of these flags are enabled, isInApp should return true.
private static final int FLAGS_IN_APP = FLAG_IN_APP | FLAG_IN_SETUP;
- // If we're in an app and any of these flags are enabled, taskbar should be stashed.
+ // If we're in an app and any of these flags are enabled, taskbar should be
+ // stashed.
private static final int FLAGS_STASHED_IN_APP = FLAG_STASHED_IN_APP_MANUAL
| FLAG_STASHED_IN_APP_SYSUI | FLAG_STASHED_IN_APP_SETUP
| FLAG_STASHED_IN_APP_IME | FLAG_STASHED_IN_TASKBAR_ALL_APPS
| FLAG_STASHED_SMALL_SCREEN | FLAG_STASHED_IN_APP_AUTO;
- // 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.
+ // 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_SYSUI;
@@ -123,8 +129,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
*
* Use {@link #getStashDuration()} to query duration
*/
- private static final long TASKBAR_STASH_DURATION =
- InsetsController.ANIMATION_DURATION_RESIZE;
+ private static final long TASKBAR_STASH_DURATION = InsetsController.ANIMATION_DURATION_RESIZE;
/**
* How long to stash/unstash transient taskbar.
@@ -146,8 +151,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
/**
* How long the hint animation plays, starting on motion down.
*/
- private static final long TASKBAR_HINT_STASH_DURATION =
- ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT;
+ private static final long TASKBAR_HINT_STASH_DURATION = ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT;
/**
* How long to delay the icon/stash handle alpha.
@@ -160,17 +164,20 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
private static final long TASKBAR_STASH_ALPHA_DURATION = 50;
/**
- * How long to delay the icon/stash handle alpha for the home to app taskbar animation.
+ * How long to delay the icon/stash handle alpha for the home to app taskbar
+ * animation.
*/
private static final long TASKBAR_STASH_ICON_ALPHA_HOME_TO_APP_START_DELAY = 66;
/**
- * The scale that TaskbarView animates to when hinting towards the stashed state.
+ * The scale that TaskbarView animates to when hinting towards the stashed
+ * state.
*/
private static final float STASHED_TASKBAR_HINT_SCALE = 0.9f;
/**
- * The scale that the stashed handle animates to when hinting towards the unstashed state.
+ * The scale that the stashed handle animates to when hinting towards the
+ * unstashed state.
*/
private static final float UNSTASHED_TASKBAR_HANDLE_HINT_SCALE = 1.1f;
@@ -187,7 +194,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
// Auto stashes when user has not interacted with the Taskbar after X ms.
private static final long NO_TOUCH_TIMEOUT_TO_STASH_MS = 5000;
- // Duration for which an unlock event is considered "current", as other events are received
+ // Duration for which an unlock event is considered "current", as other events
+ // are received
// asynchronously.
private static final long UNLOCK_TRANSITION_MEMOIZATION_MS = 200;
@@ -196,22 +204,28 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
*/
private static final int TRANSITION_DEFAULT = 0;
/**
- * Transitioning from launcher to app. Same as TRANSITION_DEFAULT, differs in internal
+ * Transitioning from launcher to app. Same as TRANSITION_DEFAULT, differs in
+ * internal
* animation timings.
*/
private static final int TRANSITION_HOME_TO_APP = 1;
/**
- * Fading the navbar in and out, where the taskbar jumpcuts in and out at the very begin/end of
- * the transition. Used to transition between the hotseat and navbar` without the stash/unstash
+ * Fading the navbar in and out, where the taskbar jumpcuts in and out at the
+ * very begin/end of
+ * the transition. Used to transition between the hotseat and navbar` without
+ * the stash/unstash
* transition.
*/
private static final int TRANSITION_HANDLE_FADE = 2;
/**
- * Same as TRANSITION_DEFAULT, but exclusively used during an "navbar unstash to hotseat
- * animation" bound to the progress of a swipe gesture. It differs from TRANSITION_DEFAULT
+ * Same as TRANSITION_DEFAULT, but exclusively used during an "navbar unstash to
+ * hotseat
+ * animation" bound to the progress of a swipe gesture. It differs from
+ * TRANSITION_DEFAULT
* by not scaling the height of the taskbar background.
*/
private static final int TRANSITION_UNSTASH_SUW_MANUAL = 3;
+
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
TRANSITION_DEFAULT,
@@ -219,7 +233,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
TRANSITION_HANDLE_FADE,
TRANSITION_UNSTASH_SUW_MANUAL,
})
- private @interface StashAnimation {}
+ private @interface StashAnimation {
+ }
private final TaskbarActivityContext mActivity;
private final SharedPreferences mPrefs;
@@ -242,7 +257,10 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
private AnimatedFloat mTaskbarStashedHandleHintScale;
private final AccessibilityManager mAccessibilityManager;
- /** Whether we are currently visually stashed (might change based on launcher state). */
+ /**
+ * Whether we are currently visually stashed (might change based on launcher
+ * state).
+ */
private boolean mIsStashed = false;
private int mState;
@@ -257,14 +275,15 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
private boolean mEnableBlockingTimeoutDuringTests = false;
// Evaluate whether the handle should be stashed
+ private final IntPredicate mIsStashedPredicate = flags -> {
+ boolean inApp = hasAnyFlag(flags, FLAGS_IN_APP);
+ boolean stashedInApp = hasAnyFlag(flags, FLAGS_STASHED_IN_APP);
+ boolean stashedLauncherState = hasAnyFlag(flags, FLAG_IN_STASHED_LAUNCHER_STATE);
+ boolean forceStashed = hasAnyFlag(flags, FLAGS_FORCE_STASHED);
+ return (inApp && stashedInApp) || (!inApp && stashedLauncherState) || forceStashed;
+ };
private final StatePropertyHolder mStatePropertyHolder = new StatePropertyHolder(
- flags -> {
- boolean inApp = hasAnyFlag(flags, FLAGS_IN_APP);
- boolean stashedInApp = hasAnyFlag(flags, FLAGS_STASHED_IN_APP);
- boolean stashedLauncherState = hasAnyFlag(flags, FLAG_IN_STASHED_LAUNCHER_STATE);
- boolean forceStashed = hasAnyFlag(flags, FLAGS_FORCE_STASHED);
- return (inApp && stashedInApp) || (!inApp && stashedLauncherState) || forceStashed;
- });
+ mIsStashedPredicate);
private boolean mIsTaskbarSystemActionRegistered = false;
private TaskbarSharedState mTaskbarSharedState;
@@ -312,16 +331,18 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
mIconScaleForStash = taskbarViewController.getTaskbarIconScaleForStash();
mIconTranslationYForStash = taskbarViewController.getTaskbarIconTranslationYForStash();
- StashedHandleViewController stashedHandleController =
- controllers.stashedHandleViewController;
+ StashedHandleViewController stashedHandleController = controllers.stashedHandleViewController;
mTaskbarStashedHandleAlpha = stashedHandleController.getStashedHandleAlpha().get(
StashedHandleViewController.ALPHA_INDEX_STASHED);
mTaskbarStashedHandleHintScale = stashedHandleController.getStashedHandleHintScale();
boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivity);
- // We use supportsVisualStashing() here instead of supportsManualStashing() because we want
- // it to work properly for tests that recreate taskbar. This check is here just to ensure
- // that taskbar unstashes when going to 3 button mode (supportsVisualStashing() false).
+ // We use supportsVisualStashing() here instead of supportsManualStashing()
+ // because we want
+ // it to work properly for tests that recreate taskbar. This check is here just
+ // to ensure
+ // that taskbar unstashes when going to 3 button mode (supportsVisualStashing()
+ // false).
boolean isManuallyStashedInApp = supportsVisualStashing()
&& !isTransientTaskbar
&& !ENABLE_TASKBAR_PINNING.get()
@@ -333,16 +354,18 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
updateStateForFlag(FLAG_IN_SETUP, isInSetup);
updateStateForFlag(FLAG_STASHED_SMALL_SCREEN, isPhoneMode()
&& !mActivity.isThreeButtonNav());
- // For now, assume we're in an app, since LauncherTaskbarUIController won't be able to tell
- // us that we're paused until a bit later. This avoids flickering upon recreating taskbar.
+ // For now, assume we're in an app, since LauncherTaskbarUIController won't be
+ // able to tell
+ // us that we're paused until a bit later. This avoids flickering upon
+ // recreating taskbar.
updateStateForFlag(FLAG_IN_APP, true);
applyState(/* duration = */ 0);
-
notifyStashChange(/* visible */ false, /* stashed */ isStashedInApp());
}
/**
- * Returns whether the taskbar can visually stash into a handle based on the current device
+ * Returns whether the taskbar can visually stash into a handle based on the
+ * current device
* state.
*/
public boolean supportsVisualStashing() {
@@ -350,7 +373,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
}
/**
- * Returns whether the user can manually stash the taskbar based on the current device state.
+ * Returns whether the user can manually stash the taskbar based on the current
+ * device state.
*/
protected boolean supportsManualStashing() {
if (ENABLE_TASKBAR_PINNING.get() && mPrefs.getBoolean(TASKBAR_PINNING_KEY, false)) {
@@ -363,7 +387,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
}
/**
- * Enables support for manual stashing. This should only be used to add this functionality
+ * Enables support for manual stashing. This should only be used to add this
+ * functionality
* to Launcher specific tests.
*/
@VisibleForTesting
@@ -372,7 +397,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
}
/**
- * Enables the auto timeout for taskbar stashing. This method should only be used for taskbar
+ * Enables the auto timeout for taskbar stashing. This method should only be
+ * used for taskbar
* testing.
*/
@VisibleForTesting
@@ -407,7 +433,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
}
/**
- * Returns whether the taskbar should be stashed in apps (e.g. user long pressed to stash).
+ * Returns whether the taskbar should be stashed in apps (e.g. user long pressed
+ * to stash).
*/
public boolean isStashedInApp() {
return hasAnyFlag(FLAGS_STASHED_IN_APP);
@@ -435,9 +462,9 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
return (flags & flagMask) != 0;
}
-
/**
- * Returns whether the taskbar is currently visible and not in the process of being stashed.
+ * Returns whether the taskbar is currently visible and not in the process of
+ * being stashed.
*/
public boolean isTaskbarVisibleAndNotStashing() {
return !mIsStashed && mControllers.taskbarViewController.areIconsVisible();
@@ -458,6 +485,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
/**
* Returns the height that taskbar will inset when inside apps.
+ *
* @see android.view.WindowInsets.Type#navigationBars()
* @see android.view.WindowInsets.Type#systemBars()
*/
@@ -479,7 +507,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
if (!mControllers.stashedHandleViewController.isStashedHandleVisible()
&& isInApp()
&& !isAnimating) {
- // We are in a settled state where we're not showing the handle even though taskbar
+ // We are in a settled state where we're not showing the handle even though
+ // taskbar
// is stashed. This can happen for example when home button is disabled (see
// StashedHandleViewController#setIsHomeButtonDisabled()).
return 0;
@@ -492,6 +521,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
/**
* Returns the height that taskbar will inset when inside apps.
+ *
* @see android.view.WindowInsets.Type#tappableElement()
*/
public int getTappableHeightToReportToApps() {
@@ -504,19 +534,30 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
}
/**
- * Stash or unstashes the transient taskbar.
+ * Stash or unstashes the transient taskbar, using the default
+ * TASKBAR_STASH_DURATION.
+ * If bubble bar exists, it will match taskbars stashing behavior.
*/
public void updateAndAnimateTransientTaskbar(boolean stash) {
+ updateAndAnimateTransientTaskbar(stash, /* shouldBubblesFollow= */ true);
+ }
+
+ /**
+ * Stash or unstashes the transient taskbar.
+ *
+ * @param stash whether transient taskbar should be stashed.
+ * @param shouldBubblesFollow whether bubbles should match taskbars behavior.
+ */
+ public void updateAndAnimateTransientTaskbar(boolean stash, boolean shouldBubblesFollow) {
if (!DisplayController.isTransientTaskbar(mActivity)) {
return;
}
- if (
- stash
+ if (stash
&& !mControllers.taskbarAutohideSuspendController
- .isSuspendedForTransientTaskbarInLauncher()
+ .isSuspendedForTransientTaskbarInLauncher()
&& mControllers.taskbarAutohideSuspendController
- .isTransientTaskbarStashingSuspended()) {
+ .isTransientTaskbarStashingSuspended()) {
// Avoid stashing if autohide is currently suspended.
return;
}
@@ -525,15 +566,45 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, stash);
applyState();
}
+
+ mControllers.bubbleControllers.ifPresent(controllers -> {
+ if (shouldBubblesFollow) {
+ final boolean willStash = mIsStashedPredicate.test(mState);
+ if (willStash != controllers.bubbleStashController.isStashed()) {
+ // Typically bubbles gets stashed / unstashed along with Taskbar, however, if
+ // taskbar is becoming stashed because bubbles is being expanded, we don't want
+ // to stash bubbles.
+ if (willStash) {
+ controllers.bubbleStashController.stashBubbleBar();
+ } else {
+ controllers.bubbleStashController.showBubbleBar(false /* expandBubbles */);
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Stashes transient taskbar after it has timed out.
+ */
+ private void updateAndAnimateTransientTaskbarForTimeout() {
+ // If bubbles are expanded we shouldn't stash them when taskbar is hidden
+ // for the timeout.
+ boolean bubbleBarExpanded = mControllers.bubbleControllers.isPresent()
+ && mControllers.bubbleControllers.get().bubbleBarViewController.isExpanded();
+ updateAndAnimateTransientTaskbar(/* stash= */ true,
+ /* shouldBubblesFollow= */ !bubbleBarExpanded);
}
/**
* Should be called when long pressing the nav region when taskbar is present.
+ *
* @return Whether taskbar was stashed and now is unstashed.
*/
public boolean onLongPressToUnstashTaskbar() {
if (!isStashed()) {
- // We only listen for long press on the nav region to unstash the taskbar. To stash the
+ // We only listen for long press on the nav region to unstash the taskbar. To
+ // stash the
// taskbar, we use an OnLongClickListener on TaskbarView instead.
return false;
}
@@ -548,18 +619,23 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
}
/**
- * Returns whether taskbar will unstash when long pressing it based on the current state. The
- * only time this is true is if the user is in an app and the taskbar is only stashed because
- * the user previously long pressed to manually stash (not due to other reasons like IME).
+ * Returns whether taskbar will unstash when long pressing it based on the
+ * current state. The
+ * only time this is true is if the user is in an app and the taskbar is only
+ * stashed because
+ * the user previously long pressed to manually stash (not due to other reasons
+ * like IME).
*/
private boolean canCurrentlyManuallyUnstash() {
- return (mState & (FLAG_IN_APP | FLAGS_STASHED_IN_APP))
- == (FLAG_IN_APP | FLAG_STASHED_IN_APP_MANUAL);
+ return (mState & (FLAG_IN_APP | FLAGS_STASHED_IN_APP)) == (FLAG_IN_APP | FLAG_STASHED_IN_APP_MANUAL);
}
/**
- * Updates whether we should stash the taskbar when in apps, and animates to the changed state.
- * @return Whether we started an animation to either be newly stashed or unstashed.
+ * Updates whether we should stash the taskbar when in apps, and animates to the
+ * changed state.
+ *
+ * @return Whether we started an animation to either be newly stashed or
+ * unstashed.
*/
public boolean updateAndAnimateIsManuallyStashedInApp(boolean isManuallyStashedInApp) {
if (!supportsManualStashing()) {
@@ -576,22 +652,28 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
/** Toggles the Taskbar's stash state. */
public void toggleTaskbarStash() {
- if (!DisplayController.isTransientTaskbar(mActivity) || !hasAnyFlag(FLAGS_IN_APP)) return;
+ 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.
*
- * This should be used to run a Taskbar unstash to Hotseat animation whose progress matches a
+ * This should be used to run a Taskbar unstash to Hotseat animation whose
+ * progress matches a
* swipe progress.
*
- * @param placeholderDuration a placeholder duration to be used to ensure all full-length
- * sub-animations are properly coordinated. This duration should not
- * actually be used since this animation tracks a swipe progress.
+ * @param placeholderDuration a placeholder duration to be used to ensure all
+ * full-length
+ * sub-animations are properly coordinated. This
+ * duration should not
+ * 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.
+ // Defer any UI updates now to avoid the UI becoming stale when the animation
+ // plays.
mControllers.taskbarViewController.setDeferUpdatesForSUW(true);
createAnimToIsStashed(
/* isStashed= */ false,
@@ -604,8 +686,9 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
/**
* Create a stash animation and save to {@link #mAnimator}.
- * @param isStashed whether it's a stash animation or an unstash animation
- * @param duration duration of the animation
+ *
+ * @param isStashed whether it's a stash animation or an unstash animation
+ * @param duration duration of the animation
* @param animationType what transition type to play.
*/
private void createAnimToIsStashed(boolean isStashed, long duration,
@@ -632,11 +715,15 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
mAnimator.playTogether(mTaskbarBackgroundOffset.animateToValue(isStashed ? 1 : 0)
.setDuration(duration));
mAnimator.playTogether(mIconTranslationYForStash.animateToValue(isStashed
- ? stashTranslation : 0)
+ ? stashTranslation
+ : 0)
.setDuration(duration));
mAnimator.play(mTaskbarImeBgAlpha.animateToValue(
hasAnyFlag(FLAG_STASHED_IN_APP_IME) ? 0 : 1).setDuration(duration));
- mAnimator.addListener(AnimatorListeners.forEndCallback(() -> mAnimator = null));
+ mAnimator.addListener(AnimatorListeners.forEndCallback(() -> {
+ mAnimator = null;
+ mIsStashed = isStashed;
+ }));
return;
}
@@ -690,18 +777,15 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
firstHalfAnimatorSet.playTogether(
mIconAlphaForStash.animateToValue(0),
- mIconScaleForStash.animateToValue(isPhoneMode() ?
- 0 : STASHED_TASKBAR_SCALE)
- );
+ mIconScaleForStash.animateToValue(isPhoneMode() ? 0 : STASHED_TASKBAR_SCALE));
secondHalfAnimatorSet.playTogether(
- mTaskbarStashedHandleAlpha.animateToValue(1)
- );
+ mTaskbarStashedHandleAlpha.animateToValue(1));
if (animationType == TRANSITION_HANDLE_FADE) {
fullLengthAnimatorSet.setInterpolator(INSTANT);
firstHalfAnimatorSet.setInterpolator(INSTANT);
}
- } else {
+ } else {
firstHalfDurationScale = 0.5f;
secondHalfDurationScale = 0.75f;
@@ -718,11 +802,9 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
}
firstHalfAnimatorSet.playTogether(
- mTaskbarStashedHandleAlpha.animateToValue(0)
- );
+ mTaskbarStashedHandleAlpha.animateToValue(0));
secondHalfAnimatorSet.playTogether(
- mIconAlphaForStash.animateToValue(1)
- );
+ mIconAlphaForStash.animateToValue(1));
if (animationType == TRANSITION_HANDLE_FADE) {
fullLengthAnimatorSet.setInterpolator(FINAL_FRAME);
@@ -732,8 +814,10 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
fullLengthAnimatorSet.play(mControllers.stashedHandleViewController
.createRevealAnimToIsStashed(isStashed));
- // Return the stashed handle to its default scale in case it was changed as part of the
- // feedforward hint. Note that the reveal animation above also visually scales it.
+ // Return the stashed handle to its default scale in case it was changed as part
+ // of the
+ // feedforward hint. Note that the reveal animation above also visually scales
+ // it.
fullLengthAnimatorSet.play(mTaskbarStashedHandleHintScale.animateToValue(1f));
fullLengthAnimatorSet.setDuration(duration);
@@ -789,7 +873,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
backgroundAndHandleAlphaStartDelay,
backgroundAndHandleAlphaDuration, LINEAR);
- // The rest of the animations might be "skipped" in TRANSITION_HANDLE_FADE transitions.
+ // The rest of the animations might be "skipped" in TRANSITION_HANDLE_FADE
+ // transitions.
AnimatorSet skippable = as;
if (animationType == TRANSITION_HANDLE_FADE) {
skippable = new AnimatorSet();
@@ -824,14 +909,19 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
play(skippable, mControllers.stashedHandleViewController
.createRevealAnimToIsStashed(isStashed), 0, duration, EMPHASIZED);
- // Return the stashed handle to its default scale in case it was changed as part of the
- // feedforward hint. Note that the reveal animation above also visually scales it.
+ // Return the stashed handle to its default scale in case it was changed as part
+ // of the
+ // feedforward hint. Note that the reveal animation above also visually scales
+ // it.
skippable.play(mTaskbarStashedHandleHintScale.animateToValue(1f)
.setDuration(isStashed ? duration / 2 : duration));
}
- private static void play(AnimatorSet as, Animator a, long startDelay, long duration,
+ private static void play(AnimatorSet as, @Nullable Animator a, long startDelay, long duration,
Interpolator interpolator) {
+ if (a == null) {
+ return;
+ }
a.setDuration(duration);
a.setStartDelay(startDelay);
a.setInterpolator(interpolator);
@@ -840,8 +930,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
private void addJankMonitorListener(AnimatorSet animator, boolean expanding) {
View v = mControllers.taskbarActivityContext.getDragLayer();
- int action = expanding ? InteractionJankMonitor.CUJ_TASKBAR_EXPAND :
- InteractionJankMonitor.CUJ_TASKBAR_COLLAPSE;
+ int action = expanding ? InteractionJankMonitor.CUJ_TASKBAR_EXPAND
+ : InteractionJankMonitor.CUJ_TASKBAR_COLLAPSE;
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(@NonNull Animator animation) {
@@ -854,10 +944,14 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
}
});
}
+
/**
- * Creates and starts a partial stash animation, hinting at the new state that will trigger when
+ * Creates and starts a partial stash animation, hinting at the new state that
+ * will trigger when
* long press is detected.
- * @param animateForward Whether we are going towards the new stashed state or returning to the
+ *
+ * @param animateForward Whether we are going towards the new stashed state or
+ * returning to the
* unstashed state.
*/
public void startStashHint(boolean animateForward) {
@@ -871,12 +965,14 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
}
/**
- * Creates and starts a partial unstash animation, hinting at the new state that will trigger
+ * 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
+ * @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.
+ * @param forceUnstash Whether we force the unstash hint to animate.
*/
protected void startUnstashHint(boolean animateForward, boolean forceUnstash) {
if (!isStashed()) {
@@ -921,7 +1017,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
}
/**
- * Returns an animator which applies the latest state if mIsStashed is changed, or {@code null}
+ * Returns an animator which applies the latest state if mIsStashed is changed,
+ * or {@code null}
* otherwise.
*/
@Nullable
@@ -930,7 +1027,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
}
/**
- * Should be called when a system gesture starts and settles, so we can defer updating
+ * Should be called when a system gesture starts and settles, so we can defer
+ * updating
* FLAG_STASHED_IN_APP_IME until after the gesture transition completes.
*/
public void setSystemGestureInProgress(boolean inProgress) {
@@ -951,7 +1049,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
}
/**
- * When hiding the IME, delay the unstash animation to align with the end of the transition.
+ * When hiding the IME, delay the unstash animation to align with the end of the
+ * transition.
*/
private long getTaskbarStashStartDelayForIme() {
if (mIsImeShowing) {
@@ -964,7 +1063,10 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
return imeExitDuration - TASKBAR_STASH_DURATION_FOR_IME;
}
- /** Called when some system ui state has changed. (See SYSUI_STATE_... in QuickstepContract) */
+ /**
+ * Called when some system ui state has changed. (See SYSUI_STATE_... in
+ * QuickstepContract)
+ */
public void updateStateForSysuiFlags(int systemUiStateFlags, boolean skipAnim) {
long animDuration = TASKBAR_STASH_DURATION;
long startDelay = 0;
@@ -993,9 +1095,9 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
/**
* We stash when IME or IME switcher is showing AND NOT
- * * in small screen AND
- * * 3 button nav AND
- * * landscape (or seascape)
+ * * in small screen AND
+ * * 3 button nav AND
+ * * landscape (or seascape)
* We do not stash if taskbar is transient
*/
private boolean shouldStashForIme() {
@@ -1010,10 +1112,12 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
/**
* Updates the proper flag to indicate whether the task bar should be stashed.
*
- * Note that this only updates the flag. {@link #applyState()} needs to be called separately.
+ * Note that this only updates the flag. {@link #applyState()} needs to be
+ * called separately.
*
- * @param flag The flag to update.
- * @param enabled Whether to enable the flag: True will cause the task bar to be stashed /
+ * @param flag The flag to update.
+ * @param enabled Whether to enable the flag: True will cause the task bar to be
+ * stashed /
* unstashed.
*/
public void updateStateForFlag(int flag, boolean enabled) {
@@ -1026,6 +1130,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
/**
* Called after updateStateForFlag() and applyState() have been called.
+ *
* @param changedFlags The flags that have changed.
*/
private void onStateChangeApplied(int changedFlags) {
@@ -1034,7 +1139,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
}
if (hasAnyFlag(changedFlags, FLAGS_STASHED_IN_APP | FLAGS_IN_APP)) {
notifyStashChange(/* visible */ hasAnyFlag(FLAGS_IN_APP),
- /* stashed */ isStashedInApp());
+ /* stashed */ isStashedInApp());
mControllers.taskbarAutohideSuspendController.updateFlag(
TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_IN_LAUNCHER, !isInApp());
}
@@ -1053,6 +1158,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_TRANSIENT_TASKBAR,
!hasAnyFlag(FLAG_STASHED_IN_APP_AUTO));
}
+ mActivity.applyForciblyShownFlagWhileTransientTaskbarUnstashed(!isStashedInApp());
}
private void notifyStashChange(boolean visible, boolean stashed) {
@@ -1105,6 +1211,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
/**
* Updates the status of the taskbar timeout.
+ *
* @param isAutohideSuspended If true, cancels any existing timeout
* If false, attempts to re/start the timeout
*/
@@ -1136,7 +1243,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
}
/**
- * returns appropriate timeout for taskbar to stash depending on accessibility being on/off.
+ * returns appropriate timeout for taskbar to stash depending on accessibility
+ * being on/off.
*/
private long getTaskbarAutoHideTimeout() {
return mAccessibilityManager.getRecommendedTimeoutMillis((int) NO_TOUCH_TIMEOUT_TO_STASH_MS,
@@ -1147,7 +1255,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
if (mControllers.taskbarAutohideSuspendController.isTransientTaskbarStashingSuspended()) {
return;
}
- updateAndAnimateTransientTaskbar(true);
+ updateAndAnimateTransientTaskbarForTimeout();
}
@Override
@@ -1194,9 +1302,11 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
}
/**
- * Creates an animator (stored in mAnimator) which applies the latest state, potentially
+ * Creates an animator (stored in mAnimator) which applies the latest state,
+ * potentially
* creating a new animation (stored in mAnimator).
- * @param flags The latest flags to apply (see the top of this file).
+ *
+ * @param flags The latest flags to apply (see the top of this file).
* @param duration The length of the animation.
* @return mAnimator if mIsStashed changed, or {@code null} otherwise.
*/
@@ -1224,15 +1334,17 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
if (isUnlockTransition) {
// the launcher might not be resumed at the time the device is considered
// unlocked (when the keyguard goes away), but possibly shortly afterwards.
- // To play the unlock transition at the time the unstash animation actually happens,
+ // To play the unlock transition at the time the unstash animation actually
+ // happens,
// this memoizes the state transition for UNLOCK_TRANSITION_MEMOIZATION_MS.
- mLastUnlockTransitionTimeout =
- SystemClock.elapsedRealtime() + UNLOCK_TRANSITION_MEMOIZATION_MS;
+ mLastUnlockTransitionTimeout = SystemClock.elapsedRealtime() + UNLOCK_TRANSITION_MEMOIZATION_MS;
}
- @StashAnimation int animationType = computeTransitionType(changedFlags);
+ @StashAnimation
+ int animationType = computeTransitionType(changedFlags);
- // Allow re-starting animation if upgrading from default animation type, otherwise
+ // Allow re-starting animation if upgrading from default animation type,
+ // otherwise
// stick with the already started transition.
boolean transitionTypeChanged = mAnimator != null && mAnimator.isStarted()
&& mLastStartedTransitionType == TRANSITION_DEFAULT
@@ -1260,21 +1372,21 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
private @StashAnimation int computeTransitionType(int changedFlags) {
- boolean hotseatHiddenDuringAppLaunch =
- !mControllers.uiController.isHotseatIconOnTopWhenAligned()
- && hasAnyFlag(changedFlags, FLAG_IN_APP);
+ boolean hotseatHiddenDuringAppLaunch = !mControllers.uiController.isHotseatIconOnTopWhenAligned()
+ && hasAnyFlag(changedFlags, FLAG_IN_APP);
if (hotseatHiddenDuringAppLaunch) {
- // When launching an app from the all-apps drawer, the hotseat is hidden behind the
- // drawer. In this case, the navbar must just fade in, without a stash transition,
+ // When launching an app from the all-apps drawer, the hotseat is hidden behind
+ // the
+ // drawer. In this case, the navbar must just fade in, without a stash
+ // transition,
// as the taskbar stash animation would otherwise be visible above the all-apps
// drawer once the hotseat is detached.
return TRANSITION_HANDLE_FADE;
}
- boolean isUnlockTransition =
- SystemClock.elapsedRealtime() < mLastUnlockTransitionTimeout;
+ boolean isUnlockTransition = SystemClock.elapsedRealtime() < mLastUnlockTransitionTimeout;
if (isUnlockTransition) {
- // When transitioning to unlocked device, the hotseat will already be visible on
+ // When transitioning to unlocked device, the hotseat will already be visible on
// the homescreen, thus do not play an un-stash animation.
// Keep isUnlockTransition in sync with its counterpart in
// TaskbarLauncherStateController#onStateChangeApplied.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashViaTouchController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashViaTouchController.kt
index 1cc667211c..deaf0244e9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashViaTouchController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashViaTouchController.kt
@@ -16,9 +16,9 @@
package com.android.launcher3.taskbar
import android.view.MotionEvent
+import com.android.app.animation.Interpolators.LINEAR
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
@@ -108,7 +108,17 @@ class TaskbarStashViaTouchController(val controllers: TaskbarControllers) : Touc
}
override fun onControllerInterceptTouchEvent(ev: MotionEvent): Boolean {
- if (!enabled || controllers.taskbarStashController.isStashed) {
+ if (!enabled) {
+ return false
+ }
+ val bubbleControllers = controllers.bubbleControllers.orElse(null)
+ if (bubbleControllers != null && bubbleControllers.bubbleBarViewController.isExpanded) {
+ return false
+ }
+ if (
+ (bubbleControllers == null || bubbleControllers.bubbleStashController.isStashed) &&
+ controllers.taskbarStashController.isStashed
+ ) {
return false
}
@@ -122,7 +132,12 @@ class TaskbarStashViaTouchController(val controllers: TaskbarControllers) : Touc
return true
}
} else if (ev.action == MotionEvent.ACTION_DOWN) {
- if (screenCoordinatesEv.y < gestureHeightYThreshold) {
+ val isDownOnBubbleBar =
+ (bubbleControllers != null &&
+ bubbleControllers.bubbleBarViewController.isEventOverAnyItem(
+ screenCoordinatesEv
+ ))
+ if (!isDownOnBubbleBar && screenCoordinatesEv.y < gestureHeightYThreshold) {
controllers.taskbarStashController.updateAndAnimateTransientTaskbar(true)
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarThresholdUtils.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarThresholdUtils.java
new file mode 100644
index 0000000000..5b6fbef4fd
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarThresholdUtils.java
@@ -0,0 +1,87 @@
+/*
+ * 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;
+
+import static com.android.launcher3.Utilities.dpToPx;
+import static com.android.launcher3.Utilities.dpiFromPx;
+
+import android.content.res.Resources;
+import android.util.DisplayMetrics;
+
+import androidx.core.content.res.ResourcesCompat;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
+
+/**
+ * Utility class that contains the different taskbar thresholds logic.
+ */
+public class TaskbarThresholdUtils {
+
+ // We divide the screen into this many parts, and use the result to scale the thresholds to
+ // any size device. Note that this value was calculated arbitrarily by using two tablet devices
+ // as data points.
+ private static final float SCREEN_UNITS = 1 / 80f;
+
+ private static int getThreshold(Resources r, DeviceProfile dp, int thresholdDimen,
+ int multiplierDimen) {
+ if (!FeatureFlags.ENABLE_DYNAMIC_TASKBAR_THRESHOLDS.get()) {
+ return r.getDimensionPixelSize(thresholdDimen);
+ }
+
+ float landscapeScreenHeight = dp.isLandscape ? dp.heightPx : dp.widthPx;
+ float screenPart = (landscapeScreenHeight * SCREEN_UNITS);
+ float defaultDp = dpiFromPx(screenPart, DisplayMetrics.DENSITY_DEVICE_STABLE);
+ float thisDp = dpToPx(defaultDp);
+ float multiplier = ResourcesCompat.getFloat(r, multiplierDimen);
+ float value = (thisDp) * multiplier;
+
+ return Math.round(value);
+ }
+
+ /**
+ * Returns the threshold that determines if we should show taskbar.
+ */
+ public static int getFromNavThreshold(Resources r, DeviceProfile dp) {
+ return getThreshold(r, dp, R.dimen.taskbar_from_nav_threshold,
+ R.dimen.taskbar_nav_threshold_mult);
+ }
+
+ /**
+ * Returns the threshold that we start moving the app window.
+ */
+ public static int getAppWindowThreshold(Resources r, DeviceProfile dp) {
+ return getThreshold(r, dp, R.dimen.taskbar_app_window_threshold,
+ R.dimen.taskbar_app_window_threshold_mult);
+ }
+
+ /**
+ * Returns the threshold for whether we land in home or overview.
+ */
+ public static int getHomeOverviewThreshold(Resources r, DeviceProfile dp) {
+ return getThreshold(r, dp, R.dimen.taskbar_home_overview_threshold,
+ R.dimen.taskbar_home_overview_threshold_mult);
+ }
+
+ /**
+ * Returns the threshold that we use to allow swipe to catch up to finger.
+ */
+ public static int getCatchUpThreshold(Resources r, DeviceProfile dp) {
+ return getThreshold(r, dp, R.dimen.taskbar_catch_up_threshold,
+ R.dimen.taskbar_catch_up_threshold_mult);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java
index 065d1117c8..916b1e6c43 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java
@@ -26,8 +26,8 @@ import android.animation.ValueAnimator;
import androidx.annotation.Nullable;
import androidx.dynamicanimation.animation.SpringForce;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.anim.AnimatedFloat;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.SpringAnimationBuilder;
import com.android.launcher3.util.DisplayController;
@@ -92,6 +92,10 @@ public class TaskbarTranslationController implements TaskbarControllers.Loggable
mControllers.stashedHandleViewController.setTranslationYForSwipe(transY);
mControllers.taskbarViewController.setTranslationYForSwipe(transY);
mControllers.taskbarDragLayerController.setTranslationYForSwipe(transY);
+ mControllers.bubbleControllers.ifPresent(controllers -> {
+ controllers.bubbleBarViewController.setTranslationYForSwipe(transY);
+ controllers.bubbleStashedHandleViewController.setTranslationYForSwipe(transY);
+ });
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 7154731ee5..2feef22279 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -40,8 +40,11 @@ import com.android.quickstep.util.GroupTask;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
+import com.android.systemui.shared.recents.model.Task;
import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.stream.Stream;
/**
@@ -69,26 +72,32 @@ public class TaskbarUIController {
}
/**
- * This should only be called by TaskbarStashController so that a TaskbarUIController can
+ * This should only be called by TaskbarStashController so that a
+ * TaskbarUIController can
* disable stashing. All other controllers should use
- * {@link TaskbarStashController#supportsVisualStashing()} as the source of truth.
+ * {@link TaskbarStashController#supportsVisualStashing()} as the source of
+ * truth.
*/
public boolean supportsVisualStashing() {
return true;
}
- protected void onStashedInAppChanged() { }
+ protected void onStashedInAppChanged() {
+ }
/**
* Called when taskbar icon layout bounds change.
*/
- protected void onIconLayoutBoundsChanged() { }
+ protected void onIconLayoutBoundsChanged() {
+ }
/** Called when an icon is launched. */
@CallSuper
public void onTaskbarIconLaunched(ItemInfo item) {
- // When launching from Taskbar, e.g. from Overview, set FLAG_IN_APP immediately instead of
- // waiting for onPause, to reduce potential visual noise during the app open transition.
+ // When launching from Taskbar, e.g. from Overview, set FLAG_IN_APP immediately
+ // instead of
+ // waiting for onPause, to reduce potential visual noise during the app open
+ // transition.
mControllers.taskbarStashController.updateStateForFlag(FLAG_IN_APP, true);
mControllers.taskbarStashController.applyState();
}
@@ -99,7 +108,9 @@ public class TaskbarUIController {
/**
* Called when swiping from the bottom nav region in fully gestural mode.
- * @param inProgress True if the animation started, false if we just settled on an end target.
+ *
+ * @param inProgress True if the animation started, false if we just settled on
+ * an end target.
*/
public void setSystemGestureInProgress(boolean inProgress) {
mControllers.taskbarStashController.setSystemGestureInProgress(inProgress);
@@ -116,7 +127,8 @@ public class TaskbarUIController {
}
/**
- * User expands PiP to full-screen (or split-screen) mode, try to hide the Taskbar.
+ * User expands PiP to full-screen (or split-screen) mode, try to hide the
+ * Taskbar.
*/
public void onExpandPip() {
if (mControllers != null) {
@@ -155,7 +167,9 @@ public class TaskbarUIController {
/*
* @param ev MotionEvent in screen coordinates.
- * @return Whether any Taskbar item could handle the given MotionEvent if given the chance.
+ *
+ * @return Whether any Taskbar item could handle the given MotionEvent if given
+ * the chance.
*/
public boolean isEventOverAnyTaskbarItem(MotionEvent ev) {
return mControllers.taskbarViewController.isEventOverAnyItem(ev)
@@ -170,7 +184,8 @@ public class TaskbarUIController {
}
/**
- * Returns true if hotseat icons are on top of view hierarchy when aligned in the current state.
+ * Returns true if hotseat icons are on top of view hierarchy when aligned in
+ * the current state.
*/
public boolean isHotseatIconOnTopWhenAligned() {
return true;
@@ -191,8 +206,10 @@ public class TaskbarUIController {
/**
* Returns RecentsView. Overwritten in LauncherTaskbarUIController and
- * FallbackTaskbarUIController with Launcher-specific implementations. Returns null for other
- * UI controllers (like DesktopTaskbarUIController) that don't have a RecentsView.
+ * FallbackTaskbarUIController with Launcher-specific implementations. Returns
+ * null for other
+ * UI controllers (like DesktopTaskbarUIController) that don't have a
+ * RecentsView.
*/
public @Nullable RecentsView getRecentsView() {
return null;
@@ -204,16 +221,17 @@ public class TaskbarUIController {
return;
}
- recentsView.getSplitSelectController().findLastActiveTaskAndRunCallback(
- splitSelectSource.itemInfo.getComponentKey(),
- foundTask -> {
+ recentsView.getSplitSelectController().findLastActiveTasksAndRunCallback(
+ Collections.singletonList(splitSelectSource.itemInfo.getComponentKey()),
+ foundTasks -> {
+ @Nullable
+ Task foundTask = foundTasks.get(0);
splitSelectSource.alreadyRunningTaskId = foundTask == null
? INVALID_TASK_ID
: foundTask.key.id;
splitSelectSource.animateCurrentTaskDismissal = foundTask != null;
recentsView.initiateSplitSelect(splitSelectSource);
- }
- );
+ });
}
/**
@@ -221,9 +239,11 @@ public class TaskbarUIController {
*/
public void triggerSecondAppForSplit(ItemInfoWithIcon info, Intent intent, View startingView) {
RecentsView recents = getRecentsView();
- recents.getSplitSelectController().findLastActiveTaskAndRunCallback(
- info.getComponentKey(),
- foundTask -> {
+ recents.getSplitSelectController().findLastActiveTasksAndRunCallback(
+ Collections.singletonList(info.getComponentKey()),
+ foundTasks -> {
+ @Nullable
+ Task foundTask = foundTasks.get(0);
if (foundTask != null) {
TaskView foundTaskView = recents.getTaskViewByTaskId(foundTask.key.id);
// TODO (b/266482558): This additional null check is needed because there
@@ -234,8 +254,8 @@ public class TaskbarUIController {
if (foundTaskView != null) {
// There is already a running app of this type, use that as second app.
// Get index of task (0 or 1), in case it's a GroupedTaskView
- TaskIdAttributeContainer taskAttributes =
- foundTaskView.getTaskAttributesById(foundTask.key.id);
+ TaskIdAttributeContainer taskAttributes = foundTaskView
+ .getTaskAttributesById(foundTask.key.id);
recents.confirmSplitSelect(
foundTaskView,
foundTask,
@@ -257,14 +277,14 @@ public class TaskbarUIController {
null /* thumbnail */,
intent,
info.user);
- }
- );
+ });
}
/**
* Opens the Keyboard Quick Switch View.
*
- * This will set the focus to the first task from the right (from the left in RTL)
+ * This will set the focus to the first task from the right (from the left in
+ * RTL)
*/
public void openQuickSwitchView() {
mControllers.keyboardQuickSwitchController.openQuickSwitchView();
@@ -273,10 +293,13 @@ public class TaskbarUIController {
/**
* Launches the focused task and closes the Keyboard Quick Switch View.
*
- * If the overlay or view are closed, or the overview task is focused, then Overview is
- * launched. If the overview task is launched, then the first hidden task is focused.
+ * If the overlay or view are closed, or the overview task is focused, then
+ * Overview is
+ * launched. If the overview task is launched, then the first hidden task is
+ * focused.
*
- * @return the index of what task should be focused in ; -1 iff Overview shouldn't be launched
+ * @return the index of what task should be focused in ; -1 iff Overview
+ * shouldn't be launched
*/
public int launchFocusedTask() {
int focusedTaskIndex = mControllers.keyboardQuickSwitchController.launchFocusedTask();
@@ -289,10 +312,12 @@ public class TaskbarUIController {
*
* No-op if the view is not yet open.
*/
- public void launchSplitTasks(@NonNull View taskview, @NonNull GroupTask groupTask) { }
+ public void launchSplitTasks(@NonNull View taskview, @NonNull GroupTask groupTask) {
+ }
/**
* Returns the matching view (if any) in the taskbar.
+ *
* @param view The view to match.
*/
public @Nullable View findMatchingView(View view) {
@@ -304,7 +329,8 @@ public class TaskbarUIController {
return null;
}
- // Taskbar has the same items as the hotseat and we can use screenId to find the match.
+ // Taskbar has the same items as the hotseat and we can use screenId to find the
+ // match.
int screenId = info.screenId;
View[] views = mControllers.taskbarViewController.getIconViews();
for (int i = views.length - 1; i >= 0; --i) {
@@ -320,7 +346,8 @@ public class TaskbarUIController {
/**
* Refreshes the resumed state of this ui controller.
*/
- public void refreshResumedState() {}
+ public void refreshResumedState() {
+ }
/**
* Returns a stream of split screen menu options appropriate to the device.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
index 466a05bf02..b911115909 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
@@ -47,10 +47,9 @@ public class TaskbarUnfoldAnimationController implements
WindowManager windowManager,
RotationChangeProvider rotationChangeProvider) {
mScopedUnfoldTransitionProgressProvider = source;
- mNaturalUnfoldTransitionProgressProvider =
- new NaturalRotationUnfoldProgressProvider(context,
- rotationChangeProvider,
- source);
+ mNaturalUnfoldTransitionProgressProvider = new NaturalRotationUnfoldProgressProvider(context,
+ rotationChangeProvider,
+ source);
mMoveFromCenterAnimator = new UnfoldMoveFromCenterAnimator(windowManager,
new LauncherViewsMoveFromCenterTranslationApplier());
}
@@ -63,8 +62,8 @@ public class TaskbarUnfoldAnimationController implements
public void init(TaskbarControllers taskbarControllers) {
mNaturalUnfoldTransitionProgressProvider.init();
mTaskbarViewController = taskbarControllers.taskbarViewController;
- mTaskbarViewController.addOneTimePreDrawListener(() ->
- mScopedUnfoldTransitionProgressProvider.setReadyToHandleTransition(true));
+ mTaskbarViewController.addOneTimePreDrawListener(
+ () -> mScopedUnfoldTransitionProgressProvider.setReadyToHandleTransition(true));
mNaturalUnfoldTransitionProgressProvider.addCallback(mTransitionListener);
mTaskbarDragLayerController = taskbarControllers.taskbarDragLayerController;
}
@@ -91,8 +90,9 @@ public class TaskbarUnfoldAnimationController implements
mMoveFromCenterAnimator.updateDisplayProperties();
View[] icons = mTaskbarViewController.getIconViews();
for (View icon : icons) {
- // TODO(b/193794563) we should re-register views if they are re-bound/re-inflated
- // during the animation
+ // TODO(b/193794563) we should re-register views if they are
+ // re-bound/re-inflated
+ // during the animation
mMoveFromCenterAnimator.registerViewForAnimation(icon);
}
@@ -112,9 +112,5 @@ public class TaskbarUnfoldAnimationController implements
float insetPercentage = (1 - progress) * MAX_WIDTH_INSET_FRACTION;
mTaskbarDragLayerController.setBackgroundHorizontalInsets(insetPercentage);
}
-
- @Override
- public void onTransitionFinishing() {
- }
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 9955b16f9a..00ba5180ac 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -18,6 +18,7 @@ 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.config.FeatureFlags.ENABLE_CURSOR_HOVER_STATES;
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
import android.content.Context;
@@ -61,7 +62,8 @@ import app.lawnchair.hotseat.HotseatMode;
import app.lawnchair.preferences2.PreferenceManager2;
/**
- * Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
+ * Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of
+ * other apps.
*/
public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconParent, Insettable,
DeviceProfile.OnDeviceProfileChangeListener {
@@ -91,7 +93,7 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
private @Nullable IconButtonView mAllAppsButton;
// Only non-null when device supports having an All Apps button.
- private @Nullable View mTaskbarDivider;
+ private @Nullable IconButtonView mTaskbarDivider;
private View mQsb;
@@ -110,12 +112,12 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
}
public TaskbarView(@NonNull Context context, @Nullable AttributeSet attrs,
- int defStyleAttr) {
+ int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public TaskbarView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
+ int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
PreferenceManager2 preferenceManager2 = PreferenceManager2.getInstance(context);
HotseatMode hotseatMode = PreferenceExtensionsKt.firstBlocking(preferenceManager2.getHotseatMode());
@@ -127,10 +129,9 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
mIsRtl = Utilities.isRtl(resources);
mTransientTaskbarMinWidth = context.getResources().getDimension(
R.dimen.transient_taskbar_min_width);
- mTransientTaskbarAllAppsButtonTranslationXOffset =
- resources.getDimension(isTransientTaskbar
- ? R.dimen.transient_taskbar_all_apps_button_translation_x_offset
- : R.dimen.taskbar_all_apps_button_translation_x_offset);
+ mTransientTaskbarAllAppsButtonTranslationXOffset = resources.getDimension(isTransientTaskbar
+ ? R.dimen.transient_taskbar_all_apps_button_translation_x_offset
+ : R.dimen.taskbar_all_apps_button_translation_x_offset);
onDeviceProfileChanged(mActivityContext.getDeviceProfile());
@@ -163,13 +164,17 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
mActivityContext.getColor(R.color.all_apps_button_color));
if (FeatureFlags.ENABLE_TASKBAR_PINNING.get()) {
- mTaskbarDivider = LayoutInflater.from(context).inflate(R.layout.taskbar_divider,
+ mTaskbarDivider = (IconButtonView) LayoutInflater.from(context).inflate(
+ R.layout.taskbar_divider,
this, false);
+ mTaskbarDivider.setIconDrawable(
+ resources.getDrawable(R.drawable.taskbar_divider_button));
+ mTaskbarDivider.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
}
}
// TODO: Disable touch events on QSB otherwise it can crash.
- if (hotseatMode.isAvailable (context)) {
+ if (hotseatMode.isAvailable(context)) {
mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
} else {
mQsb = LayoutInflater.from(context).inflate(R.layout.empty_view, this, false);
@@ -207,7 +212,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
protected void announceAccessibilityChanges() {
this.performAccessibilityAction(
isVisibleToUser() ? AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS
- : AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
+ : AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS,
+ null);
ActivityContext.lookupContext(getContext()).getDragLayer()
.sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED);
@@ -221,7 +227,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.
+ // 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();
@@ -249,7 +256,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
}
/**
- * Inflates/binds the Hotseat views to show in the Taskbar given their ItemInfos.
+ * Inflates/binds the Hotseat views to show in the Taskbar given their
+ * ItemInfos.
*/
protected void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
int nextViewIndex = 0;
@@ -264,14 +272,14 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
}
removeView(mQsb);
-
for (int i = 0; i < hotseatItemInfos.length; i++) {
ItemInfo hotseatItemInfo = hotseatItemInfos[i];
if (hotseatItemInfo == null) {
continue;
}
- // Replace any Hotseat views with the appropriate type if it's not already that type.
+ // Replace any Hotseat views with the appropriate type if it's not already that
+ // type.
final int expectedLayoutResId;
boolean isFolder = false;
if (hotseatItemInfo.isPredictedItem()) {
@@ -316,7 +324,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
addView(hotseatView, nextViewIndex, lp);
}
- // Apply the Hotseat ItemInfos, or hide the view if there is none for a given index.
+ // Apply the Hotseat ItemInfos, or hide the view if there is none for a given
+ // index.
if (hotseatView instanceof BubbleTextView
&& hotseatItemInfo instanceof WorkspaceItemInfo) {
BubbleTextView btv = (BubbleTextView) hotseatView;
@@ -329,6 +338,9 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
}
}
setClickAndLongClickListenersForIcon(hotseatView);
+ if (ENABLE_CURSOR_HOVER_STATES.get()) {
+ setHoverListenerForIcon(hotseatView);
+ }
nextViewIndex++;
}
// Remove remaining views
@@ -338,7 +350,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
if (mAllAppsButton != null) {
mAllAppsButton.setTranslationXForTaskbarAllAppsIcon(getChildCount() > 0
- ? mTransientTaskbarAllAppsButtonTranslationXOffset : 0f);
+ ? mTransientTaskbarAllAppsButtonTranslationXOffset
+ : 0f);
addView(mAllAppsButton, mIsRtl ? getChildCount() : 0);
// if only all apps button present, don't include divider view.
@@ -376,16 +389,18 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
icon.setOnLongClickListener(mIconLongClickListener);
}
+ /**
+ * Sets OnHoverListener for the given view.
+ */
+ private void setHoverListenerForIcon(View icon) {
+ icon.setOnHoverListener(mControllerCallbacks.getIconOnHoverListener(icon));
+ }
+
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int count = getChildCount();
DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
int spaceNeeded = getIconLayoutWidth();
- // We are removing the margin from taskbar divider item in taskbar,
- // so remove it from spacing also.
- if (FeatureFlags.ENABLE_TASKBAR_PINNING.get() && count > 1) {
- spaceNeeded -= mIconTouchSize;
- }
int navSpaceNeeded = deviceProfile.hotseatBarEndOffset;
boolean layoutRtl = isLayoutRtl();
int centerAlignIconEnd = right - (right - left - spaceNeeded) / 2;
@@ -483,7 +498,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
}
/**
- * Returns whether the given MotionEvent, *in screen coorindates*, is within any Taskbar item's
+ * Returns whether the given MotionEvent, *in screen coorindates*, is within any
+ * Taskbar item's
* touch bounds.
*/
public boolean isEventOverAnyItem(MotionEvent ev) {
@@ -506,7 +522,14 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
if (deviceProfile.isQsbInline) {
countExcludingQsb--;
}
- return countExcludingQsb * (mItemMarginLeftRight * 2 + mIconTouchSize);
+ int iconLayoutBoundsWidth = countExcludingQsb * (mItemMarginLeftRight * 2 + mIconTouchSize);
+
+ if (FeatureFlags.ENABLE_TASKBAR_PINNING.get() && countExcludingQsb > 1) {
+ // We are removing 4 * mItemMarginLeftRight as there should be no space between
+ // All Apps icon, divider icon, and first app icon in taskbar
+ iconLayoutBoundsWidth -= mItemMarginLeftRight * 4;
+ }
+ return iconLayoutBoundsWidth;
}
/**
@@ -600,7 +623,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
}
/**
- * Finds the first icon to match one of the given matchers, from highest to lowest priority.
+ * Finds the first icon to match one of the given matchers, from highest to
+ * lowest priority.
*
* @return The first match, or All Apps button if no match was found.
*/
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 4abd9957b2..4614e8ddc7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.taskbar;
+import static com.android.app.animation.Interpolators.FINAL_FRAME;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
@@ -22,9 +24,8 @@ import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.anim.AnimatedFloat.VALUE;
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
-import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP;
+import static com.android.launcher3.taskbar.TaskbarManager.isPhoneButtonNavMode;
import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_ALIGNMENT_ANIM;
@@ -45,6 +46,7 @@ import androidx.annotation.Nullable;
import androidx.core.graphics.ColorUtils;
import androidx.core.view.OneShotPreDrawListener;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
@@ -53,7 +55,6 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AlphaUpdateListener;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.RevealOutlineAnimation;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
@@ -121,7 +122,9 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
private AnimatorPlaybackController mIconAlignControllerLazy = null;
private Runnable mOnControllerPreCreateCallback = NO_OP;
+ // Stored here as signals to determine if the mIconAlignController needs to be recreated.
private boolean mIsHotseatIconOnTopWhenAligned;
+ private boolean mIsStashed;
private final DeviceProfile.OnDeviceProfileChangeListener mDeviceProfileChangeListener =
dp -> commitRunningAppsToUI();
@@ -133,7 +136,8 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
mTaskbarView = taskbarView;
mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, NUM_ALPHA_CHANNELS);
mTaskbarIconAlpha.setUpdateVisibility(true);
- mModelCallbacks = new TaskbarModelCallbacks(activity, mTaskbarView);
+ mModelCallbacks = TaskbarModelCallbacksFactory.newInstance(mActivity)
+ .create(mActivity, mTaskbarView);
mTaskbarBottomMargin = activity.getDeviceProfile().taskbarBottomMargin;
mStashedHandleHeight = activity.getResources()
.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_height);
@@ -170,6 +174,13 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
.getTaskbarNavButtonTranslationYForInAppDisplay();
mActivity.addOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
+
+ if (TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW) {
+ // This gets modified in NavbarButtonsViewController, but the initial value it reads
+ // may be incorrect since it's state gets destroyed on taskbar recreate, so reset here
+ mTaskbarIconAlpha.get(ALPHA_INDEX_SMALL_SCREEN)
+ .animateToValue(isPhoneButtonNavMode(mActivity) ? 0 : 1).start();
+ }
}
/**
@@ -426,10 +437,13 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
public void setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp) {
boolean isHotseatIconOnTopWhenAligned =
mControllers.uiController.isHotseatIconOnTopWhenAligned();
- // When mIsHotseatIconOnTopWhenAligned changes, animation needs to be re-created.
+ boolean isStashed = mControllers.taskbarStashController.isStashed();
+ // Re-create animation when mIsHotseatIconOnTopWhenAligned or mIsStashed changes.
if (mIconAlignControllerLazy == null
- || mIsHotseatIconOnTopWhenAligned != isHotseatIconOnTopWhenAligned) {
+ || mIsHotseatIconOnTopWhenAligned != isHotseatIconOnTopWhenAligned
+ || mIsStashed != isStashed) {
mIsHotseatIconOnTopWhenAligned = isHotseatIconOnTopWhenAligned;
+ mIsStashed = isStashed;
mIconAlignControllerLazy = createIconAlignmentController(launcherDp);
}
mIconAlignControllerLazy.setPlayFraction(alignmentRatio);
@@ -443,8 +457,13 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
* Creates an animation for aligning the Taskbar icons with the provided Launcher device profile
*/
private AnimatorPlaybackController createIconAlignmentController(DeviceProfile launcherDp) {
- mOnControllerPreCreateCallback.run();
PendingAnimation setter = new PendingAnimation(100);
+ if (TaskbarManager.isPhoneButtonNavMode(mActivity)) {
+ // No animation for icons in small-screen
+ return setter.createPlaybackController();
+ }
+
+ mOnControllerPreCreateCallback.run();
DeviceProfile taskbarDp = mActivity.getDeviceProfile();
Rect hotseatPadding = launcherDp.getHotseatLayoutPadding(mActivity);
float scaleUp = ((float) launcherDp.iconSizePx) / taskbarDp.taskbarIconSize;
@@ -486,7 +505,7 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|| (isTaskbarDividerView && FeatureFlags.ENABLE_TASKBAR_PINNING.get())) {
if (!isToHome
&& mIsHotseatIconOnTopWhenAligned
- && mControllers.taskbarStashController.isStashed()) {
+ && mIsStashed) {
// Prevent All Apps icon from appearing when going from hotseat to nav handle.
setter.setViewAlpha(child, 0, Interpolators.clampToProgress(LINEAR, 0f, 0f));
} else {
@@ -676,6 +695,11 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
.updateAndAnimateIsManuallyStashedInApp(true);
}
+ /** Gets the hover listener for the provided icon view. */
+ public View.OnHoverListener getIconOnHoverListener(View icon) {
+ return new TaskbarHoverToolTipController(mActivity, mTaskbarView, icon);
+ }
+
/**
* Get the first chance to handle TaskbarView#onTouchEvent, and return whether we want to
* consume the touch so TaskbarView treats it as an ACTION_CANCEL.
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContainerView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContainerView.java
index b4b83f6f24..05f1a2f2fb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContainerView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContainerView.java
@@ -47,17 +47,17 @@ public class TaskbarAllAppsContainerView extends
}
@Override
- protected View inflateSearchBox() {
+ protected View inflateSearchBar() {
if (isSearchSupported()) {
- return super.inflateSearchBox();
+ return super.inflateSearchBar();
}
// Remove top padding of header, since we do not have any search
mHeader.setPadding(mHeader.getPaddingLeft(), 0,
mHeader.getPaddingRight(), mHeader.getPaddingBottom());
- TaskbarAllAppsFallbackSearchContainer searchView =
- new TaskbarAllAppsFallbackSearchContainer(getContext(), null);
+ TaskbarAllAppsFallbackSearchContainer searchView = new TaskbarAllAppsFallbackSearchContainer(getContext(),
+ null);
searchView.setId(R.id.search_container_all_apps);
searchView.setVisibility(GONE);
return searchView;
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
index e0a502bdb2..fdd5ad5c0c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
@@ -15,11 +15,17 @@
*/
package com.android.launcher3.taskbar.allapps;
+import static com.android.launcher3.model.data.AppInfo.EMPTY_ARRAY;
+
+import android.view.View;
+
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.R;
import com.android.launcher3.appprediction.PredictionRowView;
+import com.android.launcher3.dragndrop.DragOptions.PreDragCondition;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.taskbar.TaskbarControllers;
@@ -34,24 +40,31 @@ import java.util.function.Predicate;
/**
* Handles the all apps overlay window initialization, updates, and its data.
*
- * All apps is in an application overlay window instead of taskbar's navigation bar panel window,
- * because a navigation bar panel is higher than UI components that all apps should be below such as
+ * All apps is in an application overlay window instead of taskbar's navigation
+ * bar panel window,
+ * because a navigation bar panel is higher than UI components that all apps
+ * should be below such as
* the notification tray.
*
- * The all apps window is created and destroyed upon opening and closing all apps, respectively.
- * Application data may be bound while the window does not exist, so this controller will store
+ * The all apps window is created and destroyed upon opening and closing all
+ * apps, respectively.
+ * Application data may be bound while the window does not exist, so this
+ * controller will store
* the models for the next all apps session.
*/
public final class TaskbarAllAppsController {
private TaskbarControllers mControllers;
+ private @Nullable TaskbarOverlayContext mOverlayContext;
private @Nullable TaskbarAllAppsSlideInView mSlideInView;
private @Nullable TaskbarAllAppsContainerView mAppsView;
+ private @Nullable TaskbarSearchSessionController mSearchSessionController;
// Application data models.
- private AppInfo[] mApps;
+ private @NonNull AppInfo[] mApps = EMPTY_ARRAY;
private int mAppsModelFlags;
- private List mPredictedApps;
+ private @NonNull List mPredictedApps = Collections.emptyList();
+ private @Nullable List mZeroStateSearchSuggestions;
private boolean mDisallowGlobalDrag;
private boolean mDisallowLongClick;
@@ -62,7 +75,8 @@ public final class TaskbarAllAppsController {
mControllers = controllers;
/*
- * Recreate All Apps if it was open in the previous Taskbar instance (e.g. the configuration
+ * Recreate All Apps if it was open in the previous Taskbar instance (e.g. the
+ * configuration
* changed).
*/
if (allAppsVisible) {
@@ -70,9 +84,14 @@ public final class TaskbarAllAppsController {
}
}
+ /** Clean up the controller. */
+ public void onDestroy() {
+ cleanUpOverlay();
+ }
+
/** Updates the current {@link AppInfo} instances. */
- public void setApps(AppInfo[] apps, int flags, Map map) {
- mApps = apps;
+ public void setApps(@Nullable AppInfo[] apps, int flags, Map map) {
+ mApps = apps == null ? EMPTY_ARRAY : apps;
mAppsModelFlags = flags;
mPackageUserKeytoUidMap = map;
if (mAppsView != null) {
@@ -96,6 +115,14 @@ public final class TaskbarAllAppsController {
.findFixedRowByType(PredictionRowView.class)
.setPredictedApps(mPredictedApps);
}
+ if (mSearchSessionController != null) {
+ mSearchSessionController.setZeroStatePredictedItems(predictedApps);
+ }
+ }
+
+ /** Updates the current search suggestions. */
+ public void setZeroStateSearchSuggestions(List zeroStateSearchSuggestions) {
+ mZeroStateSearchSuggestions = zeroStateSearchSuggestions;
}
/** Updates the current notification dots. */
@@ -105,7 +132,10 @@ public final class TaskbarAllAppsController {
}
}
- /** Toggles visibility of {@link TaskbarAllAppsContainerView} in the overlay window. */
+ /**
+ * Toggles visibility of {@link TaskbarAllAppsContainerView} in the overlay
+ * window.
+ */
public void toggle() {
if (isOpen()) {
mSlideInView.close(true);
@@ -123,45 +153,87 @@ public final class TaskbarAllAppsController {
if (mAppsView != null) {
return;
}
- // mControllers and getSharedState should never be null here. Do not handle null-pointer
+ // mControllers and getSharedState should never be null here. Do not handle
+ // null-pointer
// to catch invalid states.
mControllers.getSharedState().allAppsVisible = true;
- TaskbarOverlayContext overlayContext =
- mControllers.taskbarOverlayController.requestWindow();
- mSlideInView = (TaskbarAllAppsSlideInView) overlayContext.getLayoutInflater().inflate(
- R.layout.taskbar_all_apps, overlayContext.getDragLayer(), false);
+ mOverlayContext = mControllers.taskbarOverlayController.requestWindow();
+
+ // Initialize search session for All Apps.
+ mSearchSessionController = TaskbarSearchSessionController.newInstance(mOverlayContext);
+ mOverlayContext.setSearchSessionController(mSearchSessionController);
+ mSearchSessionController.setZeroStatePredictedItems(mPredictedApps);
+ if (mZeroStateSearchSuggestions != null) {
+ mSearchSessionController.setZeroStateSearchSuggestions(mZeroStateSearchSuggestions);
+ }
+ mSearchSessionController.startLifecycle();
+
+ mSlideInView = (TaskbarAllAppsSlideInView) mOverlayContext.getLayoutInflater().inflate(
+ R.layout.taskbar_all_apps_sheet, mOverlayContext.getDragLayer(), false);
mSlideInView.addOnCloseListener(() -> {
mControllers.getSharedState().allAppsVisible = false;
- mSlideInView = null;
- mAppsView = null;
+ cleanUpOverlay();
});
TaskbarAllAppsViewController viewController = new TaskbarAllAppsViewController(
- overlayContext, mSlideInView, mControllers);
+ mOverlayContext, mSlideInView, mControllers, mSearchSessionController);
viewController.show(animate);
- mAppsView = overlayContext.getAppsView();
+ mAppsView = mOverlayContext.getAppsView();
mAppsView.getAppsStore().setApps(mApps, mAppsModelFlags, mPackageUserKeytoUidMap);
mAppsView.getFloatingHeaderView()
.findFixedRowByType(PredictionRowView.class)
.setPredictedApps(mPredictedApps);
// 1 alternative that would be more work:
- // Create a shared drag layer between taskbar and taskbarAllApps so that when dragging
- // starts and taskbarAllApps can close, but the drag layer that the view is being dragged in
+ // Create a shared drag layer between taskbar and taskbarAllApps so that when
+ // dragging
+ // starts and taskbarAllApps can close, but the drag layer that the view is
+ // being dragged in
// doesn't also close
- overlayContext.getDragController().setDisallowGlobalDrag(mDisallowGlobalDrag);
- overlayContext.getDragController().setDisallowLongClick(mDisallowLongClick);
+ mOverlayContext.getDragController().setDisallowGlobalDrag(mDisallowGlobalDrag);
+ mOverlayContext.getDragController().setDisallowLongClick(mDisallowLongClick);
+ }
+
+ private void cleanUpOverlay() {
+ // Floating search bar is added to the drag layer in
+ // ActivityAllAppsContainerView onAttach;
+ // removed here as this is a special case that we remove the all apps panel.
+ if (mAppsView != null && mOverlayContext != null
+ && mAppsView.getSearchUiDelegate().isSearchBarFloating()) {
+ mOverlayContext.getDragLayer().removeView(mAppsView.getSearchView());
+ mAppsView.getSearchUiDelegate().onDestroySearchBar();
+ }
+ if (mSearchSessionController != null) {
+ mSearchSessionController.onDestroy();
+ mSearchSessionController = null;
+ }
+ if (mOverlayContext != null) {
+ mOverlayContext.setSearchSessionController(null);
+ mOverlayContext = null;
+ }
+ mSlideInView = null;
+ mAppsView = null;
}
@VisibleForTesting
public int getTaskbarAllAppsTopPadding() {
- // Allow null-pointer since this should only be null if the apps view is not showing.
+ // 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.
+ // Allow null-pointer since this should only be null if the apps view is not
+ // showing.
return mAppsView.getActiveRecyclerView().computeVerticalScrollOffset();
}
+
+ /** @see TaskbarSearchSessionController#createPreDragConditionForSearch(View) */
+ @Nullable
+ public PreDragCondition createPreDragConditionForSearch(View view) {
+ return mSearchSessionController != null
+ ? mSearchSessionController.createPreDragConditionForSearch(view)
+ : null;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
index cfa1027dcc..001c3bcfc0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
@@ -15,20 +15,27 @@
*/
package com.android.launcher3.taskbar.allapps;
-import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
+import static com.android.app.animation.Interpolators.EMPHASIZED;
-import android.animation.PropertyValuesHolder;
+import android.animation.Animator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
import android.util.AttributeSet;
import android.view.MotionEvent;
+import android.view.View;
import android.view.animation.Interpolator;
import android.window.OnBackInvokedDispatcher;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
+import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.taskbar.allapps.TaskbarAllAppsViewController.TaskbarAllAppsCallbacks;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
@@ -37,8 +44,11 @@ import com.android.launcher3.views.AbstractSlideInView;
/** Wrapper for taskbar all apps with slide-in behavior. */
public class TaskbarAllAppsSlideInView extends AbstractSlideInView
implements Insettable, DeviceProfile.OnDeviceProfileChangeListener {
+ private final Handler mHandler;
+
private TaskbarAllAppsContainerView mAppsView;
private float mShiftRange;
+ private @Nullable Runnable mShowOnFullyAttachedToWindowRunnable;
// Initialized in init.
private TaskbarAllAppsCallbacks mAllAppsCallbacks;
@@ -50,6 +60,7 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView showOnFullyAttachedToWindow(animate);
+ mHandler.post(mShowOnFullyAttachedToWindowRunnable);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ removeOnAttachStateChangeListener(this);
+ }
+ });
+ attachToContainer();
+ }
+
+ private void showOnFullyAttachedToWindow(boolean animate) {
+ mAllAppsCallbacks.onAllAppsTransitionStart(true);
+ if (!animate) {
+ mAllAppsCallbacks.onAllAppsTransitionEnd(true);
mTranslationShift = TRANSLATION_SHIFT_OPENED;
+ return;
}
+
+ setUpOpenAnimation(mAllAppsCallbacks.getOpenDuration());
+ Animator animator = mOpenCloseAnimation.getAnimationPlayer();
+ animator.setInterpolator(EMPHASIZED);
+ animator.addListener(AnimatorListeners.forEndCallback(() -> {
+ if (mIsOpen) {
+ mAllAppsCallbacks.onAllAppsTransitionEnd(true);
+ }
+ }));
+ animator.start();
+ }
+
+ @Override
+ protected void onOpenCloseAnimationPending(PendingAnimation animation) {
+ mAllAppsCallbacks.onAllAppsAnimationPending(
+ animation, mToTranslationShift == TRANSLATION_SHIFT_OPENED);
}
/** The apps container inside this view. */
@@ -81,9 +123,22 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView) = Unit
+
+ /** Updates the search suggestions shown in the zero-state. */
+ open fun setZeroStateSearchSuggestions(items: List) = Unit
+
+ override fun onAllAppsTransitionStart(toAllApps: Boolean) = Unit
+
+ override fun onAllAppsTransitionEnd(toAllApps: Boolean) = Unit
+
+ /** Creates a [PreDragCondition] for [view], if it is a search result that requires one. */
+ open fun createPreDragConditionForSearch(view: View): PreDragCondition? = null
+
+ open fun handleBackInvoked(): Boolean = false
+
+ open fun onAllAppsAnimationPending(animation: PendingAnimation, toAllApps: Boolean) = Unit
+
+ companion object {
+ @JvmStatic
+ fun newInstance(context: Context): TaskbarSearchSessionController {
+ if (!FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
+ return TaskbarSearchSessionController()
+ }
+
+ return Overrides.getObject(
+ TaskbarSearchSessionController::class.java,
+ context,
+ R.string.taskbar_search_session_controller_class,
+ )
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
index 7397159f42..1e3f4f13f2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
@@ -21,10 +21,10 @@ import android.graphics.ColorFilter
import android.graphics.Paint
import android.graphics.drawable.Drawable
import android.graphics.drawable.ShapeDrawable
+import com.android.app.animation.Interpolators
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
@@ -48,6 +48,8 @@ class BubbleBarBackground(context: TaskbarActivityContext, private val backgroun
private var showingArrow: Boolean = false
private var arrowDrawable: ShapeDrawable
+ var width: Float = 0f
+
init {
paint.color = context.getColor(R.color.taskbar_background)
paint.flags = Paint.ANTI_ALIAS_FLAG
@@ -59,8 +61,11 @@ class BubbleBarBackground(context: TaskbarActivityContext, private val backgroun
pointerSize = res.getDimension(R.dimen.bubblebar_pointer_size)
shadowAlpha =
- if (Utilities.isDarkTheme(context)) DARK_THEME_SHADOW_ALPHA
- else LIGHT_THEME_SHADOW_ALPHA
+ if (Utilities.isDarkTheme(context)) {
+ DARK_THEME_SHADOW_ALPHA
+ } else {
+ LIGHT_THEME_SHADOW_ALPHA
+ }
arrowDrawable =
ShapeDrawable(TriangleShape.create(pointerSize, pointerSize, /* pointUp= */ true))
@@ -102,7 +107,7 @@ class BubbleBarBackground(context: TaskbarActivityContext, private val backgroun
// Draw background.
val radius = backgroundHeight / 2f
canvas.drawRoundRect(
- 0f,
+ canvas.width.toFloat() - width,
0f,
canvas.width.toFloat(),
canvas.height.toFloat(),
@@ -132,4 +137,8 @@ class BubbleBarBackground(context: TaskbarActivityContext, private val backgroun
override fun setColorFilter(colorFilter: ColorFilter?) {
paint.colorFilter = colorFilter
}
+
+ fun setArrowAlpha(alpha: Int) {
+ arrowDrawable.paint.alpha = alpha
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 6d196924b3..dcae460379 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -31,19 +31,27 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_Q
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 static java.lang.Math.abs;
+
import android.annotation.BinderThread;
import android.annotation.Nullable;
+import android.app.Notification;
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.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Path;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.InsetDrawable;
import android.os.Bundle;
+import android.os.Process;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.ArrayMap;
@@ -51,6 +59,8 @@ import android.util.Log;
import android.util.PathParser;
import android.view.LayoutInflater;
+import androidx.appcompat.content.res.AppCompatResources;
+
import com.android.internal.graphics.ColorUtils;
import com.android.launcher3.R;
import com.android.launcher3.icons.BitmapInfo;
@@ -71,10 +81,13 @@ 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
+ * 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.
+ * {@link BubbleBarViewController} which will then update {@link BubbleBarView}
+ * as needed.
*
* For details around the behavior of the bubble bar, see {@link BubbleBarView}.
*/
@@ -84,8 +97,7 @@ public class BubbleBarController extends IBubblesListener.Stub {
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);
+ 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
@@ -112,15 +124,18 @@ public class BubbleBarController extends IBubblesListener.Stub {
private final Executor mMainExecutor;
private final LauncherApps mLauncherApps;
private final BubbleIconFactory mIconFactory;
+ private final SystemUiProxy mSystemUiProxy;
- private BubbleBarBubble mSelectedBubble;
+ private BubbleBarItem mSelectedBubble;
+ private BubbleBarOverflow mOverflowBubble;
private BubbleBarViewController mBubbleBarViewController;
private BubbleStashController mBubbleStashController;
private BubbleStashedHandleViewController mBubbleStashedHandleViewController;
/**
- * Similar to {@link BubbleBarUpdate} but rather than {@link BubbleInfo}s it uses
+ * 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 {
@@ -152,8 +167,10 @@ public class BubbleBarController extends IBubblesListener.Stub {
mContext = context;
mBarView = bubbleView; // Need the view for inflating bubble views.
+ mSystemUiProxy = SystemUiProxy.INSTANCE.get(context);
+
if (BUBBLE_BAR_ENABLED) {
- SystemUiProxy.INSTANCE.get(context).setBubblesListener(this);
+ mSystemUiProxy.setBubblesListener(this);
}
mMainExecutor = MAIN_EXECUTOR;
mLauncherApps = context.getSystemService(LauncherApps.class);
@@ -166,7 +183,7 @@ public class BubbleBarController extends IBubblesListener.Stub {
}
public void onDestroy() {
- SystemUiProxy.INSTANCE.get(mContext).setBubblesListener(null);
+ mSystemUiProxy.setBubblesListener(null);
}
public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
@@ -177,11 +194,38 @@ public class BubbleBarController extends IBubblesListener.Stub {
bubbleControllers.runAfterInit(() -> {
mBubbleBarViewController.setHiddenForBubbles(!BUBBLE_BAR_ENABLED);
mBubbleStashedHandleViewController.setHiddenForBubbles(!BUBBLE_BAR_ENABLED);
+ mBubbleBarViewController.setUpdateSelectedBubbleAfterCollapse(
+ key -> setSelectedBubble(mBubbles.get(key)));
});
}
/**
- * Updates the bubble bar, handle bar, and stash controllers based on sysui state flags.
+ * Creates and adds the overflow bubble to the bubble bar if it hasn't been
+ * created yet.
+ *
+ *
+ * This should be called on the {@link #BUBBLE_STATE_EXECUTOR} executor to avoid
+ * inflating
+ * the overflow multiple times.
+ */
+ private void createAndAddOverflowIfNeeded() {
+ if (mOverflowBubble == null) {
+ BubbleBarOverflow overflow = createOverflow(mContext);
+ mMainExecutor.execute(() -> {
+ // we're on the main executor now, so check that the overflow hasn't been
+ // created
+ // again to avoid races.
+ if (mOverflowBubble == null) {
+ mBubbleBarViewController.addBubble(overflow);
+ mOverflowBubble = overflow;
+ }
+ });
+ }
+ }
+
+ /**
+ * 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;
@@ -209,18 +253,21 @@ public class BubbleBarController extends IBubblesListener.Stub {
|| !update.currentBubbleList.isEmpty()) {
// We have bubbles to load
BUBBLE_STATE_EXECUTOR.execute(() -> {
+ createAndAddOverflowIfNeeded();
if (update.addedBubble != null) {
- viewUpdate.addedBubble = populateBubble(update.addedBubble, mContext, mBarView);
+ viewUpdate.addedBubble = populateBubble(mContext, update.addedBubble, mBarView,
+ null /* existingBubble */);
}
if (update.updatedBubble != null) {
- viewUpdate.updatedBubble =
- populateBubble(update.updatedBubble, mContext, mBarView);
+ BubbleBarBubble existingBubble = mBubbles.get(update.updatedBubble.getKey());
+ viewUpdate.updatedBubble = populateBubble(mContext, update.updatedBubble, mBarView,
+ existingBubble);
}
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);
+ BubbleBarBubble b = populateBubble(mContext, update.currentBubbleList.get(i), mBarView,
+ null /* existingBubble */);
currentBubbles.add(b);
}
viewUpdate.currentBubbles = currentBubbles;
@@ -237,6 +284,7 @@ public class BubbleBarController extends IBubblesListener.Stub {
private void applyViewChanges(BubbleBarViewUpdate update) {
final boolean isCollapsed = (update.expandedChanged && !update.expanded)
|| (!update.expandedChanged && !mBubbleBarViewController.isExpanded());
+ BubbleBarItem previouslySelectedBubble = mSelectedBubble;
BubbleBarBubble bubbleToSelect = null;
if (!update.removedBubbles.isEmpty()) {
for (int i = 0; i < update.removedBubbles.size(); i++) {
@@ -260,7 +308,8 @@ public class BubbleBarController extends IBubblesListener.Stub {
}
if (update.currentBubbles != null && !update.currentBubbles.isEmpty()) {
- // Iterate in reverse because new bubbles are added in front and the list is in order.
+ // 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) {
@@ -277,14 +326,23 @@ public class BubbleBarController extends IBubblesListener.Stub {
}
}
- // Adds and removals have happened, update visibility before any other visual changes.
+ // Adds and removals have happened, update visibility before any other visual
+ // changes.
mBubbleBarViewController.setHiddenForBubbles(mBubbles.isEmpty());
mBubbleStashedHandleViewController.setHiddenForBubbles(mBubbles.isEmpty());
+ if (mBubbles.isEmpty()) {
+ // all bubbles were removed. clear the selected bubble
+ mSelectedBubble = null;
+ }
+
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
+ // Updates mean the dot state may have changed; any other changes were updated
+ // in
+ // the populateBubble step.
+ BubbleBarBubble bb = mBubbles.get(update.updatedBubble.getKey());
+ // If we're not stashed, we're visible so animate
+ bb.getView().updateDotVisibility(!mBubbleStashController.isStashed() /* animate */);
}
if (update.bubbleKeysInOrder != null && !update.bubbleKeysInOrder.isEmpty()) {
// Create the new list
@@ -301,8 +359,8 @@ public class BubbleBarController extends IBubblesListener.Stub {
// TODO: (b/273316505) handle suppression
}
if (update.selectedBubbleKey != null) {
- if (mSelectedBubble != null
- && !update.selectedBubbleKey.equals(mSelectedBubble.getKey())) {
+ if (mSelectedBubble == null
+ || !update.selectedBubbleKey.equals(mSelectedBubble.getKey())) {
BubbleBarBubble newlySelected = mBubbles.get(update.selectedBubbleKey);
if (newlySelected != null) {
bubbleToSelect = newlySelected;
@@ -314,7 +372,11 @@ public class BubbleBarController extends IBubblesListener.Stub {
}
if (bubbleToSelect != null) {
setSelectedBubble(bubbleToSelect);
+ if (previouslySelectedBubble == null) {
+ mBubbleStashController.animateToInitialState(update.expanded);
+ }
}
+
if (update.expandedChanged) {
if (update.expanded != mBubbleBarViewController.isExpanded()) {
mBubbleBarViewController.setExpandedFromSysui(update.expanded);
@@ -324,14 +386,47 @@ public class BubbleBarController extends IBubblesListener.Stub {
}
}
+ /** Tells WMShell to show the currently selected bubble. */
+ public void showSelectedBubble() {
+ if (getSelectedBubbleKey() != null) {
+ if (mSelectedBubble instanceof BubbleBarBubble) {
+ // Because we've visited this bubble, we should suppress the notification.
+ // This is updated on WMShell side when we show the bubble, but that update
+ // isn't
+ // passed to launcher, instead we apply it directly here.
+ BubbleInfo info = ((BubbleBarBubble) mSelectedBubble).getInfo();
+ info.setFlags(
+ info.getFlags() | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
+ mSelectedBubble.getView().updateDotVisibility(true /* animate */);
+ }
+ mSystemUiProxy.showBubble(getSelectedBubbleKey(),
+ getBubbleBarOffsetX(), getBubbleBarOffsetY());
+ } else {
+ Log.w(TAG, "Trying to show the selected bubble but it's null");
+ }
+ }
+
/**
- * 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}.
+ * Updates the currently selected bubble for launcher views and tells WMShell to
+ * show it.
*/
- public void setSelectedBubble(BubbleBarBubble b) {
+ public void showAndSelectBubble(BubbleBarItem b) {
+ if (DEBUG)
+ Log.w(TAG, "showingSelectedBubble: " + b.getKey());
+ setSelectedBubble(b);
+ showSelectedBubble();
+ }
+
+ /**
+ * 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 either
+ * {@link #showSelectedBubble()} or {@link #showAndSelectBubble(BubbleBarItem)}.
+ */
+ private void setSelectedBubble(BubbleBarItem b) {
if (!Objects.equals(b, mSelectedBubble)) {
- if (DEBUG) Log.w(TAG, "selectingBubble: " + b.getKey());
+ if (DEBUG)
+ Log.w(TAG, "selectingBubble: " + b.getKey());
mSelectedBubble = b;
mBubbleBarViewController.updateSelectedBubble(mSelectedBubble);
}
@@ -353,7 +448,8 @@ public class BubbleBarController extends IBubblesListener.Stub {
//
@Nullable
- private BubbleBarBubble populateBubble(BubbleInfo b, Context context, BubbleBarView bbv) {
+ private BubbleBarBubble populateBubble(Context context, BubbleInfo b, BubbleBarView bbv,
+ @Nullable BubbleBarBubble existingBubble) {
String appName;
Bitmap badgeBitmap;
Bitmap bubbleBitmap;
@@ -422,16 +518,68 @@ public class BubbleBarController extends IBubblesListener.Stub {
iconPath.transform(matrix);
dotPath = iconPath;
dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color,
- Color.WHITE, WHITE_SCRIM_ALPHA);
+ Color.WHITE, WHITE_SCRIM_ALPHA / 255f);
+ if (existingBubble == null) {
+ 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;
+ } else {
+ // If we already have a bubble (so it already has an inflated view), update it.
+ existingBubble.setInfo(b);
+ existingBubble.setBadge(badgeBitmap);
+ existingBubble.setIcon(bubbleBitmap);
+ existingBubble.setDotColor(dotColor);
+ existingBubble.setDotPath(dotPath);
+ existingBubble.setAppName(appName);
+ return existingBubble;
+ }
+ }
+
+ private BubbleBarOverflow createOverflow(Context context) {
+ Bitmap bitmap = createOverflowBitmap(context);
LayoutInflater inflater = LayoutInflater.from(context);
BubbleView bubbleView = (BubbleView) inflater.inflate(
- R.layout.bubblebar_item_view, bbv, false /* attachToRoot */);
+ R.layout.bubblebar_item_view, mBarView, false /* attachToRoot */);
+ BubbleBarOverflow overflow = new BubbleBarOverflow(bubbleView);
+ bubbleView.setOverflow(overflow, bitmap);
+ return overflow;
+ }
- BubbleBarBubble bubble = new BubbleBarBubble(b, bubbleView,
- badgeBitmap, bubbleBitmap, dotColor, dotPath, appName);
- bubbleView.setBubble(bubble);
- return bubble;
+ private Bitmap createOverflowBitmap(Context context) {
+ Drawable iconDrawable = AppCompatResources.getDrawable(mContext,
+ R.drawable.bubble_ic_overflow_button);
+
+ final TypedArray ta = mContext.obtainStyledAttributes(
+ new int[] {
+ com.android.internal.R.attr.materialColorOnPrimaryFixed,
+ com.android.internal.R.attr.materialColorPrimaryFixed
+ });
+ int overflowIconColor = ta.getColor(0, Color.WHITE);
+ int overflowBackgroundColor = ta.getColor(1, Color.BLACK);
+ ta.recycle();
+
+ iconDrawable.setTint(overflowIconColor);
+
+ int inset = context.getResources().getDimensionPixelSize(R.dimen.bubblebar_overflow_inset);
+ Drawable foreground = new InsetDrawable(iconDrawable, inset);
+ Drawable drawable = new AdaptiveIconDrawable(new ColorDrawable(overflowBackgroundColor),
+ foreground);
+
+ return mIconFactory.createBadgedIconBitmap(drawable, Process.myUserHandle(), true).icon;
+ }
+
+ private int getBubbleBarOffsetY() {
+ final int translation = (int) abs(mBubbleStashController.getBubbleBarTranslationY());
+ return translation + mBarView.getHeight();
+ }
+
+ private int getBubbleBarOffsetX() {
+ return mBarView.getWidth() + mBarView.getHorizontalMargin();
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBubble.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt
similarity index 65%
rename from quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBubble.kt
rename to quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt
index 3cd5f75934..43e21f4085 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBubble.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt
@@ -19,16 +19,19 @@ import android.graphics.Bitmap
import android.graphics.Path
import com.android.wm.shell.common.bubbles.BubbleInfo
+/** An entity in the bubble bar. */
+sealed class BubbleBarItem(open var key: String, open var view: BubbleView)
+
/** 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
-) {
+ var info: BubbleInfo,
+ override var view: BubbleView,
+ var badge: Bitmap,
+ var icon: Bitmap,
+ var dotColor: Int,
+ var dotPath: Path,
+ var appName: String
+) : BubbleBarItem(info.key, view)
- val key: String = info.key
-}
+/** Represents the overflow bubble in the bubble bar. */
+data class BubbleBarOverflow(override var view: BubbleView) : BubbleBarItem("Overflow", view)
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 0e1e0e1a1b..a77262f17e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.taskbar.bubbles;
+import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
import android.content.Context;
@@ -31,46 +32,64 @@ import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.views.ActivityContext;
import java.util.List;
+import java.util.function.Consumer;
/**
- * 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
+ * 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
+ * - 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"
+ * - 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
+ * - 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
+ * 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
+ * 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.
+ // 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 static final int WIDTH_ANIMATION_DURATION_MS = 200;
- private final TaskbarActivityContext mActivityContext;
private final BubbleBarBackground mBubbleBarBackground;
- // The current bounds of all the bubble bar.
+ /**
+ * The current bounds of all the bubble bar. Note that these bounds may not
+ * account for
+ * translation. The bounds should be retrieved using
+ * {@link #getBubbleBarBounds()} which
+ * updates the bounds and accounts for translation.
+ */
private final Rect mBubbleBarBounds = new Rect();
// The amount the bubbles overlap when they are stacked in the bubble bar
private final float mIconOverlapAmount;
@@ -89,13 +108,28 @@ public class BubbleBarView extends FrameLayout {
private View.OnClickListener mOnClickListener;
private final Rect mTempRect = new Rect();
+ private float mRelativePivotX = 1f;
+ private float mRelativePivotY = 1f;
- // 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
+ // An animator that represents the expansion state of the bubble bar, where 0
+ // corresponds to the
+ // collapsed state and 1 to the fully expanded state.
+ private final ValueAnimator mWidthAnimator = ValueAnimator.ofFloat(0, 1);
+
+ // 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;
+ @Nullable
+ private Consumer mUpdateSelectedBubbleAfterCollapse;
+
+ @Nullable
+ private BubbleView mDraggedBubbleView;
+
public BubbleBarView(Context context) {
this(context, null);
}
@@ -110,7 +144,7 @@ public class BubbleBarView extends FrameLayout {
public BubbleBarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- mActivityContext = ActivityContext.lookupContext(context);
+ TaskbarActivityContext activityContext = ActivityContext.lookupContext(context);
mIconOverlapAmount = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_overlap);
mIconSpacing = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_spacing);
@@ -118,9 +152,46 @@ public class BubbleBarView extends FrameLayout {
mBubbleElevation = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_elevation);
setClipToPadding(false);
- mBubbleBarBackground = new BubbleBarBackground(mActivityContext,
+ mBubbleBarBackground = new BubbleBarBackground(activityContext,
getResources().getDimensionPixelSize(R.dimen.bubblebar_size));
setBackgroundDrawable(mBubbleBarBackground);
+
+ mWidthAnimator.setDuration(WIDTH_ANIMATION_DURATION_MS);
+ mWidthAnimator.addUpdateListener(animation -> {
+ updateChildrenRenderNodeProperties();
+ invalidate();
+ });
+ mWidthAnimator.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mBubbleBarBackground.showArrow(mIsBarExpanded);
+ if (!mIsBarExpanded && mReorderRunnable != null) {
+ mReorderRunnable.run();
+ mReorderRunnable = null;
+ }
+ // If the bar was just collapsed and the overflow was the last bubble that was
+ // selected, set the first bubble as selected.
+ if (!mIsBarExpanded && mUpdateSelectedBubbleAfterCollapse != null
+ && mSelectedBubbleView.getBubble() instanceof BubbleBarOverflow) {
+ BubbleView firstBubble = (BubbleView) getChildAt(0);
+ mUpdateSelectedBubbleAfterCollapse.accept(firstBubble.getBubble().getKey());
+ }
+ updateWidth();
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mBubbleBarBackground.showArrow(true);
+ }
+ });
}
@Override
@@ -131,63 +202,167 @@ public class BubbleBarView extends FrameLayout {
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());
+ // The bubble bar handle is aligned according to the relative pivot,
+ // by default it's aligned to the bottom edge of the screen so scale towards
+ // that
+ setPivotX(mRelativePivotX * getWidth());
+ setPivotY(mRelativePivotY * getHeight());
// Position the views
updateChildrenRenderNodeProperties();
}
/**
- * Returns the bounds of the bubble bar.
+ * Updates the bounds with translation that may have been applied and returns
+ * the result.
*/
public Rect getBubbleBarBounds() {
+ mBubbleBarBounds.top = getTop() + (int) getTranslationY();
+ mBubbleBarBounds.bottom = getBottom() + (int) getTranslationY();
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);
+ /**
+ * Set bubble bar relative pivot value for X and Y, applied as a fraction of
+ * view width/height
+ * respectively. If the value is not in range of 0 to 1 it will be normalized.
+ *
+ * @param x relative X pivot value in range 0..1
+ * @param y relative Y pivot value in range 0..1
+ */
+ public void setRelativePivot(float x, float y) {
+ mRelativePivotX = Float.max(Float.min(x, 1), 0);
+ mRelativePivotY = Float.max(Float.min(y, 1), 0);
+ requestLayout();
}
/**
- * Updates the z order, positions, and badge visibility of the bubble views in the bar based
+ * Get current relative pivot for X axis
+ */
+ public float getRelativePivotX() {
+ return mRelativePivotX;
+ }
+
+ /**
+ * Get current relative pivot for Y axis
+ */
+ public float getRelativePivotY() {
+ return mRelativePivotY;
+ }
+
+ // TODO: (b/280605790) animate it
+ @Override
+ public void addView(View child, int index, ViewGroup.LayoutParams params) {
+ if (getChildCount() + 1 > MAX_BUBBLES) {
+ // the last child view is the overflow bubble and we shouldn't remove that.
+ // remove the
+ // second to last child view.
+ removeViewInLayout(getChildAt(getChildCount() - 2));
+ }
+ super.addView(child, index, params);
+ updateWidth();
+ }
+
+ // TODO: (b/283309949) animate it
+ @Override
+ public void removeView(View view) {
+ super.removeView(view);
+ updateWidth();
+ }
+
+ private void updateWidth() {
+ LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+ lp.width = (int) (mIsBarExpanded ? expandedWidth() : collapsedWidth());
+ setLayoutParams(lp);
+ }
+
+ /**
+ * @return the horizontal margin between the bubble bar and the edge of the
+ * screen.
+ */
+ int getHorizontalMargin() {
+ LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+ return lp.getMarginEnd();
+ }
+
+ /**
+ * 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() {
+ final float widthState = (float) mWidthAnimator.getAnimatedValue();
+ final float currentWidth = getWidth();
+ final float expandedWidth = expandedWidth();
+ final float collapsedWidth = collapsedWidth();
int bubbleCount = getChildCount();
final float ty = (mBubbleBarBounds.height() - mIconSize) / 2f;
+ final boolean animate = getVisibility() == VISIBLE;
for (int i = 0; i < bubbleCount; i++) {
BubbleView bv = (BubbleView) getChildAt(i);
bv.setTranslationY(ty);
+
+ // the position of the bubble when the bar is fully expanded
+ final float expandedX = i * (mIconSize + mIconSpacing);
+ // the position of the bubble when the bar is fully collapsed
+ final float collapsedX = i == 0 ? 0 : mIconOverlapAmount;
+
if (mIsBarExpanded) {
- final float tx = i * (mIconSize + mIconSpacing);
- bv.setTranslationX(tx);
- bv.setZ(0);
- bv.showBadge();
+ // where the bubble will end up when the animation ends
+ final float targetX = currentWidth - expandedWidth + expandedX;
+ bv.setTranslationX(widthState * (targetX - collapsedX) + collapsedX);
+ // if we're fully expanded, set the z level to 0 or to bubble elevation if
+ // dragged
+ if (widthState == 1f) {
+ bv.setZ(bv == mDraggedBubbleView ? mBubbleElevation : 0);
+ }
+ // When we're expanded, we're not stacked so we're not behind the stack
+ bv.setBehindStack(false, animate);
+ bv.setAlpha(1);
} else {
+ final float targetX = currentWidth - collapsedWidth + collapsedX;
+ bv.setTranslationX(widthState * (expandedX - targetX) + targetX);
bv.setZ((MAX_BUBBLES * mBubbleElevation) - i);
- bv.setTranslationX(i * mIconOverlapAmount);
- if (i > 0) {
- bv.hideBadge();
- } else {
- bv.showBadge();
+ // If we're not the first bubble we're behind the stack
+ bv.setBehindStack(i > 0, animate);
+ // If we're fully collapsed, hide all bubbles except for the first 2. If there
+ // are
+ // only 2 bubbles, hide the second bubble as well because it's the overflow.
+ if (widthState == 0) {
+ if (i > 1) {
+ bv.setAlpha(0);
+ } else if (i == 1 && bubbleCount == 2) {
+ bv.setAlpha(0);
+ }
}
}
}
+
+ // update the arrow position
+ final float collapsedArrowPosition = arrowPositionForSelectedWhenCollapsed();
+ final float expandedArrowPosition = arrowPositionForSelectedWhenExpanded();
+ final float interpolatedWidth = widthState * (expandedWidth - collapsedWidth) + collapsedWidth;
+ if (mIsBarExpanded) {
+ // when the bar is expanding, the selected bubble is always the first, so the
+ // arrow
+ // always shifts with the interpolated width.
+ final float arrowPosition = currentWidth - interpolatedWidth + collapsedArrowPosition;
+ mBubbleBarBackground.setArrowPosition(arrowPosition);
+ } else {
+ final float targetPosition = currentWidth - collapsedWidth + collapsedArrowPosition;
+ final float arrowPosition = targetPosition + widthState * (expandedArrowPosition - targetPosition);
+ mBubbleBarBackground.setArrowPosition(arrowPosition);
+ }
+
+ mBubbleBarBackground.setArrowAlpha((int) (255 * widthState));
+ mBubbleBarBackground.setWidth(interpolatedWidth);
}
/**
* Reorders the views to match the provided list.
*/
public void reorder(List viewOrder) {
- if (isExpanded()) {
+ if (isExpanded() || mWidthAnimator.isRunning()) {
mReorderRunnable = () -> doReorder(viewOrder);
} else {
doReorder(viewOrder);
@@ -199,7 +374,10 @@ public class BubbleBarView extends FrameLayout {
if (!isExpanded()) {
for (int i = 0; i < viewOrder.size(); i++) {
View child = viewOrder.get(i);
- if (child != null) {
+ // this child view may have already been removed so verify that it still exists
+ // before reordering it, otherwise it will be re-added.
+ int indexOfChild = indexOfChild(child);
+ if (child != null && indexOfChild >= 0) {
removeViewInLayout(child);
addViewInLayout(child, i, child.getLayoutParams());
}
@@ -208,6 +386,11 @@ public class BubbleBarView extends FrameLayout {
}
}
+ public void setUpdateSelectedBubbleAfterCollapse(
+ Consumer updateSelectedBubbleAfterCollapse) {
+ mUpdateSelectedBubbleAfterCollapse = updateSelectedBubbleAfterCollapse;
+ }
+
/**
* Sets which bubble view should be shown as selected.
*/
@@ -216,11 +399,22 @@ public class BubbleBarView extends FrameLayout {
updateArrowForSelected(/* shouldAnimate= */ true);
}
+ /**
+ * Sets the dragged bubble view to correctly apply Z order. Dragged view should
+ * appear on top
+ */
+ public void setDraggedBubble(@Nullable BubbleView view) {
+ mDraggedBubbleView = view;
+ requestLayout();
+ }
+
/**
* 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}.
+ * @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) {
@@ -228,7 +422,8 @@ public class BubbleBarView extends FrameLayout {
return;
}
final int index = indexOfChild(mSelectedBubbleView);
- // Find the center of the bubble when it's expanded, set the arrow position to it.
+ // 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) {
@@ -247,6 +442,16 @@ public class BubbleBarView extends FrameLayout {
}
}
+ private float arrowPositionForSelectedWhenExpanded() {
+ final int index = indexOfChild(mSelectedBubbleView);
+ return getPaddingStart() + index * (mIconSize + mIconSpacing) + mIconSize / 2f;
+ }
+
+ private float arrowPositionForSelectedWhenCollapsed() {
+ final int index = indexOfChild(mSelectedBubbleView);
+ return getPaddingStart() + index * (mIconOverlapAmount) + mIconSize / 2f;
+ }
+
@Override
public void setOnClickListener(View.OnClickListener listener) {
mOnClickListener = listener;
@@ -254,8 +459,10 @@ public class BubbleBarView extends FrameLayout {
}
/**
- * 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.
+ * 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);
@@ -264,18 +471,16 @@ public class BubbleBarView extends FrameLayout {
/**
* 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;
+ if (isBarExpanded) {
+ mWidthAnimator.start();
+ } else {
+ mWidthAnimator.reverse();
}
- mBubbleBarBackground.showArrow(mIsBarExpanded);
- requestLayout(); // trigger layout to reposition views & update size for expansion
}
}
@@ -286,23 +491,27 @@ public class BubbleBarView extends FrameLayout {
return mIsBarExpanded;
}
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ private float expandedWidth() {
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));
+ final int horizontalPadding = getPaddingStart() + getPaddingEnd();
+ return childCount * (mIconSize + mIconSpacing) + horizontalPadding;
+ }
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- measureChild(child, (int) mIconSize, (int) mIconSize);
- }
+ private float collapsedWidth() {
+ final int childCount = getChildCount();
+ final int horizontalPadding = getPaddingStart() + getPaddingEnd();
+ // If there are more than 2 bubbles, the first 2 should be visible when
+ // collapsed.
+ // Otherwise just the first bubble should be visible because we don't show the
+ // overflow.
+ return childCount > 2
+ ? mIconSize + mIconOverlapAmount + horizontalPadding
+ : mIconSize + horizontalPadding;
}
/**
- * Returns whether the given MotionEvent, *in screen coordinates*, is within bubble bar
+ * Returns whether the given MotionEvent, *in screen coordinates*, is within
+ * bubble bar
* touch bounds.
*/
public boolean isEventOverAnyItem(MotionEvent ev) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 82494c6fda..f3c7273e16 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -24,19 +24,25 @@ import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
+import androidx.annotation.NonNull;
+
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.taskbar.TaskbarInsetsController;
+import com.android.launcher3.taskbar.TaskbarStashController;
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;
+import java.util.function.Consumer;
/**
- * Controller for {@link BubbleBarView}. Manages the visibility of the bubble bar as well as
+ * 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 {
@@ -51,10 +57,14 @@ public class BubbleBarViewController {
// Initialized in init.
private BubbleStashController mBubbleStashController;
private BubbleBarController mBubbleBarController;
+ private BubbleDragController mBubbleDragController;
+ private TaskbarStashController mTaskbarStashController;
+ private TaskbarInsetsController mTaskbarInsetsController;
private View.OnClickListener mBubbleClickListener;
private View.OnClickListener mBubbleBarClickListener;
- // These are exposed to {@link BubbleStashController} to animate for stashing/un-stashing
+ // 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(
@@ -80,20 +90,24 @@ public class BubbleBarViewController {
public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
mBubbleStashController = bubbleControllers.bubbleStashController;
mBubbleBarController = bubbleControllers.bubbleBarController;
+ mBubbleDragController = bubbleControllers.bubbleDragController;
+ mTaskbarStashController = controllers.taskbarStashController;
+ mTaskbarInsetsController = controllers.taskbarInsetsController;
- mActivity.addOnDeviceProfileChangeListener(dp ->
- mBarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarHeight
- );
+ 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);
+ mBubbleDragController.setupBubbleBarView(mBarView);
mBarView.setOnClickListener(mBubbleBarClickListener);
- // TODO: when barView layout changes tell taskbarInsetsController the insets have changed.
+ mBarView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) -> mTaskbarInsetsController
+ .onTaskbarOrBubblebarWindowHeightOrInsetsChanged());
}
private void onBubbleClicked(View v) {
- BubbleBarBubble bubble = ((BubbleView) v).getBubble();
+ BubbleBarItem bubble = ((BubbleView) v).getBubble();
if (bubble == null) {
Log.e(TAG, "bubble click listener, bubble was null");
}
@@ -103,14 +117,13 @@ public class BubbleBarViewController {
setExpanded(false);
mBubbleStashController.stashBubbleBar();
} else {
- mBubbleBarController.setSelectedBubble(bubble);
- mSystemUiProxy.showBubble(bubble.getKey(),
- mBubbleStashController.isBubblesShowingOnHome());
+ mBubbleBarController.showAndSelectBubble(bubble);
}
}
//
- // The below animators are exposed to BubbleStashController so it can manage the stashing
+ // The below animators are exposed to BubbleStashController so it can manage the
+ // stashing
// animation.
//
@@ -133,6 +146,11 @@ public class BubbleBarViewController {
return mBarView.getVisibility() == VISIBLE;
}
+ /** Whether the bubble bar has bubbles. */
+ public boolean hasBubbles() {
+ return mBubbleBarController.getSelectedBubbleKey() != null;
+ }
+
/**
* The bounds of the bubble bar.
*/
@@ -140,9 +158,16 @@ public class BubbleBarViewController {
return mBarView.getBubbleBarBounds();
}
+ /** The horizontal margin of the bubble bar from the edge of the screen. */
+ public int getHorizontalMargin() {
+ return mBarView.getHorizontalMargin();
+ }
+
/**
- * 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.
+ * 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();
@@ -177,7 +202,17 @@ public class BubbleBarViewController {
}
/**
- * Sets whether the bubble bar should be hidden due to SysUI state (e.g. on lockscreen).
+ * Sets a callback that updates the selected bubble after the bubble bar
+ * collapses.
+ */
+ public void setUpdateSelectedBubbleAfterCollapse(
+ Consumer updateSelectedBubbleAfterCollapse) {
+ mBarView.setUpdateSelectedBubbleAfterCollapse(updateSelectedBubbleAfterCollapse);
+ }
+
+ /**
+ * Sets whether the bubble bar should be hidden due to SysUI state (e.g. on
+ * lockscreen).
*/
public void setHiddenForSysui(boolean hidden) {
if (mHiddenForSysui != hidden) {
@@ -188,10 +223,12 @@ public class BubbleBarViewController {
// TODO: (b/273592694) animate it
private void updateVisibilityForStateChange() {
- if (!mHiddenForSysui && !mBubbleStashController.isStashed() && !mHiddenForNoBubbles) {
+ if (!mHiddenForSysui && !mHiddenForNoBubbles) {
mBarView.setVisibility(VISIBLE);
} else {
mBarView.setVisibility(INVISIBLE);
+ mBarView.setAlpha(0);
+ mBarView.setExpanded(false);
}
}
@@ -228,7 +265,7 @@ public class BubbleBarViewController {
/**
* Removes the provided bubble from the bubble bar.
*/
- public void removeBubble(BubbleBarBubble b) {
+ public void removeBubble(BubbleBarItem b) {
if (b != null) {
mBarView.removeView(b.getView());
} else {
@@ -239,10 +276,11 @@ public class BubbleBarViewController {
/**
* Adds the provided bubble to the bubble bar.
*/
- public void addBubble(BubbleBarBubble b) {
+ public void addBubble(BubbleBarItem b) {
if (b != null) {
mBarView.addView(b.getView(), 0, new FrameLayout.LayoutParams(mIconSize, mIconSize));
b.getView().setOnClickListener(mBubbleClickListener);
+ mBubbleDragController.setupBubbleView(b.getView());
} else {
Log.w(TAG, "addBubble, bubble was null!");
}
@@ -260,14 +298,17 @@ public class BubbleBarViewController {
/**
* Updates the selected bubble.
*/
- public void updateSelectedBubble(BubbleBarBubble newlySelected) {
+ public void updateSelectedBubble(BubbleBarItem 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
+ * 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) {
@@ -276,20 +317,16 @@ public class BubbleBarViewController {
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
+ mBubbleBarController.showSelectedBubble();
+ mTaskbarStashController.updateAndAnimateTransientTaskbar(true /* stash */,
+ false /* shouldBubblesFollow */);
}
}
}
/**
- * Sets whether the bubble bar should be expanded. This method is used in response to UI events
+ * Sets whether the bubble bar should be expanded. This method is used in
+ * response to UI events
* from SystemUI.
*/
public void setExpandedFromSysui(boolean isExpanded) {
@@ -299,4 +336,51 @@ public class BubbleBarViewController {
mBubbleStashController.showBubbleBar(true /* expand the bubbles */);
}
}
+
+ /**
+ * Updates the dragged bubble view in the bubble bar view, and notifies SystemUI
+ * that a bubble is being dragged to dismiss.
+ *
+ * @param bubbleView dragged bubble view
+ */
+ public void onDragStart(@NonNull BubbleView bubbleView) {
+ if (bubbleView.getBubble() == null)
+ return;
+ mSystemUiProxy.onBubbleDrag(bubbleView.getBubble().getKey(), /* isBeingDragged = */ true);
+ mBarView.setDraggedBubble(bubbleView);
+ }
+
+ /**
+ * Notifies SystemUI to expand the selected bubble when the bubble is released.
+ *
+ * @param bubbleView dragged bubble view
+ */
+ public void onDragRelease(@NonNull BubbleView bubbleView) {
+ if (bubbleView.getBubble() == null)
+ return;
+ mSystemUiProxy.onBubbleDrag(bubbleView.getBubble().getKey(), /* isBeingDragged = */ false);
+ }
+
+ /**
+ * Removes the dragged bubble view in the bubble bar view
+ */
+ public void onDragEnd() {
+ mBarView.setDraggedBubble(null);
+ }
+
+ /**
+ * Called when bubble was dragged into the dismiss target. Notifies System
+ *
+ * @param bubble dismissed bubble item
+ */
+ public void onDismissBubbleWhileDragging(@NonNull BubbleBarItem bubble) {
+ mSystemUiProxy.removeBubble(bubble.getKey());
+ }
+
+ /**
+ * Called when bubble stack was dragged into the dismiss target
+ */
+ public void onDismissAllBubblesWhileDragging() {
+ mSystemUiProxy.removeAllBubbles();
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
index 6417f3c585..50bc3b3975 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
@@ -27,28 +27,36 @@ public class BubbleControllers {
public final BubbleBarViewController bubbleBarViewController;
public final BubbleStashController bubbleStashController;
public final BubbleStashedHandleViewController bubbleStashedHandleViewController;
+ public final BubbleDragController bubbleDragController;
+ public final BubbleDismissController bubbleDismissController;
private final RunnableList mPostInitRunnables = new RunnableList();
/**
* Want to add a new controller? Don't forget to:
- * * Call init
- * * Call onDestroy
+ * * Call init
+ * * Call onDestroy
*/
public BubbleControllers(
BubbleBarController bubbleBarController,
BubbleBarViewController bubbleBarViewController,
BubbleStashController bubbleStashController,
- BubbleStashedHandleViewController bubbleStashedHandleViewController) {
+ BubbleStashedHandleViewController bubbleStashedHandleViewController,
+ BubbleDragController bubbleDragController,
+ BubbleDismissController bubbleDismissController) {
this.bubbleBarController = bubbleBarController;
this.bubbleBarViewController = bubbleBarViewController;
this.bubbleStashController = bubbleStashController;
this.bubbleStashedHandleViewController = bubbleStashedHandleViewController;
+ this.bubbleDragController = bubbleDragController;
+ this.bubbleDismissController = bubbleDismissController;
}
/**
- * 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
+ * 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) {
@@ -56,14 +64,19 @@ public class BubbleControllers {
bubbleBarViewController.init(taskbarControllers, this);
bubbleStashedHandleViewController.init(taskbarControllers, this);
bubbleStashController.init(taskbarControllers, this);
+ bubbleDragController.init(/* bubbleControllers = */ this);
+ bubbleDismissController.init(/* bubbleControllers = */ 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().
+ * 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.
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissController.java
new file mode 100644
index 0000000000..838df35c0f
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissController.java
@@ -0,0 +1,208 @@
+/*
+ * 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.ViewGroup.LayoutParams.MATCH_PARENT;
+
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+
+import com.android.launcher3.R;
+import com.android.launcher3.taskbar.TaskbarActivityContext;
+import com.android.launcher3.taskbar.TaskbarDragLayer;
+
+import app.lawnchair.bubbles.DismissView;
+import app.lawnchair.common.MagnetizedObject;
+
+
+/**
+ * Controls dismiss view presentation for the bubble bar dismiss functionality.
+ * Provides the dragged view snapping to the target dismiss area and animates it.
+ * When the dragged bubble/bubble stack is released inside of the target area, it gets dismissed.
+ *
+ * @see BubbleDragController
+ */
+public class BubbleDismissController {
+ private static final String TAG = BubbleDismissController.class.getSimpleName();
+ private static final float FLING_TO_DISMISS_MIN_VELOCITY = 6000f;
+ private final TaskbarActivityContext mActivity;
+ private final TaskbarDragLayer mDragLayer;
+ @Nullable
+ private BubbleBarViewController mBubbleBarViewController;
+
+ // Dismiss view that's attached to drag layer. It consists of the scrim view and the circular
+ // dismiss view used as a dismiss target.
+ @Nullable
+ private DismissView mDismissView;
+
+ // The currently magnetized object, which is being dragged and will be attracted to the magnetic
+ // dismiss target. This is either the stack itself, or an individual bubble.
+ @Nullable
+ private MagnetizedObject mMagnetizedObject;
+
+ // The MagneticTarget instance for our circular dismiss view. This is added to the
+ // MagnetizedObject instances for the stack and any dragged-out bubbles.
+ @Nullable
+ private MagnetizedObject.MagneticTarget mMagneticTarget;
+
+ // The bubble drag animator that synchronizes bubble drag and dismiss view animations
+ // A new instance is provided when the dismiss view is setup
+ @Nullable
+ private BubbleDragAnimator mAnimator;
+
+ public BubbleDismissController(TaskbarActivityContext activity, TaskbarDragLayer dragLayer) {
+ mActivity = activity;
+ mDragLayer = dragLayer;
+ }
+
+ /**
+ * Initializes dependencies when bubble controllers are created.
+ * Should be careful to only access things that were created in constructors for now, as some
+ * controllers may still be waiting for init().
+ */
+ public void init(@NonNull BubbleControllers bubbleControllers) {
+ mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
+ }
+
+ /**
+ * Setup the dismiss view and magnetized object that will be attracted to magnetic target.
+ * Should be called before handling events or showing/hiding dismiss view.
+ *
+ * @param magnetizedView the view to be pulled into target dismiss area
+ * @param animator the bubble animator to be used for the magnetized view, it syncs bubble
+ * dragging and dismiss animations with the dismiss view provided.
+ */
+ public void setupDismissView(@NonNull View magnetizedView,
+ @NonNull BubbleDragAnimator animator) {
+ setupDismissView();
+ setupMagnetizedObject(magnetizedView);
+ if (mDismissView != null) {
+ animator.setDismissView(mDismissView);
+ mAnimator = animator;
+ }
+ }
+
+ /**
+ * Handle the touch event and pass it to the magnetized object.
+ * It should be called after {@code setupDismissView}
+ */
+ public boolean handleTouchEvent(@NonNull MotionEvent event) {
+ return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event);
+ }
+
+ /**
+ * Show dismiss view with animation
+ * It should be called after {@code setupDismissView}
+ */
+ public void showDismissView() {
+ if (mDismissView == null) return;
+ mDismissView.show();
+ }
+
+ /**
+ * Hide dismiss view with animation
+ * It should be called after {@code setupDismissView}
+ */
+ public void hideDismissView() {
+ if (mDismissView == null) return;
+ mDismissView.hide();
+ }
+
+ /**
+ * Dismiss magnetized object when it's released in the dismiss target area
+ */
+ private void dismissMagnetizedObject() {
+ if (mMagnetizedObject == null || mBubbleBarViewController == null) return;
+ if (mMagnetizedObject.getUnderlyingObject() instanceof BubbleView) {
+ BubbleView bubbleView = (BubbleView) mMagnetizedObject.getUnderlyingObject();
+ if (bubbleView.getBubble() != null) {
+ mBubbleBarViewController.onDismissBubbleWhileDragging(bubbleView.getBubble());
+ }
+ } else if (mMagnetizedObject.getUnderlyingObject() instanceof BubbleBarView) {
+ mBubbleBarViewController.onDismissAllBubblesWhileDragging();
+ }
+ }
+
+ private void setupDismissView() {
+ if (mDismissView != null) return;
+ mDismissView = new DismissView(mActivity.getApplicationContext());
+ BubbleDismissViewUtils.setup(mDismissView);
+ mDragLayer.addView(mDismissView, /* index = */ 0,
+ new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ mDismissView.setElevation(mDismissView.getResources().getDimensionPixelSize(
+ R.dimen.bubblebar_elevation));
+ setupMagneticTarget(mDismissView.getCircle());
+ }
+
+ private void setupMagneticTarget(@NonNull View view) {
+ int magneticFieldRadius = mActivity.getResources().getDimensionPixelSize(
+ R.dimen.bubblebar_dismiss_target_size);
+ mMagneticTarget = new MagnetizedObject.MagneticTarget(view, magneticFieldRadius);
+ }
+
+ private void setupMagnetizedObject(@NonNull View magnetizedView) {
+ mMagnetizedObject = new MagnetizedObject<>(mActivity.getApplicationContext(),
+ magnetizedView, DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y) {
+ @Override
+ public float getWidth(@NonNull View underlyingObject) {
+ return underlyingObject.getWidth() * underlyingObject.getScaleX();
+ }
+
+ @Override
+ public float getHeight(@NonNull View underlyingObject) {
+ return underlyingObject.getHeight() * underlyingObject.getScaleY();
+ }
+
+ @Override
+ public void getLocationOnScreen(@NonNull View underlyingObject, @NonNull int[] loc) {
+ underlyingObject.getLocationOnScreen(loc);
+ }
+ };
+
+ mMagnetizedObject.setHapticsEnabled(true);
+ mMagnetizedObject.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
+ if (mMagneticTarget != null) {
+ mMagnetizedObject.addTarget(mMagneticTarget);
+ } else {
+ Log.e(TAG,"Requires MagneticTarget to add target to MagnetizedObject!");
+ }
+ mMagnetizedObject.setMagnetListener(new MagnetizedObject.MagnetListener() {
+ @Override
+ public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ if (mAnimator == null) return;
+ mAnimator.animateDismissCaptured();
+ }
+
+ @Override
+ public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ float velX, float velY, boolean wasFlungOut) {
+ if (mAnimator == null) return;
+ mAnimator.animateDismissReleased();
+ }
+
+ @Override
+ public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ dismissMagnetizedObject();
+ }
+ });
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissViewExt.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissViewExt.kt
new file mode 100644
index 0000000000..978dc34585
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissViewExt.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+@file:JvmName("BubbleDismissViewUtils")
+
+package com.android.launcher3.taskbar.bubbles
+
+import app.lawnchair.bubbles.DismissView
+import com.android.launcher3.R
+
+/**
+ * Dismiss view is shared from WMShell. It requires setup with local resources.
+ *
+ * Usage:
+ * - Kotlin `dismissView.setup()`
+ * - Java `BubbleDismissViewUtils.setup(dismissView)`
+ */
+fun DismissView.setup() {
+ setup(
+ DismissView.Config(
+ targetSizeResId = R.dimen.bubblebar_dismiss_target_size,
+ iconSizeResId = R.dimen.bubblebar_dismiss_target_icon_size,
+ bottomMarginResId = R.dimen.bubblebar_dismiss_target_bottom_margin,
+ floatingGradientHeightResId = R.dimen.bubblebar_dismiss_floating_gradient_height,
+ floatingGradientColorResId = R.color.system_neutral1_900,
+ backgroundResId = R.drawable.bg_bubble_dismiss_circle,
+ iconResId = R.drawable.ic_bubble_dismiss_white
+ )
+ )
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragAnimator.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragAnimator.java
new file mode 100644
index 0000000000..212bdd80ad
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragAnimator.java
@@ -0,0 +1,223 @@
+/*
+ * 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 androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY;
+import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW;
+import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
+
+import android.content.res.Resources;
+import android.graphics.PointF;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+
+import com.android.launcher3.R;
+import com.android.wm.shell.common.bubbles.DismissCircleView;
+
+import app.lawnchair.animation.PhysicsAnimator;
+import app.lawnchair.bubbles.DismissView;
+
+/**
+ * The animator performs the bubble animations while dragging and coordinates bubble and dismiss
+ * view animations when it gets magnetized, released or dismissed.
+ */
+public class BubbleDragAnimator {
+ private static final float SCALE_BUBBLE_FOCUSED = 1.2f;
+ private static final float SCALE_BUBBLE_CAPTURED = 0.9f;
+ private static final float SCALE_BUBBLE_BAR_FOCUSED = 1.1f;
+
+ private final PhysicsAnimator.SpringConfig mDefaultConfig =
+ new PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY);
+ private final PhysicsAnimator.SpringConfig mTranslationConfig =
+ new PhysicsAnimator.SpringConfig(STIFFNESS_MEDIUM, DAMPING_RATIO_LOW_BOUNCY);
+ @NonNull
+ private final View mView;
+ @NonNull
+ private final PhysicsAnimator mBubbleAnimator;
+ @Nullable
+ private DismissView mDismissView;
+ @Nullable
+ private PhysicsAnimator mDismissAnimator;
+ private final float mBubbleFocusedScale;
+ private final float mBubbleCapturedScale;
+ private final float mDismissCapturedScale;
+
+ /**
+ * Should be initialised for each dragged view
+ *
+ * @param view the dragged view to animate
+ */
+ public BubbleDragAnimator(@NonNull View view) {
+ mView = view;
+ mBubbleAnimator = PhysicsAnimator.getInstance(view);
+ mBubbleAnimator.setDefaultSpringConfig(mDefaultConfig);
+
+ Resources resources = view.getResources();
+ final int collapsedSize = resources.getDimensionPixelSize(
+ R.dimen.bubblebar_dismiss_target_small_size);
+ final int expandedSize = resources.getDimensionPixelSize(
+ R.dimen.bubblebar_dismiss_target_size);
+ mDismissCapturedScale = (float) collapsedSize / expandedSize;
+
+ if (view instanceof BubbleBarView) {
+ mBubbleFocusedScale = SCALE_BUBBLE_BAR_FOCUSED;
+ mBubbleCapturedScale = mDismissCapturedScale;
+ } else {
+ mBubbleFocusedScale = SCALE_BUBBLE_FOCUSED;
+ mBubbleCapturedScale = SCALE_BUBBLE_CAPTURED;
+ }
+ }
+
+ /**
+ * Sets dismiss view to be animated alongside the dragged bubble
+ */
+ public void setDismissView(@NonNull DismissView dismissView) {
+ mDismissView = dismissView;
+ mDismissAnimator = PhysicsAnimator.getInstance(dismissView.getCircle());
+ mDismissAnimator.setDefaultSpringConfig(mDefaultConfig);
+ }
+
+ /**
+ * Animates the focused state of the bubble when the dragging starts
+ */
+ public void animateFocused() {
+ mBubbleAnimator.cancel();
+ mBubbleAnimator
+ .spring(DynamicAnimation.SCALE_X, mBubbleFocusedScale)
+ .spring(DynamicAnimation.SCALE_Y, mBubbleFocusedScale)
+ .start();
+ }
+
+ /**
+ * Animates the dragged bubble movement back to the initial position.
+ *
+ * @param initialPosition the position to animate to
+ * @param velocity the initial velocity to use for the spring animation
+ * @param endActions gets called when the animation completes or gets cancelled
+ */
+ public void animateToInitialState(@NonNull PointF initialPosition, @NonNull PointF velocity,
+ @Nullable Runnable endActions) {
+ mBubbleAnimator.cancel();
+ mBubbleAnimator
+ .spring(DynamicAnimation.SCALE_X, 1f)
+ .spring(DynamicAnimation.SCALE_Y, 1f)
+ .spring(DynamicAnimation.TRANSLATION_X, initialPosition.x, velocity.x,
+ mTranslationConfig)
+ .spring(DynamicAnimation.TRANSLATION_Y, initialPosition.y, velocity.y,
+ mTranslationConfig)
+ .addEndListener((View target, @NonNull FloatPropertyCompat super View> property,
+ boolean wasFling, boolean canceled, float finalValue, float finalVelocity,
+ boolean allRelevantPropertyAnimationsEnded) -> {
+ if (canceled || allRelevantPropertyAnimationsEnded) {
+ resetAnimatedViews(initialPosition);
+ if (endActions != null) {
+ endActions.run();
+ }
+ }
+ })
+ .start();
+ }
+
+ /**
+ * Animates the dragged view alongside the dismiss view when it gets captured in the dismiss
+ * target area.
+ */
+ public void animateDismissCaptured() {
+ mBubbleAnimator.cancel();
+ mBubbleAnimator
+ .spring(DynamicAnimation.SCALE_X, mBubbleCapturedScale)
+ .spring(DynamicAnimation.SCALE_Y, mBubbleCapturedScale)
+ .spring(DynamicAnimation.ALPHA, mDismissCapturedScale)
+ .start();
+
+ if (mDismissAnimator != null) {
+ mDismissAnimator.cancel();
+ mDismissAnimator
+ .spring(DynamicAnimation.SCALE_X, mDismissCapturedScale)
+ .spring(DynamicAnimation.SCALE_Y, mDismissCapturedScale)
+ .start();
+ }
+ }
+
+ /**
+ * Animates the dragged view alongside the dismiss view when it gets released from the dismiss
+ * target area.
+ */
+ public void animateDismissReleased() {
+ mBubbleAnimator.cancel();
+ mBubbleAnimator
+ .spring(DynamicAnimation.SCALE_X, mBubbleFocusedScale)
+ .spring(DynamicAnimation.SCALE_Y, mBubbleFocusedScale)
+ .spring(DynamicAnimation.ALPHA, 1f)
+ .start();
+
+ if (mDismissAnimator != null) {
+ mDismissAnimator.cancel();
+ mDismissAnimator
+ .spring(DynamicAnimation.SCALE_X, 1f)
+ .spring(DynamicAnimation.SCALE_Y, 1f)
+ .start();
+ }
+ }
+
+ /**
+ * Animates the dragged bubble dismiss when it's released in the dismiss target area.
+ *
+ * @param initialPosition the initial position to move the bubble too after animation finishes
+ * @param endActions gets called when the animation completes or gets cancelled
+ */
+ public void animateDismiss(@NonNull PointF initialPosition, @Nullable Runnable endActions) {
+ float dismissHeight = mDismissView != null ? mDismissView.getHeight() : 0f;
+ float translationY = mView.getTranslationY() + dismissHeight;
+ mBubbleAnimator
+ .spring(DynamicAnimation.TRANSLATION_Y, translationY)
+ .spring(DynamicAnimation.SCALE_X, 0f)
+ .spring(DynamicAnimation.SCALE_Y, 0f)
+ .spring(DynamicAnimation.ALPHA, 0f)
+ .addEndListener((View target, @NonNull FloatPropertyCompat super View> property,
+ boolean wasFling, boolean canceled, float finalValue, float finalVelocity,
+ boolean allRelevantPropertyAnimationsEnded) -> {
+ if (canceled || allRelevantPropertyAnimationsEnded) {
+ resetAnimatedViews(initialPosition);
+ if (endActions != null) endActions.run();
+ }
+ })
+ .start();
+ }
+
+ /**
+ * Reset the animated views to the initial state
+ *
+ * @param initialPosition position of the bubble
+ */
+ private void resetAnimatedViews(@NonNull PointF initialPosition) {
+ mView.setScaleX(1f);
+ mView.setScaleY(1f);
+ mView.setAlpha(1f);
+ mView.setTranslationX(initialPosition.x);
+ mView.setTranslationY(initialPosition.y);
+
+ if (mDismissView != null) {
+ mDismissView.getCircle().setScaleX(1f);
+ mDismissView.getCircle().setScaleY(1f);
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
new file mode 100644
index 0000000000..08fd681b4c
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
@@ -0,0 +1,355 @@
+/*
+ * 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.SuppressLint;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.taskbar.TaskbarActivityContext;
+
+/**
+ * Controls bubble bar drag to dismiss interaction.
+ * Interacts with {@link BubbleDismissController}, used by {@link BubbleBarViewController}.
+ * Supported interactions:
+ * - Drag a single bubble view into dismiss target to remove it.
+ * - Drag the bubble stack into dismiss target to remove all.
+ * Restores initial position of dragged view if released outside of the dismiss target.
+ */
+public class BubbleDragController {
+ private final TaskbarActivityContext mActivity;
+ private BubbleBarViewController mBubbleBarViewController;
+ private BubbleDismissController mBubbleDismissController;
+
+ public BubbleDragController(TaskbarActivityContext activity) {
+ mActivity = activity;
+ }
+
+ /**
+ * Initializes dependencies when bubble controllers are created.
+ * Should be careful to only access things that were created in constructors for now, as some
+ * controllers may still be waiting for init().
+ */
+ public void init(@NonNull BubbleControllers bubbleControllers) {
+ mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
+ mBubbleDismissController = bubbleControllers.bubbleDismissController;
+ }
+
+ /**
+ * Setup the bubble view for dragging and attach touch listener to it
+ */
+ @SuppressLint("ClickableViewAccessibility")
+ public void setupBubbleView(@NonNull BubbleView bubbleView) {
+ if (!(bubbleView.getBubble() instanceof BubbleBarBubble)) {
+ // Don't setup dragging for overflow bubble view
+ return;
+ }
+
+ bubbleView.setOnTouchListener(new BubbleTouchListener() {
+ @Override
+ void onDragStart() {
+ mBubbleBarViewController.onDragStart(bubbleView);
+ }
+
+ @Override
+ void onDragEnd() {
+ mBubbleBarViewController.onDragEnd();
+ }
+
+ @Override
+ protected void onDragRelease() {
+ mBubbleBarViewController.onDragRelease(bubbleView);
+ }
+ });
+ }
+
+ /**
+ * Setup the bubble bar view for dragging and attach touch listener to it
+ */
+ @SuppressLint("ClickableViewAccessibility")
+ public void setupBubbleBarView(@NonNull BubbleBarView bubbleBarView) {
+ PointF initialRelativePivot = new PointF();
+ bubbleBarView.setOnTouchListener(new BubbleTouchListener() {
+ @Override
+ protected boolean onTouchDown(@NonNull View view, @NonNull MotionEvent event) {
+ if (bubbleBarView.isExpanded()) return false;
+ return super.onTouchDown(view, event);
+ }
+
+ @Override
+ void onDragStart() {
+ initialRelativePivot.set(bubbleBarView.getRelativePivotX(),
+ bubbleBarView.getRelativePivotY());
+ // By default the bubble bar view pivot is in bottom right corner, while dragging
+ // it should be centered in order to align it with the dismiss target view
+ bubbleBarView.setRelativePivot(/* x = */ 0.5f, /* y = */ 0.5f);
+ }
+
+ @Override
+ void onDragEnd() {
+ // Restoring the initial pivot for the bubble bar view
+ bubbleBarView.setRelativePivot(initialRelativePivot.x, initialRelativePivot.y);
+ }
+ });
+ }
+
+ /**
+ * Bubble touch listener for handling a single bubble view or bubble bar view while dragging.
+ * The dragging starts after "shorter" long click (the long click duration might change):
+ * - When the touch gesture moves out of the {@code ACTION_DOWN} location the dragging
+ * interaction is cancelled.
+ * - When {@code ACTION_UP} happens before long click is registered and there was no significant
+ * movement the view will perform click.
+ * - When the listener registers long click it starts dragging interaction, all the subsequent
+ * {@code ACTION_MOVE} events will drag the view, and the interaction finishes when
+ * {@code ACTION_UP} or {@code ACTION_CANCEL} are received.
+ * Lifecycle methods can be overridden do add extra setup/clean up steps.
+ */
+ private abstract class BubbleTouchListener implements View.OnTouchListener {
+ /**
+ * The internal state of the touch listener
+ */
+ private enum State {
+ // Idle and ready for the touch events.
+ // Changes to:
+ // - TOUCHED, when the {@code ACTION_DOWN} is handled
+ IDLE,
+
+ // Touch down was handled and the lister is recognising the gestures.
+ // Changes to:
+ // - IDLE, when performs the click
+ // - DRAGGING, when registers the long click and starts dragging interaction
+ // - CANCELLED, when the touch events move out of the initial location before the long
+ // click is recognised
+
+ TOUCHED,
+
+ // The long click was registered and the view is being dragged.
+ // Changes to:
+ // - IDLE, when the gesture ends with the {@code ACTION_UP} or {@code ACTION_CANCEL}
+ DRAGGING,
+
+ // The dragging was cancelled.
+ // Changes to:
+ // - IDLE, when the current gesture completes
+ CANCELLED
+ }
+
+ private final PointF mTouchDownLocation = new PointF();
+ private final PointF mViewInitialPosition = new PointF();
+ private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
+ private final long mPressToDragTimeout = ViewConfiguration.getLongPressTimeout() / 2;
+ private State mState = State.IDLE;
+ private int mTouchSlop = -1;
+ private BubbleDragAnimator mAnimator;
+ @Nullable
+ private Runnable mLongClickRunnable;
+
+ /**
+ * Called when the dragging interaction has started
+ */
+ abstract void onDragStart();
+
+ /**
+ * Called when the dragging interaction has ended and all the animations have completed
+ */
+ abstract void onDragEnd();
+
+ /**
+ * Called when the dragged bubble is released outside of the dismiss target area and will
+ * move back to its initial position
+ */
+ protected void onDragRelease() {
+ }
+
+ /**
+ * Called when the dragged bubble is released inside of the dismiss target area and will get
+ * dismissed with animation
+ */
+ protected void onDragDismiss() {
+ }
+
+ @Override
+ @SuppressLint("ClickableViewAccessibility")
+ public boolean onTouch(@NonNull View view, @NonNull MotionEvent event) {
+ updateVelocity(event);
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ return onTouchDown(view, event);
+ case MotionEvent.ACTION_MOVE:
+ onTouchMove(view, event);
+ break;
+ case MotionEvent.ACTION_UP:
+ onTouchUp(view, event);
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ onTouchCancel(view, event);
+ break;
+ }
+ return true;
+ }
+
+ /**
+ * The touch down starts the interaction and schedules the long click handler.
+ *
+ * @param view the view that received the event
+ * @param event the motion event
+ * @return true if the gesture should be intercepted and handled, false otherwise. Note if
+ * the false is returned subsequent events in the gesture won't get reported.
+ */
+ protected boolean onTouchDown(@NonNull View view, @NonNull MotionEvent event) {
+ mState = State.TOUCHED;
+ mTouchSlop = ViewConfiguration.get(view.getContext()).getScaledTouchSlop();
+ mTouchDownLocation.set(event.getRawX(), event.getRawY());
+ mViewInitialPosition.set(view.getTranslationX(), view.getTranslationY());
+ setupLongClickHandler(view);
+ return true;
+ }
+
+ /**
+ * The move event drags the view or cancels the interaction if hasn't long clicked yet.
+ *
+ * @param view the view that received the event
+ * @param event the motion event
+ */
+ protected void onTouchMove(@NonNull View view, @NonNull MotionEvent event) {
+ final float dx = event.getRawX() - mTouchDownLocation.x;
+ final float dy = event.getRawY() - mTouchDownLocation.y;
+ switch (mState) {
+ case TOUCHED:
+ final boolean movedOut = Math.hypot(dx, dy) > mTouchSlop;
+ if (movedOut) {
+ // Moved out of the initial location before the long click was registered
+ mState = State.CANCELLED;
+ cleanUpLongClickHandler(view);
+ }
+ break;
+ case DRAGGING:
+ drag(view, event, dx, dy);
+ break;
+ }
+ }
+
+ /**
+ * On touch up performs click or finishes the dragging depending on the state.
+ *
+ * @param view the view that received the event
+ * @param event the motion event
+ */
+ protected void onTouchUp(@NonNull View view, @NonNull MotionEvent event) {
+ switch (mState) {
+ case TOUCHED:
+ view.performClick();
+ cleanUp(view);
+ break;
+ case DRAGGING:
+ stopDragging(view, event);
+ break;
+ default:
+ cleanUp(view);
+ break;
+ }
+ }
+
+ /**
+ * The gesture is cancelled and the interaction should clean up and complete.
+ *
+ * @param view the view that received the event
+ * @param event the motion event
+ */
+ protected void onTouchCancel(@NonNull View view, @NonNull MotionEvent event) {
+ if (mState == State.DRAGGING) {
+ stopDragging(view, event);
+ } else {
+ cleanUp(view);
+ }
+ }
+
+ private void startDragging(@NonNull View view) {
+ onDragStart();
+ mActivity.setTaskbarWindowFullscreen(true);
+ mAnimator = new BubbleDragAnimator(view);
+ mAnimator.animateFocused();
+ mBubbleDismissController.setupDismissView(view, mAnimator);
+ mBubbleDismissController.showDismissView();
+ }
+
+ private void drag(@NonNull View view, @NonNull MotionEvent event, float dx, float dy) {
+ if (mBubbleDismissController.handleTouchEvent(event)) return;
+ view.setTranslationX(mViewInitialPosition.x + dx);
+ view.setTranslationY(mViewInitialPosition.y + dy);
+ }
+
+ private void stopDragging(@NonNull View view, @NonNull MotionEvent event) {
+ Runnable onComplete = () -> {
+ mActivity.setTaskbarWindowFullscreen(false);
+ cleanUp(view);
+ onDragEnd();
+ };
+
+ if (mBubbleDismissController.handleTouchEvent(event)) {
+ onDragDismiss();
+ mAnimator.animateDismiss(mViewInitialPosition, onComplete);
+ } else {
+ onDragRelease();
+ mAnimator.animateToInitialState(mViewInitialPosition, getCurrentVelocity(),
+ onComplete);
+ }
+ mBubbleDismissController.hideDismissView();
+ }
+
+ private void setupLongClickHandler(@NonNull View view) {
+ cleanUpLongClickHandler(view);
+ mLongClickRunnable = () -> {
+ // Register long click and start dragging interaction
+ mState = State.DRAGGING;
+ startDragging(view);
+ };
+ view.getHandler().postDelayed(mLongClickRunnable, mPressToDragTimeout);
+ }
+
+ private void cleanUpLongClickHandler(@NonNull View view) {
+ if (mLongClickRunnable == null || view.getHandler() == null) return;
+ view.getHandler().removeCallbacks(mLongClickRunnable);
+ mLongClickRunnable = null;
+ }
+
+ private void cleanUp(@NonNull View view) {
+ cleanUpLongClickHandler(view);
+ mVelocityTracker.clear();
+ mState = State.IDLE;
+ }
+
+ private void updateVelocity(MotionEvent event) {
+ final float deltaX = event.getRawX() - event.getX();
+ final float deltaY = event.getRawY() - event.getY();
+ event.offsetLocation(deltaX, deltaY);
+ mVelocityTracker.addMovement(event);
+ event.offsetLocation(-deltaX, -deltaY);
+ }
+
+ private PointF getCurrentVelocity() {
+ mVelocityTracker.computeCurrentVelocity(/* units = */ 1000);
+ return new PointF(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
index 0ab53b0c20..c231de9a61 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.taskbar.bubbles;
+import static java.lang.Math.abs;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -25,11 +27,13 @@ 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.TaskbarInsetsController;
import com.android.launcher3.taskbar.TaskbarStashController;
import com.android.launcher3.util.MultiPropertyFactory;
/**
- * Coordinates between controllers such as BubbleBarView and BubbleHandleViewController to
+ * Coordinates between controllers such as BubbleBarView and
+ * BubbleHandleViewController to
* create a cohesive animation between stashed/unstashed states.
*/
public class BubbleStashController {
@@ -50,6 +54,7 @@ public class BubbleStashController {
// Initialized in init.
private TaskbarControllers mControllers;
+ private TaskbarInsetsController mTaskbarInsetsController;
private BubbleBarViewController mBarViewController;
private BubbleStashedHandleViewController mHandleViewController;
private TaskbarStashController mTaskbarStashController;
@@ -67,6 +72,7 @@ public class BubbleStashController {
private int mUnstashedHeight;
private boolean mBubblesShowingOnHome;
private boolean mBubblesShowingOnOverview;
+ private boolean mIsSysuiLocked;
@Nullable
private AnimatorSet mAnimator;
@@ -77,6 +83,7 @@ public class BubbleStashController {
public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
mControllers = controllers;
+ mTaskbarInsetsController = controllers.taskbarInsetsController;
mBarViewController = bubbleControllers.bubbleBarViewController;
mHandleViewController = bubbleControllers.bubbleStashedHandleViewController;
mTaskbarStashController = controllers.taskbarStashController;
@@ -90,14 +97,6 @@ public class BubbleStashController {
mStashedHeight = mHandleViewController.getStashedHeight();
mUnstashedHeight = mHandleViewController.getUnstashedHeight();
-
- bubbleControllers.runAfterInit(() -> {
- if (mTaskbarStashController.isStashed()) {
- stashBubbleBar();
- } else {
- showBubbleBar(false /* expandBubbles */);
- }
- });
}
/**
@@ -115,13 +114,71 @@ public class BubbleStashController {
}
/**
- * Called when launcher enters or exits the home page. Bubbles are unstashed on home.
+ * Animates the bubble bar and handle to their initial state, transitioning from
+ * the state where
+ * both views are invisible. Called when the first bubble is added or when the
+ * device is
+ * unlocked.
+ *
+ *
+ * Normally either the bubble bar or the handle is visible,
+ * and {@link #showBubbleBar(boolean)} and {@link #stashBubbleBar()} are used to
+ * transition
+ * between these two states. But the transition from the state where both the
+ * bar and handle
+ * are invisible is slightly different.
+ *
+ *
+ * The initial state will depend on the current state of the device, i.e.
+ * overview, home etc
+ * and whether bubbles are requested to be expanded.
+ */
+ public void animateToInitialState(boolean expanding) {
+ AnimatorSet animatorSet = new AnimatorSet();
+ if (expanding || mBubblesShowingOnHome || mBubblesShowingOnOverview) {
+ mIsStashed = false;
+ animatorSet.playTogether(mIconScaleForStash.animateToValue(1),
+ mIconTranslationYForStash.animateToValue(getBubbleBarTranslationY()),
+ mIconAlphaForStash.animateToValue(1));
+ } else {
+ mIsStashed = true;
+ animatorSet.playTogether(mBubbleStashedHandleAlpha.animateToValue(1));
+ }
+
+ animatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ onIsStashedChanged();
+ }
+ });
+ animatorSet.setDuration(BAR_STASH_DURATION).start();
+ }
+
+ /**
+ * 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 (!mBarViewController.hasBubbles()) {
+ // if there are no bubbles, there's nothing to show, so just return.
+ return;
+ }
+
if (mBubblesShowingOnHome) {
showBubbleBar(/* expanded= */ false);
+ // When transitioning from app to home the stash animator may already have been
+ // created, so we need to animate the bubble bar here to align with hotseat.
+ if (!mIsStashed) {
+ mIconTranslationYForStash.animateToValue(getBubbleBarTranslationYForHotseat())
+ .start();
+ }
+ // If the bubble bar is already unstashed, the taskbar touchable region won't be
+ // updated correctly, so force an update here.
+ mControllers
+ .runAfterInit(() -> mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged());
} else if (!mBarViewController.isExpanded()) {
stashBubbleBar();
}
@@ -133,30 +190,46 @@ public class BubbleStashController {
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. */
+ // 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();
+ } else {
+ // When transitioning to overview the stash animator may already have been
+ // created, so we need to animate the bubble bar here to align with taskbar.
+ mIconTranslationYForStash.animateToValue(getBubbleBarTranslationYForTaskbar())
+ .start();
}
}
}
- /** Called when sysui locked state changes, when locked, bubble bar is stashed. */
+ /** Whether bubbles are showing on Overview. */
+ public boolean isBubblesShowingOnOverview() {
+ return mBubblesShowingOnOverview;
+ }
+
+ /**
+ * 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();
+ if (isSysuiLocked != mIsSysuiLocked) {
+ mIsSysuiLocked = isSysuiLocked;
+ if (!mIsSysuiLocked && mBarViewController.hasBubbles()) {
+ animateToInitialState(false /* expanding */);
+ }
}
}
/**
- * Stashes the bubble bar if allowed based on other state (e.g. on home and overview the
+ * Stashes the bubble bar if allowed based on other state (e.g. on home and
+ * overview the
* bar does not stash).
*/
public void stashBubbleBar() {
@@ -166,7 +239,8 @@ public class BubbleStashController {
}
/**
- * Shows the bubble bar, and expands bubbles depending on {@param expandBubbles}.
+ * Shows the bubble bar, and expands bubbles depending on
+ * {@param expandBubbles}.
*/
public void showBubbleBar(boolean expandBubbles) {
mRequestedStashState = false;
@@ -200,7 +274,7 @@ public class BubbleStashController {
* Create a stash animation.
*
* @param isStashed whether it's a stash animation or an unstash animation
- * @param duration duration of the animation
+ * @param duration duration of the animation
* @return the animation
*/
private AnimatorSet createStashAnimator(boolean isStashed, long duration) {
@@ -226,23 +300,20 @@ public class BubbleStashController {
mIconScaleForStash.animateToValue(STASHED_BAR_SCALE));
secondHalfAnimatorSet.playTogether(
mBubbleStashedHandleAlpha.animateToValue(1));
- } else {
+ } 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;
+ final float translationY = getBubbleBarTranslationY();
+
fullLengthAnimatorSet.playTogether(
mIconScaleForStash.animateToValue(1),
mIconTranslationYForStash.animateToValue(translationY));
firstHalfAnimatorSet.playTogether(
- mBubbleStashedHandleAlpha.animateToValue(0)
- );
+ mBubbleStashedHandleAlpha.animateToValue(0));
secondHalfAnimatorSet.playTogether(
- mIconAlphaForStash.animateToValue(1)
- );
+ mIconAlphaForStash.animateToValue(1));
}
fullLengthAnimatorSet.play(mHandleViewController.createRevealAnimToIsStashed(isStashed));
@@ -262,6 +333,7 @@ public class BubbleStashController {
if (isStashed) {
mBarViewController.setExpanded(false);
}
+ mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
});
}
});
@@ -271,7 +343,28 @@ public class BubbleStashController {
private void onIsStashedChanged() {
mControllers.runAfterInit(() -> {
mHandleViewController.onIsStashedChanged();
- // TODO: when stash changes tell taskbarInsetsController the insets have changed.
+ mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
});
}
+
+ private float getBubbleBarTranslationYForTaskbar() {
+ return -mActivity.getDeviceProfile().taskbarBottomMargin;
+ }
+
+ private float getBubbleBarTranslationYForHotseat() {
+ final float hotseatBottomSpace = mActivity.getDeviceProfile().hotseatBarBottomSpacePx;
+ final float hotseatCellHeight = mActivity.getDeviceProfile().hotseatCellHeightPx;
+ return -hotseatBottomSpace - hotseatCellHeight + mUnstashedHeight - abs(
+ hotseatCellHeight - mUnstashedHeight) / 2;
+ }
+
+ float getBubbleBarTranslationY() {
+ // If we're on home, adjust the translation so the bubble bar aligns with
+ // hotseat.
+ // Otherwise we're either showing in an app or in overview. In either case
+ // adjust it so
+ // the bubble bar aligns with the taskbar.
+ return mBubblesShowingOnHome ? getBubbleBarTranslationYForHotseat()
+ : getBubbleBarTranslationYForTaskbar();
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
index 2170a5dc5b..5c99363fed 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
@@ -39,7 +39,8 @@ 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.
+ * Handles properties/data collection, then passes the results to our stashed
+ * handle View to render.
*/
public class BubbleStashedHandleViewController {
@@ -52,13 +53,16 @@ public class BubbleStashedHandleViewController {
private BubbleStashController mBubbleStashController;
private RegionSamplingHelper mRegionSamplingHelper;
private int mBarSize;
+ private int mStashedTaskbarHeight;
private int mStashedHandleWidth;
private int mStashedHandleHeight;
- // The bounds we want to clip to in the settled state when showing the stashed handle.
+ // 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,
+ // 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;
@@ -92,7 +96,7 @@ public class BubbleStashedHandleViewController {
mTaskbarStashedHandleAlpha.get(0).setValue(0);
- final int stashedTaskbarHeight = resources.getDimensionPixelSize(
+ mStashedTaskbarHeight = resources.getDimensionPixelSize(
R.dimen.bubblebar_stashed_size);
mStashedHandleView.setOutlineProvider(new ViewOutlineProvider() {
@Override
@@ -115,22 +119,26 @@ public class BubbleStashedHandleViewController {
}
}, 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;
+ mStashedHandleView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) -> updateBounds());
+ }
- mStashedHandleBounds.set(
- bubblebarRect.right - mStashedHandleWidth,
- stashedCenterY - mStashedHandleHeight / 2,
- bubblebarRect.right,
- stashedCenterY + mStashedHandleHeight / 2);
- mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
+ private void updateBounds() {
+ // 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.
+ final int right = mActivity.getDeviceProfile().widthPx - mBarViewController.getHorizontalMargin();
- view.setPivotX(view.getWidth());
- view.setPivotY(view.getHeight() - stashedTaskbarHeight / 2f);
- });
+ final int stashedCenterY = mStashedHandleView.getHeight() - mStashedTaskbarHeight / 2;
+
+ mStashedHandleBounds.set(
+ right - mStashedHandleWidth,
+ stashedCenterY - mStashedHandleHeight / 2,
+ right,
+ stashedCenterY + mStashedHandleHeight / 2);
+ mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
+
+ mStashedHandleView.setPivotX(mStashedHandleView.getWidth());
+ mStashedHandleView.setPivotY(mStashedHandleView.getHeight() - mStashedTaskbarHeight / 2f);
}
public void onDestroy() {
@@ -146,14 +154,16 @@ public class BubbleStashedHandleViewController {
}
/**
- * Returns the height when the bubble bar is unstashed (so the height of the bubble bar).
+ * 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.
+ * Called when system ui state changes. Bubbles don't show when the device is
+ * locked.
*/
public void setHiddenForSysui(boolean hidden) {
if (mHiddenForSysui != hidden) {
@@ -163,7 +173,8 @@ public class BubbleStashedHandleViewController {
}
/**
- * Called when the handle should be hidden (or shown) because there are no bubbles
+ * Called when the handle should be hidden (or shown) because there are no
+ * bubbles
* (or 1+ bubbles).
*/
public void setHiddenForBubbles(boolean hidden) {
@@ -174,7 +185,8 @@ public class BubbleStashedHandleViewController {
}
/**
- * Called when the home button is enabled / disabled. Bubbles don't show if home is disabled.
+ * 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) {
@@ -188,12 +200,14 @@ public class BubbleStashedHandleViewController {
mStashedHandleView.setVisibility(VISIBLE);
} else {
mStashedHandleView.setVisibility(INVISIBLE);
+ mStashedHandleView.setAlpha(0);
}
updateRegionSampling();
}
/**
- * Called when bubble bar is stash state changes so that updates to the stashed handle color
+ * Called when bubble bar is stash state changes so that updates to the stashed
+ * handle color
* can be started or stopped.
*/
public void onIsStashedChanged() {
@@ -203,12 +217,14 @@ public class BubbleStashedHandleViewController {
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();
+ if (mRegionSamplingHelper != null) {
+ mRegionSamplingHelper.setWindowVisible(handleVisible);
+ if (handleVisible) {
+ mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
+ mRegionSamplingHelper.start(mStashedHandleView.getSampledRegion());
+ } else {
+ mRegionSamplingHelper.stop();
+ }
}
}
@@ -220,15 +236,18 @@ public class BubbleStashedHandleViewController {
}
/**
- * Used by {@link BubbleStashController} to animate the handle when stashing or un stashing.
+ * 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
+ * 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) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
index e22e63aa36..1e2a244694 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
@@ -18,7 +18,9 @@ package com.android.launcher3.taskbar.bubbles;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Bitmap;
+import android.graphics.Canvas;
import android.graphics.Outline;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -28,27 +30,66 @@ import android.widget.ImageView;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.launcher3.R;
+import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.IconNormalizer;
+import com.android.wm.shell.animation.Interpolators;
+import com.patrykmichalik.opto.core.PreferenceExtensionsKt;
+import app.lawnchair.preferences2.PreferenceManager2;
+import app.lawnchair.theme.color.ColorOption;
+
+import java.util.EnumSet;
// 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
+ * 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;
+ /**
+ * Flags that suppress the visibility of the 'new' dot or the app badge, for one
+ * reason or
+ * another. If any of these flags are set, the dot will not be shown.
+ * If {@link SuppressionFlag#BEHIND_STACK} then the app badge will not be shown.
+ */
+ enum SuppressionFlag {
+ // TODO: (b/277815200) implement flyout
+ // Suppressed because the flyout is visible - it will morph into the dot via
+ // animation.
+ FLYOUT_VISIBLE,
+ // Suppressed because this bubble is behind others in the collapsed stack.
+ BEHIND_STACK,
+ }
+
+ private final EnumSet mSuppressionFlags = EnumSet.noneOf(SuppressionFlag.class);
+
private final ImageView mBubbleIcon;
private final ImageView mAppIcon;
private final int mBubbleSize;
+ private DotRenderer mDotRenderer;
+ private DotRenderer.DrawParams mDrawParams;
+ private int mDotColor;
+ private Rect mTempBounds = new Rect();
+
+ // Whether the dot is animating
+ private boolean mDotIsAnimating;
+ // What scale value the dot is animating to
+ private float mAnimatingToDotScale;
+ // The current scale value of the dot
+ private float mDotScale;
+
// TODO: (b/273310265) handle RTL
+ // Whether the bubbles are positioned on the left or right side of the screen
private boolean mOnLeft = false;
- private BubbleBarBubble mBubble;
+ private BubbleBarItem mBubble;
+
+ PreferenceManager2 preferenceManager2;
public BubbleView(Context context) {
this(context, null);
@@ -74,6 +115,10 @@ public class BubbleView extends ConstraintLayout {
mBubbleIcon = findViewById(R.id.icon_view);
mAppIcon = findViewById(R.id.app_icon_view);
+ preferenceManager2 = PreferenceManager2.INSTANCE.get(context);
+
+ mDrawParams = new DotRenderer.DrawParams();
+
setFocusable(true);
setClickable(true);
setOutlineProvider(new ViewOutlineProvider() {
@@ -90,45 +135,155 @@ public class BubbleView extends ConstraintLayout {
outline.setOval(inset, inset, inset + normalizedSize, inset + normalizedSize);
}
+ @Override
+ public void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+
+ if (!shouldDrawDot()) {
+ return;
+ }
+
+ getDrawingRect(mTempBounds);
+
+ mDrawParams.appColor = mDotColor;
+ mDrawParams.iconBounds = mTempBounds;
+ mDrawParams.leftAlign = mOnLeft;
+ mDrawParams.scale = mDotScale;
+
+ mDotRenderer.draw(canvas, mDrawParams, -1);
+ }
+
/** Sets the bubble being rendered in this view. */
void setBubble(BubbleBarBubble bubble) {
mBubble = bubble;
mBubbleIcon.setImageBitmap(bubble.getIcon());
mAppIcon.setImageBitmap(bubble.getBadge());
+ mDotColor = bubble.getDotColor();
+ ColorOption dotColorOption = PreferenceExtensionsKt.firstBlocking(preferenceManager2.getNotificationDotColor());
+ int dotColor = dotColorOption.getColorPreferenceEntry().getLightColor().invoke(getContext());
+ ColorOption counterColorOption = PreferenceExtensionsKt
+ .firstBlocking(preferenceManager2.getNotificationDotTextColor());
+ int countColor = counterColorOption.getColorPreferenceEntry().getLightColor().invoke(getContext());
+ mDotRenderer = new DotRenderer(mBubbleSize, bubble.getDotPath(), DEFAULT_PATH_SIZE, false, null, dotColor, countColor);
+ }
+
+ /**
+ * Sets that this bubble represents the overflow. The overflow appears in the
+ * list of bubbles
+ * but does not represent app content, instead it shows recent bubbles that
+ * couldn't fit into
+ * the list of bubbles. It doesn't show an app icon because it is part of system
+ * UI / doesn't
+ * come from an app.
+ */
+ void setOverflow(BubbleBarOverflow overflow, Bitmap bitmap) {
+ mBubble = overflow;
+ mBubbleIcon.setImageBitmap(bitmap);
+ mAppIcon.setVisibility(GONE); // Overflow doesn't show the app badge
}
/** Returns the bubble being rendered in this view. */
@Nullable
- BubbleBarBubble getBubble() {
+ BubbleBarItem getBubble() {
return mBubble;
}
- /** Shows the app badge on this bubble. */
- void showBadge() {
- Bitmap appBadgeBitmap = mBubble.getBadge();
- if (appBadgeBitmap == null) {
- mAppIcon.setVisibility(GONE);
+ void updateDotVisibility(boolean animate) {
+ final float targetScale = shouldDrawDot() ? 1f : 0f;
+ if (animate) {
+ animateDotScale();
+ } else {
+ mDotScale = targetScale;
+ mAnimatingToDotScale = targetScale;
+ invalidate();
+ }
+ }
+
+ void updateBadgeVisibility() {
+ if (mBubble instanceof BubbleBarOverflow) {
+ // The overflow bubble does not have a badge, so just bail.
+ return;
+ }
+ BubbleBarBubble bubble = (BubbleBarBubble) mBubble;
+ Bitmap appBadgeBitmap = bubble.getBadge();
+ int translationX = mOnLeft
+ ? -(bubble.getIcon().getWidth() - appBadgeBitmap.getWidth())
+ : 0;
+ mAppIcon.setTranslationX(translationX);
+ mAppIcon.setVisibility(isBehindStack() ? GONE : VISIBLE);
+ }
+
+ /** Sets whether this bubble is in the stack & not the first bubble. **/
+ void setBehindStack(boolean behindStack, boolean animate) {
+ if (behindStack) {
+ mSuppressionFlags.add(SuppressionFlag.BEHIND_STACK);
+ } else {
+ mSuppressionFlags.remove(SuppressionFlag.BEHIND_STACK);
+ }
+ updateDotVisibility(animate);
+ updateBadgeVisibility();
+ }
+
+ /** Whether this bubble is in the stack & not the first bubble. **/
+ boolean isBehindStack() {
+ return mSuppressionFlags.contains(SuppressionFlag.BEHIND_STACK);
+ }
+
+ /** Whether the dot indicating unseen content in a bubble should be shown. */
+ private boolean shouldDrawDot() {
+ boolean bubbleHasUnseenContent = mBubble != null
+ && mBubble instanceof BubbleBarBubble
+ && mSuppressionFlags.isEmpty()
+ && !((BubbleBarBubble) mBubble).getInfo().isNotificationSuppressed();
+
+ // Always render the dot if it's animating, since it could be animating out.
+ // Otherwise, show
+ // it if the bubble wants to show it, and we aren't suppressing it.
+ return bubbleHasUnseenContent || mDotIsAnimating;
+ }
+
+ /** How big the dot should be, fraction from 0 to 1. */
+ private void setDotScale(float fraction) {
+ mDotScale = fraction;
+ invalidate();
+ }
+
+ /**
+ * Animates the dot to the given scale.
+ */
+ private void animateDotScale() {
+ float toScale = shouldDrawDot() ? 1f : 0f;
+ mDotIsAnimating = true;
+
+ // Don't restart the animation if we're already animating to the given value.
+ if (mAnimatingToDotScale == toScale || !shouldDrawDot()) {
+ mDotIsAnimating = false;
return;
}
- int translationX;
- if (mOnLeft) {
- translationX = -(mBubble.getIcon().getWidth() - appBadgeBitmap.getWidth());
- } else {
- translationX = 0;
- }
+ mAnimatingToDotScale = toScale;
- mAppIcon.setTranslationX(translationX);
- mAppIcon.setVisibility(VISIBLE);
- }
+ final boolean showDot = toScale > 0f;
- /** Hides the app badge on this bubble. */
- void hideBadge() {
- mAppIcon.setVisibility(GONE);
+ // Do NOT wait until after animation ends to setShowDot
+ // to avoid overriding more recent showDot states.
+ clearAnimation();
+ animate()
+ .setDuration(200)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .setUpdateListener((valueAnimator) -> {
+ float fraction = valueAnimator.getAnimatedFraction();
+ fraction = showDot ? fraction : 1f - fraction;
+ setDotScale(fraction);
+ }).withEndAction(() -> {
+ setDotScale(showDot ? 1f : 0f);
+ mDotIsAnimating = false;
+ }).start();
}
@Override
public String toString() {
- return "BubbleView{" + mBubble + "}";
+ String toString = mBubble != null ? mBubble.getKey() : "null";
+ return "BubbleView{" + toString + "}";
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/OWNERS b/quickstep/src/com/android/launcher3/taskbar/bubbles/OWNERS
new file mode 100644
index 0000000000..7af0389997
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/OWNERS
@@ -0,0 +1 @@
+madym@google.com
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
index e704e5116b..b6820817c8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
@@ -17,10 +17,12 @@
package com.android.launcher3.taskbar.navbutton
import android.content.res.Resources
+import android.graphics.drawable.RotateDrawable
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import com.android.launcher3.R
+import com.android.launcher3.Utilities
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter
/**
@@ -40,7 +42,18 @@ abstract class AbstractNavButtonLayoutter(
protected val endContextualContainer: ViewGroup,
protected val startContextualContainer: ViewGroup
) : NavButtonLayoutter {
- protected val homeButton: ImageView = navButtonContainer.requireViewById(R.id.home)
- protected val recentsButton: ImageView = navButtonContainer.requireViewById(R.id.recent_apps)
- protected val backButton: ImageView = navButtonContainer.requireViewById(R.id.back)
+ protected val homeButton: ImageView? = navButtonContainer.findViewById(R.id.home)
+ protected val recentsButton: ImageView? = navButtonContainer.findViewById(R.id.recent_apps)
+ protected val backButton: ImageView? = navButtonContainer.findViewById(R.id.back)
+
+ init {
+ // setup back button drawable
+ if (backButton != null) {
+ val rotateDrawable = RotateDrawable()
+ rotateDrawable.drawable = backButton.context?.getDrawable(R.drawable.ic_sysbar_back)
+ rotateDrawable.fromDegrees = 0f
+ rotateDrawable.toDegrees = if (Utilities.isRtl(backButton.resources)) 90f else -90f
+ backButton.setImageDrawable(rotateDrawable)
+ }
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
index 468a1a7093..4a53c0c3b1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
@@ -51,10 +51,10 @@ class KidsNavLayoutter(
val paddingTop = (buttonHeight - iconSize) / 2
// Update icons
- backButton.setImageDrawable(backButton.context.getDrawable(DRAWABLE_SYSBAR_BACK_KIDS))
+ backButton!!.setImageDrawable(backButton.context.getDrawable(DRAWABLE_SYSBAR_BACK_KIDS))
backButton.scaleType = ImageView.ScaleType.FIT_CENTER
backButton.setPadding(paddingLeft, paddingTop, paddingLeft, paddingTop)
- homeButton.setImageDrawable(homeButton.getContext().getDrawable(DRAWABLE_SYSBAR_HOME_KIDS))
+ homeButton!!.setImageDrawable(homeButton.context.getDrawable(DRAWABLE_SYSBAR_HOME_KIDS))
homeButton.scaleType = ImageView.ScaleType.FIT_CENTER
homeButton.setPadding(paddingLeft, paddingTop, paddingLeft, paddingTop)
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
index 000778d9df..931f692fa2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
@@ -17,6 +17,8 @@
package com.android.launcher3.taskbar.navbutton
import android.content.res.Resources
+import android.view.Surface.ROTATION_90
+import android.view.Surface.Rotation
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.LinearLayout
@@ -56,7 +58,8 @@ class NavButtonLayoutFactory {
isKidsMode: Boolean,
isInSetup: Boolean,
isThreeButtonNav: Boolean,
- phoneMode: Boolean
+ phoneMode: Boolean,
+ @Rotation surfaceRotation: Int
): NavButtonLayoutter {
val navButtonContainer =
navButtonsView.requireViewById(ID_END_NAV_BUTTONS)
@@ -65,6 +68,7 @@ class NavButtonLayoutFactory {
val startContextualContainer =
navButtonsView.requireViewById(ID_START_CONTEXTUAL_BUTTONS)
val isPhoneNavMode = phoneMode && isThreeButtonNav
+ val isPhoneGestureMode = phoneMode && !isThreeButtonNav
return when {
isPhoneNavMode -> {
if (!deviceProfile.isLandscape) {
@@ -74,15 +78,30 @@ class NavButtonLayoutFactory {
endContextualContainer,
startContextualContainer
)
- } else {
+ } else if (surfaceRotation == ROTATION_90) {
PhoneLandscapeNavLayoutter(
resources,
navButtonContainer,
endContextualContainer,
startContextualContainer
)
+ } else {
+ PhoneSeascapeNavLayoutter(
+ resources,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer
+ )
}
}
+ isPhoneGestureMode ->{
+ PhoneGestureLayoutter(
+ resources,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer
+ )
+ }
deviceProfile.isTaskbarPresent -> {
return when {
isInSetup -> {
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
new file mode 100644
index 0000000000..8525c6c90c
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.navbutton
+
+import android.content.res.Resources
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import com.android.launcher3.DeviceProfile
+
+/** Layoutter for showing gesture navigation on phone screen. No buttons here, no-op container */
+class PhoneGestureLayoutter(
+ resources: Resources,
+ navBarContainer: LinearLayout,
+ endContextualContainer: ViewGroup,
+ startContextualContainer: ViewGroup
+) :
+ AbstractNavButtonLayoutter(
+ resources,
+ navBarContainer,
+ endContextualContainer,
+ startContextualContainer
+ ) {
+
+ override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
+ // no-op
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
index 201895fc67..2acd5d4eff 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
@@ -27,7 +27,7 @@ import com.android.launcher3.R
import com.android.launcher3.taskbar.TaskbarManager
import com.android.launcher3.util.DimensionUtils
-class PhoneLandscapeNavLayoutter(
+open class PhoneLandscapeNavLayoutter(
resources: Resources,
navBarContainer: LinearLayout,
endContextualContainer: ViewGroup,
@@ -42,17 +42,15 @@ class PhoneLandscapeNavLayoutter(
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
// TODO(b/230395757): Polish pending, this is just to make it usable
- val navContainerParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
val endStartMargins = resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
- val taskbarDimensions =
- DimensionUtils.getTaskbarPhoneDimensions(dp, resources, TaskbarManager.isPhoneMode(dp))
+ val taskbarDimensions = DimensionUtils.getTaskbarPhoneDimensions(dp, resources,
+ TaskbarManager.isPhoneMode(dp))
navButtonContainer.removeAllViews()
navButtonContainer.orientation = LinearLayout.VERTICAL
+ val navContainerParams = FrameLayout.LayoutParams(
+ taskbarDimensions.x, ViewGroup.LayoutParams.MATCH_PARENT)
navContainerParams.apply {
- width = taskbarDimensions.x
- height = ViewGroup.LayoutParams.MATCH_PARENT
- gravity = Gravity.CENTER
topMargin = endStartMargins
bottomMargin = endStartMargins
marginEnd = 0
@@ -65,6 +63,7 @@ class PhoneLandscapeNavLayoutter(
navButtonContainer.addView(backButton)
navButtonContainer.layoutParams = navContainerParams
+ navButtonContainer.gravity = Gravity.CENTER
// Add the spaces in between the nav buttons
val spaceInBetween: Int =
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
index f7ac9745f0..c76311582d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
@@ -41,27 +41,31 @@ class PhonePortraitNavLayoutter(
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
// TODO(b/230395757): Polish pending, this is just to make it usable
- val navContainerParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
val taskbarDimensions =
- DimensionUtils.getTaskbarPhoneDimensions(dp, resources, TaskbarManager.isPhoneMode(dp))
+ DimensionUtils.getTaskbarPhoneDimensions(dp, resources,
+ TaskbarManager.isPhoneMode(dp))
val endStartMargins = resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
- navContainerParams.width = taskbarDimensions.x
- navContainerParams.height = ViewGroup.LayoutParams.MATCH_PARENT
- navContainerParams.gravity = Gravity.CENTER_VERTICAL
// Ensure order of buttons is correct
navButtonContainer.removeAllViews()
navButtonContainer.orientation = LinearLayout.HORIZONTAL
- navContainerParams.topMargin = 0
- navContainerParams.bottomMargin = 0
- navContainerParams.marginEnd = endStartMargins
- navContainerParams.marginStart = endStartMargins
+
+ val navContainerParams = FrameLayout.LayoutParams(
+ taskbarDimensions.x, ViewGroup.LayoutParams.MATCH_PARENT)
+ navContainerParams.apply {
+ topMargin = 0
+ bottomMargin = 0
+ marginEnd = endStartMargins
+ marginStart = endStartMargins
+ }
+
// Swap recents and back button in case we were landscape prior to this
navButtonContainer.addView(backButton)
navButtonContainer.addView(homeButton)
navButtonContainer.addView(recentsButton)
navButtonContainer.layoutParams = navContainerParams
+ navButtonContainer.gravity = Gravity.CENTER
// Add the spaces in between the nav buttons
val spaceInBetween =
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
new file mode 100644
index 0000000000..f0fe58197b
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
@@ -0,0 +1,46 @@
+/*
+* 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.navbutton
+
+import android.content.res.Resources
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import com.android.launcher3.DeviceProfile
+
+class PhoneSeascapeNavLayoutter(
+ resources: Resources,
+ navBarContainer: LinearLayout,
+ endContextualContainer: ViewGroup,
+ startContextualContainer: ViewGroup
+) :
+ PhoneLandscapeNavLayoutter(
+ resources,
+ navBarContainer,
+ endContextualContainer,
+ startContextualContainer
+ ) {
+
+ override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
+ // TODO(b/230395757): Polish pending, this is just to make it usable
+ super.layoutButtons(dp, isContextualButtonShowing)
+ navButtonContainer.removeAllViews()
+ // Flip ordering of back and recents buttons
+ navButtonContainer.addView(backButton)
+ navButtonContainer.addView(homeButton)
+ navButtonContainer.addView(recentsButton)
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
index 5ec7ca0e2e..8332b7dd74 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
@@ -40,7 +40,6 @@ class TaskbarNavLayoutter(
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
// Add spacing after the end of the last nav button
- val navButtonParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
var navMarginEnd = resources.getDimension(dp.inv.inlineNavButtonsEndSpacing).toInt()
val contextualWidth = endContextualContainer.width
// If contextual buttons are showing, we check if the end margin is enough for the
@@ -50,10 +49,10 @@ class TaskbarNavLayoutter(
navMarginEnd += resources.getDimensionPixelSize(R.dimen.taskbar_hotseat_nav_spacing) / 2
}
+ val navButtonParams = FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
navButtonParams.apply {
- gravity = Gravity.END
- width = FrameLayout.LayoutParams.WRAP_CONTENT
- height = ViewGroup.LayoutParams.MATCH_PARENT
+ gravity = Gravity.END or Gravity.CENTER_VERTICAL
marginEnd = navMarginEnd
}
navButtonContainer.orientation = LinearLayout.HORIZONTAL
diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
index a642693137..bed23b3976 100644
--- a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
@@ -18,6 +18,8 @@ package com.android.launcher3.taskbar.overlay;
import android.content.Context;
import android.view.View;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.dot.DotInfo;
@@ -29,12 +31,14 @@ import com.android.launcher3.taskbar.TaskbarControllers;
import com.android.launcher3.taskbar.TaskbarDragController;
import com.android.launcher3.taskbar.TaskbarUIController;
import com.android.launcher3.taskbar.allapps.TaskbarAllAppsContainerView;
+import com.android.launcher3.taskbar.allapps.TaskbarSearchSessionController;
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
/**
* Window context for the taskbar overlays such as All Apps and EDU.
*
- * Overlays have their own window and need a window context. Some properties are delegated to the
+ * Overlays have their own window and need a window context. Some properties are
+ * delegated to the
* {@link TaskbarActivityContext} such as {@link PopupDataProvider}.
*/
public class TaskbarOverlayContext extends BaseTaskbarContext {
@@ -47,6 +51,8 @@ public class TaskbarOverlayContext extends BaseTaskbarContext {
private final int mStashedTaskbarHeight;
private final TaskbarUIController mUiController;
+ private @Nullable TaskbarSearchSessionController mSearchSessionController;
+
public TaskbarOverlayContext(
Context windowContext,
TaskbarActivityContext taskbarContext,
@@ -62,6 +68,15 @@ public class TaskbarOverlayContext extends BaseTaskbarContext {
mUiController = controllers.uiController;
}
+ public @Nullable TaskbarSearchSessionController getSearchSessionController() {
+ return mSearchSessionController;
+ }
+
+ public void setSearchSessionController(
+ @Nullable TaskbarSearchSessionController searchSessionController) {
+ mSearchSessionController = searchSessionController;
+ }
+
int getStashedTaskbarHeight() {
return mStashedTaskbarHeight;
}
@@ -70,7 +85,10 @@ public class TaskbarOverlayContext extends BaseTaskbarContext {
return mOverlayController;
}
- /** Returns {@code true} if overlay or Taskbar windows are handling a system drag. */
+ /**
+ * Returns {@code true} if overlay or Taskbar windows are handling a system
+ * drag.
+ */
boolean isAnySystemDragInProgress() {
return mDragController.isSystemDragInProgress()
|| mTaskbarContext.getDragController().isSystemDragInProgress();
@@ -111,6 +129,11 @@ public class TaskbarOverlayContext extends BaseTaskbarContext {
return mTaskbarContext.getItemOnClickListener();
}
+ @Override
+ public View.OnLongClickListener getAllAppsItemLongClickListener() {
+ return mDragController::startDragOnLongClick;
+ }
+
@Override
public PopupDataProvider getPopupDataProvider() {
return mTaskbarContext.getPopupDataProvider();
@@ -127,7 +150,8 @@ public class TaskbarOverlayContext extends BaseTaskbarContext {
}
@Override
- public void onDragStart() {}
+ public void onDragStart() {
+ }
@Override
public void onDragEnd() {
@@ -135,7 +159,8 @@ public class TaskbarOverlayContext extends BaseTaskbarContext {
}
@Override
- public void onPopupVisibilityChanged(boolean isVisible) {}
+ public void onPopupVisibilityChanged(boolean isVisible) {
+ }
@Override
public void onSplitScreenMenuButtonClicked() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java
index add72791d4..ff00560956 100644
--- a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java
@@ -21,6 +21,7 @@ import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_
import android.content.Context;
import android.graphics.Insets;
+import android.media.permission.SafeCloseable;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
@@ -29,6 +30,7 @@ import android.view.WindowInsets;
import androidx.annotation.NonNull;
+import com.android.app.viewcapture.SettingsAwareViewCapture;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
@@ -44,6 +46,7 @@ public class TaskbarOverlayDragLayer extends
BaseDragLayer implements
ViewTreeObserver.OnComputeInternalInsetsListener {
+ private SafeCloseable mViewCaptureCloseable;
private final List mOnClickListeners = new CopyOnWriteArrayList<>();
private final TouchController mClickListenerTouchController = new TouchController() {
@Override
@@ -77,12 +80,15 @@ public class TaskbarOverlayDragLayer extends
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+ mViewCaptureCloseable = SettingsAwareViewCapture.getInstance(getContext())
+ .startCapture(getRootView(), ".TaskbarOverlay");
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+ mViewCaptureCloseable.close();
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
index 62a8e05a24..60ec4747f9 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -16,16 +16,26 @@
package com.android.launcher3.uioverrides;
+import android.app.ActivityOptions;
import android.app.Person;
import android.content.Context;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
+import android.window.RemoteTransition;
import com.android.launcher3.Utilities;
+import com.android.quickstep.util.FadeOutRemoteTransition;
+
+import java.util.Collections;
+import java.util.Map;
import java.util.Map;
+import app.lawnchair.LawnchairApp;
+import app.lawnchair.LawnchairAppKt;
+import app.lawnchair.util.LawnchairUtilsKt;
+
/**
* A wrapper for the hidden API calls
*/
@@ -41,6 +51,22 @@ public class ApiWrapper {
}
public static Map getActivityOverrides(Context context) {
- return context.getSystemService(LauncherApps.class).getActivityOverrides();
+ return LawnchairUtilsKt.isDefaultLauncher(context) ?
+ context.getSystemService(LauncherApps.class).getActivityOverrides()
+ : Collections.emptyMap();
+ }
+
+ /**
+ * Creates an ActivityOptions to play fade-out animation on closing targets
+ */
+ public static ActivityOptions createFadeOutAnimOptions(Context context) {
+ try {
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setRemoteTransition(new RemoteTransition(new FadeOutRemoteTransition()));
+ return options;
+ } catch (Exception e) {
+ // TODO Create our own custom closing animation
+ return ActivityOptions.makeCustomAnimation(context, 0, android.R.anim.fade_out);
+ }
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 955440b49a..d78ca88249 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -16,40 +16,34 @@
package com.android.launcher3.uioverrides;
+import static com.android.app.animation.Interpolators.AGGRESSIVE_EASE_IN_OUT;
+import static com.android.app.animation.Interpolators.FINAL_FRAME;
+import static com.android.app.animation.Interpolators.INSTANT;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.LauncherState.QUICK_SWITCH_FROM_HOME;
-import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
-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.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
-import static com.android.quickstep.views.FloatingTaskView.PRIMARY_TRANSLATE_OFFSCREEN;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
import static com.android.quickstep.views.RecentsView.TASK_THUMBNAIL_SPLASH_ALPHA;
-import android.graphics.Rect;
-import android.graphics.RectF;
import android.util.FloatProperty;
import android.view.animation.Interpolator;
import androidx.annotation.NonNull;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
-import com.android.quickstep.views.FloatingTaskView;
import com.android.quickstep.views.RecentsView;
/**
@@ -113,48 +107,19 @@ public abstract class BaseRecentsViewStateController
setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f,
config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
- if (mRecentsView.isSplitSelectionActive()) {
- // TODO (b/238651489): Refactor state management to avoid need for double check
- FloatingTaskView floatingTask = mRecentsView.getFirstFloatingTaskView();
- if (floatingTask != null) {
- // We are in split selection state currently, transitioning to another state
- DragLayer dragLayer = mLauncher.getDragLayer();
- RectF onScreenRectF = new RectF();
- Utilities.getBoundsForViewInDragLayer(mLauncher.getDragLayer(), floatingTask,
- new Rect(0, 0, floatingTask.getWidth(), floatingTask.getHeight()),
- false, null, onScreenRectF);
- // Get the part of the floatingTask that intersects with the DragLayer (i.e. the
- // on-screen portion)
- onScreenRectF.intersect(
- dragLayer.getLeft(),
- dragLayer.getTop(),
- dragLayer.getRight(),
- dragLayer.getBottom()
- );
-
- setter.setFloat(
- mRecentsView.getFirstFloatingTaskView(),
- PRIMARY_TRANSLATE_OFFSCREEN,
- mRecentsView.getPagedOrientationHandler()
- .getFloatingTaskOffscreenTranslationTarget(
- floatingTask,
- onScreenRectF,
- floatingTask.getStagePosition(),
- mLauncher.getDeviceProfile()
- ),
- config.getInterpolator(
- ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN,
- LINEAR
- ));
- setter.setViewAlpha(
- mRecentsView.getSplitInstructionsView(),
- 0,
- config.getInterpolator(
- ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE,
- LINEAR
- )
- );
- }
+ boolean exitingOverview = !FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()
+ && !toState.overviewUi;
+ if (mRecentsView.isSplitSelectionActive() && exitingOverview) {
+ setter.add(mRecentsView.getSplitSelectController().getSplitAnimationController()
+ .createPlaceholderDismissAnim(mLauncher));
+ setter.setViewAlpha(
+ mRecentsView.getSplitInstructionsView(),
+ 0,
+ config.getInterpolator(
+ ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE,
+ LINEAR
+ )
+ );
}
setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
diff --git a/quickstep/src/com/android/launcher3/uioverrides/DejankBinderTracker.java b/quickstep/src/com/android/launcher3/uioverrides/DejankBinderTracker.java
deleted file mode 100644
index d8aa235823..0000000000
--- a/quickstep/src/com/android/launcher3/uioverrides/DejankBinderTracker.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/**
- * Copyright (C) 2019 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.uioverrides;
-
-import static android.os.IBinder.FLAG_ONEWAY;
-
-import android.os.Binder;
-import android.os.Build;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.util.Log;
-
-import androidx.annotation.MainThread;
-
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.function.BiConsumer;
-import java.util.function.Supplier;
-
-/**
- * A binder proxy transaction listener for tracking non-whitelisted binder calls.
- */
-public class DejankBinderTracker implements Binder.ProxyTransactListener {
- private static final String TAG = "DejankBinderTracker";
-
- private static final Object sLock = new Object();
- private static final HashSet sWhitelistedFrameworkClasses = new HashSet<>();
- static {
- // Common IPCs that are ok to block the main thread.
- sWhitelistedFrameworkClasses.add("android.view.IWindowSession");
- sWhitelistedFrameworkClasses.add("android.os.IPowerManager");
- }
- private static boolean sTemporarilyIgnoreTracking = false;
-
- // Used by the client to limit binder tracking to specific regions
- private static boolean sTrackingAllowed = false;
-
- private BiConsumer mUnexpectedTransactionCallback;
- private boolean mIsTracking = false;
-
- /**
- * Temporarily ignore blocking binder calls for the duration of this {@link Runnable}.
- */
- @MainThread
- public static void whitelistIpcs(Runnable runnable) {
- sTemporarilyIgnoreTracking = true;
- runnable.run();
- sTemporarilyIgnoreTracking = false;
- }
-
- /**
- * Temporarily ignore blocking binder calls for the duration of this {@link Supplier}.
- */
- @MainThread
- public static T whitelistIpcs(Supplier supplier) {
- sTemporarilyIgnoreTracking = true;
- T value = supplier.get();
- sTemporarilyIgnoreTracking = false;
- return value;
- }
-
- /**
- * Enables binder tracking during a test.
- */
- @MainThread
- public static void allowBinderTrackingInTests() {
- sTrackingAllowed = true;
- }
-
- /**
- * Disables binder tracking during a test.
- */
- @MainThread
- public static void disallowBinderTrackingInTests() {
- sTrackingAllowed = false;
- }
-
- public DejankBinderTracker(BiConsumer unexpectedTransactionCallback) {
- mUnexpectedTransactionCallback = unexpectedTransactionCallback;
- }
-
- @MainThread
- public void startTracking() {
- if (!Build.TYPE.toLowerCase(Locale.ROOT).contains("debug")
- && !Build.TYPE.toLowerCase(Locale.ROOT).equals("eng")) {
- Log.wtf(TAG, "Unexpected use of binder tracker in non-debug build", new Exception());
- return;
- }
- if (mIsTracking) {
- return;
- }
- mIsTracking = true;
- Binder.setProxyTransactListener(this);
- }
-
- @MainThread
- public void stopTracking() {
- if (!mIsTracking) {
- return;
- }
- mIsTracking = false;
- Binder.setProxyTransactListener(null);
- }
-
- // Override the hidden Binder#onTransactStarted method
- public synchronized Object onTransactStarted(IBinder binder, int transactionCode, int flags) {
- if (!mIsTracking
- || !sTrackingAllowed
- || sTemporarilyIgnoreTracking
- || (flags & FLAG_ONEWAY) == FLAG_ONEWAY
- || !isMainThread()) {
- return null;
- }
-
- String descriptor;
- try {
- descriptor = binder.getInterfaceDescriptor();
- if (sWhitelistedFrameworkClasses.contains(descriptor)) {
- return null;
- }
- } catch (RemoteException e) {
- e.printStackTrace();
- descriptor = binder.getClass().getSimpleName();
- }
-
- mUnexpectedTransactionCallback.accept(descriptor, transactionCode);
- return null;
- }
-
- @Override
- public Object onTransactStarted(IBinder binder, int transactionCode) {
- // Do nothing
- return null;
- }
-
- @Override
- public void onTransactEnded(Object session) {
- // Do nothing
- }
-
- public static boolean isMainThread() {
- return Thread.currentThread() == Looper.getMainLooper().getThread();
- }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 470830a805..d19441f476 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -15,7 +15,8 @@
*/
package com.android.launcher3.uioverrides;
-import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
+import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
+import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED;
import static com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter;
import android.animation.Animator;
@@ -95,8 +96,8 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
private Animator mSlotMachineAnim;
private float mSlotMachineIconTranslationY;
- private static final FloatProperty SLOT_MACHINE_TRANSLATION_Y =
- new FloatProperty("slotMachineTranslationY") {
+ private static final FloatProperty SLOT_MACHINE_TRANSLATION_Y = new FloatProperty(
+ "slotMachineTranslationY") {
@Override
public void setValue(PredictedAppIcon predictedAppIcon, float transY) {
predictedAppIcon.mSlotMachineIconTranslationY = transY;
@@ -118,6 +119,7 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
}
Context mContext;
+
public PredictedAppIcon(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;
@@ -177,7 +179,8 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
@Override
public void applyFromWorkspaceItem(WorkspaceItemInfo info, boolean animate, int staggerIndex) {
- // Create the slot machine animation first, since it uses the current icon to start.
+ // Create the slot machine animation first, since it uses the current icon to
+ // start.
Animator slotMachineAnim = animate
? createSlotMachineAnim(Collections.singletonList(info.bitmap), false)
: null;
@@ -222,7 +225,8 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
}
/**
- * Returns an Animator that translates the given icons in a "slot-machine" fashion, beginning
+ * Returns an Animator that translates the given icons in a "slot-machine"
+ * fashion, beginning
* and ending with the original icon.
*/
public @Nullable Animator createSlotMachineAnim(List iconsToAnimate) {
@@ -230,10 +234,14 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
}
/**
- * Returns an Animator that translates the given icons in a "slot-machine" fashion, beginning
- * with the original icon, then cycling through the given icons, optionally ending back with
+ * Returns an Animator that translates the given icons in a "slot-machine"
+ * fashion, beginning
+ * with the original icon, then cycling through the given icons, optionally
+ * ending back with
* the original icon.
- * @param endWithOriginalIcon Whether we should land back on the icon we started with, rather
+ *
+ * @param endWithOriginalIcon Whether we should land back on the icon we started
+ * with, rather
* than the last item in iconsToAnimate.
*/
public @Nullable Animator createSlotMachineAnim(List iconsToAnimate,
@@ -261,8 +269,8 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
Keyframe.ofFloat(0.82f, finalTrans - getOutlineOffsetY() / 2f), // Overshoot
Keyframe.ofFloat(1f, finalTrans) // Ease back into the final position
};
- keyframes[1].setInterpolator(ACCEL_DEACCEL);
- keyframes[2].setInterpolator(ACCEL_DEACCEL);
+ keyframes[1].setInterpolator(ACCELERATE_DECELERATE);
+ keyframes[2].setInterpolator(ACCELERATE_DECELERATE);
mSlotMachineAnim = ObjectAnimator.ofPropertyValuesHolder(this,
PropertyValuesHolder.ofKeyframe(SLOT_MACHINE_TRANSLATION_Y, keyframes));
@@ -279,7 +287,8 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
* Removes prediction ring from app icon
*/
public void pin(WorkspaceItemInfo info) {
- if (mIsPinned) return;
+ if (mIsPinned)
+ return;
mIsPinned = true;
applyFromWorkspaceItem(info);
setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
@@ -338,7 +347,6 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
if (getTag() instanceof WorkspaceItemInfo) {
WorkspaceItemInfo info = (WorkspaceItemInfo) getTag();
isBadged = !Process.myUserHandle().equals(info.user)
- || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
|| info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
index 3fcf324093..cb1c7b56ec 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
@@ -43,13 +43,13 @@ import dev.rikka.tools.refine.Refine;
* Provides a Quickstep specific animation when launching an activity from an
* app widget.
*/
-class QuickstepInteractionHandler implements RemoteViews.InteractionHandler {
+public class QuickstepInteractionHandler implements RemoteViews.InteractionHandler {
private static final String TAG = "QuickstepInteractionHandler";
private final QuickstepLauncher mLauncher;
- QuickstepInteractionHandler(QuickstepLauncher launcher) {
+ public QuickstepInteractionHandler(QuickstepLauncher launcher) {
mLauncher = launcher;
}
@@ -97,8 +97,8 @@ 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);
+ Utilities.allowBGLaunch(activityOptions.options);
+
}
options = Pair.create(options.first, activityOptions.options);
if (pendingIntent.isActivity()) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index d7b1977693..5138a5e789 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -19,23 +19,21 @@ 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;
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
+
+import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.launcher3.LauncherSettings.Animation.DEFAULT_NO_ICON;
import static com.android.launcher3.LauncherSettings.Animation.VIEW_BACKGROUND;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.NO_OFFSET;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
-import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE;
import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE;
-import static com.android.launcher3.config.FeatureFlags.RECEIVE_UNFOLD_EVENTS_FROM_SYSUI;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition;
@@ -58,8 +56,6 @@ import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SY
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
-import android.animation.ValueAnimator;
-import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
@@ -69,19 +65,20 @@ import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.hardware.SensorManager;
-import android.hardware.devicestate.DeviceStateManager;
import android.hardware.display.DisplayManager;
import android.media.permission.SafeCloseable;
import android.os.Build;
import android.os.Bundle;
-import android.os.CancellationSignal;
import android.os.IBinder;
import android.os.SystemProperties;
+import android.os.Trace;
+import android.util.AttributeSet;
+import android.util.Log;
import android.view.Display;
import android.view.HapticFeedbackConstants;
-import android.view.RemoteAnimationTarget;
import android.view.View;
+import android.widget.AnalogClock;
+import android.widget.TextClock;
import android.window.BackEvent;
import android.window.OnBackAnimationCallback;
import android.window.OnBackInvokedDispatcher;
@@ -106,7 +103,6 @@ import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.appprediction.PredictionRowView;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager;
@@ -114,9 +110,9 @@ import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.WellbeingModel;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.proxy.ProxyActivityStarter;
-import com.android.launcher3.proxy.StartActivityParams;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
@@ -148,6 +144,7 @@ import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
+import com.android.launcher3.util.StartActivityParams;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.widget.LauncherWidgetHolder;
import com.android.quickstep.OverviewCommandHelper;
@@ -155,12 +152,10 @@ import com.android.quickstep.RecentsModel;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.TouchInteractionService.TISBinder;
+import com.android.quickstep.util.AsyncClockEventDelegate;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.LauncherUnfoldAnimationController;
-import com.android.quickstep.util.ProxyScreenStatusProvider;
import com.android.quickstep.util.QuickstepOnboardingPrefs;
-import com.android.quickstep.util.RemoteAnimationProvider;
-import com.android.quickstep.util.RemoteFadeOutAnimationListener;
import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.util.SplitToWorkspaceController;
import com.android.quickstep.util.SplitWithKeyboardShortcutController;
@@ -170,16 +165,14 @@ import com.android.quickstep.views.FloatingTaskView;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.unfold.RemoteUnfoldSharedComponent;
-import com.android.systemui.unfold.UnfoldSharedComponent;
import com.android.systemui.unfold.UnfoldTransitionFactory;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig;
import com.android.systemui.unfold.config.UnfoldTransitionConfig;
import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver;
-import com.android.systemui.unfold.system.ActivityManagerActivityTypeProvider;
-import com.android.systemui.unfold.system.DeviceStateManagerFoldProvider;
import com.android.systemui.unfold.updates.RotationChangeProvider;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -188,13 +181,21 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.function.Consumer;
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);
+ private static final boolean TRACE_LAYOUTS = SystemProperties.getBoolean("persist.debug.trace_layouts", false);
+ private static final String TRACE_RELAYOUT_CLASS = SystemProperties.get("persist.debug.trace_request_layout_class",
+ null);
+
+ private static final String TAG = "QuickstepLauncher";
+
public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
+
+ protected static final String RING_APPEAR_ANIMATION_PREFIX = "RingAppearAnimation\t";
+
private FixedContainerItems mAllAppsPredictions;
private HotseatPredictionController mHotseatPredictionController;
private DepthController mDepthController;
@@ -202,16 +203,16 @@ public class QuickstepLauncher extends Launcher {
private QuickstepTransitionManager mAppTransitionManager;
private OverviewActionsView mActionsView;
private TISBindHelper mTISBindHelper;
- private @Nullable TaskbarManager mTaskbarManager;
- private @Nullable OverviewCommandHelper mOverviewCommandHelper;
private @Nullable LauncherTaskbarUIController mTaskbarUIController;
// Will be updated when dragging from taskbar.
- private @Nullable DragOptions mNextWorkspaceDragOptions = null;
private @Nullable UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
private @Nullable LauncherUnfoldAnimationController mLauncherUnfoldAnimationController;
private SplitSelectStateController mSplitSelectStateController;
private SplitWithKeyboardShortcutController mSplitWithKeyboardShortcutController;
private SplitToWorkspaceController mSplitToWorkspaceController;
+
+ private AsyncClockEventDelegate mAsyncClockEventDelegate;
+
/**
* If Launcher restarted while in the middle of an Overview split select, it
* needs this data to
@@ -242,6 +243,10 @@ public class QuickstepLauncher extends Launcher {
mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
mDepthController = new DepthController(this);
mDesktopVisibilityController = new DesktopVisibilityController(this);
+ if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+ mDesktopVisibilityController.registerSystemUiListener();
+ mSplitSelectStateController.initSplitFromDesktopController(this);
+ }
mHotseatPredictionController = new HotseatPredictionController(this);
mEnableWidgetDepth = SystemProperties.getBoolean("ro.launcher.depth.widget", true);
getWorkspace().addOverlayCallback(
@@ -261,7 +266,6 @@ 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)) {
int count = mAllAppsPredictions.items.size();
for (int i = 0; i < count; i++) {
@@ -331,9 +335,7 @@ public class QuickstepLauncher extends Launcher {
mHotseatPredictionController.setPauseUIUpdate(getTaskbarUIController() == null);
RunnableList result = super.startActivitySafely(v, intent, item);
if (result == null) {
- if (getTaskbarUIController() == null) {
- mHotseatPredictionController.setPauseUIUpdate(false);
- }
+ mHotseatPredictionController.setPauseUIUpdate(false);
} else {
result.add(() -> mHotseatPredictionController.setPauseUIUpdate(false));
}
@@ -386,7 +388,7 @@ public class QuickstepLauncher extends Launcher {
}
private List> getSplitShortcuts() {
- if (!mDeviceProfile.isTablet) {
+ if (!mDeviceProfile.isTablet || mSplitSelectStateController.isSplitSelectActive()) {
return Collections.emptyList();
}
RecentsView recentsView = (RecentsView) getOverviewPanel();
@@ -414,12 +416,8 @@ public class QuickstepLauncher extends Launcher {
boolean visible = (state == NORMAL || state == OVERVIEW)
&& (willUserBeActive || isUserActive())
&& !profile.isVerticalBarLayout();
- if (ENABLE_PIP_KEEP_CLEAR_ALGORITHM) {
- SystemUiProxy.INSTANCE.get(this)
- .setLauncherKeepClearAreaHeight(visible, profile.hotseatBarSizePx);
- } else {
- SystemUiProxy.INSTANCE.get(this).setShelfHeight(visible, profile.hotseatBarSizePx);
- }
+ SystemUiProxy.INSTANCE.get(this)
+ .setLauncherKeepClearAreaHeight(visible, profile.hotseatBarSizePx);
}
if (state == NORMAL && !inTransition) {
((RecentsView) getOverviewPanel()).setSwipeDownShouldLaunchApp(false);
@@ -428,12 +426,14 @@ public class QuickstepLauncher extends Launcher {
@Override
public void bindExtraContainerItems(FixedContainerItems item) {
+ Log.d(TAG, "Bind extra container items");
if (item.containerId == Favorites.CONTAINER_PREDICTION) {
mAllAppsPredictions = item;
PredictionRowView> predictionRowView = getAppsView().getFloatingHeaderView().findFixedRowByType(
PredictionRowView.class);
predictionRowView.setPredictedApps(item.items);
} else if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) {
+ Log.d(TAG, "Bind extra container item is hotseat prediction");
mHotseatPredictionController.setPredictedItems(item);
} else if (item.containerId == Favorites.CONTAINER_WIDGETS_PREDICTION) {
getPopupDataProvider().setRecommendedWidgets(item.items);
@@ -451,16 +451,26 @@ public class QuickstepLauncher extends Launcher {
mAppTransitionManager.onActivityDestroyed();
if (mUnfoldTransitionProgressProvider != null) {
SystemUiProxy.INSTANCE.get(this).setUnfoldAnimationListener(null);
-
mUnfoldTransitionProgressProvider.destroy();
}
mTISBindHelper.onDestroy();
- if (mTaskbarManager != null) {
- mTaskbarManager.clearActivity(this);
- }
+
if (mLauncherUnfoldAnimationController != null) {
mLauncherUnfoldAnimationController.onDestroy();
}
+
+ if (mDesktopVisibilityController != null) {
+ mDesktopVisibilityController.unregisterSystemUiListener();
+ }
+
+ if (mSplitSelectStateController != null) {
+ mSplitSelectStateController.onDestroy();
+ }
+
+ if (mAsyncClockEventDelegate != null) {
+ mAsyncClockEventDelegate.onDestroy();
+ }
+
super.onDestroy();
mHotseatPredictionController.destroy();
mSplitWithKeyboardShortcutController.onDestroy();
@@ -493,8 +503,8 @@ public class QuickstepLauncher extends Launcher {
break;
}
case QUICK_SWITCH_STATE_ORDINAL: {
- RecentsView rv = (RecentsView) getOverviewPanel();
- TaskView tasktolaunch = rv.getTaskViewAt(0);
+ RecentsView rv = getOverviewPanel();
+ TaskView tasktolaunch = rv.getCurrentPageTaskView();
if (tasktolaunch != null) {
tasktolaunch.launchTask(success -> {
if (!success) {
@@ -516,11 +526,14 @@ public class QuickstepLauncher extends Launcher {
NavigationMode mode = DisplayController.getNavigationMode(this);
ArrayList list = new ArrayList<>();
list.add(getDragController());
+ Consumer splitAnimator = animatorSet -> animatorSet
+ .play(mSplitSelectStateController.getSplitAnimationController()
+ .createPlaceholderDismissAnim(this));
switch (mode) {
case NO_BUTTON:
list.add(new NoButtonQuickSwitchTouchController(this));
- list.add(new NavBarToHomeTouchController(this));
- list.add(new NoButtonNavbarToOverviewTouchController(this));
+ list.add(new NavBarToHomeTouchController(this, splitAnimator));
+ list.add(new NoButtonNavbarToOverviewTouchController(this, splitAnimator));
break;
case TWO_BUTTONS:
list.add(new TwoButtonNavbarTouchController(this));
@@ -530,8 +543,14 @@ public class QuickstepLauncher extends Launcher {
list.add(new PortraitStatesTouchController(this));
break;
case THREE_BUTTONS:
+ list.add(new NoButtonQuickSwitchTouchController(this));
+ list.add(new NavBarToHomeTouchController(this, splitAnimator));
+ list.add(new NoButtonNavbarToOverviewTouchController(this, splitAnimator));
+ list.add(new PortraitStatesTouchController(this));
+ break;
default:
list.add(new PortraitStatesTouchController(this));
+ break;
}
if (!getDeviceProfile().isMultiWindowMode) {
list.add(new StatusBarTouchController(this));
@@ -570,6 +589,10 @@ public class QuickstepLauncher extends Launcher {
mViewCapture = SettingsAwareViewCapture.getInstance(this).startCapture(getWindow());
}
getWindow().addPrivateFlags(PRIVATE_FLAG_OPTIMIZE_MEASURE);
+ if (Utilities.ATLEAST_U) {
+ View.setTraceLayoutSteps(TRACE_LAYOUTS);
+ View.setTracedRequestLayoutClassClass(TRACE_RELAYOUT_CLASS);
+ }
}
@Override
@@ -578,12 +601,15 @@ public class QuickstepLauncher extends Launcher {
// Check if there is already an instance of this app running, if so, initiate
// the split
// using that.
- mSplitSelectStateController.findLastActiveTaskAndRunCallback(
- splitSelectSource.itemInfo.getComponentKey(),
- foundTask -> {
- splitSelectSource.alreadyRunningTaskId = foundTask == null
- ? INVALID_TASK_ID
- : foundTask.key.id;
+ mSplitSelectStateController.findLastActiveTasksAndRunCallback(
+ Collections.singletonList(splitSelectSource.itemInfo.getComponentKey()),
+ foundTasks -> {
+ @Nullable
+ Task foundTask = foundTasks.get(0);
+ boolean taskWasFound = foundTask != null;
+ splitSelectSource.alreadyRunningTaskId = taskWasFound
+ ? foundTask.key.id
+ : INVALID_TASK_ID;
if (ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
startSplitToHome(splitSelectSource);
} else {
@@ -622,9 +648,13 @@ public class QuickstepLauncher extends Launcher {
@Override
public void onAnimationCancel(Animator animation) {
getDragLayer().removeView(floatingTaskView);
+ mSplitSelectStateController.getSplitAnimationController()
+ .removeSplitInstructionsView(QuickstepLauncher.this);
mSplitSelectStateController.resetState();
}
});
+ anim.add(mSplitSelectStateController.getSplitAnimationController()
+ .getShowSplitInstructionsAnim(this).buildAnim());
anim.buildAnim().start();
}
@@ -647,8 +677,9 @@ public class QuickstepLauncher extends Launcher {
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
- if (mOverviewCommandHelper != null) {
- mOverviewCommandHelper.clearPendingCommands();
+ OverviewCommandHelper overviewCommandHelper = mTISBindHelper.getOverviewCommandHelper();
+ if (overviewCommandHelper != null) {
+ overviewCommandHelper.clearPendingCommands();
}
}
@@ -774,8 +805,9 @@ public class QuickstepLauncher extends Launcher {
}
private void onTaskbarInAppDisplayProgressUpdate(float progress, int flag) {
- if (mTaskbarManager == null
- || mTaskbarManager.getCurrentActivityContext() == null
+ TaskbarManager taskbarManager = mTISBindHelper.getTaskbarManager();
+ if (taskbarManager == null
+ || taskbarManager.getCurrentActivityContext() == null
|| mTaskbarUIController == null) {
return;
}
@@ -819,7 +851,7 @@ public class QuickstepLauncher extends Launcher {
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
DesktopVisibilityController controller = mDesktopVisibilityController;
if (controller != null && controller.areFreeformTasksVisible()
- && !controller.isGestureInProgress()) {
+ && !controller.isRecentsGestureInProgress()) {
// 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
@@ -849,11 +881,10 @@ public class QuickstepLauncher extends Launcher {
}
private void onTISConnected(TISBinder binder) {
- mTaskbarManager = binder.getTaskbarManager();
- if (mTaskbarManager != null) {
- mTaskbarManager.setActivity(this);
+ TaskbarManager taskbarManager = mTISBindHelper.getTaskbarManager();
+ if (taskbarManager != null) {
+ taskbarManager.setActivity(this);
}
- mOverviewCommandHelper = binder.getOverviewCommandHelper();
}
@Override
@@ -864,41 +895,10 @@ public class QuickstepLauncher extends Launcher {
private void initUnfoldTransitionProgressProvider() {
final UnfoldTransitionConfig config = new ResourceUnfoldTransitionConfig();
if (config.isEnabled()) {
- if (RECEIVE_UNFOLD_EVENTS_FROM_SYSUI.get()) {
- initRemotelyCalculatedUnfoldAnimation(config);
- } else {
- initLocallyCalculatedUnfoldAnimation(config);
- }
+ initRemotelyCalculatedUnfoldAnimation(config);
}
}
- /**
- * 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));
- mUnfoldTransitionProgressProvider = unfoldComponent.getUnfoldTransitionProvider()
- .orElseThrow(() -> new IllegalStateException(
- "Trying to create UnfoldTransitionProgressProvider when the "
- + "transition is disabled"));
- initUnfoldAnimationController(mUnfoldTransitionProgressProvider,
- unfoldComponent.getRotationChangeProvider());
- }
-
/** Receives animation progress from sysui process. */
private void initRemotelyCalculatedUnfoldAnimation(UnfoldTransitionConfig config) {
RemoteUnfoldSharedComponent unfoldComponent = UnfoldTransitionFactory.createRemoteUnfoldSharedComponent(
@@ -942,6 +942,13 @@ public class QuickstepLauncher extends Launcher {
return mSplitToWorkspaceController;
}
+ @Override
+ protected void handleSplitAnimationGoingToHome() {
+ super.handleSplitAnimationGoingToHome();
+ mSplitSelectStateController.getSplitAnimationController()
+ .playPlaceholderDismissAnim(this);
+ }
+
public T getActionsView() {
return (T) mActionsView;
}
@@ -977,39 +984,6 @@ public class QuickstepLauncher extends Launcher {
return mAppTransitionManager.hasControlRemoteAppTransitionPermission();
}
- @Override
- public DragOptions getDefaultWorkspaceDragOptions() {
- if (mNextWorkspaceDragOptions != null) {
- DragOptions options = mNextWorkspaceDragOptions;
- mNextWorkspaceDragOptions = null;
- return options;
- }
- 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) {
- // On the first call clear the reference.
- signal.cancel();
- ValueAnimator fadeAnimation = ValueAnimator.ofFloat(1, 0);
- fadeAnimation.addUpdateListener(new RemoteFadeOutAnimationListener(appTargets,
- wallpaperTargets));
- AnimatorSet anim = new AnimatorSet();
- anim.play(fadeAnimation);
- return anim;
- }
- }, signal);
- }
-
@Override
public float[] getNormalOverviewScaleAndOffset() {
return DisplayController.getNavigationMode(this).hasGestures
@@ -1064,6 +1038,7 @@ public class QuickstepLauncher extends Launcher {
(v != null && v.getDisplay() != null) ? v.getDisplay().getDisplayId()
: Display.DEFAULT_DISPLAY);
if (app.lawnchair.LawnchairApp.isAtleastT()) {
+ Utilities.allowBGLaunch(activityOptions.options);
addLaunchCookie(item, activityOptions.options);
}
return activityOptions;
@@ -1077,6 +1052,7 @@ public class QuickstepLauncher extends Launcher {
Executors.MAIN_EXECUTOR.getHandler(), null,
elapsedRealTime -> callbacks.executeAllAndDestroy());
options.setSplashScreenStyle(splashScreenStyle);
+ Utilities.allowBGLaunch(options);
return new ActivityOptionsWrapper(options, callbacks);
}
@@ -1126,7 +1102,6 @@ public class QuickstepLauncher extends Launcher {
}
switch (info.itemType) {
case Favorites.ITEM_TYPE_APPLICATION:
- case Favorites.ITEM_TYPE_SHORTCUT:
case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case Favorites.ITEM_TYPE_APPWIDGET:
// Fall through and continue if it's an app, shortcut, or widget
@@ -1242,8 +1217,9 @@ public class QuickstepLauncher extends Launcher {
public void dispatchDeviceProfileChanged() {
super.dispatchDeviceProfileChanged();
SystemUiProxy.INSTANCE.get(this).setLauncherAppIconSize(mDeviceProfile.iconSizePx);
- if (mTaskbarManager != null) {
- mTaskbarManager.debugWhyTaskbarNotDestroyed("QuickstepLauncher#onDeviceProfileChanged");
+ TaskbarManager taskbarManager = mTISBindHelper.getTaskbarManager();
+ if (taskbarManager != null) {
+ taskbarManager.debugWhyTaskbarNotDestroyed("QuickstepLauncher#onDeviceProfileChanged");
}
}
@@ -1264,8 +1240,7 @@ public class QuickstepLauncher extends Launcher {
groupTask.task1.key.id,
groupTask.task2.key.id,
SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
- /* callback= */ success -> {
- },
+ /* callback= */ success -> mSplitSelectStateController.resetState(),
/* freezeTaskList= */ true,
groupTask.mSplitBounds == null
? DEFAULT_SPLIT_RATIO
@@ -1274,6 +1249,18 @@ public class QuickstepLauncher extends Launcher {
: groupTask.mSplitBounds.leftTaskPercent);
}
+ /**
+ * Launches two apps as an app pair.
+ */
+ public void launchAppPair(WorkspaceItemInfo app1, WorkspaceItemInfo app2) {
+ mSplitSelectStateController.getAppPairsController().launchAppPair(app1, app2);
+ }
+
+ public boolean canStartHomeSafely() {
+ OverviewCommandHelper overviewCommandHelper = mTISBindHelper.getOverviewCommandHelper();
+ return overviewCommandHelper == null || overviewCommandHelper.canStartHomeSafely();
+ }
+
private static final class LauncherTaskViewController extends
TaskViewTouchController {
LauncherTaskViewController(Launcher activity) {
@@ -1309,5 +1296,34 @@ public class QuickstepLauncher extends Launcher {
if (recentsView != null) {
recentsView.getSplitSelectController().dump(prefix, writer);
}
+ if (mAppTransitionManager != null) {
+ mAppTransitionManager.dump(prefix + "\t" + RING_APPEAR_ANIMATION_PREFIX, writer);
+ }
+ if (mHotseatPredictionController != null) {
+ mHotseatPredictionController.dump(prefix, writer);
+ }
+ }
+
+ @Override
+ public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
+ switch (name) {
+ case "TextClock", "android.widget.TextClock" -> {
+ TextClock tc = new TextClock(context, attrs);
+ if (mAsyncClockEventDelegate == null) {
+ mAsyncClockEventDelegate = new AsyncClockEventDelegate(this);
+ }
+// tc.setClockEventDelegate(mAsyncClockEventDelegate);
+ return tc;
+ }
+ case "AnalogClock", "android.widget.AnalogClock" -> {
+ AnalogClock ac = new AnalogClock(context, attrs);
+ if (mAsyncClockEventDelegate == null) {
+ mAsyncClockEventDelegate = new AsyncClockEventDelegate(this);
+ }
+// ac.setClockEventDelegate(mAsyncClockEventDelegate);
+ return ac;
+ }
+ }
+ return super.onCreateView(parent, name, context, attrs);
}
}
\ 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 39543b0d7d..9dbca581fb 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
@@ -49,7 +49,7 @@ import java.util.function.IntConsumer;
/**
* {@link LauncherWidgetHolder} that puts the app widget host in the background
*/
-public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
+public class QuickstepWidgetHolder extends LauncherWidgetHolder {
private static final String TAG = "QuickstepWidgetHolder";
@@ -76,7 +76,7 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
// Map to all pending updated keyed with appWidgetId;
private final SparseArray mPendingUpdateMap = new SparseArray<>();
- private QuickstepWidgetHolder(@NonNull Context context,
+ public QuickstepWidgetHolder(@NonNull Context context,
@Nullable IntConsumer appWidgetRemovedCallback,
@Nullable RemoteViews.InteractionHandler interactionHandler) {
super(context, appWidgetRemovedCallback);
@@ -243,6 +243,7 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
} else {
widgetView = new LauncherAppWidgetHostView(context);
}
+ widgetView.setIsWidgetCachingDisabled(true);
widgetView.setInteractionHandler(mInteractionHandler);
widgetView.setAppWidget(appWidgetId, appWidget);
mViews.put(appWidgetId, widgetView);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index f16b43df5e..23e922c945 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -15,10 +15,10 @@
*/
package com.android.launcher3.uioverrides;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON;
import static com.android.launcher3.LauncherState.OVERVIEW_ACTIONS;
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsFragment.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsFragment.java
index b901a87753..a76eb437de 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsFragment.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsFragment.java
@@ -23,6 +23,7 @@ import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
+import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT_ARG_KEY;
import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.PLUGIN_CHANGED;
import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.pluginEnabledKey;
@@ -59,6 +60,7 @@ import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
import androidx.preference.PreferenceViewHolder;
+import androidx.preference.SeekBarPreference;
import androidx.preference.SwitchPreference;
import com.android.launcher3.LauncherPrefs;
@@ -106,6 +108,9 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
loadPluginPrefs();
maybeAddSandboxCategory();
addOnboardingPrefsCatergory();
+ if (FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get()) {
+ addAllAppsFromOverviewCatergory();
+ }
if (getActivity() != null) {
getActivity().setTitle("Developer Options");
@@ -393,6 +398,33 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
}
}
+ private void addAllAppsFromOverviewCatergory() {
+ PreferenceCategory category = newCategory("All Apps from Overview Config");
+
+ SeekBarPreference thresholdPref = new SeekBarPreference(getContext());
+ thresholdPref.setTitle("Threshold to open All Apps from Overview");
+ thresholdPref.setSingleLineTitle(false);
+
+ // These values are 100x swipe up shift value (100 = where overview sits).
+ thresholdPref.setMax(500);
+ thresholdPref.setMin(105);
+ thresholdPref.setUpdatesContinuously(true);
+ thresholdPref.setIconSpaceReserved(false);
+ // Don't directly save to shared prefs, use LauncherPrefs instead.
+ thresholdPref.setPersistent(false);
+ thresholdPref.setOnPreferenceChangeListener((preference, newValue) -> {
+ LauncherPrefs.get(getContext()).put(ALL_APPS_OVERVIEW_THRESHOLD, newValue);
+ preference.setSummary(String.valueOf((int) newValue / 100f));
+ return true;
+ });
+ int value = LauncherPrefs.get(getContext()).get(ALL_APPS_OVERVIEW_THRESHOLD);
+ thresholdPref.setValue(value);
+ // For some reason the initial value is not triggering the summary update, so call manually.
+ thresholdPref.getOnPreferenceChangeListener().onPreferenceChange(thresholdPref, value);
+
+ category.addPreference(thresholdPref);
+ }
+
private String toName(String action) {
String str = action.replace("com.android.systemui.action.PLUGIN_", "")
.replace("com.android.launcher3.action.PLUGIN_", "");
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index c8fb2a2890..d7763112b2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -15,14 +15,16 @@
*/
package com.android.launcher3.uioverrides.states;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
+import static com.android.app.animation.Interpolators.DECELERATE_2;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS;
import android.content.Context;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
@@ -94,7 +96,7 @@ public class AllAppsState extends LauncherState {
@Override
public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
PageAlphaProvider superPageAlphaProvider = super.getWorkspacePageAlphaProvider(launcher);
- return new PageAlphaProvider(DEACCEL_2) {
+ return new PageAlphaProvider(DECELERATE_2) {
@Override
public float getPageAlpha(int pageIndex) {
return launcher.getDeviceProfile().isTablet
@@ -106,14 +108,52 @@ public class AllAppsState extends LauncherState {
@Override
public int getVisibleElements(Launcher launcher) {
- // Don't add HOTSEAT_ICONS for non-tablets in ALL_APPS state.
- return launcher.getDeviceProfile().isTablet ? ALL_APPS_CONTENT | HOTSEAT_ICONS
- : ALL_APPS_CONTENT;
+ int elements = ALL_APPS_CONTENT | FLOATING_SEARCH_BAR;
+ // Only add HOTSEAT_ICONS for tablets in ALL_APPS state.
+ if (launcher.getDeviceProfile().isTablet) {
+ elements |= HOTSEAT_ICONS;
+ }
+ return elements;
+ }
+
+ @Override
+ public int getFloatingSearchBarRestingMarginBottom(Launcher launcher) {
+ return 0;
+ }
+
+ @Override
+ public int getFloatingSearchBarRestingMarginStart(Launcher launcher) {
+ DeviceProfile dp = launcher.getDeviceProfile();
+ return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin();
+ }
+
+ @Override
+ public int getFloatingSearchBarRestingMarginEnd(Launcher launcher) {
+ DeviceProfile dp = launcher.getDeviceProfile();
+ return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin();
+ }
+
+ @Override
+ public boolean shouldFloatingSearchBarUsePillWhenUnfocused(Launcher launcher) {
+ DeviceProfile dp = launcher.getDeviceProfile();
+ return dp.isPhone && !dp.isLandscape;
}
@Override
public LauncherState getHistoryForState(LauncherState previousState) {
- return previousState == OVERVIEW ? OVERVIEW : NORMAL;
+ return previousState == BACKGROUND_APP ? QUICK_SWITCH_FROM_HOME
+ : previousState == OVERVIEW ? OVERVIEW : NORMAL;
+ }
+
+ @Override
+ public float[] getOverviewScaleAndOffset(Launcher launcher) {
+ if (!FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get()) {
+ return super.getOverviewScaleAndOffset(launcher);
+ }
+ // This handles the case of returning to the previous app from Overview -> All Apps gesture.
+ // This is the start scale/offset of overview that will be used for that transition.
+ // TODO (b/283336332): Translate in Y direction (ideally with overview resistance).
+ return new float[] {0.5f /* scale */, NO_OFFSET};
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 6f17221d5b..2f3608f4b8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -15,7 +15,7 @@
*/
package com.android.launcher3.uioverrides.states;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
+import static com.android.app.animation.Interpolators.DECELERATE_2;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
import android.content.Context;
@@ -102,7 +102,7 @@ public class OverviewState extends LauncherState {
@Override
public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
- return new PageAlphaProvider(DEACCEL_2) {
+ return new PageAlphaProvider(DECELERATE_2) {
@Override
public float getPageAlpha(int pageIndex) {
return 0;
@@ -115,7 +115,32 @@ public class OverviewState extends LauncherState {
if (PreferenceManager.getInstance(launcher).getRecentsActionClearAll().get()) {
return OVERVIEW_ACTIONS;
}
- return CLEAR_ALL_BUTTON | OVERVIEW_ACTIONS;
+ int elements = CLEAR_ALL_BUTTON | OVERVIEW_ACTIONS;
+ DeviceProfile dp = launcher.getDeviceProfile();
+ boolean showFloatingSearch;
+ if (dp.isPhone) {
+ // Only show search in phone overview in portrait mode.
+ showFloatingSearch = !dp.isLandscape;
+ } else {
+ // Only show search in tablet overview if taskbar is not visible.
+ showFloatingSearch = !dp.isTaskbarPresent || isTaskbarStashed(launcher);
+ }
+ if (showFloatingSearch) {
+ elements |= FLOATING_SEARCH_BAR;
+ }
+ return elements;
+ }
+
+ @Override
+ public int getFloatingSearchBarRestingMarginBottom(Launcher launcher) {
+ return areElementsVisible(launcher, FLOATING_SEARCH_BAR) ? 0
+ : super.getFloatingSearchBarRestingMarginBottom(launcher);
+ }
+
+ @Override
+ public boolean shouldFloatingSearchBarUsePillWhenUnfocused(Launcher launcher) {
+ DeviceProfile dp = launcher.getDeviceProfile();
+ return dp.isPhone && !dp.isLandscape;
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index a8d7538687..599e1dfc72 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -17,6 +17,20 @@ package com.android.launcher3.uioverrides.states;
import static android.view.View.VISIBLE;
+import static com.android.app.animation.Interpolators.ACCELERATE;
+import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
+import static com.android.app.animation.Interpolators.DECELERATE;
+import static com.android.app.animation.Interpolators.DECELERATE_1_7;
+import static com.android.app.animation.Interpolators.DECELERATE_3;
+import static com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE;
+import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE;
+import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.app.animation.Interpolators.FINAL_FRAME;
+import static com.android.app.animation.Interpolators.INSTANT;
+import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.app.animation.Interpolators.OVERSHOOT_0_75;
+import static com.android.app.animation.Interpolators.OVERSHOOT_1_2;
+import static com.android.app.animation.Interpolators.clampToProgress;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.HINT_STATE;
import static com.android.launcher3.LauncherState.HINT_STATE_TWO_BUTTON;
@@ -25,20 +39,6 @@ import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
import static com.android.launcher3.QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION;
import static com.android.launcher3.WorkspaceStateTransitionAnimation.getWorkspaceSpringScaleAnimator;
-import static com.android.launcher3.anim.Interpolators.ACCEL;
-import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
-import static com.android.launcher3.anim.Interpolators.EMPHASIZED_ACCELERATE;
-import static com.android.launcher3.anim.Interpolators.EMPHASIZED_DECELERATE;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
-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.anim.Interpolators.OVERSHOOT_0_75;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
-import static com.android.launcher3.anim.Interpolators.clampToProgress;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
@@ -55,12 +55,14 @@ import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_T
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
import android.animation.ValueAnimator;
+import android.util.Log;
import com.android.launcher3.CellLayout;
import com.android.launcher3.Hotseat;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Workspace;
import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.touch.AllAppsSwipeController;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.DisplayController;
@@ -78,12 +80,14 @@ public class QuickstepAtomicAnimationFactory extends
private static final float RECENTS_PREPARE_SCALE = 1.33f;
// Scale workspace takes before animating in
private static final float WORKSPACE_PREPARE_SCALE = 0.92f;
- // Constants to specify how to scroll RecentsView to the default page if it's not already there.
+ // Constants to specify how to scroll RecentsView to the default page if it's
+ // not already there.
private static final int DEFAULT_PAGE = 0;
private static final int PER_PAGE_SCROLL_DURATION = 150;
private static final int MAX_PAGE_SCROLL_DURATION = 750;
- // Due to use of physics, duration may differ between devices so we need to calculate and
+ // Due to use of physics, duration may differ between devices so we need to
+ // calculate and
// cache the value.
private int mHintToNormalDuration = -1;
@@ -94,11 +98,11 @@ public class QuickstepAtomicAnimationFactory extends
@Override
public void prepareForAtomicAnimation(LauncherState fromState, LauncherState toState,
StateAnimationConfig config) {
-
+ Log.d(TestProtocol.OVERVIEW_OVER_HOME, "creating animation fromState: "
+ + fromState + " toState: " + toState);
RecentsView overview = mActivity.getOverviewPanel();
if ((fromState == OVERVIEW || fromState == OVERVIEW_SPLIT_SELECT) && toState == NORMAL) {
- overview.switchToScreenshot(() ->
- overview.finishRecentsAnimation(true /* toRecents */, null));
+ overview.switchToScreenshot(() -> overview.finishRecentsAnimation(true /* toRecents */, null));
if (fromState == OVERVIEW_SPLIT_SELECT) {
config.setInterpolator(ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN,
@@ -112,8 +116,8 @@ public class QuickstepAtomicAnimationFactory extends
fromState == OVERVIEW_SPLIT_SELECT
? clampToProgress(LINEAR, 0.33f, 1)
: LINEAR);
- config.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL);
- config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
+ config.setInterpolator(ANIM_WORKSPACE_SCALE, DECELERATE);
+ config.setInterpolator(ANIM_WORKSPACE_FADE, ACCELERATE);
if (DisplayController.getNavigationMode(mActivity).hasGestures
&& overview.getTaskViewCount() > 0) {
@@ -132,20 +136,22 @@ public class QuickstepAtomicAnimationFactory extends
numPagesToScroll * PER_PAGE_SCROLL_DURATION);
config.duration = Math.max(config.duration, scrollDuration);
- // Sync scroll so that it ends before or at the same time as the taskbar animation.
+ // Sync scroll so that it ends before or at the same time as the taskbar
+ // animation.
if (DisplayController.isTransientTaskbar(mActivity)
&& mActivity.getDeviceProfile().isTaskbarPresent) {
config.duration = Math.min(config.duration, TASKBAR_TO_HOME_DURATION);
}
overview.snapToPage(DEFAULT_PAGE, Math.toIntExact(config.duration));
} else {
- config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL_DEACCEL);
- config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
- config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
+ config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCELERATE_DECELERATE);
+ config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCELERATE, 0, 0.9f));
+ config.setInterpolator(ANIM_OVERVIEW_FADE, DECELERATE_1_7);
}
Workspace> workspace = mActivity.getWorkspace();
- // Start from a higher workspace scale, but only if we're invisible so we don't jump.
+ // Start from a higher workspace scale, but only if we're invisible so we don't
+ // jump.
boolean isWorkspaceVisible = workspace.getVisibility() == VISIBLE;
if (isWorkspaceVisible) {
CellLayout currentChild = (CellLayout) workspace.getChildAt(
@@ -167,8 +173,8 @@ public class QuickstepAtomicAnimationFactory extends
|| fromState == HINT_STATE_TWO_BUTTON) && toState == OVERVIEW) {
if (DisplayController.getNavigationMode(mActivity).hasGestures) {
config.setInterpolator(ANIM_WORKSPACE_SCALE,
- fromState == NORMAL ? ACCEL : OVERSHOOT_1_2);
- config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL);
+ fromState == NORMAL ? ACCELERATE : OVERSHOOT_1_2);
+ config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCELERATE);
// Scrolling in tasks, so show straight away
if (overview.getTaskViewCount() > 0) {
@@ -196,7 +202,7 @@ public class QuickstepAtomicAnimationFactory extends
config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_2);
config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, OVERSHOOT_1_2);
} else if (fromState == HINT_STATE && toState == NORMAL) {
- config.setInterpolator(ANIM_DEPTH, DEACCEL_3);
+ config.setInterpolator(ANIM_DEPTH, DECELERATE_3);
if (mHintToNormalDuration == -1) {
ValueAnimator va = getWorkspaceSpringScaleAnimator(mActivity,
mActivity.getWorkspace(),
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index 8cbd6e8b47..b266bcd246 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -15,19 +15,22 @@
*/
package com.android.launcher3.uioverrides.touchcontrollers;
+import static com.android.app.animation.Interpolators.DECELERATE_3;
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU;
import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent;
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PULL_BACK_ALPHA;
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PULL_BACK_TRANSLATION;
import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
+import static com.android.launcher3.util.NavigationMode.THREE_BUTTONS;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.view.MotionEvent;
import android.view.animation.Interpolator;
@@ -41,24 +44,29 @@ 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.DisplayController;
import com.android.launcher3.util.TouchController;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.OverviewToHomeAnim;
import com.android.quickstep.views.RecentsView;
+import java.util.function.Consumer;
+
/**
* Handles swiping up on the nav bar to go home from launcher, e.g. overview or all apps.
*/
public class NavBarToHomeTouchController implements TouchController,
SingleAxisSwipeDetector.Listener {
- private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL_3;
+ private static final Interpolator PULLBACK_INTERPOLATOR = DECELERATE_3;
// The min amount of overview scrim we keep during the transition.
private static final float OVERVIEW_TO_HOME_SCRIM_MULTIPLIER = 0.5f;
private final Launcher mLauncher;
+ private final Consumer mCancelSplitRunnable;
private final SingleAxisSwipeDetector mSwipeDetector;
private final float mPullbackDistance;
@@ -67,8 +75,14 @@ public class NavBarToHomeTouchController implements TouchController,
private LauncherState mEndState = NORMAL;
private AnimatorPlaybackController mCurrentAnimation;
- public NavBarToHomeTouchController(Launcher launcher) {
+ /**
+ * @param cancelSplitRunnable Called when split placeholder view needs to be cancelled.
+ * Animation should be added to the provided AnimatorSet
+ */
+ public NavBarToHomeTouchController(Launcher launcher,
+ Consumer cancelSplitRunnable) {
mLauncher = launcher;
+ mCancelSplitRunnable = cancelSplitRunnable;
mSwipeDetector = new SingleAxisSwipeDetector(mLauncher, this,
SingleAxisSwipeDetector.VERTICAL);
mPullbackDistance = mLauncher.getResources().getDimension(R.dimen.home_pullback_distance);
@@ -95,6 +109,10 @@ public class NavBarToHomeTouchController implements TouchController,
}
private boolean canInterceptTouch(MotionEvent ev) {
+ if (!isTrackpadMotionEvent(ev) && DisplayController.getNavigationMode(mLauncher)
+ == THREE_BUTTONS) {
+ return false;
+ }
boolean cameFromNavBar = (ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) != 0;
if (!cameFromNavBar) {
return false;
@@ -176,7 +194,10 @@ public class NavBarToHomeTouchController implements TouchController,
recentsView.switchToScreenshot(null,
() -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
if (mStartState.overviewUi) {
- new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(mEndState))
+ new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(mEndState),
+ FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()
+ ? mCancelSplitRunnable
+ : null)
.animateWithVelocity(velocity);
} else {
mLauncher.getStateManager().goToState(mEndState, true,
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index b7bafd8969..ca598c8411 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -16,19 +16,22 @@
package com.android.launcher3.uioverrides.touchcontrollers;
+import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR;
import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.HINT_STATE;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent;
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
-import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
+import static com.android.launcher3.util.NavigationMode.THREE_BUTTONS;
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
+import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.graphics.PointF;
@@ -43,6 +46,7 @@ import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.taskbar.LauncherTaskbarUIController;
import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.VibratorWrapper;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.AnimatorControllerWithResistance;
@@ -50,6 +54,8 @@ import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.OverviewToHomeAnim;
import com.android.quickstep.views.RecentsView;
+import java.util.function.Consumer;
+
/**
* Touch controller which handles swipe and hold from the nav bar to go to Overview. Swiping above
* the nav bar falls back to go to All Apps. Swiping from the nav bar without holding goes to the
@@ -64,6 +70,7 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
private static final float TRANSLATION_ANIM_VELOCITY_DP_PER_MS = 0.8f;
private final VibratorWrapper mVibratorWrapper;
+ private final Consumer mCancelSplitRunnable;
private final RecentsView mRecentsView;
private final MotionPauseDetector mMotionPauseDetector;
private final float mMotionPauseMinDisplacement;
@@ -79,16 +86,26 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
// Normal to Hint animation has flag SKIP_OVERVIEW, so we update this scrim with this animator.
private ObjectAnimator mNormalToHintOverviewScrimAnimator;
- public NoButtonNavbarToOverviewTouchController(Launcher l) {
+ /**
+ * @param cancelSplitRunnable Called when split placeholder view needs to be cancelled.
+ * Animation should be added to the provided AnimatorSet
+ */
+ public NoButtonNavbarToOverviewTouchController(Launcher l,
+ Consumer cancelSplitRunnable) {
super(l);
mRecentsView = l.getOverviewPanel();
mMotionPauseDetector = new MotionPauseDetector(l);
mMotionPauseMinDisplacement = ViewConfiguration.get(l).getScaledTouchSlop();
mVibratorWrapper = VibratorWrapper.INSTANCE.get(l.getApplicationContext());
+ mCancelSplitRunnable = cancelSplitRunnable;
}
@Override
protected boolean canInterceptTouch(MotionEvent ev) {
+ if (!isTrackpadMotionEvent(ev) && DisplayController.getNavigationMode(mLauncher)
+ == THREE_BUTTONS) {
+ return false;
+ }
mDidTouchStartInNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
boolean isOneHandedModeActive = (SystemUiProxy.INSTANCE.get(mLauncher)
.getLastSystemUiStateFlags() & SYSUI_STATE_ONE_HANDED_ACTIVE) != 0;
@@ -102,9 +119,6 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
if (fromState == NORMAL && mDidTouchStartInNavBar) {
return HINT_STATE;
- } else if (fromState == OVERVIEW && isDragTowardPositive) {
- // Don't allow swiping up to all apps.
- return OVERVIEW;
}
return super.getTargetState(fromState, isDragTowardPositive);
}
@@ -190,6 +204,9 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
// state, but since the hint state tracks the entire screen without a clear endpoint, we
// need to manually set the duration to a reasonable value.
animator.setDuration(HINT_STATE.getTransitionDuration(mLauncher, true /* isToState */));
+ AnimatorSet animatorSet = new AnimatorSet();
+ mCancelSplitRunnable.accept(animatorSet);
+ animatorSet.start();
}
if (FeatureFlags.ENABLE_PREMIUM_HAPTICS_ALL_APPS.get() &&
((mFromState == NORMAL && mToState == ALL_APPS)
@@ -261,7 +278,7 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
private void goToOverviewOrHomeOnDragEnd(float velocity) {
boolean goToHomeInsteadOfOverview = !mMotionPauseDetector.isPaused();
if (goToHomeInsteadOfOverview) {
- new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(NORMAL))
+ new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(NORMAL), null)
.animateWithVelocity(velocity);
}
if (mReachedOverview) {
@@ -273,7 +290,7 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
mRecentsView.animate()
.translationX(0)
.translationY(0)
- .setInterpolator(ACCEL_DEACCEL)
+ .setInterpolator(ACCELERATE_DECELERATE)
.setDuration(duration)
.withEndAction(goToHomeInsteadOfOverview
? null
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 80f5558315..60d4ad9bc0 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -18,19 +18,20 @@ package com.android.launcher3.uioverrides.touchcontrollers;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
+import static com.android.app.animation.Interpolators.ACCELERATE_0_75;
+import static com.android.app.animation.Interpolators.DECELERATE_3;
+import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.app.animation.Interpolators.scrollInterpolatorForVelocity;
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.isTrackpadMotionEvent;
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;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_UNKNOWN_SWIPEDOWN;
@@ -46,6 +47,7 @@ import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_SCRIM;
import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT;
import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP;
+import static com.android.launcher3.util.NavigationMode.THREE_BUTTONS;
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
@@ -74,6 +76,7 @@ import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.BaseSwipeDetector;
import com.android.launcher3.touch.BothAxesSwipeDetector;
import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.util.VibratorWrapper;
import com.android.quickstep.SystemUiProxy;
@@ -84,17 +87,20 @@ import com.android.quickstep.util.WorkspaceRevealAnim;
import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.LauncherRecentsView;
import com.android.quickstep.views.RecentsView;
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
/**
- * Handles quick switching to a recent task from the home screen. To give as much flexibility to
- * the user as possible, also handles swipe up and hold to go to overview and swiping back home.
+ * Handles quick switching to a recent task from the home screen. To give as
+ * much flexibility to
+ * the user as possible, also handles swipe up and hold to go to overview and
+ * swiping back home.
*/
public class NoButtonQuickSwitchTouchController implements TouchController,
BothAxesSwipeDetector.Listener {
private static final float Y_ANIM_MIN_PROGRESS = 0.25f;
- private static final Interpolator FADE_OUT_INTERPOLATOR = DEACCEL_3;
- private static final Interpolator TRANSLATE_OUT_INTERPOLATOR = ACCEL_0_75;
+ private static final Interpolator FADE_OUT_INTERPOLATOR = DECELERATE_3;
+ private static final Interpolator TRANSLATE_OUT_INTERPOLATOR = ACCELERATE_0_75;
private static final Interpolator SCALE_DOWN_INTERPOLATOR = LINEAR;
private static final long ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW = 300;
@@ -106,8 +112,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
private final MotionPauseDetector mMotionPauseDetector;
private final float mMotionPauseMinDisplacement;
private final LauncherRecentsView mRecentsView;
- protected final AnimatorListener mClearStateOnCancelListener =
- newCancelListener(this::clearState);
+ protected final AnimatorListener mClearStateOnCancelListener = newCancelListener(this::clearState);
private boolean mNoIntercept;
private Boolean mIsTrackpadFourFingerSwipe;
@@ -115,7 +120,8 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
private boolean mIsHomeScreenVisible = true;
- // As we drag, we control 3 animations: one to get non-overview components out of the way,
+ // As we drag, we control 3 animations: one to get non-overview components out
+ // of the way,
// and the other two to set overview properties based on x and y progress.
private AnimatorPlaybackController mNonOverviewAnim;
private AnimatorPlaybackController mXOverviewAnim;
@@ -127,7 +133,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
mRecentsView = mLauncher.getOverviewPanel();
mXRange = mLauncher.getDeviceProfile().widthPx / 2f;
mYRange = LayoutUtils.getShelfTrackingDistance(
- mLauncher, mLauncher.getDeviceProfile(), mRecentsView.getPagedOrientationHandler());
+ mLauncher, mLauncher.getDeviceProfile(), mRecentsView.getPagedOrientationHandler());
mMaxYProgress = mLauncher.getDeviceProfile().heightPx / mYRange;
mMotionPauseDetector = new MotionPauseDetector(mLauncher);
mMotionPauseMinDisplacement = mLauncher.getResources().getDimension(
@@ -136,19 +142,18 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
- int action = ev.getActionMasked();
- if (action == ACTION_DOWN) {
- mIsTrackpadFourFingerSwipe = null;
+ if (ev.getActionMasked() == ACTION_DOWN) {
mNoIntercept = !canInterceptTouch(ev);
if (mNoIntercept) {
return false;
}
- // Only detect horizontal swipe for intercept, then we will allow swipe up as well.
+ // 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) {
+ && ev.getActionMasked() == ACTION_MOVE) {
mIsTrackpadFourFingerSwipe = isTrackpadFourFingerSwipe(ev);
mNoIntercept = !mIsTrackpadFourFingerSwipe;
if (mNoIntercept) {
@@ -170,6 +175,9 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
}
private boolean canInterceptTouch(MotionEvent ev) {
+ if (!isTrackpadMotionEvent(ev) && DisplayController.getNavigationMode(mLauncher) == THREE_BUTTONS) {
+ return false;
+ }
if (!mLauncher.isInState(LauncherState.NORMAL)) {
return false;
}
@@ -184,6 +192,9 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
// TODO(b/268075592): add support for quickswitch to/from desktop
return false;
}
+ if (isTrackpadMultiFingerSwipe(ev)) {
+ return isTrackpadFourFingerSwipe(ev);
+ }
return true;
}
@@ -191,6 +202,9 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
public void onDragStart(boolean start) {
mMotionPauseDetector.clear();
if (start) {
+ InteractionJankMonitorWrapper.begin(mRecentsView,
+ InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
+
mStartState = mLauncher.getStateManager().getState();
mMotionPauseDetector.setOnMotionPauseListener(this::onMotionPauseDetected);
@@ -255,9 +269,9 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
float[] scaleAndOffset = toState.getOverviewScaleAndOffset(mLauncher);
// As we drag right, animate the following properties:
- // - RecentsView translationX
- // - OverviewScrim
- // - RecentsView fade (if it's empty)
+ // - RecentsView translationX
+ // - OverviewScrim
+ // - RecentsView fade (if it's empty)
PendingAnimation xAnim = new PendingAnimation((long) (mXRange * 2));
xAnim.setFloat(mRecentsView, ADJACENT_PAGE_HORIZONTAL_OFFSET, scaleAndOffset[1], LINEAR);
// Use QuickSwitchState instead of OverviewState to determine scrim color,
@@ -271,8 +285,8 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
mXOverviewAnim.dispatchOnStart();
// As we drag up, animate the following properties:
- // - RecentsView scale
- // - RecentsView fullscreenProgress
+ // - RecentsView scale
+ // - RecentsView fullscreenProgress
PendingAnimation yAnim = new PendingAnimation((long) (mYRange * 2));
yAnim.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0],
SCALE_DOWN_INTERPOLATOR);
@@ -302,8 +316,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
if (wasHomeScreenVisible && mNonOverviewAnim != null) {
mNonOverviewAnim.setPlayFraction(xProgress);
}
- mIsHomeScreenVisible = FADE_OUT_INTERPOLATOR.getInterpolation(xProgress)
- <= 1 - ALPHA_CUTOFF_THRESHOLD;
+ mIsHomeScreenVisible = FADE_OUT_INTERPOLATOR.getInterpolation(xProgress) <= 1 - ALPHA_CUTOFF_THRESHOLD;
mMotionPauseDetector.setDisallowPause(-displacement.y < mMotionPauseMinDisplacement);
mMotionPauseDetector.addPosition(ev);
@@ -325,6 +338,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
if (mMotionPauseDetector.isPaused() && noFling) {
// Going to Overview.
cancelAnimations();
+ InteractionJankMonitorWrapper.cancel(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
StateAnimationConfig config = new StateAnimationConfig();
config.duration = ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
@@ -340,7 +354,8 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
});
overviewAnim.start();
- // Create an empty state transition so StateListeners get onStateTransitionStart().
+ // Create an empty state transition so StateListeners get
+ // onStateTransitionStart().
mLauncher.getStateManager().createAnimationToNewWorkspace(
OVERVIEW, config.duration, StateAnimationConfig.SKIP_ALL_ANIMATIONS)
.dispatchOnStart();
@@ -360,7 +375,8 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
// Flinging up and right could go either home or to quick switch.
// Determine the target based on the higher velocity.
targetState = Math.abs(velocity.x) > Math.abs(velocity.y)
- ? QUICK_SWITCH_FROM_HOME : NORMAL;
+ ? QUICK_SWITCH_FROM_HOME
+ : NORMAL;
}
}
} else if (horizontalFling) {
@@ -372,7 +388,8 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
boolean passedHorizontalThreshold = mXOverviewAnim.getInterpolatedProgress() > 0.5f;
boolean passedVerticalThreshold = mYOverviewAnim.value > 1f;
targetState = passedHorizontalThreshold && !passedVerticalThreshold
- ? QUICK_SWITCH_FROM_HOME : NORMAL;
+ ? QUICK_SWITCH_FROM_HOME
+ : NORMAL;
}
// Animate the various components to the target state.
@@ -441,6 +458,8 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
RecentsView.SCROLL_VIBRATION_PRIMITIVE,
RecentsView.SCROLL_VIBRATION_PRIMITIVE_SCALE,
RecentsView.SCROLL_VIBRATION_FALLBACK);
+ } else {
+ InteractionJankMonitorWrapper.cancel(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
}
nonOverviewAnim.setDuration(Math.max(xDuration, yDuration));
@@ -462,6 +481,11 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
: targetState.ordinal > mStartState.ordinal
? LAUNCHER_UNKNOWN_SWIPEUP
: LAUNCHER_UNKNOWN_SWIPEDOWN));
+
+ if (targetState == QUICK_SWITCH_FROM_HOME) {
+ InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
+ }
+
mLauncher.getStateManager().goToState(targetState, false, forEndCallback(this::clearState));
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 8368f9ca03..e30fe667ff 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -24,11 +24,12 @@ import static com.android.launcher3.LauncherState.OVERVIEW;
import android.view.MotionEvent;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.allapps.AllAppsTransitionController;
-import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
import com.android.launcher3.touch.AllAppsSwipeController;
@@ -92,9 +93,9 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
@Override
protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
if (fromState == ALL_APPS && !isDragTowardPositive) {
- return NORMAL;
- } else if (fromState == OVERVIEW) {
- return isDragTowardPositive ? OVERVIEW : NORMAL;
+ return FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get()
+ ? mLauncher.getStateManager().getLastState()
+ : NORMAL;
} else if (fromState == NORMAL && isDragTowardPositive) {
return ALL_APPS;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index f941b02065..9a35bb2ae7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -15,12 +15,12 @@
*/
package com.android.launcher3.uioverrides.touchcontrollers;
+import static com.android.app.animation.Interpolators.ACCELERATE_2;
+import static com.android.app.animation.Interpolators.DECELERATE_2;
+import static com.android.app.animation.Interpolators.INSTANT;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.QUICK_SWITCH_FROM_HOME;
-import static com.android.launcher3.anim.Interpolators.ACCEL_2;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
-import static com.android.launcher3.anim.Interpolators.INSTANT;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
@@ -128,14 +128,14 @@ public class QuickSwitchTouchController extends AbstractStateChangeTouchControll
}
private void setupInterpolators(StateAnimationConfig stateAnimationConfig) {
- stateAnimationConfig.setInterpolator(ANIM_WORKSPACE_FADE, DEACCEL_2);
- stateAnimationConfig.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_2);
+ stateAnimationConfig.setInterpolator(ANIM_WORKSPACE_FADE, DECELERATE_2);
+ stateAnimationConfig.setInterpolator(ANIM_ALL_APPS_FADE, DECELERATE_2);
if (DisplayController.getNavigationMode(mLauncher) == NavigationMode.NO_BUTTON) {
// Overview lives to the left of workspace, so translate down later than over
- stateAnimationConfig.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL_2);
- stateAnimationConfig.setInterpolator(ANIM_VERTICAL_PROGRESS, ACCEL_2);
- stateAnimationConfig.setInterpolator(ANIM_OVERVIEW_SCALE, ACCEL_2);
- stateAnimationConfig.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, ACCEL_2);
+ stateAnimationConfig.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCELERATE_2);
+ stateAnimationConfig.setInterpolator(ANIM_VERTICAL_PROGRESS, ACCELERATE_2);
+ stateAnimationConfig.setInterpolator(ANIM_OVERVIEW_SCALE, ACCELERATE_2);
+ stateAnimationConfig.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, ACCELERATE_2);
stateAnimationConfig.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
} else {
stateAnimationConfig.setInterpolator(ANIM_WORKSPACE_TRANSLATE, LINEAR);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
index 5da0188312..17092b48d1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
@@ -92,32 +92,7 @@ public class StatusBarTouchController implements TouchController {
private void dispatchTouchEvent(MotionEvent ev) {
if (mSystemUiProxy.isActive()) {
mLastAction = ev.getActionMasked();
- mSystemUiProxy.onStatusBarMotionEvent(ev);
- } else if (!mExpanded) {
- mExpanded = true;
- expand();
- }
- if (!mVibrated) {
- mVibrated = true;
- vibrate();
- }
- }
-
- @SuppressLint({ "WrongConstant", "PrivateApi" })
- private void expand() {
- try {
- Class.forName("android.app.StatusBarManager")
- .getMethod("expandNotificationsPanel")
- .invoke(mLauncher.getSystemService("statusbar"));
- } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException
- | ClassNotFoundException e) {
- e.printStackTrace();
- }
- }
-
- private void vibrate() {
- if (!LawnchairAppKt.getLawnchairApp(mLauncher).isVibrateOnIconAnimation()) {
- VibratorWrapper.INSTANCE.get(mLauncher).vibrate(VibratorWrapper.OVERVIEW_HAPTIC);
+ mSystemUiProxy.onStatusBarTouchEvent(ev);
}
}
@@ -131,8 +106,6 @@ public class StatusBarTouchController implements TouchController {
if (!mCanIntercept) {
return false;
}
- mExpanded = false;
- mVibrated = false;
mDownEvents.clear();
mDownEvents.put(pid, new PointF(ev.getX(), ev.getY()));
} else if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index eddc50c64f..3d94857848 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -27,13 +27,13 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.touch.BaseSwipeDetector;
import com.android.launcher3.touch.PagedOrientationHandler;
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 918eedb10f..6d222a3364 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -21,19 +21,19 @@ import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static android.widget.Toast.LENGTH_SHORT;
+import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
+import static com.android.app.animation.Interpolators.DECELERATE;
+import static com.android.app.animation.Interpolators.OVERSHOOT_1_2;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
+import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
import static com.android.launcher3.PagedView.INVALID_PAGE;
-import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_GESTURE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
-import static com.android.launcher3.uioverrides.QuickstepLauncher.ENABLE_PIP_KEEP_CLEAR_ALGORITHM;
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.SystemUiController.UI_STATE_FULLSCREEN_TASK;
@@ -94,6 +94,7 @@ import androidx.annotation.UiThread;
import com.android.internal.util.LatencyTracker;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
@@ -102,14 +103,17 @@ import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.taskbar.TaskbarThresholdUtils;
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.SafeCloseable;
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.util.WindowBounds;
@@ -123,7 +127,6 @@ import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.InputConsumerProxy;
import com.android.quickstep.util.InputProxyHandlerFactory;
import com.android.quickstep.util.MotionPauseDetector;
-import com.android.quickstep.util.ProtoTracer;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.StaggeredWorkspaceAnim;
@@ -134,6 +137,7 @@ import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
+import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -150,6 +154,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.Objects;
import java.util.Optional;
+import java.util.OptionalInt;
import java.util.function.Consumer;
import app.lawnchair.util.CompatibilityKt;
@@ -188,18 +193,19 @@ public abstract class AbsSwipeUpHandler, Q extends
protected MultiStateCallback mStateCallback;
protected boolean mCanceled;
private boolean mRecentsViewScrollLinked = false;
- private final ActivityLifecycleCallbacksAdapter mLifecycleCallbacks = new ActivityLifecycleCallbacksAdapter() {
- @Override
- public void onActivityDestroyed(Activity activity) {
- if (mActivity != activity) {
- return;
- }
- ActiveGestureLog.INSTANCE.addLog("Launcher destroyed", LAUNCHER_DESTROYED);
- mRecentsView = null;
- mActivity = null;
- }
- };
-
+ private final ActivityLifecycleCallbacksAdapter mLifecycleCallbacks =
+ new ActivityLifecycleCallbacksAdapter() {
+ @Override
+ public void onActivityDestroyed(Activity activity) {
+ if (mActivity != activity) {
+ return;
+ }
+ ActiveGestureLog.INSTANCE.addLog("Launcher destroyed", LAUNCHER_DESTROYED);
+ mRecentsView = null;
+ mActivity = null;
+ mStateCallback.clearState(STATE_LAUNCHER_PRESENT);
+ }
+ };
private static int FLAG_COUNT = 0;
private static int getNextStateFlag(String name) {
@@ -284,7 +290,8 @@ public abstract class AbsSwipeUpHandler, Q extends
private boolean mContinuingLastGesture;
- private ThumbnailData mTaskSnapshot;
+ // Cache of recently-updated task snapshots, mapping task id to ThumbnailData
+ private HashMap mTaskSnapshotCache = new HashMap<>();
// Used to control launcher components throughout the swipe gesture.
private AnimatorControllerWithResistance mLauncherTransitionController;
@@ -369,12 +376,12 @@ public abstract class AbsSwipeUpHandler, Q extends
TaskbarUIController controller = mActivityInterface.getTaskbarController();
mTaskbarAlreadyOpen = controller != null && !controller.isTaskbarStashed();
mIsTaskbarAllAppsOpen = controller != null && controller.isTaskbarAllAppsOpen();
- mTaskbarAppWindowThreshold = res.getDimensionPixelSize(R.dimen.taskbar_app_window_threshold);
+ mTaskbarAppWindowThreshold = TaskbarThresholdUtils.getAppWindowThreshold(res, mDp);
boolean swipeWillNotShowTaskbar = mTaskbarAlreadyOpen || mGestureState.isTrackpadGesture();
mTaskbarHomeOverviewThreshold = swipeWillNotShowTaskbar
? 0
- : res.getDimensionPixelSize(R.dimen.taskbar_home_overview_threshold);
- mTaskbarCatchUpThreshold = res.getDimensionPixelSize(R.dimen.taskbar_catch_up_threshold);
+ : TaskbarThresholdUtils.getHomeOverviewThreshold(res, mDp);
+ mTaskbarCatchUpThreshold = TaskbarThresholdUtils.getCatchUpThreshold(res, mDp);
}
@Nullable
@@ -510,6 +517,9 @@ public abstract class AbsSwipeUpHandler, Q extends
// view when
// the animation is canceled
mGestureState.runOnceAtState(STATE_RECENTS_ANIMATION_CANCELED, () -> {
+ if (mRecentsView == null)
+ return;
+
HashMap snapshots = mGestureState.consumeRecentsAnimationCanceledSnapshot();
if (snapshots != null) {
mRecentsView.switchToScreenshot(snapshots, () -> {
@@ -521,9 +531,6 @@ public abstract class AbsSwipeUpHandler, Q extends
}
});
mRecentsView.onRecentsAnimationComplete();
- if (mRecentsAnimationController != null) {
- mRecentsAnimationController.cleanupScreenshot();
- }
}
});
@@ -586,7 +593,7 @@ public abstract class AbsSwipeUpHandler, Q extends
if (mWasLauncherAlreadyVisible) {
mStateCallback.setState(STATE_LAUNCHER_DRAWN);
} else {
- Object traceToken = TraceHelper.INSTANCE.beginSection("WTS-init");
+ SafeCloseable traceToken = TraceHelper.INSTANCE.beginAsyncSection("WTS-init");
View dragLayer = activity.getDragLayer();
dragLayer.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {
boolean mHandled = false;
@@ -598,7 +605,7 @@ public abstract class AbsSwipeUpHandler, Q extends
}
mHandled = true;
- TraceHelper.INSTANCE.endSection(traceToken);
+ traceToken.close();
dragLayer.post(() -> dragLayer.getViewTreeObserver().removeOnDrawListener(this));
if (activity != mActivity) {
return;
@@ -657,11 +664,22 @@ public abstract class AbsSwipeUpHandler, Q extends
protected void notifyGestureAnimationStartToRecents() {
Task[] runningTasks;
+ TopTaskTracker.CachedTaskInfo cachedTaskInfo = mGestureState.getRunningTask();
if (mIsSwipeForSplit) {
int[] splitTaskIds = TopTaskTracker.INSTANCE.get(mContext).getRunningSplitTaskIds();
- runningTasks = mGestureState.getRunningTask().getPlaceholderTasks(splitTaskIds);
+ runningTasks = cachedTaskInfo.getPlaceholderTasks(splitTaskIds);
} else {
- runningTasks = mGestureState.getRunningTask().getPlaceholderTasks();
+ runningTasks = cachedTaskInfo.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;
}
// Safeguard against any null tasks being sent to recents view, happens when
@@ -683,11 +701,10 @@ public abstract class AbsSwipeUpHandler, Q extends
private void initializeLauncherAnimationController() {
buildAnimationController();
- Object traceToken = TraceHelper.INSTANCE.beginSection("logToggleRecents",
- TraceHelper.FLAG_IGNORE_BINDERS);
- LatencyTracker.getInstance(mContext).logAction(LatencyTracker.ACTION_TOGGLE_RECENTS,
- (int) (mLauncherFrameDrawnTime - mTouchTimeMs));
- TraceHelper.INSTANCE.endSection(traceToken);
+ try (SafeCloseable c = TraceHelper.INSTANCE.allowIpcs("logToggleRecents")) {
+ LatencyTracker.getInstance(mContext).logAction(LatencyTracker.ACTION_TOGGLE_RECENTS,
+ (int) (mLauncherFrameDrawnTime - mTouchTimeMs));
+ }
// This method is only called when STATE_GESTURE_STARTED is set, so we can
// enable the
@@ -739,11 +756,15 @@ public abstract class AbsSwipeUpHandler, Q extends
* @param moveRunningTask whether to move running task to front when attaching
*/
private void maybeUpdateRecentsAttachedState(boolean animate, boolean moveRunningTask) {
- if (!mDeviceState.isFullyGesturalNavMode() || mRecentsView == null) {
+ if ((!mDeviceState.isFullyGesturalNavMode() && !mGestureState.isTrackpadGesture())
+ || mRecentsView == null) {
return;
}
+ // looking at single target is fine here since either app of a split pair would
+ // have their "isInRecents" field set? (that's what this is used for below)
RemoteAnimationTarget runningTaskTarget = mRecentsAnimationTargets != null
- ? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId())
+ ? mRecentsAnimationTargets
+ .findTask(mGestureState.getTopRunningTaskId())
: null;
final boolean recentsAttachedToAppWindow;
if (mIsInAllAppsRegion) {
@@ -822,6 +843,10 @@ public abstract class AbsSwipeUpHandler, Q extends
VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
maybeUpdateRecentsAttachedState(true);
+ if (mActivity != null) {
+ mActivity.getAppsView().getSearchUiManager().prepareToFocusEditText(mIsInAllAppsRegion);
+ }
+
// Draw active task below Launcher so that All Apps can appear over it.
runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
.setDrawsBelowRecents(isInAllAppsRegion));
@@ -891,7 +916,8 @@ public abstract class AbsSwipeUpHandler, Q extends
@UiThread
@Override
public void onCurrentShiftUpdated() {
- setIsInAllAppsRegion(mCurrentShift.value >= ALL_APPS_SHIFT_THRESHOLD);
+ float threshold = LauncherPrefs.get(mContext).get(ALL_APPS_OVERVIEW_THRESHOLD) / 100f;
+ setIsInAllAppsRegion(mCurrentShift.value >= threshold);
updateSysUiFlags(mCurrentShift.value);
applyScrollAndTransform();
@@ -1180,6 +1206,14 @@ public abstract class AbsSwipeUpHandler, Q extends
mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
// Notify the SysUI to use fade-in animation when entering PiP
SystemUiProxy.INSTANCE.get(mContext).setPipAnimationTypeToAlpha();
+ if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
+ // Notify the SysUI to stash desktop apps if they are visible
+ DesktopVisibilityController desktopVisibilityController = mActivityInterface
+ .getDesktopVisibilityController();
+ if (desktopVisibilityController != null) {
+ desktopVisibilityController.onHomeActionTriggered();
+ }
+ }
break;
case RECENTS:
mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
@@ -1213,7 +1247,7 @@ public abstract class AbsSwipeUpHandler, Q extends
return false;
}
boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTarget).anyMatch(
- targetCompat -> targetCompat.taskId == mGestureState.getLastStartedTaskId());
+ mGestureState.mLastStartedTaskIdPredicate);
if (mStateCallback.hasStates(STATE_START_NEW_TASK) && hasStartedTaskBefore) {
reset();
return true;
@@ -1360,11 +1394,11 @@ public abstract class AbsSwipeUpHandler, Q extends
Interpolator interpolator;
S state = mActivityInterface.stateFromGestureEndTarget(endTarget);
if (state.displayOverviewTasksAsGrid(mDp)) {
- interpolator = ACCEL_DEACCEL;
+ interpolator = ACCELERATE_DECELERATE;
} else if (endTarget == RECENTS) {
interpolator = OVERSHOOT_1_2;
} else {
- interpolator = DEACCEL;
+ interpolator = DECELERATE;
}
if (endTarget.isLauncher) {
@@ -1479,22 +1513,13 @@ public abstract class AbsSwipeUpHandler, Q extends
ArrayList launchCookies, long duration, boolean isTargetTranslucent,
boolean appCanEnterPip, RemoteAnimationTarget runningTaskTarget);
- private final TaskStackChangeListener mActivityRestartListener = new TaskStackChangeListener() {
- @Override
- public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
- 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
- // options (since we don't know if there's an associated app icon to launch
- // from)
- endRunningWindowAnim(true /* cancel */);
- TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
- mActivityRestartListener);
- ActivityManagerWrapper.getInstance().startActivityFromRecents(task.taskId, null);
- }
- }
- };
+ private final TaskStackChangeListener mActivityRestartListener=new TaskStackChangeListener(){@Override public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,boolean homeTaskVisible,boolean clearedTask,boolean wasVisible){boolean taskRunningAndNotHome=Arrays.stream(mGestureState.getRunningTaskIds(true /*
+ * getMultipleTasks
+ */)).anyMatch(taskId->task.taskId==taskId&&task.configuration.windowConfiguration.getActivityType()!=ACTIVITY_TYPE_HOME);if(taskRunningAndNotHome){
+ // Since this is an edge case, just cancel and relaunch with default activity
+ // options (since we don't know if there's an associated app icon to launch
+ // from)
+ endRunningWindowAnim(true /* cancel */);TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mActivityRestartListener);ActivityManagerWrapper.getInstance().startActivityFromRecents(task.taskId,null);}}};
@UiThread
private void animateToProgressInternal(float start, float end, long duration,
@@ -1529,8 +1554,12 @@ public abstract class AbsSwipeUpHandler, Q extends
if (mGestureState.getEndTarget() == HOME) {
getOrientationHandler().adjustFloatingIconStartVelocity(velocityPxPerMs);
+ // Take first task ID, if there are multiple we don't have any special home
+ // animation so doesn't matter for splitscreen.. though the "allowEnterPip" might change
+ // depending on which task it is..
final RemoteAnimationTarget runningTaskTarget = mRecentsAnimationTargets != null
- ? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId())
+ ? mRecentsAnimationTargets
+ .findTask(mGestureState.getTopRunningTaskId())
: null;
final ArrayList cookies = runningTaskTarget != null
&& runningTaskTarget.taskInfo != null
@@ -1558,11 +1587,12 @@ public abstract class AbsSwipeUpHandler, Q extends
mSwipePipToHomeReleaseCheck.setCanRelease(false);
}
- // grab a screenshot before the PipContentOverlay gets parented on top of the
- // task
+ // grab a screenshot before the PipContentOverlay gets parented on top of the task
UI_HELPER_EXECUTOR.execute(() -> {
- mTaskSnapshot = mRecentsAnimationController.screenshotTask(
- mGestureState.getRunningTaskId());
+ // Directly use top task, split to pip handled on shell side
+ final int taskId = mGestureState.getTopRunningTaskId();
+ mTaskSnapshotCache.put(taskId,
+ mRecentsAnimationController.screenshotTask(taskId));
});
// let SystemUi reparent the overlay leash as soon as possible
@@ -1582,7 +1612,8 @@ public abstract class AbsSwipeUpHandler, Q extends
windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
windowAnim[0].addAnimatorListener(new AnimationSuccessListener() {
- @Override
+
+ @Override
public void onAnimationSuccess(Animator animator) {
if (mRecentsAnimationController == null) {
// If the recents animation is interrupted, we still end the running
@@ -1778,11 +1809,6 @@ public abstract class AbsSwipeUpHandler, Q extends
private Rect getKeepClearAreaForHotseat() {
Rect keepClearArea;
- if (!ENABLE_PIP_KEEP_CLEAR_ALGORITHM) {
- // make the height equal to hotseatBarSizePx only
- keepClearArea = new Rect(0, 0, 0, mDp.hotseatBarSizePx);
- return keepClearArea;
- }
// the keep clear area in global screen coordinates, in pixels
if (mDp.isPhone) {
if (mDp.isSeascape()) {
@@ -1982,7 +2008,7 @@ public abstract class AbsSwipeUpHandler, Q extends
mActivityInitListener.unregister();
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
mActivityRestartListener);
- mTaskSnapshot = null;
+ mTaskSnapshotCache.clear();
}
private void invalidateHandler() {
@@ -2000,7 +2026,7 @@ public abstract class AbsSwipeUpHandler, Q extends
mActivityInitListener.unregister();
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
mActivityRestartListener);
- mTaskSnapshot = null;
+ mTaskSnapshotCache.clear();
}
private void invalidateHandlerWithLauncher() {
@@ -2058,38 +2084,40 @@ public abstract class AbsSwipeUpHandler, Q extends
// If there are no targets, then we don't need to capture anything
mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
} else {
- final int runningTaskId = mGestureState.getRunningTaskId();
boolean finishTransitionPosted = false;
+ // If we already have cached screenshot(s) from running tasks, skip update
+ boolean shouldUpdate = false;
+ int[] runningTaskIds = mGestureState.getRunningTaskIds(mIsSwipeForSplit);
+ for (int id : runningTaskIds) {
+ if (!mTaskSnapshotCache.containsKey(id)) {
+ shouldUpdate = true;
+ break;
+ }
+ }
+
if (mRecentsAnimationController != null) {
// Update the screenshot of the task
- if (mTaskSnapshot == null) {
+ if (shouldUpdate) {
UI_HELPER_EXECUTOR.execute(() -> {
if (mRecentsAnimationController == null)
return;
- final ThumbnailData taskSnapshot = mRecentsAnimationController.screenshotTask(runningTaskId);
- // If split case, we should update all split tasks snapshot
- if (mIsSwipeForSplit) {
- int[] splitTaskIds = TopTaskTracker.INSTANCE.get(
- mContext).getRunningSplitTaskIds();
- for (int i = 0; i < splitTaskIds.length; i++) {
- // Skip running one because done above.
- if (splitTaskIds[i] == runningTaskId)
- continue;
-
- mRecentsAnimationController.screenshotTask(splitTaskIds[i]);
- }
+ for (int id : runningTaskIds) {
+ mTaskSnapshotCache.put(
+ id, mRecentsAnimationController.screenshotTask(id));
}
+
MAIN_EXECUTOR.execute(() -> {
- mTaskSnapshot = taskSnapshot;
- if (!updateThumbnail(runningTaskId, false /* refreshView */)) {
+ if (!updateThumbnail(false /* refreshView */)) {
setScreenshotCapturedState();
}
});
});
return;
}
- finishTransitionPosted = updateThumbnail(runningTaskId, false /* refreshView */);
+
+ finishTransitionPosted = updateThumbnail(false /* refreshView */);
}
+
if (!finishTransitionPosted) {
setScreenshotCapturedState();
}
@@ -2097,9 +2125,7 @@ public abstract class AbsSwipeUpHandler, Q extends
}
// Returns whether finish transition was posted.
- private boolean updateThumbnail(int runningTaskId, boolean refreshView) {
- boolean finishTransitionPosted = false;
- final TaskView taskView;
+ private boolean updateThumbnail(boolean refreshView) {
if (mGestureState.getEndTarget() == HOME
|| mGestureState.getEndTarget() == NEW_TASK
|| mGestureState.getEndTarget() == ALL_APPS
@@ -2108,26 +2134,27 @@ public abstract class AbsSwipeUpHandler, Q extends
// quickswitching to
// ensure it's taken in the correct orientation, but no need to update the
// thumbnail.
- taskView = null;
- } else {
- taskView = mRecentsView.updateThumbnail(runningTaskId, mTaskSnapshot, refreshView);
+ return false;
}
- if (taskView != null && refreshView && !mCanceled) {
+
+ boolean finishTransitionPosted = false;
+ TaskView updatedTaskView = mRecentsView.updateThumbnail(mTaskSnapshotCache, refreshView);
+ if (updatedTaskView != null && refreshView && !mCanceled) {
// Defer finishing the animation until the next launcher frame with the
// new thumbnail
- finishTransitionPosted = ViewUtils.postFrameDrawn(taskView,
+ finishTransitionPosted = ViewUtils.postFrameDrawn(updatedTaskView,
() -> mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED),
this::isCanceled);
}
+
return finishTransitionPosted;
}
private void setScreenshotCapturedState() {
// If we haven't posted a draw callback, set the state immediately.
- Object traceToken = TraceHelper.INSTANCE.beginSection(SCREENSHOT_CAPTURED_EVT,
- TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS);
+ TraceHelper.INSTANCE.beginSection(SCREENSHOT_CAPTURED_EVT);
mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
- TraceHelper.INSTANCE.endSection(traceToken);
+ TraceHelper.INSTANCE.endSection();
}
private void finishCurrentTransitionToRecents() {
@@ -2269,16 +2296,27 @@ public abstract class AbsSwipeUpHandler, Q extends
if (!mCanceled) {
TaskView nextTask = mRecentsView == null ? null : mRecentsView.getNextPageTaskView();
if (nextTask != null) {
- Task.TaskKey nextTaskKey = nextTask.getTask().key;
- int taskId = nextTaskKey.id;
- mGestureState.updateLastStartedTaskId(taskId);
- boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds()
- .contains(taskId);
+ int[] taskIds = nextTask.getTaskIds();
+ StringBuilder nextTaskLog = new StringBuilder();
+ for (TaskIdAttributeContainer c : nextTask.getTaskIdAttributeContainers()) {
+ if (c == null) {
+ continue;
+ }
+ nextTaskLog
+ .append("[id: ")
+ .append(c.getTask().key.id)
+ .append(", pkg: ")
+ .append(c.getTask().key.getPackageName())
+ .append("] | ");
+ }
+ mGestureState.updateLastStartedTaskIds(taskIds);
+ boolean hasTaskPreviouslyAppeared = Arrays.stream(taskIds).anyMatch(
+ taskId -> mGestureState.getPreviouslyAppearedTaskIds()
+ .contains(taskId));
if (!hasTaskPreviouslyAppeared) {
ActiveGestureLog.INSTANCE.trackEvent(EXPECTING_TASK_APPEARED);
}
- ActiveGestureLog.INSTANCE.addLog("Launching task: id=" + taskId
- + " pkg=" + nextTaskKey.getPackageName());
+ ActiveGestureLog.INSTANCE.addLog("Launching task: " + nextTaskLog);
nextTask.launchTask(success -> {
resultCallback.accept(success);
if (success) {
@@ -2353,7 +2391,7 @@ public abstract class AbsSwipeUpHandler, Q extends
public void onTasksAppeared(RemoteAnimationTarget[] appearedTaskTargets) {
if (mRecentsAnimationController != null) {
boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTargets).anyMatch(
- targetCompat -> targetCompat.taskId == mGestureState.getLastStartedTaskId());
+ mGestureState.mLastStartedTaskIdPredicate);
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
@@ -2366,7 +2404,7 @@ public abstract class AbsSwipeUpHandler, Q extends
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
} else if (handleTaskAppeared(appearedTaskTargets)) {
Optional taskTargetOptional = Arrays.stream(appearedTaskTargets)
- .filter(targetCompat -> targetCompat.taskId == mGestureState.getLastStartedTaskId())
+ .filter(mGestureState.mLastStartedTaskIdPredicate)
.findFirst();
if (!taskTargetOptional.isPresent()) {
ActiveGestureLog.INSTANCE.addLog("No appeared task matching started task id");
@@ -2437,11 +2475,16 @@ public abstract class AbsSwipeUpHandler, Q extends
* resume if we finish the controller.
*/
protected int getLastAppearedTaskIndex() {
- return mRecentsView == null
- ? -1
- : mGestureState.getLastAppearedTaskId() != -1
- ? mRecentsView.getTaskIndexForId(mGestureState.getLastAppearedTaskId())
- : mRecentsView.getRunningTaskIndex();
+ if (mRecentsView == null) {
+ return -1;
+ }
+
+ OptionalInt firstValidTaskId = Arrays.stream(mGestureState.getLastAppearedTaskIds())
+ .filter(i -> i != -1)
+ .findFirst();
+ return firstValidTaskId.isPresent()
+ ? mRecentsView.getTaskIndexForId(firstValidTaskId.getAsInt())
+ : mRecentsView.getRunningTaskIndex();
}
/**
@@ -2450,7 +2493,7 @@ public abstract class AbsSwipeUpHandler, Q extends
* but before that task appeared.
*/
protected boolean hasStartedNewTask() {
- return mGestureState.getLastStartedTaskId() != -1;
+ return mGestureState.getLastStartedTaskIds()[0] != -1;
}
/**
@@ -2493,7 +2536,6 @@ public abstract class AbsSwipeUpHandler, Q extends
taskViewSimulator.apply(remoteHandle.getTransformParams());
}
}
- ProtoTracer.INSTANCE.get(mContext).scheduleFrameUpdate();
}
// Scaling of RecentsView during quick switch based on amount of recents scroll
@@ -2514,11 +2556,11 @@ public abstract class AbsSwipeUpHandler, Q extends
if (scrollOffset < mQuickSwitchScaleScrollThreshold) {
scaleProgress = Utilities.mapToRange(scrollOffset, 0, mQuickSwitchScaleScrollThreshold,
- 0, maxScaleProgress, ACCEL_DEACCEL);
+ 0, maxScaleProgress, ACCELERATE_DECELERATE);
} else if (scrollOffset > (maxScrollOffset - mQuickSwitchScaleScrollThreshold)) {
scaleProgress = Utilities.mapToRange(scrollOffset,
(maxScrollOffset - mQuickSwitchScaleScrollThreshold), maxScrollOffset,
- maxScaleProgress, 0, ACCEL_DEACCEL);
+ maxScaleProgress, 0, ACCELERATE_DECELERATE);
}
return scaleProgress;
@@ -2549,7 +2591,7 @@ public abstract class AbsSwipeUpHandler, Q extends
// "Catch up" with the displacement at mTaskbarCatchUpThreshold.
if (displacement < mTaskbarCatchUpThreshold) {
return Utilities.mapToRange(displacement, mTaskbarAppWindowThreshold,
- mTaskbarCatchUpThreshold, 0, mTaskbarCatchUpThreshold, ACCEL_DEACCEL);
+ mTaskbarCatchUpThreshold, 0, mTaskbarCatchUpThreshold, ACCELERATE_DECELERATE);
}
return displacement;
@@ -2564,27 +2606,6 @@ public abstract class AbsSwipeUpHandler, Q extends
mRecentsAnimationTargets.nonApps, shown, null /* animatorHandler */);
}
- /**
- * Used for winscope tracing, see launcher_trace.proto
- *
- * @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto
- * @param inputConsumerProto The parent of this proto message.
- */
- public void writeToProto(InputConsumerProto.Builder inputConsumerProto) {
- SwipeHandlerProto.Builder swipeHandlerProto = SwipeHandlerProto.newBuilder();
-
- mGestureState.writeToProto(swipeHandlerProto);
-
- swipeHandlerProto.setIsRecentsAttachedToAppWindow(
- mAnimationFactory.isRecentsAttachedToAppWindow());
- swipeHandlerProto.setScrollOffset(mRecentsView == null
- ? 0
- : mRecentsView.getScrollOffset());
- swipeHandlerProto.setAppToOverviewProgress(mCurrentShift.value);
-
- inputConsumerProto.setSwipeHandler(swipeHandlerProto);
- }
-
public interface Factory {
AbsSwipeUpHandler newHandler(GestureState gestureState, long touchTimeMs);
}
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 60083c67e7..b0557b4de3 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -15,11 +15,11 @@
*/
package com.android.quickstep;
+import static com.android.app.animation.Interpolators.ACCELERATE_2;
+import static com.android.app.animation.Interpolators.INSTANT;
+import static com.android.app.animation.Interpolators.LINEAR;
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;
import static com.android.quickstep.AbsSwipeUpHandler.RECENTS_ATTACH_DURATION;
import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
@@ -75,11 +75,11 @@ import java.util.function.Consumer;
import java.util.function.Predicate;
/**
- * Utility class which abstracts out the logical differences between Launcher and RecentsActivity.
+ * Utility class which abstracts out the logical differences between Launcher
+ * and RecentsActivity.
*/
@TargetApi(Build.VERSION_CODES.P)
-public abstract class BaseActivityInterface,
- ACTIVITY_TYPE extends StatefulActivity> {
+public abstract class BaseActivityInterface, ACTIVITY_TYPE extends StatefulActivity> {
public final boolean rotationSupportedByActivity;
@@ -87,7 +87,8 @@ public abstract class BaseActivityInterface onInitListener);
/**
- * Sets a callback to be run when an activity launch happens while launcher is not yet resumed.
+ * Sets a callback to be run when an activity launch happens while launcher is
+ * not yet resumed.
*/
- public void setOnDeferredActivityLaunchCallback(Runnable r) {}
+ public void setOnDeferredActivityLaunchCallback(Runnable r) {
+ }
@Nullable
public abstract ACTIVITY_TYPE getCreatedActivity();
@@ -330,7 +337,8 @@ public abstract class BaseActivityInterface mActivity.getStateManager().goToState(
controller.getInterpolatedProgress() > 0.5 ? mTargetState : mBackgroundState,
false));
RecentsView recentsView = mActivity.getOverviewPanel();
- AnimatorControllerWithResistance controllerWithResistance =
- AnimatorControllerWithResistance.createForRecents(controller, mActivity,
+ AnimatorControllerWithResistance controllerWithResistance = AnimatorControllerWithResistance
+ .createForRecents(controller, mActivity,
recentsView.getPagedViewOrientedState(), mActivity.getDeviceProfile(),
recentsView, RECENTS_SCALE_PROPERTY, recentsView,
TASK_SECONDARY_TRANSLATION);
mCallback.accept(controllerWithResistance);
- // Creating the activity controller animation sometimes reapplies the launcher state
- // (because we set the animation as the current state animation), so we reapply the
+ // Creating the activity controller animation sometimes reapplies the launcher
+ // state
+ // (because we set the animation as the current state animation), so we reapply
+ // the
// attached state here as well to ensure recents is shown/hidden appropriately.
if (DisplayController.getNavigationMode(mActivity) == NavigationMode.NO_BUTTON) {
setRecentsAttachedToAppWindow(mIsAttachedToWindow, false);
@@ -548,12 +566,13 @@ public abstract class BaseActivityInterface sAllowedFrameworkClasses = Set.of(
+ "android.view.IWindowSession",
+ "android.os.IPowerManager",
+ "android.os.IServiceManager");
+
+ /**
+ * Starts tracking binder class and returns a {@link SafeCloseable} to end tracking
+ */
+ public static SafeCloseable startTracking(Consumer callback) {
+ TraceHelper current = TraceHelper.INSTANCE;
+
+ TraceHelperExtension helper = new TraceHelperExtension(callback);
+ TraceHelper.INSTANCE = helper;
+ Binder.setProxyTransactListener(helper);
+
+ return () -> {
+ Binder.setProxyTransactListener(null);
+ TraceHelper.INSTANCE = current;
+ };
+ }
+
+ private static final LinkedList mMainThreadTraceStack = new LinkedList<>();
+ private static final LinkedList mMainThreadIgnoreIpcStack = new LinkedList<>();
+
+ private static class TraceHelperExtension extends TraceHelper implements ProxyTransactListener {
+
+ private final Consumer mUnexpectedTransactionCallback;
+
+ TraceHelperExtension(Consumer unexpectedTransactionCallback) {
+ mUnexpectedTransactionCallback = unexpectedTransactionCallback;
+ }
+
+ @Override
+ public void beginSection(String sectionName) {
+ if (isMainThread()) {
+ mMainThreadTraceStack.add(sectionName);
+ }
+ super.beginSection(sectionName);
+ }
+
+ @Override
+ public SafeCloseable beginAsyncSection(String sectionName) {
+ if (!isMainThread()) {
+ return super.beginAsyncSection(sectionName);
+ }
+
+ mMainThreadTraceStack.add(sectionName);
+ int cookie = Random.Default.nextInt();
+ Trace.beginAsyncSection(sectionName, cookie);
+ return () -> {
+ Trace.endAsyncSection(sectionName, cookie);
+ mMainThreadTraceStack.remove(sectionName);
+ };
+ }
+
+ @Override
+ public void endSection() {
+ super.endSection();
+ if (isMainThread()) {
+ mMainThreadTraceStack.pollLast();
+ }
+ }
+
+ @Override
+ public SafeCloseable allowIpcs(String rpcName) {
+ if (!isMainThread()) {
+ return super.allowIpcs(rpcName);
+ }
+
+ mMainThreadTraceStack.add(rpcName);
+ mMainThreadIgnoreIpcStack.add(rpcName);
+ int cookie = Random.Default.nextInt();
+ Trace.beginAsyncSection(rpcName, cookie);
+ return () -> {
+ Trace.endAsyncSection(rpcName, cookie);
+ mMainThreadTraceStack.remove(rpcName);
+ mMainThreadIgnoreIpcStack.remove(rpcName);
+ };
+ }
+
+ @Override
+ public Object onTransactStarted(IBinder binder, int transactionCode, int flags) {
+ if (!isMainThread() || (flags & FLAG_ONEWAY) == FLAG_ONEWAY) {
+ return null;
+ }
+
+ String ipcBypass = mMainThreadIgnoreIpcStack.peekLast();
+ String descriptor;
+ try {
+ descriptor = binder.getInterfaceDescriptor();
+ if (sAllowedFrameworkClasses.contains(descriptor)) {
+ return null;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error getting IPC descriptor", e);
+ descriptor = binder.getClass().getSimpleName();
+ }
+
+ if (ipcBypass == null) {
+ mUnexpectedTransactionCallback.accept(new BinderCallSite(
+ mMainThreadTraceStack.peekLast(), descriptor, transactionCode));
+ } else {
+ Log.d(TAG, "MainThread-IPC " + descriptor + " ignored due to " + ipcBypass);
+ }
+ return null;
+ }
+
+ @Override
+ public Object onTransactStarted(IBinder binder, int transactionCode) {
+ // Do nothing
+ return null;
+ }
+
+ @Override
+ public void onTransactEnded(Object session) {
+ // Do nothing
+ }
+ }
+
+ private static boolean isMainThread() {
+ return Thread.currentThread() == Looper.getMainLooper().getThread();
+ }
+
+ /**
+ * Information about a binder call
+ */
+ public static class BinderCallSite {
+
+ @Nullable
+ public final String activeTrace;
+ public final String descriptor;
+ public final int transactionCode;
+
+ BinderCallSite(String activeTrace, String descriptor, int transactionCode) {
+ this.activeTrace = activeTrace;
+ this.descriptor = descriptor;
+ this.transactionCode = transactionCode;
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index eff53f3c85..c5a88bc073 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -19,13 +19,13 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.content.Intent.EXTRA_COMPONENT_NAME;
import static android.content.Intent.EXTRA_USER;
+import static com.android.app.animation.Interpolators.ACCELERATE;
import static com.android.launcher3.GestureNavContract.EXTRA_GESTURE_CONTRACT;
import static com.android.launcher3.GestureNavContract.EXTRA_ICON_POSITION;
import static com.android.launcher3.GestureNavContract.EXTRA_ICON_SURFACE;
import static com.android.launcher3.GestureNavContract.EXTRA_ON_FINISH_CALLBACK;
import static com.android.launcher3.GestureNavContract.EXTRA_REMOTE_CALLBACK;
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
-import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
import android.animation.ObjectAnimator;
@@ -296,7 +296,7 @@ public class FallbackSwipeHandler extends
@Override
public AnimatorPlaybackController createActivityAnimationToHome() {
PendingAnimation pa = new PendingAnimation(mDuration);
- pa.setFloat(mRecentsAlpha, AnimatedFloat.VALUE, 0, ACCEL);
+ pa.setFloat(mRecentsAlpha, AnimatedFloat.VALUE, 0, ACCELERATE);
return pa.createPlaybackController();
}
@@ -324,7 +324,7 @@ public class FallbackSwipeHandler extends
@Override
public void playAtomicAnimation(float velocity) {
ObjectAnimator alphaAnim = mHomeAlpha.animateToValue(mHomeAlpha.value, 1);
- alphaAnim.setDuration(mDuration).setInterpolator(ACCEL);
+ alphaAnim.setDuration(mDuration).setInterpolator(ACCELERATE);
alphaAnim.start();
if (mRunningOverHome) {
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 9d7ccb42e4..4c74489d4d 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -15,8 +15,9 @@
*/
package com.android.quickstep;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+
import static com.android.launcher3.MotionEventsUtils.isTrackpadFourFingerSwipe;
-import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
import static com.android.launcher3.MotionEventsUtils.isTrackpadThreeFingerSwipe;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
@@ -37,8 +38,6 @@ import android.view.RemoteAnimationTarget;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.launcher3.tracing.GestureStateProto;
-import com.android.launcher3.tracing.SwipeHandlerProto;
import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
import com.android.quickstep.util.ActiveGestureErrorDetector;
import com.android.quickstep.util.ActiveGestureLog;
@@ -46,51 +45,68 @@ import com.android.systemui.shared.recents.model.ThumbnailData;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.function.Predicate;
/**
- * Manages the state for an active system gesture, listens for events from the system and Launcher,
+ * Manages the state for an active system gesture, listens for events from the
+ * system and Launcher,
* and fires events when the states change.
*/
@TargetApi(Build.VERSION_CODES.R)
public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationListener {
+ final Predicate mLastStartedTaskIdPredicate = new Predicate<>() {
+ @Override
+ public boolean test(RemoteAnimationTarget targetCompat) {
+ for (int taskId : mLastStartedTaskId) {
+ if (targetCompat.taskId == taskId) {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+
/**
* Defines the end targets of a gesture and the associated state.
*/
public enum GestureEndTarget {
- HOME(true, LAUNCHER_STATE_HOME, false, GestureStateProto.GestureEndTarget.HOME),
+ HOME(true, LAUNCHER_STATE_HOME, false),
- RECENTS(true, LAUNCHER_STATE_OVERVIEW, true, GestureStateProto.GestureEndTarget.RECENTS),
+ RECENTS(true, LAUNCHER_STATE_OVERVIEW, true),
- NEW_TASK(false, LAUNCHER_STATE_BACKGROUND, true,
- GestureStateProto.GestureEndTarget.NEW_TASK),
+ NEW_TASK(false, LAUNCHER_STATE_BACKGROUND, true),
- LAST_TASK(false, LAUNCHER_STATE_BACKGROUND, true,
- GestureStateProto.GestureEndTarget.LAST_TASK),
+ LAST_TASK(false, LAUNCHER_STATE_BACKGROUND, true),
- ALL_APPS(true, LAUNCHER_STATE_ALLAPPS, false, GestureStateProto.GestureEndTarget.ALL_APPS);
+ ALL_APPS(true, LAUNCHER_STATE_ALLAPPS, false);
- GestureEndTarget(boolean isLauncher, int containerType, boolean recentsAttachedToAppWindow,
- GestureStateProto.GestureEndTarget protoEndTarget) {
+ GestureEndTarget(boolean isLauncher, int containerType,
+ boolean recentsAttachedToAppWindow) {
this.isLauncher = isLauncher;
this.containerType = containerType;
this.recentsAttachedToAppWindow = recentsAttachedToAppWindow;
- this.protoEndTarget = protoEndTarget;
}
- /** Whether the target is in the launcher activity. Implicitly, if the end target is going
- to Launcher, then we can not interrupt the animation to start another gesture. */
+ /**
+ * Whether the target is in the launcher activity. Implicitly, if the end target
+ * is going
+ * to Launcher, then we can not interrupt the animation to start another
+ * gesture.
+ */
public final boolean isLauncher;
/** Used to log where the user ended up after the gesture ends */
public final int containerType;
- /** Whether RecentsView should be attached to the window as we animate to this target */
+ /**
+ * Whether RecentsView should be attached to the window as we animate to this
+ * target
+ */
public final boolean recentsAttachedToAppWindow;
- /** The GestureStateProto enum value, used for winscope tracing. See launcher_trace.proto */
- public final GestureStateProto.GestureEndTarget protoEndTarget;
}
private static final String TAG = "GestureState";
@@ -99,6 +115,7 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
public static final GestureState DEFAULT_STATE = new GestureState();
private static int FLAG_COUNT = 0;
+
private static int getNextStateFlag(String name) {
if (DEBUG_STATES) {
STATE_NAMES.add(name);
@@ -109,37 +126,33 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
}
// Called when the end target as been set
- public static final int STATE_END_TARGET_SET =
- getNextStateFlag("STATE_END_TARGET_SET");
+ public static final int STATE_END_TARGET_SET = getNextStateFlag("STATE_END_TARGET_SET");
// Called when the end target animation has finished
- public static final int STATE_END_TARGET_ANIMATION_FINISHED =
- getNextStateFlag("STATE_END_TARGET_ANIMATION_FINISHED");
+ public static final int STATE_END_TARGET_ANIMATION_FINISHED = getNextStateFlag(
+ "STATE_END_TARGET_ANIMATION_FINISHED");
// Called when the recents animation has been requested to start
- public static final int STATE_RECENTS_ANIMATION_INITIALIZED =
- getNextStateFlag("STATE_RECENTS_ANIMATION_INITIALIZED");
+ public static final int STATE_RECENTS_ANIMATION_INITIALIZED = getNextStateFlag(
+ "STATE_RECENTS_ANIMATION_INITIALIZED");
- // Called when the recents animation is started and the TaskAnimationManager has been updated
+ // Called when the recents animation is started and the TaskAnimationManager has
+ // been updated
// with the controller and targets
- public static final int STATE_RECENTS_ANIMATION_STARTED =
- getNextStateFlag("STATE_RECENTS_ANIMATION_STARTED");
+ public static final int STATE_RECENTS_ANIMATION_STARTED = getNextStateFlag("STATE_RECENTS_ANIMATION_STARTED");
// Called when the recents animation is canceled
- public static final int STATE_RECENTS_ANIMATION_CANCELED =
- getNextStateFlag("STATE_RECENTS_ANIMATION_CANCELED");
+ public static final int STATE_RECENTS_ANIMATION_CANCELED = getNextStateFlag("STATE_RECENTS_ANIMATION_CANCELED");
// Called when the recents animation finishes
- public static final int STATE_RECENTS_ANIMATION_FINISHED =
- getNextStateFlag("STATE_RECENTS_ANIMATION_FINISHED");
+ public static final int STATE_RECENTS_ANIMATION_FINISHED = getNextStateFlag("STATE_RECENTS_ANIMATION_FINISHED");
- // Always called when the recents animation ends (regardless of cancel or finish)
- public static final int STATE_RECENTS_ANIMATION_ENDED =
- getNextStateFlag("STATE_RECENTS_ANIMATION_ENDED");
+ // Always called when the recents animation ends (regardless of cancel or
+ // finish)
+ public static final int STATE_RECENTS_ANIMATION_ENDED = getNextStateFlag("STATE_RECENTS_ANIMATION_ENDED");
// Called when RecentsView stops scrolling and settles on a TaskView.
- public static final int STATE_RECENTS_SCROLLING_FINISHED =
- getNextStateFlag("STATE_RECENTS_SCROLLING_FINISHED");
+ public static final int STATE_RECENTS_SCROLLING_FINISHED = getNextStateFlag("STATE_RECENTS_SCROLLING_FINISHED");
// Needed to interact with the current activity
private final Intent mHomeIntent;
@@ -150,15 +163,10 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
public enum TrackpadGestureType {
NONE,
- // Assigned before we know whether it's a 3-finger or 4-finger gesture.
- MULTI_FINGER,
THREE_FINGER,
FOUR_FINGER;
public static TrackpadGestureType getTrackpadGestureType(MotionEvent event) {
- if (!isTrackpadMultiFingerSwipe(event)) {
- return TrackpadGestureType.NONE;
- }
if (isTrackpadThreeFingerSwipe(event)) {
return TrackpadGestureType.THREE_FINGER;
}
@@ -166,16 +174,16 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
return TrackpadGestureType.FOUR_FINGER;
}
- return TrackpadGestureType.MULTI_FINGER;
+ return TrackpadGestureType.NONE;
}
}
private TrackpadGestureType mTrackpadGestureType = TrackpadGestureType.NONE;
private CachedTaskInfo mRunningTask;
private GestureEndTarget mEndTarget;
- private RemoteAnimationTarget mLastAppearedTaskTarget;
+ private RemoteAnimationTarget[] mLastAppearedTaskTargets;
private Set mPreviouslyAppearedTaskIds = new HashSet<>();
- private int mLastStartedTaskId = -1;
+ private int[] mLastStartedTaskId = new int[] { INVALID_TASK_ID, INVALID_TASK_ID };
private RecentsAnimationController mRecentsAnimationController;
private HashMap mRecentsAnimationCanceledSnapshots;
@@ -201,7 +209,7 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
mGestureId = other.mGestureId;
mRunningTask = other.mRunningTask;
mEndTarget = other.mEndTarget;
- mLastAppearedTaskTarget = other.mLastAppearedTaskTarget;
+ mLastAppearedTaskTargets = other.mLastAppearedTaskTargets;
mPreviouslyAppearedTaskIds = other.mPreviouslyAppearedTaskIds;
mLastStartedTaskId = other.mLastStartedTaskId;
}
@@ -229,7 +237,8 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
}
/**
- * @return whether the gesture state has the provided {@param stateMask} flags set.
+ * @return whether the gesture state has the provided {@param stateMask} flags
+ * set.
*/
public boolean hasState(int stateMask) {
return mStateCallback.hasStates(stateMask);
@@ -243,7 +252,8 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
}
/**
- * Adds a callback for when the states matching the given {@param stateMask} is set.
+ * Adds a callback for when the states matching the given {@param stateMask} is
+ * set.
*/
public void runOnceAtState(int stateMask, Runnable callback) {
mStateCallback.runOnceAtState(stateMask, callback);
@@ -264,10 +274,10 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
}
/**
- * @return the interface to the activity handing the UI updates for this gesture.
+ * @return the interface to the activity handing the UI updates for this
+ * gesture.
*/
- public ,
- T extends StatefulActivity> BaseActivityInterface getActivityInterface() {
+ public , T extends StatefulActivity> BaseActivityInterface getActivityInterface() {
return mActivityInterface;
}
@@ -279,7 +289,8 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
}
/**
- * Sets if the gesture is is from the trackpad, if so, whether 3-finger, or 4-finger
+ * Sets if the gesture is is from the trackpad, if so, whether 3-finger, or
+ * 4-finger
*/
public void setTrackpadGestureType(TrackpadGestureType trackpadGestureType) {
mTrackpadGestureType = trackpadGestureType;
@@ -305,14 +316,35 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
}
/**
- * @return the running task id for this gesture.
+ * @param getMultipleTasks Whether multiple tasks or not are to be returned (for
+ * split)
+ * @return the running task ids for this gesture.
*/
- public int getRunningTaskId() {
- return mRunningTask != null ? mRunningTask.getTaskId() : -1;
+ public int[] getRunningTaskIds(boolean getMultipleTasks) {
+ if (mRunningTask == null) {
+ return new int[] { INVALID_TASK_ID, INVALID_TASK_ID };
+ } else {
+ int cachedTasksSize = mRunningTask.mAllCachedTasks.size();
+ int count = Math.min(cachedTasksSize, getMultipleTasks ? 2 : 1);
+ int[] runningTaskIds = new int[count];
+ for (int i = 0; i < count; i++) {
+ runningTaskIds[i] = mRunningTask.mAllCachedTasks.get(i).taskId;
+ }
+ return runningTaskIds;
+ }
}
/**
- * Updates the running task for the gesture to be the given {@param runningTask}.
+ * @see #getRunningTaskIds(boolean)
+ * @return the single top-most running taskId for this gesture
+ */
+ public int getTopRunningTaskId() {
+ return getRunningTaskIds(false /* getMultipleTasks */)[0];
+ }
+
+ /**
+ * Updates the running task for the gesture to be the given
+ * {@param runningTask}.
*/
public void updateRunningTask(CachedTaskInfo runningTask) {
mRunningTask = runningTask;
@@ -321,18 +353,26 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
/**
* Updates the last task that appeared during this gesture.
*/
- public void updateLastAppearedTaskTarget(RemoteAnimationTarget lastAppearedTaskTarget) {
- mLastAppearedTaskTarget = lastAppearedTaskTarget;
- if (lastAppearedTaskTarget != null) {
- mPreviouslyAppearedTaskIds.add(lastAppearedTaskTarget.taskId);
+ public void updateLastAppearedTaskTargets(RemoteAnimationTarget[] lastAppearedTaskTargets) {
+ mLastAppearedTaskTargets = lastAppearedTaskTargets;
+ for (RemoteAnimationTarget target : lastAppearedTaskTargets) {
+ if (target == null) {
+ continue;
+ }
+ mPreviouslyAppearedTaskIds.add(target.taskId);
}
}
/**
* @return The id of the task that appeared during this gesture.
*/
- public int getLastAppearedTaskId() {
- return mLastAppearedTaskTarget != null ? mLastAppearedTaskTarget.taskId : -1;
+ public int[] getLastAppearedTaskIds() {
+ if (mLastAppearedTaskTargets == null) {
+ return new int[] { INVALID_TASK_ID, INVALID_TASK_ID };
+ } else {
+ return Arrays.stream(mLastAppearedTaskTargets)
+ .mapToInt(target -> target != null ? target.taskId : INVALID_TASK_ID).toArray();
+ }
}
public void updatePreviouslyAppearedTaskIds(Set previouslyAppearedTaskIds) {
@@ -344,17 +384,19 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
}
/**
- * Updates the last task that we started via startActivityFromRecents() during this gesture.
+ * Updates the last task that we started via startActivityFromRecents() during
+ * this gesture.
*/
- public void updateLastStartedTaskId(int lastStartedTaskId) {
+ public void updateLastStartedTaskIds(int[] lastStartedTaskId) {
mLastStartedTaskId = lastStartedTaskId;
}
/**
- * @return The id of the task that was most recently started during this gesture, or -1 if
- * no task has been started yet (i.e. we haven't settled on a new task).
+ * @return The id of the task that was most recently started during this
+ * gesture, or -1 if
+ * no task has been started yet (i.e. we haven't settled on a new task).
*/
- public int getLastStartedTaskId() {
+ public int[] getLastStartedTaskIds() {
return mLastStartedTaskId;
}
@@ -366,15 +408,18 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
}
/**
- * Sets the end target of this gesture and immediately notifies the state changes.
+ * Sets the end target of this gesture and immediately notifies the state
+ * changes.
*/
public void setEndTarget(GestureEndTarget target) {
setEndTarget(target, true /* isAtomic */);
}
/**
- * Sets the end target of this gesture, but if {@param isAtomic} is {@code false}, then the
- * caller must explicitly set {@link #STATE_END_TARGET_ANIMATION_FINISHED} themselves.
+ * Sets the end target of this gesture, but if {@param isAtomic} is
+ * {@code false}, then the
+ * caller must explicitly set {@link #STATE_END_TARGET_ANIMATION_FINISHED}
+ * themselves.
*/
public void setEndTarget(GestureEndTarget target, boolean isAtomic) {
mEndTarget = target;
@@ -411,7 +456,8 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
}
/**
- * Returns true if the gesture is handling an atomic event like a click and not a
+ * Returns true if the gesture is handling an atomic event like a click and not
+ * a
* user controlled gesture.
*/
public boolean isHandlingAtomicEvent() {
@@ -419,7 +465,8 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
}
/**
- * @return whether the current gesture is still running a recents animation to a state in the
+ * @return whether the current gesture is still running a recents animation to a
+ * state in the
* Launcher or Recents activity.
*/
public boolean isRunningAnimationToLauncher() {
@@ -462,15 +509,17 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
}
/**
- * Returns and clears the canceled animation thumbnail data. This call only returns a value
- * while STATE_RECENTS_ANIMATION_CANCELED state is being set, and the caller is responsible for
+ * Returns and clears the canceled animation thumbnail data. This call only
+ * returns a value
+ * while STATE_RECENTS_ANIMATION_CANCELED state is being set, and the caller is
+ * responsible for
* calling {@link RecentsAnimationController#cleanupScreenshot()}.
*/
@Nullable
HashMap consumeRecentsAnimationCanceledSnapshot() {
if (mRecentsAnimationCanceledSnapshots != null) {
- HashMap data =
- new HashMap(mRecentsAnimationCanceledSnapshots);
+ HashMap data = new HashMap(
+ mRecentsAnimationCanceledSnapshots);
mRecentsAnimationCanceledSnapshots = null;
return data;
}
@@ -490,21 +539,8 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
pw.println(" gestureID=" + mGestureId);
pw.println(" runningTask=" + mRunningTask);
pw.println(" endTarget=" + mEndTarget);
- pw.println(" lastAppearedTaskTargetId=" + getLastAppearedTaskId());
- pw.println(" lastStartedTaskId=" + mLastStartedTaskId);
+ pw.println(" lastAppearedTaskTargetId=" + Arrays.toString(mLastAppearedTaskTargets));
+ pw.println(" lastStartedTaskId=" + Arrays.toString(mLastStartedTaskId));
pw.println(" isRecentsAnimationRunning=" + isRecentsAnimationRunning());
}
-
- /**
- * Used for winscope tracing, see launcher_trace.proto
- * @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto
- * @param swipeHandlerProto The parent of this proto message.
- */
- public void writeToProto(SwipeHandlerProto.Builder swipeHandlerProto) {
- GestureStateProto.Builder gestureStateProto = GestureStateProto.newBuilder();
- gestureStateProto.setEndTarget(mEndTarget == null
- ? GestureStateProto.GestureEndTarget.UNSET
- : mEndTarget.protoEndTarget);
- swipeHandlerProto.setGestureState(gestureStateProto);
- }
}
diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java
index 6b189cf4d6..2b22491773 100644
--- a/quickstep/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/src/com/android/quickstep/InputConsumer.java
@@ -21,9 +21,6 @@ import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.MotionEvent;
-import com.android.launcher3.tracing.InputConsumerProto;
-import com.android.launcher3.tracing.TouchInteractionServiceProto;
-
@TargetApi(Build.VERSION_CODES.O)
public interface InputConsumer {
@@ -42,23 +39,25 @@ public interface InputConsumer {
int TYPE_TASKBAR_STASH = 1 << 12;
int TYPE_STATUS_BAR = 1 << 13;
int TYPE_CURSOR_HOVER = 1 << 14;
+ int TYPE_NAV_HANDLE_LONG_PRESS = 1 << 15;
String[] NAMES = new String[] {
- "TYPE_NO_OP", // 0
- "TYPE_OVERVIEW", // 1
- "TYPE_OTHER_ACTIVITY", // 2
- "TYPE_ASSISTANT", // 3
- "TYPE_DEVICE_LOCKED", // 4
- "TYPE_ACCESSIBILITY", // 5
- "TYPE_SCREEN_PINNED", // 6
- "TYPE_OVERVIEW_WITHOUT_FOCUS", // 7
- "TYPE_RESET_GESTURE", // 8
- "TYPE_PROGRESS_DELEGATE", // 9
- "TYPE_SYSUI_OVERLAY", // 10
- "TYPE_ONE_HANDED", // 11
- "TYPE_TASKBAR_STASH", // 12
- "TYPE_STATUS_BAR", // 13
- "TYPE_CURSOR_HOVER", // 14
+ "TYPE_NO_OP", // 0
+ "TYPE_OVERVIEW", // 1
+ "TYPE_OTHER_ACTIVITY", // 2
+ "TYPE_ASSISTANT", // 3
+ "TYPE_DEVICE_LOCKED", // 4
+ "TYPE_ACCESSIBILITY", // 5
+ "TYPE_SCREEN_PINNED", // 6
+ "TYPE_OVERVIEW_WITHOUT_FOCUS", // 7
+ "TYPE_RESET_GESTURE", // 8
+ "TYPE_PROGRESS_DELEGATE", // 9
+ "TYPE_SYSUI_OVERLAY", // 10
+ "TYPE_ONE_HANDED", // 11
+ "TYPE_TASKBAR_STASH", // 12
+ "TYPE_STATUS_BAR", // 13
+ "TYPE_CURSOR_HOVER", // 14
+ "TYPE_NAV_HANDLE_LONG_PRESS", // 15
};
InputConsumer NO_OP = () -> TYPE_NO_OP;
@@ -66,16 +65,20 @@ public interface InputConsumer {
int getType();
/**
- * Returns true if the user has crossed the threshold for it to be an explicit action.
+ * Returns true if the user has crossed the threshold for it to be an explicit
+ * action.
*/
default boolean allowInterceptByParent() {
return true;
}
/**
- * Returns true if the lifecycle of this input consumer is detached from the normal gesture
- * down/up flow. If so, it is the responsibility of the input consumer to call back to
- * {@link TouchInteractionService#onConsumerInactive(InputConsumer)} after the consumer is
+ * Returns true if the lifecycle of this input consumer is detached from the
+ * normal gesture
+ * down/up flow. If so, it is the responsibility of the input consumer to call
+ * back to
+ * {@link TouchInteractionService#onConsumerInactive(InputConsumer)} after the
+ * consumer is
* finished.
*/
default boolean isConsumerDetachedFromGesture() {
@@ -85,7 +88,8 @@ public interface InputConsumer {
/**
* Handle and specific setup necessary based on the orientation of the device
*/
- default void notifyOrientationSetup() {}
+ default void notifyOrientationSetup() {
+ }
/**
* Returns the active input consumer is in the hierarchy of this input consumer.
@@ -95,17 +99,23 @@ public interface InputConsumer {
}
/**
- * Called by the event queue when the consumer is about to be switched to a new consumer.
- * Consumers should update the state accordingly here before the state is passed to the new
+ * Called by the event queue when the consumer is about to be switched to a new
+ * consumer.
+ * Consumers should update the state accordingly here before the state is passed
+ * to the new
* consumer.
*/
- default void onConsumerAboutToBeSwitched() { }
+ default void onConsumerAboutToBeSwitched() {
+ }
- default void onMotionEvent(MotionEvent ev) { }
+ default void onMotionEvent(MotionEvent ev) {
+ }
- default void onHoverEvent(MotionEvent ev) { }
+ default void onHoverEvent(MotionEvent ev) {
+ }
- default void onKeyEvent(KeyEvent ev) { }
+ default void onKeyEvent(KeyEvent ev) {
+ }
default void onInputEvent(InputEvent ev) {
if (ev instanceof MotionEvent) {
@@ -127,21 +137,4 @@ public interface InputConsumer {
}
return name.toString();
}
-
- /**
- * Used for winscope tracing, see launcher_trace.proto
- * @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto
- * @param serviceProto The parent of this proto message.
- */
- default void writeToProto(TouchInteractionServiceProto.Builder serviceProto) {
- InputConsumerProto.Builder inputConsumerProto = InputConsumerProto.newBuilder();
- inputConsumerProto.setName(getName());
- writeToProtoInternal(inputConsumerProto);
- serviceProto.setInputConsumer(inputConsumerProto);
- }
-
- /**
- * @see #writeToProto - allows subclasses to write additional info to the proto.
- */
- default void writeToProtoInternal(InputConsumerProto.Builder inputConsumerProto) {}
}
diff --git a/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java b/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java
index 7638541023..33a2366157 100644
--- a/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java
+++ b/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java
@@ -20,6 +20,9 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.Log;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.util.InstantAppResolver;
@@ -49,4 +52,18 @@ public class InstantAppResolverImpl extends InstantAppResolver {
ComponentName cn = info.getTargetComponent();
return cn != null && cn.getClassName().equals(COMPONENT_CLASS_MARKER);
}
+
+ @Override
+ public boolean isInstantApp(String packageName, int userId) {
+ if (!Process.myUserHandle().equals(UserHandle.of(userId))) {
+ // Instant app can only exist on current user
+ return false;
+ }
+ try {
+ return mPM.isInstantApp(packageName);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to determine whether package is instant app " + packageName, e);
+ return false;
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index a2e709b80b..e8a48e7601 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -15,12 +15,13 @@
*/
package com.android.quickstep;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
+import static com.android.launcher3.LauncherState.FLOATING_SEARCH_BAR;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
@@ -89,10 +90,14 @@ public final class LauncherActivityInterface extends
if (launcher == null) {
return;
}
- // When going to home, the state animator we use has SKIP_OVERVIEW because we assume that
- // setRecentsAttachedToAppWindow() will handle animating Overview instead. Thus, at the end
- // of the animation, we should ensure recents is at the correct position for NORMAL state.
- // For example, when doing a long swipe to home, RecentsView may be scaled down. This is
+ // When going to home, the state animator we use has SKIP_OVERVIEW because we
+ // assume that
+ // setRecentsAttachedToAppWindow() will handle animating Overview instead. Thus,
+ // at the end
+ // of the animation, we should ensure recents is at the correct position for
+ // NORMAL state.
+ // For example, when doing a long swipe to home, RecentsView may be scaled down.
+ // This is
// relatively expensive, so do it on the next frame instead of critical path.
MAIN_EXECUTOR.getHandler().post(launcher.getStateManager()::reapplyState);
@@ -138,8 +143,7 @@ public final class LauncherActivityInterface extends
@Override
public ActivityInitListener createActivityInitListener(Predicate onInitListener) {
- return new LauncherInitListener((activity, alreadyOnHome) ->
- onInitListener.test(alreadyOnHome));
+ return new LauncherInitListener((activity, alreadyOnHome) -> onInitListener.test(alreadyOnHome));
}
@Override
@@ -191,9 +195,9 @@ public final class LauncherActivityInterface extends
@Override
public RecentsView getVisibleRecentsView() {
Launcher launcher = getVisibleLauncher();
- RecentsView recentsView =
- launcher != null && launcher.getStateManager().getState().overviewUi
- ? launcher.getOverviewPanel() : null;
+ RecentsView recentsView = launcher != null && launcher.getStateManager().getState().overviewUi
+ ? launcher.getOverviewPanel()
+ : null;
if (recentsView == null || (!launcher.hasBeenResumed()
&& recentsView.getRunningTaskViewId() == -1)) {
// If live tile has ended, return null.
@@ -230,7 +234,6 @@ public final class LauncherActivityInterface extends
return true;
}
-
@Override
public void onExitOverview(RotationTouchHelper deviceState, Runnable exitRunnable) {
final StateManager stateManager = getCreatedActivity().getStateManager();
@@ -271,7 +274,10 @@ public final class LauncherActivityInterface extends
@Override
public boolean allowAllAppsFromOverview() {
- return FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get();
+ return FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get()
+ // If floating search bar would not show in overview, don't allow all apps
+ // gesture.
+ && OVERVIEW.areElementsVisible(getCreatedActivity(), FLOATING_SEARCH_BAR);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index f1f7acbae3..993c615270 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -111,7 +111,7 @@ public class LauncherBackAnimationController {
private boolean mBackInProgress = false;
private IOnBackInvokedCallback mBackCallback;
private IRemoteAnimationFinishedCallback mAnimationFinishedCallback;
- private BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+ private BackProgressAnimator mProgressAnimator;
private SurfaceControl mScrimLayer;
private ValueAnimator mScrimAlphaAnimator;
private float mScrimAlpha;
@@ -132,12 +132,13 @@ public class LauncherBackAnimationController {
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.standard_interpolator);
try {
mProgressAnimator = new BackProgressAnimator();
} catch (Throwable throwable) {
-
+ // ignore
}
+
}
/**
@@ -147,74 +148,83 @@ public class LauncherBackAnimationController {
* @param handler Handler to the thread to run the animations on.
*/
public void registerBackCallbacks(Handler handler) {
- mBackCallback = new IOnBackInvokedCallback.Stub() {
- @Override
- public void onBackStarted(BackMotionEvent backMotionEvent) {
- startBack (backMotionEvent);
- handler.post(() -> {
- if (mProgressAnimator == null) {
- return;
- }
- 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);
+ try {
+ mBackCallback = new IOnBackInvokedCallback.Stub() {
+ @Override
+ public void onBackStarted(BackMotionEvent backMotionEvent) {
+ startBack(backMotionEvent);
+ handler.post(() -> {
+ if (mProgressAnimator == null) {
+ return;
+ }
+ 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);
- });
- }
-
- @Override
- public void onBackInvoked() {
- handler.post(() -> {
- startTransition();
- if (mProgressAnimator == null) {
- return;
- }
- mProgressAnimator.reset();
- });
- }
- };
-
- final IRemoteAnimationRunner runner = new IRemoteAnimationRunner.Stub() {
- @Override
- 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;
- }
}
- mAnimationFinishedCallback = finishedCallback;
- }
- @Override
- public void onAnimationCancelled() {
- }
- };
+ @Override
+ public void onBackProgressed(BackMotionEvent backMotionEvent) {
+ handler.post(() -> {
+ if (mProgressAnimator == null) {
+ return;
+ }
+ mProgressAnimator.onBackProgressed(backMotionEvent);
+ });
+ }
+
+ @Override
+ public void onBackCancelled() {
+ handler.post(() -> {
+ if (mProgressAnimator == null) {
+ return;
+ }
+ mProgressAnimator.onBackCancelled(
+ LauncherBackAnimationController.this::resetPositionAnimated);
+ });
+ }
+
+ @Override
+ public void onBackInvoked() {
+ handler.post(() -> {
+ startTransition();
+ if (mProgressAnimator == null) {
+ return;
+ }
+ mProgressAnimator.reset();
+ });
+ }
+ };
+
+ final IRemoteAnimationRunner runner = new IRemoteAnimationRunner.Stub() {
+ @Override
+ 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;
+ }
+ }
+ mAnimationFinishedCallback = finishedCallback;
+ }
+
+ @Override
+ public void onAnimationCancelled() {
+ }
+ };
+
+ SystemUiProxy.INSTANCE.get(mLauncher).setBackToLauncherCallback(mBackCallback, runner);
+
+ } catch (Throwable t) {
+ // Ignore
+ }
- SystemUiProxy.INSTANCE.get(mLauncher).setBackToLauncherCallback(mBackCallback, runner);
}
private void resetPositionAnimated() {
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index fd5c1a7a78..a9d8afcecf 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -15,10 +15,10 @@
*/
package com.android.quickstep;
+import static com.android.app.animation.Interpolators.EXAGGERATED_EASE;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.Utilities.mapBoundToRange;
-import static com.android.launcher3.anim.Interpolators.EXAGGERATED_EASE;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
import static com.android.launcher3.views.FloatingIconView.getFloatingIconView;
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 07db194ff4..42bf1ac2ed 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -37,7 +37,6 @@ import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.taskbar.TaskbarUIController;
import com.android.launcher3.util.RunnableList;
import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
-import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -141,6 +140,11 @@ public class OverviewCommandHelper {
mPendingCommands.clear();
}
+ @UiThread
+ public boolean canStartHomeSafely() {
+ return mPendingCommands.isEmpty() || mPendingCommands.get(0).type == TYPE_HOME;
+ }
+
@Nullable
private TaskView getNextTask(RecentsView view) {
final TaskView runningTaskView = view.getRunningTaskView();
@@ -186,11 +190,6 @@ public class OverviewCommandHelper {
&& dp != null
&& (dp.isTablet || dp.isTwoPanels);
- if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
- // TODO(b/268075592): add support for quickswitch to/from desktop
- allowQuickSwitch = false;
- }
-
if (cmd.type == TYPE_HIDE) {
if (!allowQuickSwitch) {
return true;
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index a8f3c3af46..60713cf28f 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -38,8 +38,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.R;
-import com.android.launcher3.tracing.OverviewComponentObserverProto;
-import com.android.launcher3.tracing.TouchInteractionServiceProto;
import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.systemui.shared.system.PackageManagerWrapper;
@@ -275,19 +273,6 @@ public final class OverviewComponentObserver {
pw.println(" homeIntent=" + mCurrentHomeIntent);
}
- /**
- * Used for winscope tracing, see launcher_trace.proto
- * @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto
- * @param serviceProto The parent of this proto message.
- */
- public void writeToProto(TouchInteractionServiceProto.Builder serviceProto) {
- OverviewComponentObserverProto.Builder overviewComponentObserver =
- OverviewComponentObserverProto.newBuilder();
- overviewComponentObserver.setOverviewActivityStarted(mActivityInterface.isStarted());
- overviewComponentObserver.setOverviewActivityResumed(mActivityInterface.isResumed());
- serviceProto.setOverviewComponentObvserver(overviewComponentObserver);
- }
-
/**
* Starts the intent for the current home activity.
*/
diff --git a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
index 6b3c846819..1ccf5068de 100644
--- a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
+++ b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
@@ -19,6 +19,8 @@ import android.annotation.TargetApi;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
+import android.os.Looper;
+import android.os.Trace;
import android.os.UserManager;
import android.util.Log;
import android.view.ThreadedRenderer;
@@ -43,16 +45,18 @@ public class QuickstepProcessInitializer extends MainProcessInitializer {
@Override
protected void init(Context context) {
- // Workaround for b/120550382, an external app can cause the launcher process to start for
- // a work profile user which we do not support. Disable the application immediately when we
+ // Workaround for b/120550382, an external app can cause the launcher process to
+ // start for
+ // a work profile user which we do not support. Disable the application
+ // immediately when we
// detect this to be the case.
UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
if (um.isManagedProfile()) {
PackageManager pm = context.getPackageManager();
pm.setApplicationEnabledSetting(context.getPackageName(),
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0 /* flags */);
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0 /* flags */);
Log.w(TAG, "Disabling " + BuildConfig.APPLICATION_ID
- + ", unable to run in a managed profile");
+ + ", unable to run in a managed profile");
return;
}
@@ -60,10 +64,10 @@ public class QuickstepProcessInitializer extends MainProcessInitializer {
// Elevate GPU priority for Quickstep and Remote animations.
try {
- ThreadedRenderer.setContextPriority(
- ThreadedRenderer.EGL_CONTEXT_PRIORITY_HIGH_IMG);
- } catch (Exception e){
- Log.e(TAG , "init: " + e);
+ ThreadedRenderer.setContextPriority(
+ ThreadedRenderer.EGL_CONTEXT_PRIORITY_HIGH_IMG);
+ } catch (Exception e) {
+ Log.e(TAG, "init: " + e);
}
}
}
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 27ce10aaac..d3f59d15b6 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -16,6 +16,8 @@
package com.android.quickstep;
+import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
+
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.views.DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED;
import static com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_FREEFORM;
@@ -271,6 +273,7 @@ public class RecentTasksList {
TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size());
+ int numVisibleTasks = 0;
for (GroupedRecentTaskInfo rawTask : rawTasks) {
if (DESKTOP_IS_PROTO2_ENABLED && rawTask.getType() == TYPE_FREEFORM) {
GroupTask desktopTask = createDesktopTask(rawTask);
@@ -287,12 +290,27 @@ public class RecentTasksList {
task1.setLastSnapshotData(taskInfo1);
Task task2 = null;
if (taskInfo2 != null) {
+ // Is split task
Task.TaskKey task2Key = new Task.TaskKey(taskInfo2);
task2 = loadKeysOnly
? new Task(task2Key)
: Task.from(task2Key, taskInfo2,
tmpLockedUsers.get(task2Key.userId) /* isLocked */);
task2.setLastSnapshotData(taskInfo2);
+ } else {
+ // Is fullscreen task
+ if (numVisibleTasks > 0) {
+ boolean isExcluded = (taskInfo1.baseIntent.getFlags()
+ & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
+ if (isExcluded) {
+ // If there are already visible tasks, then ignore the excluded tasks and
+ // don't add them to the returned list
+ continue;
+ }
+ }
+ }
+ if (taskInfo1.isVisible) {
+ numVisibleTasks++;
}
final SplitConfigurationOptions.SplitBounds launcherSplitBounds =
convertSplitBounds(rawTask.getSplitBounds());
@@ -310,7 +328,6 @@ public class RecentTasksList {
task.setLastSnapshotData(taskInfo);
task.positionInParent = taskInfo.positionInParent;
task.appBounds = taskInfo.configuration.windowConfiguration.getAppBounds();
- // TODO(b/244348395): tasks should be sorted from oldest to most recently used
tasks.add(task);
}
return new DesktopTask(tasks);
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 9d29e7e155..4aa5112c3a 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -22,7 +22,6 @@ import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_DURATION;
import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_PRE_DELAY;
-import static com.android.launcher3.graphics.SysUiScrim.SYSUI_PROGRESS;
import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL;
import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
@@ -38,6 +37,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Trace;
+import android.util.Log;
import android.view.Display;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
@@ -46,8 +46,10 @@ import android.view.View;
import android.window.RemoteTransition;
import android.window.SplashScreen;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
@@ -57,7 +59,6 @@ import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.model.data.ItemInfo;
@@ -67,6 +68,7 @@ import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.taskbar.FallbackTaskbarUIController;
import com.android.launcher3.taskbar.TaskbarManager;
+import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.ActivityOptionsWrapper;
import com.android.launcher3.util.ActivityTracker;
import com.android.launcher3.util.RunnableList;
@@ -108,7 +110,6 @@ public final class RecentsActivity extends StatefulActivity {
private FallbackRecentsView mFallbackRecentsView;
private OverviewActionsView mActionsView;
private TISBindHelper mTISBindHelper;
- private @Nullable TaskbarManager mTaskbarManager;
private @Nullable FallbackTaskbarUIController mTaskbarUIController;
private StateManager mStateManager;
@@ -121,6 +122,7 @@ public final class RecentsActivity extends StatefulActivity {
// animation callback
private final Handler mHandler = new Handler();
private final Runnable mAnimationStartTimeoutRunnable = this::onAnimationStartTimeout;
+ private SplitSelectStateController mSplitSelectStateController;
/**
* Init drag layer and overview panel views.
@@ -132,21 +134,20 @@ public final class RecentsActivity extends StatefulActivity {
mScrimView = findViewById(R.id.scrim_view);
mFallbackRecentsView = findViewById(R.id.overview_panel);
mActionsView = findViewById(R.id.overview_actions_view);
- SYSUI_PROGRESS.set(getRootView().getSysUiScrim(), 0f);
-
- SplitSelectStateController controller = new SplitSelectStateController(this, mHandler, getStateManager(),
+ getRootView().getSysUiScrim().getSysUIProgress().updateValue(0);
+ mSplitSelectStateController = new SplitSelectStateController(this, mHandler, getStateManager(),
null /* depthController */, getStatsLogManager(),
SystemUiProxy.INSTANCE.get(this), RecentsModel.INSTANCE.get(this));
mDragLayer.recreateControllers();
- mFallbackRecentsView.init(mActionsView, controller);
+ mFallbackRecentsView.init(mActionsView, mSplitSelectStateController);
mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
}
private void onTISConnected(TouchInteractionService.TISBinder binder) {
- mTaskbarManager = binder.getTaskbarManager();
- if (mTaskbarManager != null) {
- mTaskbarManager.setActivity(this);
+ TaskbarManager taskbarManager = binder.getTaskbarManager();
+ if (taskbarManager != null) {
+ taskbarManager.setActivity(this);
}
}
@@ -245,6 +246,11 @@ public final class RecentsActivity extends StatefulActivity {
}
final TaskView taskView = (TaskView) v;
+ final RecentsView recentsView = taskView.getRecentsView();
+ if (recentsView == null) {
+ return super.getActivityLaunchOptions(v, item);
+ }
+
RunnableList onEndCallback = new RunnableList();
mActivityLaunchAnimationRunner = new RemoteAnimationFactory() {
@@ -253,7 +259,7 @@ public final class RecentsActivity extends StatefulActivity {
RemoteAnimationTarget[] wallpaperTargets,
RemoteAnimationTarget[] nonAppTargets, AnimationResult result) {
mHandler.removeCallbacks(mAnimationStartTimeoutRunnable);
- AnimatorSet anim = composeRecentsLaunchAnimator(taskView, appTargets,
+ AnimatorSet anim = composeRecentsLaunchAnimator(recentsView, taskView, appTargets,
wallpaperTargets, nonAppTargets);
anim.addListener(resetStateListener());
result.setAnimation(anim, RecentsActivity.this, onEndCallback::executeAllAndDestroy,
@@ -282,6 +288,7 @@ public final class RecentsActivity extends StatefulActivity {
activityOptions.options.setLaunchDisplayId(
(v != null && v.getDisplay() != null) ? v.getDisplay().getDisplayId()
: Display.DEFAULT_DISPLAY);
+ Utilities.allowBGLaunch(activityOptions.options);
}
mHandler.postDelayed(mAnimationStartTimeoutRunnable, RECENTS_ANIMATION_TIMEOUT);
return activityOptions;
@@ -290,14 +297,16 @@ public final class RecentsActivity extends StatefulActivity {
/**
* Composes the animations for a launch from the recents list if possible.
*/
- private AnimatorSet composeRecentsLaunchAnimator(TaskView taskView,
+ private AnimatorSet composeRecentsLaunchAnimator(
+ @NonNull RecentsView recentsView,
+ @NonNull TaskView taskView,
RemoteAnimationTarget[] appTargets,
RemoteAnimationTarget[] wallpaperTargets,
RemoteAnimationTarget[] nonAppTargets) {
AnimatorSet target = new AnimatorSet();
boolean activityClosing = taskIsATargetWithMode(appTargets, getTaskId(), MODE_CLOSING);
PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
- createRecentsWindowAnimator(taskView, !activityClosing, appTargets,
+ createRecentsWindowAnimator(recentsView, taskView, !activityClosing, appTargets,
wallpaperTargets, nonAppTargets, null /* depthController */, pa);
target.play(pa.buildAnim());
@@ -306,7 +315,7 @@ public final class RecentsActivity extends StatefulActivity {
if (activityClosing) {
Animator adjacentAnimation = mFallbackRecentsView
.createAdjacentPageAnimForTaskLaunch(taskView);
- adjacentAnimation.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
+ adjacentAnimation.setInterpolator(Interpolators.TOUCH_RESPONSE);
adjacentAnimation.setDuration(RECENTS_LAUNCH_DURATION);
adjacentAnimation.addListener(resetStateListener());
target.play(adjacentAnimation);
@@ -393,11 +402,8 @@ public final class RecentsActivity extends StatefulActivity {
super.onDestroy();
ACTIVITY_TRACKER.onActivityDestroyed(this);
mActivityLaunchAnimationRunner = null;
-
+ mSplitSelectStateController.onDestroy();
mTISBindHelper.onDestroy();
- if (mTaskbarManager != null) {
- mTaskbarManager.clearActivity(this);
- }
}
@Override
@@ -407,6 +413,7 @@ public final class RecentsActivity extends StatefulActivity {
}
public void startHome() {
+ Log.d(TestProtocol.INCORRECT_HOME_STATE, "start home from recents activity");
RecentsView recentsView = getOverviewPanel();
recentsView.switchToScreenshot(() -> recentsView.finishRecentsAnimation(true,
this::startHomeInternal));
@@ -477,4 +484,9 @@ public final class RecentsActivity extends StatefulActivity {
}
};
}
+
+ public boolean canStartHomeSafely() {
+ OverviewCommandHelper overviewCommandHelper = mTISBindHelper.getOverviewCommandHelper();
+ return overviewCommandHelper == null || overviewCommandHelper.canStartHomeSafely();
+ }
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index f8e09e1e88..8972dc84c1 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -115,8 +115,8 @@ public class RecentsAnimationController {
* {@link RecentsAnimationCallbacks#onTasksAppeared}}.
*/
@UiThread
- public void removeTaskTarget(@NonNull RemoteAnimationTarget target) {
- UI_HELPER_EXECUTOR.execute(() -> mController.removeTask(target.taskId));
+ public void removeTaskTarget(int targetTaskId) {
+ UI_HELPER_EXECUTOR.execute(() -> mController.removeTask(targetTaskId));
}
@UiThread
@@ -167,7 +167,6 @@ public class RecentsAnimationController {
/* event= */ "finishRecentsAnimation",
/* extras= */ toRecents,
/* gestureEvent= */ FINISH_RECENTS_ANIMATION);
-
// Finish not yet requested
mFinishRequested = true;
mFinishTargetIsLauncher = toRecents;
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index adbf3ea67b..68d06f8ddc 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -17,7 +17,6 @@ package com.android.quickstep;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.content.Intent.ACTION_USER_UNLOCKED;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.launcher3.util.DisplayController.CHANGE_ALL;
@@ -52,10 +51,8 @@ import android.content.Context;
import android.graphics.Region;
import android.inputmethodservice.InputMethodService;
import android.net.Uri;
-import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
-import android.os.UserManager;
import android.provider.Settings;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
@@ -69,7 +66,6 @@ import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.NavigationMode;
import com.android.launcher3.util.SettingsCache;
-import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
import com.android.quickstep.util.NavBarPosition;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -120,15 +116,6 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
private final boolean mIsOneHandedModeSupported;
private boolean mPipIsActive;
- private boolean mIsUserUnlocked;
- private final ArrayList mUserUnlockedActions = new ArrayList<>();
- private final SimpleBroadcastReceiver mUserUnlockedReceiver = new SimpleBroadcastReceiver(i -> {
- if (ACTION_USER_UNLOCKED.equals(i.getAction())) {
- mIsUserUnlocked = true;
- notifyUserUnlocked();
- }
- });
-
private int mGestureBlockingTaskId = -1;
private @NonNull Region mExclusionRegion = new Region();
private SystemGestureExclusionListenerCompat mExclusionListener;
@@ -156,30 +143,20 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
runOnDestroy(mRotationTouchHelper::destroy);
}
- // Register for user unlocked if necessary
- mIsUserUnlocked = context.getSystemService(UserManager.class)
- .isUserUnlocked(Process.myUserHandle());
- if (!mIsUserUnlocked) {
- mUserUnlockedReceiver.register(mContext, ACTION_USER_UNLOCKED);
- }
- runOnDestroy(() -> mUserUnlockedReceiver.unregisterReceiverSafely(mContext));
-
- if (Utilities.ATLEAST_Q) {
- // Register for exclusion updates
- mExclusionListener = new SystemGestureExclusionListenerCompat(mDisplayId) {
- @Override
- @BinderThread
- public void onExclusionChanged(Region region) {
- if (region == null) {
- // Don't think this is possible but just in case, don't let it be null.
- region = new Region();
- }
- // Assignments are atomic, it should be safe on binder thread
- mExclusionRegion = region;
+ // Register for exclusion updates
+ mExclusionListener = new SystemGestureExclusionListenerCompat(mDisplayId) {
+ @Override
+ @BinderThread
+ public void onExclusionChanged(Region region) {
+ if (region == null) {
+ // Don't think this is possible but just in case, don't let it be null.
+ region = new Region();
}
- };
- runOnDestroy(mExclusionListener::unregister);
- }
+ }
+
+ };
+
+ runOnDestroy (()-> mExclusionListener.unregister());
// Register for display changes changes
mDisplayController.addChangeListener(this);
@@ -330,26 +307,6 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
return mDisplayId;
}
- /**
- * 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) {
- if (mIsUserUnlocked) {
- action.run();
- } else {
- mUserUnlockedActions.add(action);
- }
- }
-
- /**
- * @return whether the user is unlocked.
- */
- public boolean isUserUnlocked() {
- return mIsUserUnlocked;
- }
-
/**
* @return whether the user has completed setup wizard
*/
@@ -357,14 +314,6 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
return mIsUserSetupComplete;
}
- private void notifyUserUnlocked() {
- for (Runnable action : mUserUnlockedActions) {
- action.run();
- }
- mUserUnlockedActions.clear();
- mUserUnlockedReceiver.unregisterReceiverSafely(mContext);
- }
-
/**
* Sets the task id where gestures should be blocked
*/
@@ -401,8 +350,18 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
boolean canStartWithNavHidden = (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
|| (mSystemUiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0
|| mRotationTouchHelper.isTaskListFrozen();
- return canStartWithNavHidden
- && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
+ return canStartWithNavHidden && canStartTrackpadGesture();
+ }
+
+ /**
+ * @return whether SystemUI is in a state where we can start a system gesture
+ * from the trackpad.
+ * Trackpad gestures can start even when the nav bar / task bar is
+ * hidden in sticky immersive
+ * mode.
+ */
+ public boolean canStartTrackpadGesture() {
+ return (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
&& (mSystemUiStateFlags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING) == 0
&& (mSystemUiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0
&& (mSystemUiStateFlags & SYSUI_STATE_MAGNIFICATION_OVERLAP) == 0
@@ -633,7 +592,6 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
pw.println(" assistantAvailable=" + mAssistantAvailable);
pw.println(" assistantDisabled="
+ QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags));
- pw.println(" isUserUnlocked=" + mIsUserUnlocked);
pw.println(" isOneHandedModeEnabled=" + mIsOneHandedModeEnabled);
pw.println(" isSwipeToNotificationEnabled=" + mIsSwipeToNotificationEnabled);
pw.println(" deferredGestureRegion=" + mDeferredGestureRegion.getBounds());
diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
index 13e999928d..88d45a046d 100644
--- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -19,6 +19,7 @@ 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.MotionEventsUtils.isTrackpadScroll;
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;
@@ -198,7 +199,7 @@ public class RotationTouchHelper implements DisplayInfoChangeListener {
void onUserUnlocked() {
// We can't load custom window radius before the user had unlocked,
// so we just fallback to system values and then reload it here.
-// onNavigationModeChanged(mSysUiNavMode.getMode());
+ // onNavigationModeChanged(mSysUiNavMode.getMode());
}
private void setupOrientationSwipeHandler() {
@@ -255,8 +256,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, BaseActivityInterface activity) {
- return isInSwipeUpTouchRegion(event, 0, activity);
+ public boolean isInSwipeUpTouchRegion(MotionEvent event) {
+ return isInSwipeUpTouchRegion(event, 0);
}
/**
@@ -264,8 +265,10 @@ public class RotationTouchHelper implements DisplayInfoChangeListener {
* {@param pointerIndex}
* is in the swipe up gesture region.
*/
- public boolean isInSwipeUpTouchRegion(MotionEvent event, int pointerIndex,
- BaseActivityInterface activity) {
+ public boolean isInSwipeUpTouchRegion(MotionEvent event, int pointerIndex) {
+ if (isTrackpadScroll(event)) {
+ return false;
+ }
if (isTrackpadMultiFingerSwipe(event)) {
return true;
}
diff --git a/quickstep/src/com/android/quickstep/SplitSelectionListener.kt b/quickstep/src/com/android/quickstep/SplitSelectionListener.kt
new file mode 100644
index 0000000000..5025c1c181
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/SplitSelectionListener.kt
@@ -0,0 +1,17 @@
+package com.android.quickstep
+
+interface SplitSelectionListener {
+ /** Called when the first app has been selected with the intention to launch split screen */
+ fun onSplitSelectionActive()
+
+ /** Called when the second app has been selected with the intention to launch split screen */
+ fun onSplitSelectionConfirmed()
+
+ /**
+ * Called when the user no longer is in the process of selecting apps for split screen.
+ * [launchedSplit] will be true if selected apps have launched successfully (either in
+ * split screen or fullscreen), false if the user canceled/exited the selection process
+ */
+ fun onSplitSelectionExit(launchedSplit: Boolean) {
+ }
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index 25ac47a45f..e481165912 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -15,8 +15,8 @@
*/
package com.android.quickstep;
-import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.app.animation.Interpolators.ACCELERATE_1_5;
+import static com.android.app.animation.Interpolators.LINEAR;
import android.animation.Animator;
import android.content.Context;
@@ -218,7 +218,7 @@ public abstract class SwipeUpAnimationLogic implements
if (progress >= end) {
return 0f;
}
- return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5);
+ return Utilities.mapToRange(progress, start, end, 1, 0, ACCELERATE_1_5);
}
}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 3e2e660c5b..0bbf6f4437 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -19,6 +19,7 @@ import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.quickstep.util.LogUtils.splitFailureMessage;
import android.app.ActivityManager;
import android.app.ActivityOptions;
@@ -61,6 +62,7 @@ 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.quickstep.util.AssistUtils;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
@@ -74,6 +76,7 @@ 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.desktopmode.IDesktopTaskListener;
import com.android.wm.shell.draganddrop.IDragAndDrop;
import com.android.wm.shell.onehanded.IOneHanded;
import com.android.wm.shell.pip.IPip;
@@ -82,6 +85,7 @@ import com.android.wm.shell.recents.IRecentTasks;
import com.android.wm.shell.recents.IRecentTasksListener;
import com.android.wm.shell.splitscreen.ISplitScreen;
import com.android.wm.shell.splitscreen.ISplitScreenListener;
+import com.android.wm.shell.splitscreen.ISplitSelectListener;
import com.android.wm.shell.startingsurface.IStartingWindow;
import com.android.wm.shell.startingsurface.IStartingWindowListener;
import com.android.wm.shell.transition.IShellTransitions;
@@ -129,10 +133,12 @@ public class SystemUiProxy implements ISystemUiProxy {
private IPipAnimationListener mPipAnimationListener;
private IBubblesListener mBubblesListener;
private ISplitScreenListener mSplitScreenListener;
+ private ISplitSelectListener mSplitSelectListener;
private IStartingWindowListener mStartingWindowListener;
private ILauncherUnlockAnimationController mLauncherUnlockAnimationController;
private IRecentTasksListener mRecentTasksListener;
private IUnfoldTransitionListener mUnfoldAnimationListener;
+ private IDesktopTaskListener mDesktopTaskListener;
private final LinkedHashMap mRemoteTransitions = new LinkedHashMap<>();
private IBinder mOriginalTransactionToken = null;
private IOnBackInvokedCallback mBackToLauncherCallback;
@@ -242,6 +248,7 @@ public class SystemUiProxy implements ISystemUiProxy {
setPipAnimationListener(mPipAnimationListener);
setBubblesListener(mBubblesListener);
registerSplitScreenListener(mSplitScreenListener);
+ registerSplitSelectListener(mSplitSelectListener);
setStartingWindowListener(mStartingWindowListener);
setLauncherUnlockAnimationController(mLauncherUnlockAnimationController);
new LinkedHashMap<>(mRemoteTransitions).forEach(this::registerRemoteTransition);
@@ -249,6 +256,9 @@ public class SystemUiProxy implements ISystemUiProxy {
registerRecentTasksListener(mRecentTasksListener);
setBackToLauncherCallback(mBackToLauncherCallback, mBackToLauncherRunner);
setUnfoldAnimationListener(mUnfoldAnimationListener);
+ setDesktopTaskListener(mDesktopTaskListener);
+ setAssistantOverridesRequested(
+ AssistUtils.newInstance(mContext).getSysUiAssistOverrideInvocationTypes());
}
/**
@@ -307,8 +317,14 @@ public class SystemUiProxy implements ISystemUiProxy {
}
@Override
- public void onStatusBarTouchEvent(MotionEvent event) throws RemoteException {
-
+ public void onStatusBarTouchEvent(MotionEvent event) {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.onStatusBarTouchEvent(event);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call onStatusBarTouchEvent", e);
+ }
+ }
}
public void onOverviewShown(boolean fromHome, String tag) {
@@ -321,18 +337,6 @@ public class SystemUiProxy implements ISystemUiProxy {
}
}
- @MainThread
- public void onStatusBarMotionEvent(MotionEvent event) {
-// Preconditions.assertUIThread();
-// if (mSystemUiProxy != null) {
-// try {
-// mSystemUiProxy.onStatusBarMotionEvent(event);
-// } catch (RemoteException e) {
-// Log.w(TAG, "Failed call onStatusBarMotionEvent", e);
-// }
-// }
- }
-
@Override
public void onAssistantProgress(float progress) {
if (mSystemUiProxy != null) {
@@ -367,8 +371,14 @@ public class SystemUiProxy implements ISystemUiProxy {
}
@Override
- public void setAssistantOverridesRequested(int[] invocationTypes) throws RemoteException {
-
+ public void setAssistantOverridesRequested(int[] invocationTypes) {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.setAssistantOverridesRequested(invocationTypes);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call setAssistantOverridesRequested", e);
+ }
+ }
}
@Override
@@ -415,16 +425,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) {
@@ -467,8 +477,14 @@ public class SystemUiProxy implements ISystemUiProxy {
}
@Override
- public void onStatusBarTrackpadEvent(MotionEvent event) throws RemoteException {
-
+ public void onStatusBarTrackpadEvent(MotionEvent event) {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.onStatusBarTrackpadEvent(event);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call onStatusBarTrackpadEvent");
+ }
+ }
}
@Override
@@ -668,19 +684,50 @@ public class SystemUiProxy implements ISystemUiProxy {
/**
* 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).
+ * @param key the key of the bubble to show.
+ * @param bubbleBarOffsetX the offset of the bubble bar from the edge of the
+ * screen on the X
+ * axis.
+ * @param bubbleBarOffsetY the offset of the bubble bar from the edge of the
+ * screen on the Y
+ * axis.
*/
- public void showBubble(String key, boolean onLauncherHome) {
-// if (mBubbles != null) {
-// try {
-// mBubbles.showBubble(key, onLauncherHome);
-// } catch (RemoteException e) {
-// Log.w(TAG, "Failed call showBubble");
-// }
-// }
+ public void showBubble(String key, int bubbleBarOffsetX, int bubbleBarOffsetY) {
+ if (mBubbles != null) {
+ try {
+ mBubbles.showBubble(key, bubbleBarOffsetX, bubbleBarOffsetY);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call showBubble");
+ }
+ }
+ }
+
+ /**
+ * Tells SysUI to remove the bubble with the provided key.
+ *
+ * @param key the key of the bubble to show.
+ */
+ public void removeBubble(String key) {
+ if (mBubbles == null)
+ return;
+ try {
+ mBubbles.removeBubble(key);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call removeBubble");
+ }
+ }
+
+ /**
+ * Tells SysUI to remove all bubbles.
+ */
+ public void removeAllBubbles() {
+ if (mBubbles == null)
+ return;
+ try {
+ mBubbles.removeAllBubbles();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call removeAllBubbles");
+ }
}
/**
@@ -696,6 +743,23 @@ public class SystemUiProxy implements ISystemUiProxy {
}
}
+ /**
+ * Tells SysUI when the bubble is being dragged.
+ * Should be called only when the bubble bar is expanded.
+ *
+ * @param bubbleKey the key of the bubble to collapse/expand
+ * @param isBeingDragged whether the bubble is being dragged
+ */
+ public void onBubbleDrag(@Nullable String bubbleKey, boolean isBeingDragged) {
+ if (mBubbles == null)
+ return;
+ try {
+ mBubbles.onBubbleDrag(bubbleKey, isBeingDragged);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call onBubbleDrag");
+ }
+ }
+
//
// Splitscreen
//
@@ -722,6 +786,28 @@ public class SystemUiProxy implements ISystemUiProxy {
mSplitScreenListener = null;
}
+ public void registerSplitSelectListener(ISplitSelectListener listener) {
+ if (mSplitScreen != null) {
+ try {
+ mSplitScreen.registerSplitSelectListener(listener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call registerSplitSelectListener");
+ }
+ }
+ mSplitSelectListener = listener;
+ }
+
+ public void unregisterSplitSelectListener(ISplitSelectListener listener) {
+ if (mSplitScreen != null) {
+ try {
+ mSplitScreen.unregisterSplitSelectListener(listener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call unregisterSplitSelectListener");
+ }
+ }
+ mSplitSelectListener = null;
+ }
+
/** Start multiple tasks in split-screen simultaneously. */
public void startTasks(int taskId1, Bundle options1, int taskId2, Bundle options2,
@SplitConfigurationOptions.StagePosition int splitPosition, float splitRatio,
@@ -731,7 +817,7 @@ public class SystemUiProxy implements ISystemUiProxy {
mSplitScreen.startTasks(taskId1, options1, taskId2, options2, splitPosition,
splitRatio, remoteTransition, instanceId);
} catch (RemoteException e) {
- Log.w(TAG, "Failed call startTasks");
+ Log.w(TAG, splitFailureMessage("startTasks", "RemoteException"), e);
}
}
}
@@ -744,7 +830,7 @@ public class SystemUiProxy implements ISystemUiProxy {
mSplitScreen.startIntentAndTask(pendingIntent, userId1, options1, taskId, options2,
splitPosition, splitRatio, remoteTransition, instanceId);
} catch (RemoteException e) {
- Log.w(TAG, "Failed call startIntentAndTask");
+ Log.w(TAG, splitFailureMessage("startIntentAndTask", "RemoteException"), e);
}
}
}
@@ -760,7 +846,7 @@ public class SystemUiProxy implements ISystemUiProxy {
pendingIntent2, userId2, shortcutInfo2, options2, splitPosition, splitRatio,
remoteTransition, instanceId);
} catch (RemoteException e) {
- Log.w(TAG, "Failed call startIntents");
+ Log.w(TAG, splitFailureMessage("startIntents", "RemoteException"), e);
}
}
}
@@ -773,7 +859,7 @@ public class SystemUiProxy implements ISystemUiProxy {
mSplitScreen.startShortcutAndTask(shortcutInfo, options1, taskId, options2,
splitPosition, splitRatio, remoteTransition, instanceId);
} catch (RemoteException e) {
- Log.w(TAG, "Failed call startShortcutAndTask");
+ Log.w(TAG, splitFailureMessage("startShortcutAndTask", "RemoteException"), e);
}
}
}
@@ -789,7 +875,8 @@ public class SystemUiProxy implements ISystemUiProxy {
mSplitScreen.startTasksWithLegacyTransition(taskId1, options1, taskId2, options2,
splitPosition, splitRatio, adapter, instanceId);
} catch (RemoteException e) {
- Log.w(TAG, "Failed call startTasksWithLegacyTransition");
+ Log.w(TAG, splitFailureMessage(
+ "startTasksWithLegacyTransition", "RemoteException"), e);
}
}
}
@@ -803,7 +890,8 @@ public class SystemUiProxy implements ISystemUiProxy {
mSplitScreen.startIntentAndTaskWithLegacyTransition(pendingIntent, userId1,
options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId);
} catch (RemoteException e) {
- Log.w(TAG, "Failed call startIntentAndTaskWithLegacyTransition");
+ Log.w(TAG, splitFailureMessage(
+ "startIntentAndTaskWithLegacyTransition", "RemoteException"), e);
}
}
}
@@ -816,7 +904,8 @@ public class SystemUiProxy implements ISystemUiProxy {
mSplitScreen.startShortcutAndTaskWithLegacyTransition(shortcutInfo, options1,
taskId, options2, splitPosition, splitRatio, adapter, instanceId);
} catch (RemoteException e) {
- Log.w(TAG, "Failed call startShortcutAndTaskWithLegacyTransition");
+ Log.w(TAG, splitFailureMessage(
+ "startShortcutAndTaskWithLegacyTransition", "RemoteException"), e);
}
}
}
@@ -837,7 +926,8 @@ public class SystemUiProxy implements ISystemUiProxy {
shortcutInfo1, options1, pendingIntent2, userId2, shortcutInfo2, options2,
sidePosition, splitRatio, adapter, instanceId);
} catch (RemoteException e) {
- Log.w(TAG, "Failed call startIntentsWithLegacyTransition");
+ Log.w(TAG, splitFailureMessage(
+ "startIntentsWithLegacyTransition", "RemoteException"), e);
}
}
}
@@ -849,7 +939,7 @@ public class SystemUiProxy implements ISystemUiProxy {
mSplitScreen.startShortcut(packageName, shortcutId, position, options,
user, instanceId);
} catch (RemoteException e) {
- Log.w(TAG, "Failed call startShortcut");
+ Log.w(TAG, splitFailureMessage("startShortcut", "RemoteException"), e);
}
}
}
@@ -861,7 +951,7 @@ public class SystemUiProxy implements ISystemUiProxy {
mSplitScreen.startIntent(intent, userId, fillInIntent, position, options,
instanceId);
} catch (RemoteException e) {
- Log.w(TAG, "Failed call startIntent");
+ Log.w(TAG, splitFailureMessage("startIntent", "RemoteException"), e);
}
}
}
@@ -1210,6 +1300,41 @@ public class SystemUiProxy implements ISystemUiProxy {
}
}
+ /** Call shell to stash desktop apps */
+ public void stashDesktopApps(int displayId) {
+ if (mDesktopMode != null) {
+ try {
+ mDesktopMode.stashDesktopApps(displayId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call stashDesktopApps", e);
+ }
+ }
+ }
+
+ /** Call shell to hide desktop apps that may be stashed */
+ public void hideStashedDesktopApps(int displayId) {
+ if (mDesktopMode != null) {
+ try {
+ mDesktopMode.hideStashedDesktopApps(displayId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call hideStashedDesktopApps", e);
+ }
+ }
+ }
+
+ /**
+ * If task with the given id is on the desktop, bring it to front
+ */
+ public void showDesktopApp(int taskId) {
+ if (mDesktopMode != null) {
+ try {
+ mDesktopMode.showDesktopApp(taskId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call showDesktopApp", e);
+ }
+ }
+ }
+
/** Call shell to get number of visible freeform tasks */
public int getVisibleDesktopTaskCount(int displayId) {
if (mDesktopMode != null) {
@@ -1222,6 +1347,29 @@ public class SystemUiProxy implements ISystemUiProxy {
return 0;
}
+ /** Set a listener on shell to get updates about desktop task state */
+ public void setDesktopTaskListener(@Nullable IDesktopTaskListener listener) {
+ mDesktopTaskListener = listener;
+ if (mDesktopMode != null) {
+ try {
+ mDesktopMode.setTaskListener(listener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call setDesktopTaskListener", e);
+ }
+ }
+ }
+
+ /** Perform cleanup transactions after animation to split select is complete */
+ public void onDesktopSplitSelectAnimComplete(ActivityManager.RunningTaskInfo taskInfo) {
+ if (mDesktopMode != null) {
+ try {
+ mDesktopMode.onDesktopSplitSelectAnimComplete(taskInfo);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call onDesktopSplitSelectAnimComplete", e);
+ }
+ }
+ }
+
//
// Unfold transition
//
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index fc26bf329e..35cb28d845 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -38,6 +38,7 @@ import androidx.annotation.UiThread;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.DisplayController;
import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
import com.android.quickstep.util.ActiveGestureLog;
@@ -61,27 +62,24 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
private RecentsAnimationTargets mTargets;
// Temporary until we can hook into gesture state events
private GestureState mLastGestureState;
- private RemoteAnimationTarget mLastAppearedTaskTarget;
+ private RemoteAnimationTarget[] mLastAppearedTaskTargets;
private Runnable mLiveTileCleanUpHandler;
private Context mCtx;
private final TaskStackChangeListener mLiveTileRestartListener = new TaskStackChangeListener() {
@Override
- public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
- boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
+ public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, boolean homeTaskVisible,
+ boolean clearedTask, boolean wasVisible) {
if (mLastGestureState == null) {
- TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
- mLiveTileRestartListener);
+ TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mLiveTileRestartListener);
return;
}
BaseActivityInterface activityInterface = mLastGestureState.getActivityInterface();
- if (activityInterface.isInLiveTileMode()
- && activityInterface.getCreatedActivity() != null) {
+ if (activityInterface.isInLiveTileMode() && activityInterface.getCreatedActivity() != null) {
RecentsView recentsView = activityInterface.getCreatedActivity().getOverviewPanel();
if (recentsView != null) {
recentsView.launchSideTaskInLiveTileModeForRestartedApp(task.taskId);
- TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
- mLiveTileRestartListener);
+ TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mLiveTileRestartListener);
}
}
}
@@ -129,13 +127,14 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
// the
// previous animation so it doesn't mess up/listen to state changes in this
// animation.
- cleanUpRecentsAnimation();
+ cleanUpRecentsAnimation(mCallbacks);
}
final BaseActivityInterface activityInterface = gestureState.getActivityInterface();
mLastGestureState = gestureState;
- mCallbacks = new RecentsAnimationCallbacks(SystemUiProxy.INSTANCE.get(mCtx),
- activityInterface.allowMinimizeSplitScreen());
+ RecentsAnimationCallbacks newCallbacks = new RecentsAnimationCallbacks(
+ SystemUiProxy.INSTANCE.get(mCtx), activityInterface.allowMinimizeSplitScreen());
+ mCallbacks = newCallbacks;
mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
@Override
public void onRecentsAnimationStart(RecentsAnimationController controller,
@@ -148,18 +147,26 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
}
mController = controller;
mTargets = targets;
- mLastAppearedTaskTarget = mTargets.findTask(mLastGestureState.getRunningTaskId());
- mLastGestureState.updateLastAppearedTaskTarget(mLastAppearedTaskTarget);
+ // TODO(b/236226779): We can probably get away w/ setting
+ // mLastAppearedTaskTargets
+ // to all appeared targets directly vs just looking at running ones
+ int[] runningTaskIds = mLastGestureState.getRunningTaskIds(targets.apps.length > 1);
+ mLastAppearedTaskTargets = new RemoteAnimationTarget[runningTaskIds.length];
+ for (int i = 0; i < runningTaskIds.length; i++) {
+ RemoteAnimationTarget task = mTargets.findTask(runningTaskIds[i]);
+ mLastAppearedTaskTargets[i] = task;
+ }
+ mLastGestureState.updateLastAppearedTaskTargets(mLastAppearedTaskTargets);
}
@Override
public void onRecentsAnimationCanceled(HashMap thumbnailDatas) {
- cleanUpRecentsAnimation();
+ cleanUpRecentsAnimation(newCallbacks);
}
@Override
public void onRecentsAnimationFinished(RecentsAnimationController controller) {
- cleanUpRecentsAnimation();
+ cleanUpRecentsAnimation(newCallbacks);
}
@Override
@@ -176,6 +183,9 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
// can immediately finish the transition
RecentsView recentsView = activityInterface.getCreatedActivity().getOverviewPanel();
if (recentsView != null) {
+ Log.d(TestProtocol.INCORRECT_HOME_STATE,
+ "finish recents animation on "
+ + compat.taskInfo.description);
recentsView.finishRecentsAnimation(true, null);
}
return;
@@ -207,14 +217,18 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
true /* shown */, null /* animatorHandler */);
}
if (mController != null) {
- if (mLastAppearedTaskTarget == null
- || appearedTaskTarget.taskId != mLastAppearedTaskTarget.taskId) {
- if (mLastAppearedTaskTarget != null) {
- mController.removeTaskTarget(mLastAppearedTaskTarget);
+ if (mLastAppearedTaskTargets != null) {
+ for (RemoteAnimationTarget lastTarget : mLastAppearedTaskTargets) {
+ for (RemoteAnimationTarget appearedTarget : appearedTaskTargets) {
+ if (lastTarget != null &&
+ appearedTarget.taskId != lastTarget.taskId) {
+ mController.removeTaskTarget(lastTarget.taskId);
+ }
+ }
}
- mLastAppearedTaskTarget = appearedTaskTarget;
- mLastGestureState.updateLastAppearedTaskTarget(mLastAppearedTaskTarget);
}
+ mLastAppearedTaskTargets = appearedTaskTargets;
+ mLastGestureState.updateLastAppearedTaskTargets(mLastAppearedTaskTargets);
}
}
@@ -262,9 +276,8 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
}
if (app.lawnchair.LawnchairApp.isAtleastT()) {
options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_RECENTS_ANIMATION, eventTime);
+ SystemUiProxy.INSTANCE.getNoCreate().startRecentsActivity(intent, options, mCallbacks);
}
- 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));
@@ -283,7 +296,7 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
mCallbacks.addListener(gestureState);
gestureState.setState(STATE_RECENTS_ANIMATION_INITIALIZED
| STATE_RECENTS_ANIMATION_STARTED);
- gestureState.updateLastAppearedTaskTarget(mLastAppearedTaskTarget);
+ gestureState.updateLastAppearedTaskTargets(mLastAppearedTaskTargets);
return mCallbacks;
}
@@ -323,11 +336,11 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
*
* @param forceFinish will synchronously finish the controller
*/
- private void finishRunningRecentsAnimation(boolean toHome, boolean forceFinish) {
+
+ public void finishRunningRecentsAnimation(boolean toHome, boolean forceFinish) {
if (mController != null) {
ActiveGestureLog.INSTANCE.addLog(
/* event= */ "finishRunningRecentsAnimation", toHome);
- mCallbacks.notifyAnimationCanceled();
if (forceFinish) {
mController.finishController(toHome, null, false /* sendUserLeaveHint */,
true /* forceFinish */);
@@ -336,7 +349,6 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
? mController::finishAnimationToHome
: mController::finishAnimationToApp);
}
- cleanUpRecentsAnimation();
}
}
@@ -365,7 +377,12 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
/**
* Cleans up the recents animation entirely.
*/
- private void cleanUpRecentsAnimation() {
+ private void cleanUpRecentsAnimation(RecentsAnimationCallbacks targetCallbacks) {
+ if (mCallbacks != targetCallbacks) {
+ ActiveGestureLog.INSTANCE.addLog(
+ /* event= */ "cleanUpRecentsAnimation skipped due to wrong callbacks");
+ return;
+ }
ActiveGestureLog.INSTANCE.addLog(/* event= */ "cleanUpRecentsAnimation");
if (mLiveTileCleanUpHandler != null) {
mLiveTileCleanUpHandler.run();
@@ -387,7 +404,7 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
mCallbacks = null;
mTargets = null;
mLastGestureState = null;
- mLastAppearedTaskTarget = null;
+ mLastAppearedTaskTargets = null;
}
@Nullable
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
index ff9cfb791d..fbdbc3ff9a 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.java
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.java
@@ -15,7 +15,6 @@
*/
package com.android.quickstep;
-import static com.android.launcher3.uioverrides.QuickstepLauncher.GO_LOW_RAM_RECENTS_ENABLED;
import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
import android.annotation.Nullable;
@@ -186,17 +185,14 @@ public class TaskIconCache implements DisplayInfoChangeListener {
}
}
- // Loading content descriptions if accessibility or low RAM recents is enabled.
- if (GO_LOW_RAM_RECENTS_ENABLED || mAccessibilityManager.isEnabled()) {
- // Skip loading the content description if the activity no longer exists
- if (activityInfo == null) {
- activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(
- key.getComponent(), key.userId);
- }
- if (activityInfo != null) {
- entry.contentDescription = getBadgedContentDescription(
- activityInfo, task.key.userId, task.taskDescription);
- }
+ // Skip loading the content description if the activity no longer exists
+ if (activityInfo == null) {
+ activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(
+ key.getComponent(), key.userId);
+ }
+ if (activityInfo != null) {
+ entry.contentDescription = getBadgedContentDescription(
+ activityInfo, task.key.userId, task.taskDescription);
}
mIconCache.put(task.key, entry);
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 3df0e9bfe4..fe3a590c5f 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -126,6 +126,14 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
public void removeListeners() {
}
+ /**
+ * Clears any active state outside of the TaskOverlay lifecycle which might have
+ * built
+ * up over time
+ */
+ public void clearAllActiveState() {
+ }
+
/**
* Note that these will be shown in order from top to bottom, if available for
* the task.
@@ -166,6 +174,10 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
return mActionsView;
}
+ public TaskThumbnailView getThumbnailView() {
+ return mThumbnailView;
+ }
+
/**
* Called when the current task is interactive for the user
*/
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 411325b519..2af81d4310 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -41,7 +41,6 @@ import androidx.annotation.Nullable;
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;
@@ -146,6 +145,7 @@ public interface TaskShortcutFactory {
@Override
public void onClick(View view) {
+ dismissTaskMenuView(mTarget);
((RecentsView) mTarget.getOverviewPanel())
.getSplitSelectController().getAppPairsController().saveAppPair(mTaskView);
}
@@ -403,8 +403,9 @@ public interface TaskShortcutFactory {
@Override
public List getShortcuts(BaseDraggingActivity activity,
TaskIdAttributeContainer taskContainer) {
- return InstantAppResolver.newInstance(activity).isInstantApp(activity,
- taskContainer.getTask().getTopComponent().getPackageName())
+ Task t = taskContainer.getTask();
+ return InstantAppResolver.newInstance(activity).isInstantApp(
+ t.getTopComponent().getPackageName(), t.getKey().userId)
? Collections.singletonList(new SystemShortcut.Install(activity,
taskContainer.getItemInfo(), taskContainer.getTaskView()))
: null;
diff --git a/quickstep/src/com/android/quickstep/TaskUtils.java b/quickstep/src/com/android/quickstep/TaskUtils.java
index 9d34e8c4eb..71719bf2e6 100644
--- a/quickstep/src/com/android/quickstep/TaskUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskUtils.java
@@ -33,6 +33,7 @@ import androidx.annotation.Nullable;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.TraceHelper;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -53,7 +54,8 @@ public final class TaskUtils {
* TODO: remove this once we switch to getting the icon and label from IconCache.
*/
public static CharSequence getTitle(Context context, Task task) {
- return getTitle(context, task.key.userId, task.getTopComponent().getPackageName());
+ return TraceHelper.allowIpcs("TaskUtils.getTitle", () ->
+ getTitle(context, task.key.userId, task.getTopComponent().getPackageName()));
}
public static CharSequence getTitle(
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 1238819acd..a5ab28cbcb 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -21,6 +21,9 @@ import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.app.animation.Interpolators.TOUCH_RESPONSE;
+import static com.android.app.animation.Interpolators.clampToProgress;
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
@@ -35,9 +38,8 @@ import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DU
import static com.android.launcher3.QuickstepTransitionManager.SPLIT_DIVIDER_ANIM_DURATION;
import static com.android.launcher3.QuickstepTransitionManager.SPLIT_LAUNCH_DURATION;
import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
-import static com.android.launcher3.anim.Interpolators.clampToProgress;
+import static com.android.launcher3.testing.shared.TestProtocol.LAUNCH_SPLIT_PAIR;
+import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
import static com.android.quickstep.views.DesktopTaskView.DESKTOP_MODE_SUPPORTED;
@@ -61,12 +63,12 @@ import android.window.TransitionInfo;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.statehandlers.DepthController;
@@ -93,19 +95,25 @@ import java.util.List;
import java.util.function.Consumer;
/**
- * Utility class for helpful methods related to {@link TaskView} objects and their tasks.
+ * Utility class for helpful methods related to {@link TaskView} objects and
+ * their tasks.
*/
@TargetApi(Build.VERSION_CODES.R)
public final class TaskViewUtils {
- private TaskViewUtils() {}
+ private TaskViewUtils() {
+ }
/**
- * Try to find a TaskView that corresponds with the component of the launched view.
+ * Try to find a TaskView that corresponds with the component of the launched
+ * view.
*
- * If this method returns a non-null TaskView, it will be used in composeRecentsLaunchAnimation.
- * Otherwise, we will assume we are using a normal app transition, but it's possible that the
- * opening remote target (which we don't get until onAnimationStart) will resolve to a TaskView.
+ * If this method returns a non-null TaskView, it will be used in
+ * composeRecentsLaunchAnimation.
+ * Otherwise, we will assume we are using a normal app transition, but it's
+ * possible that the
+ * opening remote target (which we don't get until onAnimationStart) will
+ * resolve to a TaskView.
*/
public static TaskView findTaskViewToLaunch(
RecentsView recentsView, View v, RemoteAnimationTarget[] targets) {
@@ -114,7 +122,8 @@ public final class TaskViewUtils {
return recentsView.isTaskViewVisible(taskView) ? taskView : null;
}
- // It's possible that the launched view can still be resolved to a visible task view, check
+ // It's possible that the launched view can still be resolved to a visible task
+ // view, check
// the task id of the opening task and see if we can find a match.
if (v.getTag() instanceof ItemInfo) {
ItemInfo itemInfo = (ItemInfo) v.getTag();
@@ -145,12 +154,14 @@ public final class TaskViewUtils {
}
}
- // If there is no opening task id, fall back to the normal app icon launch animation
+ // If there is no opening task id, fall back to the normal app icon launch
+ // animation
if (openingTaskId == -1) {
return null;
}
- // If the opening task id is not currently visible in overview, then fall back to normal app
+ // If the opening task id is not currently visible in overview, then fall back
+ // to normal app
// icon launch animation
TaskView taskView = recentsView.getTaskViewByTaskId(openingTaskId);
if (taskView == null || !recentsView.isTaskViewVisible(taskView)) {
@@ -160,19 +171,19 @@ public final class TaskViewUtils {
}
public static void createRecentsWindowAnimator(
- @NonNull TaskView v, boolean skipViewChanges,
+ @NonNull RecentsView recentsView,
+ @NonNull TaskView v,
+ boolean skipViewChanges,
@NonNull RemoteAnimationTarget[] appTargets,
@NonNull RemoteAnimationTarget[] wallpaperTargets,
@NonNull RemoteAnimationTarget[] nonAppTargets,
@Nullable DepthController depthController,
PendingAnimation out) {
- RecentsView recentsView = v.getRecentsView();
boolean isQuickSwitch = v.isEndQuickswitchCuj();
v.setEndQuickswitchCuj(false);
- final RemoteAnimationTargets targets =
- new RemoteAnimationTargets(appTargets, wallpaperTargets, nonAppTargets,
- MODE_OPENING);
+ final RemoteAnimationTargets targets = new RemoteAnimationTargets(appTargets, wallpaperTargets, nonAppTargets,
+ MODE_OPENING);
final RemoteAnimationTarget navBarTarget = targets.getNavBarRemoteAnimationTarget();
SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
@@ -216,7 +227,8 @@ public final class TaskViewUtils {
TaskViewSimulator tvsLocal = targetHandle.getTaskViewSimulator();
tvsLocal.setDp(dp);
- // RecentsView never updates the display rotation until swipe-up so the value may
+ // RecentsView never updates the display rotation until swipe-up so the value
+ // may
// be stale. Use the display value instead.
int displayRotation = DisplayController.INSTANCE.get(context).getInfo().rotation;
tvsLocal.getOrientationState().update(displayRotation, displayRotation);
@@ -237,12 +249,12 @@ public final class TaskViewUtils {
for (RemoteTargetHandle targetHandle : remoteTargetHandles) {
TaskViewSimulator tvsLocal = targetHandle.getTaskViewSimulator();
out.setFloat(tvsLocal.fullScreenProgress,
- AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR);
+ AnimatedFloat.VALUE, 1, TOUCH_RESPONSE);
out.setFloat(tvsLocal.recentsViewScale,
AnimatedFloat.VALUE, tvsLocal.getFullScreenScale(),
- TOUCH_RESPONSE_INTERPOLATOR);
+ TOUCH_RESPONSE);
out.setFloat(tvsLocal.recentsViewScroll, AnimatedFloat.VALUE, 0,
- TOUCH_RESPONSE_INTERPOLATOR);
+ TOUCH_RESPONSE);
out.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
@@ -270,12 +282,10 @@ public final class TaskViewUtils {
@Override
public void onUpdate(float percent, boolean initOnly) {
-
// TODO Do we need to operate over multiple TVSs for the navbar leash?
for (RemoteTargetHandle handle : remoteTargetHandles) {
SurfaceTransaction transaction = new SurfaceTransaction();
- SurfaceProperties navBuilder =
- transaction.forSurface(navBarTarget.leash);
+ SurfaceProperties navBuilder = transaction.forSurface(navBarTarget.leash);
if (mNavFadeIn.value > mNavFadeIn.getStartValue()) {
TaskViewSimulator taskViewSimulator = handle.getTaskViewSimulator();
@@ -291,10 +301,10 @@ public final class TaskViewUtils {
}
});
} else {
- // There is no transition animation for app launch from recent in live tile mode so
+ // There is no transition animation for app launch from recent in live tile mode
+ // so
// we have to trigger the navigation bar animation from system here.
- final RecentsAnimationController controller =
- recentsView.getRecentsAnimationController();
+ final RecentsAnimationController controller = recentsView.getRecentsAnimationController();
if (controller != null) {
controller.animateNavigationBarToApp(RECENTS_LAUNCH_DURATION);
}
@@ -313,20 +323,28 @@ public final class TaskViewUtils {
// Mt represents the overall transformation on the thumbnailView relative to the
// Launcher's rootView
- // K(t) represents transformation on the running window by the taskViewSimulator at
+ // K(t) represents transformation on the running window by the taskViewSimulator
+ // at
// any time t.
- // at t = 0, we know that the simulator matches the thumbnailView. So if we apply K(0)`
- // on the Launcher's rootView, the thumbnailView would match the full running task
- // window. If we apply "K(0)` K(t)" thumbnailView will match the final transformed
+ // at t = 0, we know that the simulator matches the thumbnailView. So if we
+ // apply K(0)`
+ // on the Launcher's rootView, the thumbnailView would match the full running
+ // task
+ // window. If we apply "K(0)` K(t)" thumbnailView will match the final
+ // transformed
// window at any time t. This gives the overall matrix on thumbnailView to be:
- // Mt K(0)` K(t)
- // During animation we apply transformation on the thumbnailView (and not the rootView)
- // to follow the TaskViewSimulator. So the final matrix applied on the thumbnailView is:
- // Mt K(0)` K(t) Mt`
+ // Mt K(0)` K(t)
+ // During animation we apply transformation on the thumbnailView (and not the
+ // rootView)
+ // to follow the TaskViewSimulator. So the final matrix applied on the
+ // thumbnailView is:
+ // Mt K(0)` K(t) Mt`
TaskThumbnailView[] thumbnails = v.getThumbnails();
- // In case simulator copies and thumbnail size do no match, ensure we get the lesser.
- // This ensures we do not create arrays with empty elements or attempt to references
+ // In case simulator copies and thumbnail size do no match, ensure we get the
+ // lesser.
+ // This ensures we do not create arrays with empty elements or attempt to
+ // references
// indexes out of array bounds.
final int matrixSize = Math.min(simulatorCopies.length, thumbnails.length);
@@ -334,8 +352,8 @@ public final class TaskViewUtils {
Matrix[] mti = new Matrix[matrixSize];
for (int i = 0; i < matrixSize; i++) {
TaskThumbnailView ttv = thumbnails[i];
- RectF localBounds = new RectF(0, 0, ttv.getWidth(), ttv.getHeight());
- float[] tvBoundsMapped = new float[]{0, 0, ttv.getWidth(), ttv.getHeight()};
+ RectF localBounds = new RectF(0, 0, ttv.getWidth(), ttv.getHeight());
+ float[] tvBoundsMapped = new float[] { 0, 0, ttv.getWidth(), ttv.getHeight() };
getDescendantCoordRelativeToAncestor(ttv, ttv.getRootView(), tvBoundsMapped, false);
RectF localBoundsInRoot = new RectF(
tvBoundsMapped[0], tvBoundsMapped[1],
@@ -348,16 +366,16 @@ public final class TaskViewUtils {
localMt.invert(localMti);
mti[i] = localMti;
- // Translations for child thumbnails also get scaled as the parent taskView scales
+ // Translations for child thumbnails also get scaled as the parent taskView
+ // scales
// Add inverse scaling to keep translations the same
float translationY = ttv.getTranslationY();
float translationX = ttv.getTranslationX();
- float fullScreenScale =
- topMostSimulators[i].getTaskViewSimulator().getFullScreenScale();
+ float fullScreenScale = topMostSimulators[i].getTaskViewSimulator().getFullScreenScale();
out.addFloat(ttv, VIEW_TRANSLATE_Y, translationY,
- translationY / fullScreenScale, TOUCH_RESPONSE_INTERPOLATOR);
+ translationY / fullScreenScale, TOUCH_RESPONSE);
out.addFloat(ttv, VIEW_TRANSLATE_X, translationX,
- translationX / fullScreenScale, TOUCH_RESPONSE_INTERPOLATOR);
+ translationX / fullScreenScale, TOUCH_RESPONSE);
}
Matrix[] k0i = new Matrix[matrixSize];
@@ -405,31 +423,30 @@ public final class TaskViewUtils {
if (depthController != null) {
out.setFloat(depthController.stateDepth, MULTI_PROPERTY_VALUE,
- BACKGROUND_APP.getDepth(baseActivity), TOUCH_RESPONSE_INTERPOLATOR);
+ BACKGROUND_APP.getDepth(baseActivity), TOUCH_RESPONSE);
}
}
/**
- * TODO: This doesn't animate at present. Feel free to blow out everyhing in this method
- * if needed
+ * If {@param launchingTaskView} is not null, then this will play the tasks
+ * launch animation
+ * from the position of the GroupedTaskView (when user taps on the TaskView to
+ * start it).
+ * Technically this case should be taken care of by
+ * {@link #composeRecentsSplitLaunchAnimatorLegacy} below, but the way we launch
+ * tasks whether
+ * it's a single task or multiple tasks results in different entry-points.
*
- * We could manually try to animate the just the bounds for the leashes we get back, but we try
- * to do it through TaskViewSimulator(TVS) since that handles a lot of the recents UI stuff for
- * us.
- *
- * First you have to call TVS#setPreview() to indicate which leash it will operate one
- * Then operations happen in TVS#apply() on each frame callback.
- *
- * TVS uses DeviceProfile to try to figure out things like task height and such based on if the
- * device is in multiWindowMode or not. It's unclear given the two calls to startTask() when the
- * device is considered in multiWindowMode and things like insets and stuff change
- * and calculations have to be adjusted in the animations for that
+ * If it is null, then it will simply fade in the starting apps and fade out
+ * launcher (for the
+ * case where launcher handles animating starting split tasks from app icon)
*/
public static void composeRecentsSplitLaunchAnimator(GroupedTaskView launchingTaskView,
@NonNull StateManager stateManager, @Nullable DepthController depthController,
int initialTaskId, int secondTaskId, @NonNull TransitionInfo transitionInfo,
SurfaceControl.Transaction t, @NonNull Runnable finishCallback) {
if (launchingTaskView != null) {
+ testLogD(LAUNCH_SPLIT_PAIR, "composeRecentsSplitLaunchAnimator taskView not-null");
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
@@ -438,14 +455,12 @@ public final class TaskViewUtils {
}
});
- final RemoteAnimationTarget[] appTargets =
- RemoteAnimationTargetCompat.wrapApps(transitionInfo, t, null /* leashMap */);
- final RemoteAnimationTarget[] wallpaperTargets =
- RemoteAnimationTargetCompat.wrapNonApps(
- transitionInfo, true /* wallpapers */, t, null /* leashMap */);
- final RemoteAnimationTarget[] nonAppTargets =
- RemoteAnimationTargetCompat.wrapNonApps(
- transitionInfo, false /* wallpapers */, t, null /* leashMap */);
+ final RemoteAnimationTarget[] appTargets = RemoteAnimationTargetCompat.wrapApps(transitionInfo, t,
+ null /* leashMap */);
+ final RemoteAnimationTarget[] wallpaperTargets = RemoteAnimationTargetCompat.wrapNonApps(
+ transitionInfo, true /* wallpapers */, t, null /* leashMap */);
+ final RemoteAnimationTarget[] nonAppTargets = RemoteAnimationTargetCompat.wrapNonApps(
+ transitionInfo, false /* wallpapers */, t, null /* leashMap */);
final RecentsView recentsView = launchingTaskView.getRecentsView();
composeRecentsLaunchAnimator(animatorSet, launchingTaskView,
appTargets, wallpaperTargets, nonAppTargets,
@@ -457,16 +472,20 @@ public final class TaskViewUtils {
return;
}
- // TODO: consider initialTaskPendingIntent
TransitionInfo.Change splitRoot1 = null;
TransitionInfo.Change splitRoot2 = null;
+ final ArrayList openingTargets = new ArrayList<>();
for (int i = 0; i < transitionInfo.getChanges().size(); ++i) {
final TransitionInfo.Change change = transitionInfo.getChanges().get(i);
- if (change.getTaskInfo() == null) continue;
+ if (change.getTaskInfo() == null) {
+ testLogD(LAUNCH_SPLIT_PAIR, "changeTaskInfo null; change: " + change);
+ continue;
+ }
final int taskId = change.getTaskInfo().taskId;
final int mode = change.getMode();
- // Find the target tasks' root tasks since those are the split stages that need to
+ // Find the target tasks' root tasks since those are the split stages that need
+ // to
// be animated (the tasks themselves are children and thus inherit animation).
if (taskId == initialTaskId || taskId == secondTaskId) {
if (!(mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
@@ -475,46 +494,69 @@ public final class TaskViewUtils {
}
}
if (taskId == initialTaskId) {
- splitRoot1 = change.getParent() == null ? change :
- transitionInfo.getChange(change.getParent());
+ splitRoot1 = change.getParent() == null ? change : transitionInfo.getChange(change.getParent());
+ openingTargets.add(splitRoot1.getLeash());
}
if (taskId == secondTaskId) {
- splitRoot2 = change.getParent() == null ? change :
- transitionInfo.getChange(change.getParent());
+ splitRoot2 = change.getParent() == null ? change : transitionInfo.getChange(change.getParent());
+ openingTargets.add(splitRoot2.getLeash());
}
}
- // This is where we should animate the split roots. For now, though, just make them visible.
- animateSplitRoot(t, splitRoot1);
- animateSplitRoot(t, splitRoot2);
+ SurfaceControl.Transaction animTransaction = new SurfaceControl.Transaction();
+ ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
+ animator.setDuration(SPLIT_LAUNCH_DURATION);
+ animator.addUpdateListener(valueAnimator -> {
+ float progress = valueAnimator.getAnimatedFraction();
+ for (SurfaceControl leash : openingTargets) {
+ animTransaction.setAlpha(leash, progress);
+ }
+ animTransaction.apply();
+ });
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ for (SurfaceControl leash : openingTargets) {
+ animTransaction.show(leash)
+ .setAlpha(leash, 0.0f);
+ }
+ animTransaction.apply();
+ }
- // This contains the initial state (before animation), so apply this at the beginning of
- // the animation.
- t.apply();
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finishCallback.run();
+ }
+ });
- // Once there is an animation, this should be called AFTER the animation completes.
- finishCallback.run();
- }
-
- private static void animateSplitRoot(SurfaceControl.Transaction t,
- TransitionInfo.Change splitRoot) {
- if (splitRoot != null) {
- t.show(splitRoot.getLeash());
- t.setAlpha(splitRoot.getLeash(), 1.f);
+ if (splitRoot1 != null && splitRoot1.getParent() != null) {
+ // Set the highest level split root alpha; we could technically use the parent
+ // of either
+ // splitRoot1 or splitRoot2
+ t.setAlpha(transitionInfo.getChange(splitRoot1.getParent()).getLeash(), 1f);
}
+ t.apply();
+ animator.start();
}
/**
* Legacy version (until shell transitions are enabled)
*
- * If {@param launchingTaskView} is not null, then this will play the tasks launch animation
- * from the position of the GroupedTaskView (when user taps on the TaskView to start it).
+ * If {@param launchingTaskView} is not null, then this will play the tasks
+ * launch animation
+ * from the position of the GroupedTaskView (when user taps on the TaskView to
+ * start it).
* Technically this case should be taken care of by
- * {@link #composeRecentsSplitLaunchAnimatorLegacy} below, but the way we launch tasks whether
+ * {@link #composeRecentsSplitLaunchAnimatorLegacy} below, but the way we launch
+ * tasks whether
* it's a single task or multiple tasks results in different entry-points.
*
- * If it is null, then it will simply fade in the starting apps and fade out launcher (for the
- * case where launcher handles animating starting split tasks from app icon) */
+ * If it is null, then it will simply fade in the starting apps and fade out
+ * launcher (for the
+ * case where launcher handles animating starting split tasks from app icon)
+ *
+ * @deprecated with shell transitions
+ */
public static void composeRecentsSplitLaunchAnimatorLegacy(
@Nullable GroupedTaskView launchingTaskView, int initialTaskId, int secondTaskId,
@NonNull RemoteAnimationTarget[] appTargets,
@@ -571,7 +613,7 @@ public final class TaskViewUtils {
animator.setDuration(SPLIT_LAUNCH_DURATION);
animator.addUpdateListener(valueAnimator -> {
float progress = valueAnimator.getAnimatedFraction();
- for (SurfaceControl leash: openingTargets) {
+ for (SurfaceControl leash : openingTargets) {
t.setAlpha(leash, progress);
}
t.apply();
@@ -579,7 +621,7 @@ public final class TaskViewUtils {
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
- for (SurfaceControl leash: openingTargets) {
+ for (SurfaceControl leash : openingTargets) {
t.show(leash).setAlpha(leash, 0.0f);
}
t.apply();
@@ -587,7 +629,7 @@ public final class TaskViewUtils {
@Override
public void onAnimationEnd(Animator animation) {
- for (SurfaceControl leash: closingTargets) {
+ for (SurfaceControl leash : closingTargets) {
t.hide(leash);
}
finishCallback.run();
@@ -606,11 +648,12 @@ public final class TaskViewUtils {
TaskView taskView = findTaskViewToLaunch(recentsView, v, appTargets);
PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
- createRecentsWindowAnimator(taskView, skipLauncherChanges, appTargets, wallpaperTargets,
- nonAppTargets, depthController, pa);
+ createRecentsWindowAnimator(recentsView, taskView, skipLauncherChanges, appTargets,
+ wallpaperTargets, nonAppTargets, depthController, pa);
if (launcherClosing) {
- // TODO(b/182592057): differentiate between "restore split" vs "launch fullscreen app"
- TaskViewUtils.createSplitAuxiliarySurfacesAnimator(nonAppTargets, true /*shown*/,
+ // TODO(b/182592057): differentiate between "restore split" vs "launch
+ // fullscreen app"
+ TaskViewUtils.createSplitAuxiliarySurfacesAnimator(nonAppTargets, true /* shown */,
(dividerAnimator) -> {
// If split apps are launching, we want to delay showing the divider bar
// until the very end once the apps are mostly in place. This is because we
@@ -623,21 +666,24 @@ public final class TaskViewUtils {
}
Animator childStateAnimation = null;
- // Found a visible recents task that matches the opening app, lets launch the app from there
+ // Found a visible recents task that matches the opening app, lets launch the
+ // app from there
Animator launcherAnim;
final AnimatorListenerAdapter windowAnimEndListener;
if (launcherClosing) {
- // Since Overview is in launcher, just opening overview sets willFinishToHome to true.
- // Now that we are closing the launcher, we need to (re)set willFinishToHome back to
- // false. Otherwise, RecentsAnimationController can't differentiate between closing
+ // Since Overview is in launcher, just opening overview sets willFinishToHome to
+ // true.
+ // Now that we are closing the launcher, we need to (re)set willFinishToHome
+ // back to
+ // false. Otherwise, RecentsAnimationController can't differentiate between
+ // closing
// overview to 3p home vs closing overview to app.
- final RecentsAnimationController raController =
- recentsView.getRecentsAnimationController();
+ final RecentsAnimationController raController = recentsView.getRecentsAnimationController();
if (raController != null) {
raController.setWillFinishToHome(false);
}
launcherAnim = recentsView.createAdjacentPageAnimForTaskLaunch(taskView);
- launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
+ launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE);
launcherAnim.setDuration(RECENTS_LAUNCH_DURATION);
windowAnimEndListener = new AnimatorListenerAdapter() {
@@ -658,8 +704,8 @@ public final class TaskViewUtils {
}
};
} else {
- AnimatorPlaybackController controller =
- stateManager.createAnimationToNewWorkspace(NORMAL, RECENTS_LAUNCH_DURATION);
+ AnimatorPlaybackController controller = stateManager.createAnimationToNewWorkspace(NORMAL,
+ RECENTS_LAUNCH_DURATION);
controller.dispatchOnStart();
childStateAnimation = controller.getTarget();
launcherAnim = controller.getAnimationPlayer().setDuration(RECENTS_LAUNCH_DURATION);
@@ -677,15 +723,18 @@ public final class TaskViewUtils {
}
anim.play(pa.buildAnim());
- // Set the current animation first, before adding windowAnimEndListener. Setting current
- // animation adds some listeners which need to be called before windowAnimEndListener
+ // Set the current animation first, before adding windowAnimEndListener. Setting
+ // current
+ // animation adds some listeners which need to be called before
+ // windowAnimEndListener
// (the ordering of listeners matter in this case).
stateManager.setCurrentAnimation(anim, childStateAnimation);
anim.addListener(windowAnimEndListener);
}
/**
- * Creates an animation to show/hide the auxiliary surfaces (aka. divider bar), only calling
+ * Creates an animation to show/hide the auxiliary surfaces (aka. divider bar),
+ * only calling
* {@param animatorHandler} if there are valid surfaces to animate.
* Passing null handler to apply the visibility immediately.
*
diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java
index d34cddf584..f1af2edda8 100644
--- a/quickstep/src/com/android/quickstep/TopTaskTracker.java
+++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java
@@ -16,7 +16,6 @@
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;
@@ -210,7 +209,7 @@ public class TopTaskTracker extends ISplitScreenListener.Stub implements TaskSta
@Nullable
private final RunningTaskInfo mTopTask;
- private final List mAllCachedTasks;
+ public final List mAllCachedTasks;
CachedTaskInfo(List allCachedTasks) {
mAllCachedTasks = allCachedTasks;
@@ -229,12 +228,21 @@ public class TopTaskTracker extends ISplitScreenListener.Stub implements TaskSta
}
/**
- * Returns true if the given task holds an Assistant activity that is excluded from recents
+ * If the given task holds an activity that is excluded from recents, and there
+ * is another running task that is not excluded from recents, returns that underlying task.
*/
- public boolean isExcludedAssistant() {
- return mTopTask != null && mTopTask.configuration.windowConfiguration
- .getActivityType() == ACTIVITY_TYPE_ASSISTANT
- && (mTopTask.baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
+ public @Nullable CachedTaskInfo otherVisibleTaskThisIsExcludedOver() {
+ if (mTopTask == null
+ || (mTopTask.baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0) {
+ // Not an excluded task.
+ return null;
+ }
+ List visibleNonExcludedTasks = mAllCachedTasks.stream()
+ .filter(t -> t.isVisible
+ && (t.baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0)
+ .toList();
+ return visibleNonExcludedTasks.isEmpty() ? null
+ : new CachedTaskInfo(visibleNonExcludedTasks);
}
/**
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 08288446af..925fd2e97e 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -24,8 +24,11 @@ import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
import static com.android.launcher3.Launcher.INTENT_ACTION_ALL_APPS_TOGGLE;
+import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent;
+import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TRACKPAD_GESTURE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH;
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;
@@ -37,10 +40,8 @@ import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SY
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE;
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_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;
@@ -56,6 +57,8 @@ import android.annotation.TargetApi;
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.app.Service;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
@@ -67,8 +70,10 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.Trace;
import android.util.Log;
import android.view.Choreographer;
+import android.view.InputDevice;
import android.view.InputEvent;
import android.view.MotionEvent;
import android.view.SurfaceControl;
@@ -83,27 +88,26 @@ 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;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.provider.RestoreDbTask;
-import com.android.launcher3.statehandlers.DesktopVisibilityController;
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;
import com.android.launcher3.uioverrides.flags.FlagsFactory;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.OnboardingPrefs;
+import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
import com.android.quickstep.inputconsumers.AssistantInputConsumer;
import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer;
+import com.android.quickstep.inputconsumers.NavHandleLongPressInputConsumer;
import com.android.quickstep.inputconsumers.OneHandedModeInputConsumer;
import com.android.quickstep.inputconsumers.OtherActivityInputConsumer;
import com.android.quickstep.inputconsumers.OverviewInputConsumer;
@@ -111,13 +115,13 @@ 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.TaskbarUnstashInputConsumer;
+import com.android.quickstep.inputconsumers.TrackpadStatusBarInputConsumer;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.ActiveGestureLog.CompoundString;
-import com.android.quickstep.util.ProtoTracer;
-import com.android.quickstep.util.ProxyScreenStatusProvider;
+import com.android.quickstep.util.AssistStateManager;
+import com.android.quickstep.util.AssistUtils;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -125,7 +129,6 @@ 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.tracing.ProtoTraceable;
import com.android.systemui.unfold.progress.IUnfoldAnimation;
import com.android.wm.shell.back.IBackAnimation;
import com.android.wm.shell.bubbles.IBubbles;
@@ -141,8 +144,6 @@ 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;
@@ -152,8 +153,7 @@ import app.lawnchair.LawnchairApp;
* Service connected by system-UI for handling touch interaction.
*/
@TargetApi(Build.VERSION_CODES.R)
-public class TouchInteractionService extends Service
- implements ProtoTraceable {
+public class TouchInteractionService extends Service {
private static final String SUBSTRING_PREFIX = "; ";
private static final String NEWLINE_PREFIX = "\n\t\t\t-> ";
@@ -288,9 +288,20 @@ public class TouchInteractionService extends Service
}));
}
+ /**
+ * Sent when the assistant has been invoked with the given type (defined in
+ * AssistManager)
+ * and should be shown. This method is used if
+ * SystemUiProxy#setAssistantOverridesRequested
+ * was previously called including this invocation type.
+ */
@Override
- public void onAssistantOverrideInvoked(int invocationType) throws RemoteException {
-
+ public void onAssistantOverrideInvoked(int invocationType) {
+ executeForTouchInteractionService(tis -> {
+ if (!AssistUtils.newInstance(tis).tryStartAssistOverride(invocationType)) {
+ Log.w(TAG, "Failed to invoke Assist override");
+ }
+ });
}
@Override
@@ -313,21 +324,6 @@ public class TouchInteractionService extends Service
tis -> tis.mDeviceState.setDeferredGestureRegion(region)));
}
- @BinderThread
- public void onScreenTurnedOn() {
- MAIN_EXECUTOR.execute(ProxyScreenStatusProvider.INSTANCE::onScreenTurnedOn);
- }
-
- @BinderThread
- public void onScreenTurningOn() {
- MAIN_EXECUTOR.execute(ProxyScreenStatusProvider.INSTANCE::onScreenTurningOn);
- }
-
- @BinderThread
- public void onScreenTurningOff() {
- MAIN_EXECUTOR.execute(ProxyScreenStatusProvider.INSTANCE::onScreenTurningOff);
- }
-
@BinderThread
@Override
public void enterStageSplitFromRunningApp(boolean leftOrTop) {
@@ -499,11 +495,9 @@ public class TouchInteractionService extends Service
// Call runOnUserUnlocked() before any other callbacks to ensure everything is
// initialized.
- mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
- mDeviceState.runOnUserUnlocked(mTaskbarManager::onUserUnlocked);
+ LockedUserState.get(this).runOnUserUnlocked(this::onUserUnlocked);
+ LockedUserState.get(this).runOnUserUnlocked(mTaskbarManager::onUserUnlocked);
mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
-
- ProtoTracer.INSTANCE.get(this).add(this);
sConnected = true;
}
@@ -522,7 +516,7 @@ public class TouchInteractionService extends Service
private void initInputMonitor(String reason) {
disposeEventHandlers("Initializing input monitor due to: " + reason);
- if (mDeviceState.isButtonNavMode()) {
+ if (mDeviceState.isButtonNavMode() && !ENABLE_TRACKPAD_GESTURE.get()) {
return;
}
@@ -571,7 +565,7 @@ public class TouchInteractionService extends Service
}
private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
- if (!mDeviceState.isUserUnlocked() || mDeviceState.isButtonNavMode()) {
+ if (!LockedUserState.get(this).isUserUnlocked() || mDeviceState.isButtonNavMode()) {
// Skip if not yet unlocked (can't read user shared prefs) or if the current
// navigation
// mode doesn't have gestures
@@ -592,15 +586,7 @@ public class TouchInteractionService extends Service
AccessibilityManager am = getSystemService(AccessibilityManager.class);
if (isHomeAndOverviewSame) {
- Intent intent = new Intent(mOverviewComponentObserver.getHomeIntent())
- .setAction(INTENT_ACTION_ALL_APPS_TOGGLE);
- RemoteAction allAppsAction = new RemoteAction(
- Icon.createWithResource(this, R.drawable.ic_apps),
- getString(R.string.all_apps_label),
- getString(R.string.all_apps_label),
- PendingIntent.getActivity(this, GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS, intent,
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE));
- am.registerSystemAction(allAppsAction, GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS);
+ am.registerSystemAction(createAllAppsAction(), GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS);
} else {
am.unregisterSystemAction(GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS);
}
@@ -613,24 +599,43 @@ public class TouchInteractionService extends Service
mTISBinder.onOverviewTargetChange();
}
+ private RemoteAction createAllAppsAction() {
+ final Intent homeIntent = new Intent(mOverviewComponentObserver.getHomeIntent())
+ .setAction(INTENT_ACTION_ALL_APPS_TOGGLE);
+ final PendingIntent actionPendingIntent;
+
+ if (FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
+ actionPendingIntent = new PendingIntent(new IIntentSender.Stub() {
+ @Override
+ public void send(int code, Intent intent, String resolvedType,
+ IBinder allowlistToken, IIntentReceiver finishedReceiver,
+ String requiredPermission, Bundle options) {
+ MAIN_EXECUTOR.execute(() -> mTaskbarManager.toggleAllApps(homeIntent));
+ }
+ });
+ } else {
+ actionPendingIntent = PendingIntent.getActivity(
+ this,
+ GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS,
+ homeIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ }
+
+ return new RemoteAction(
+ Icon.createWithResource(this, R.drawable.ic_apps),
+ getString(R.string.all_apps_label),
+ getString(R.string.all_apps_label),
+ actionPendingIntent);
+ }
+
@UiThread
private void onSystemUiFlagsChanged(int lastSysUIFlags) {
- if (mDeviceState.isUserUnlocked()) {
+ if (LockedUserState.get(this).isUserUnlocked()) {
int systemUiStateFlags = mDeviceState.getSystemUiStateFlags();
SystemUiProxy.INSTANCE.get(this).setLastSystemUiStateFlags(systemUiStateFlags);
mOverviewComponentObserver.onSystemUiStateChanged();
mTaskbarManager.onSystemUiFlagsChanged(systemUiStateFlags);
- boolean wasFreeformActive = (lastSysUIFlags & SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0;
- boolean isFreeformActive = (systemUiStateFlags & SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0;
- if (wasFreeformActive != isFreeformActive) {
- DesktopVisibilityController controller = LauncherActivityInterface.INSTANCE
- .getDesktopVisibilityController();
- if (controller != null) {
- controller.setFreeformTasksVisible(isFreeformActive);
- }
- }
-
int isShadeExpandedFlag = SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
boolean wasExpanded = (lastSysUIFlags & isShadeExpandedFlag) != 0;
boolean isExpanded = (systemUiStateFlags & isShadeExpandedFlag) != 0;
@@ -639,24 +644,12 @@ public class TouchInteractionService extends Service
// overview.
mTaskAnimationManager.endLiveTile();
}
-
- if ((lastSysUIFlags & SYSUI_STATE_TRACING_ENABLED) != (systemUiStateFlags & SYSUI_STATE_TRACING_ENABLED)) {
- // Update the tracing state
- if ((systemUiStateFlags & SYSUI_STATE_TRACING_ENABLED) != 0) {
- Log.d(TAG, "Starting tracing.");
- ProtoTracer.INSTANCE.get(this).start();
- } else {
- Log.d(TAG, "Stopping tracing. Dumping to file="
- + ProtoTracer.INSTANCE.get(this).getTraceFile());
- ProtoTracer.INSTANCE.get(this).stop();
- }
- }
}
}
@UiThread
private void onAssistantVisibilityChanged() {
- if (mDeviceState.isUserUnlocked()) {
+ if (LockedUserState.get(this).isUserUnlocked()) {
mOverviewComponentObserver.getActivityInterface().onAssistantVisibilityChanged(
mDeviceState.getAssistantVisibility());
}
@@ -670,15 +663,13 @@ public class TouchInteractionService extends Service
}
Log.d(TAG, "Touch service destroyed: user=" + getUserId());
sIsInitialized = false;
- if (mDeviceState.isUserUnlocked()) {
+ if (LockedUserState.get(this).isUserUnlocked()) {
mInputConsumer.unregisterInputConsumer();
mOverviewComponentObserver.onDestroy();
}
disposeEventHandlers("TouchInteractionService onDestroy()");
mDeviceState.destroy();
SystemUiProxy.INSTANCE.get(this).clearProxy();
- ProtoTracer.INSTANCE.get(this).stop();
- ProtoTracer.INSTANCE.get(this).remove(this);
getSystemService(AccessibilityManager.class)
.unregisterSystemAction(GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS);
@@ -704,12 +695,12 @@ public class TouchInteractionService extends Service
TestLogging.recordMotionEvent(
TestProtocol.SEQUENCE_TIS, "TouchInteractionService.onInputEvent", event);
- if (!mDeviceState.isUserUnlocked()) {
+ if (!LockedUserState.get(this).isUserUnlocked() || (mDeviceState.isButtonNavMode()
+ && !isTrackpadMotionEvent(event))) {
return;
}
- Object traceToken = TraceHelper.INSTANCE.beginFlagsOverride(
- TraceHelper.FLAG_ALLOW_BINDER_TRACKING);
+ SafeCloseable traceToken = TraceHelper.INSTANCE.allowIpcs("TIS.onInputEvent");
final int action = event.getActionMasked();
// Note this will create a new consumer every mouse click, as after ACTION_UP
@@ -721,8 +712,7 @@ public class TouchInteractionService extends Service
mRotationTouchHelper.setOrientationTransformIfNeeded(event);
if ((!mDeviceState.isOneHandedModeActive()
- && mRotationTouchHelper.isInSwipeUpTouchRegion(event,
- mOverviewComponentObserver.getActivityInterface()))
+ && mRotationTouchHelper.isInSwipeUpTouchRegion(event))
|| isHoverActionWithoutConsumer) {
// Clone the previous gesture state since onConsumerAboutToBeSwitched might
// trigger
@@ -735,7 +725,8 @@ public class TouchInteractionService extends Service
mGestureState = newGestureState;
mConsumer = newConsumer(prevGestureState, mGestureState, event);
mUncheckedConsumer = mConsumer;
- } else if (mDeviceState.isUserUnlocked() && mDeviceState.isFullyGesturalNavMode()
+ } else if (LockedUserState.get(this).isUserUnlocked()
+ && (mDeviceState.isFullyGesturalNavMode() || isTrackpadMultiFingerSwipe(event))
&& mDeviceState.canTriggerAssistantAction(event)) {
mGestureState = createGestureState(mGestureState,
getTrackpadGestureType(event));
@@ -798,10 +789,7 @@ public class TouchInteractionService extends Service
if (mGestureState.isTrackpadGesture() && (action == ACTION_POINTER_DOWN
|| action == ACTION_POINTER_UP)) {
// Skip ACTION_POINTER_DOWN and ACTION_POINTER_UP events from trackpad.
- if (action == ACTION_POINTER_DOWN) {
- mGestureState.setTrackpadGestureType(getTrackpadGestureType(event));
- }
- } else if (event.isHoverEvent()) {
+ } else if (isCursorHoverEvent(event)) {
mUncheckedConsumer.onHoverEvent(event);
} else {
mUncheckedConsumer.onMotionEvent(event);
@@ -810,8 +798,12 @@ public class TouchInteractionService extends Service
if (cleanUpConsumer) {
reset();
}
- TraceHelper.INSTANCE.endFlagsOverride(traceToken);
- ProtoTracer.INSTANCE.get(this).scheduleFrameUpdate();
+ traceToken.close();
+ }
+
+ // Talkback generates hover events on touch, which we do not want to consume.
+ private boolean isCursorHoverEvent(MotionEvent event) {
+ return event.isHoverEvent() && event.getSource() == InputDevice.SOURCE_MOUSE;
}
private InputConsumer tryCreateAssistantInputConsumer(
@@ -845,7 +837,7 @@ public class TouchInteractionService extends Service
ActiveGestureLog.INSTANCE.getLogId());
taskInfo = previousGestureState.getRunningTask();
gestureState.updateRunningTask(taskInfo);
- gestureState.updateLastStartedTaskId(previousGestureState.getLastStartedTaskId());
+ gestureState.updateLastStartedTaskIds(previousGestureState.getLastStartedTaskIds());
gestureState.updatePreviouslyAppearedTaskIds(
previousGestureState.getPreviouslyAppearedTaskIds());
} else {
@@ -877,9 +869,10 @@ public class TouchInteractionService extends Service
return consumer;
}
- boolean canStartSystemGesture = mDeviceState.canStartSystemGesture();
+ boolean canStartSystemGesture = mGestureState.isTrackpadGesture() ? mDeviceState.canStartTrackpadGesture()
+ : mDeviceState.canStartSystemGesture();
- if (!mDeviceState.isUserUnlocked()) {
+ if (!LockedUserState.get(this).isUserUnlocked()) {
CompoundString reasonString = newCompoundString("device locked");
InputConsumer consumer;
if (canStartSystemGesture) {
@@ -915,11 +908,12 @@ public class TouchInteractionService extends Service
.append(", trying to use default input consumer");
base = getDefaultInputConsumer(reasonString);
}
- if (mDeviceState.isGesturalNavMode()) {
+ if (mDeviceState.isGesturalNavMode() || newGestureState.isTrackpadGesture()) {
handleOrientationSetup(base);
}
- if (mDeviceState.isFullyGesturalNavMode()) {
- String reasonPrefix = "device is in gesture navigation mode";
+ if (mDeviceState.isFullyGesturalNavMode() || newGestureState.isTrackpadGesture()) {
+ String reasonPrefix = "device is in gesture navigation mode or 3-button mode with a"
+ + " trackpad gesture";
if (mDeviceState.canTriggerAssistantAction(event)) {
reasonString.append(NEWLINE_PREFIX)
.append(reasonPrefix)
@@ -943,8 +937,17 @@ public class TouchInteractionService extends Service
.append(SUBSTRING_PREFIX)
.append("TaskbarActivityContext != null, "
+ "using TaskbarUnstashInputConsumer");
- base = new TaskbarUnstashInputConsumer(this, base, mInputMonitorCompat, tac);
+ base = new TaskbarUnstashInputConsumer(this, base, mInputMonitorCompat, tac,
+ mOverviewCommandHelper);
}
+ } else if (canStartSystemGesture && FeatureFlags.ENABLE_LONG_PRESS_NAV_HANDLE.get()
+ && !previousGestureState.isRecentsAnimationRunning()) {
+ reasonString.append(NEWLINE_PREFIX)
+ .append(reasonPrefix)
+ .append(SUBSTRING_PREFIX)
+ .append("Long press nav handle enabled, "
+ + "using NavHandleLongPressInputConsumer");
+ base = new NavHandleLongPressInputConsumer(this, base, mInputMonitorCompat);
}
if (mDeviceState.isBubblesExpanded()) {
@@ -964,11 +967,12 @@ public class TouchInteractionService extends Service
}
if (ENABLE_TRACKPAD_GESTURE.get() && mGestureState.isTrackpadGesture()
- && !previousGestureState.isRecentsAnimationRunning()) {
+ && canStartSystemGesture && !previousGestureState.isRecentsAnimationRunning()) {
reasonString = newCompoundString(reasonPrefix)
.append(SUBSTRING_PREFIX)
- .append("Trackpad 3-finger gesture, using StatusBarInputConsumer");
- base = new StatusBarInputConsumer(getBaseContext(), base, mInputMonitorCompat);
+ .append("Trackpad 3-finger gesture, using TrackpadStatusBarInputConsumer");
+ base = new TrackpadStatusBarInputConsumer(getBaseContext(), base,
+ mInputMonitorCompat);
}
if (mDeviceState.isScreenPinningActive()) {
@@ -1057,18 +1061,25 @@ public class TouchInteractionService extends Service
}
reasonString.append(SUBSTRING_PREFIX).append("keyguard is not showing occluded");
+
// Use overview input consumer for sharesheets on top of home.
boolean forceOverviewInputConsumer = gestureState.getActivityInterface().isStarted()
&& gestureState.getRunningTask() != null
&& gestureState.getRunningTask().isRootChooseActivity();
- if (gestureState.getRunningTask() != null
- && gestureState.getRunningTask().isExcludedAssistant()) {
- // In the case where we are in the excluded assistant state, ignore it and treat
- // the
- // running activity as the task behind the assistant
- gestureState.updateRunningTask(TopTaskTracker.INSTANCE.get(this)
- .getCachedTopTask(true /* filterOnlyVisibleRecents */));
- forceOverviewInputConsumer = gestureState.getRunningTask().isHomeTask();
+
+ // In the case where we are in an excluded, translucent overlay, ignore it and
+ // treat the
+ // running activity as the task behind the overlay.
+ TopTaskTracker.CachedTaskInfo otherVisibleTask = gestureState.getRunningTask() == null
+ ? null
+ : gestureState.getRunningTask().otherVisibleTaskThisIsExcludedOver();
+ if (otherVisibleTask != null) {
+ ActiveGestureLog.INSTANCE.addLog(new CompoundString("Changing active task to ")
+ .append(otherVisibleTask.getPackageName())
+ .append(" because the previous task running on top of this one (")
+ .append(gestureState.getRunningTask().getPackageName())
+ .append(") was excluded from recents"));
+ gestureState.updateRunningTask(otherVisibleTask);
}
boolean previousGestureAnimatedToLauncher = previousGestureState.isRunningAnimationToLauncher();
@@ -1132,18 +1143,21 @@ public class TouchInteractionService extends Service
private InputConsumer createDeviceLockedInputConsumer(
GestureState gestureState, CompoundString reasonString) {
- if (mDeviceState.isFullyGesturalNavMode() && gestureState.getRunningTask() != null) {
+ if ((mDeviceState.isFullyGesturalNavMode() || gestureState.isTrackpadGesture())
+ && gestureState.getRunningTask() != null) {
reasonString.append(SUBSTRING_PREFIX)
- .append("device is in gesture nav mode and running task != null")
+ .append("device is in gesture nav mode or 3-button mode with a trackpad gesture"
+ + "and running task != null")
.append(", using DeviceLockedInputConsumer");
return new DeviceLockedInputConsumer(
this, mDeviceState, mTaskAnimationManager, gestureState, mInputMonitorCompat);
} else {
return getDefaultInputConsumer(reasonString
.append(SUBSTRING_PREFIX)
- .append(mDeviceState.isFullyGesturalNavMode()
- ? "running task == null"
- : "device is not in gesture nav mode")
+ .append((mDeviceState.isFullyGesturalNavMode()
+ || gestureState.isTrackpadGesture())
+ ? "running task == null"
+ : "device is not in gesture nav mode and it's not a trackpad gesture")
.append(", trying to use default input consumer"));
}
}
@@ -1236,11 +1250,13 @@ public class TouchInteractionService extends Service
}
private void preloadOverview(boolean fromInit) {
+ Trace.beginSection("preloadOverview(fromInit=" + fromInit + ")");
preloadOverview(fromInit, false);
+ Trace.endSection();
}
private void preloadOverview(boolean fromInit, boolean forSUWAllSet) {
- if (!mDeviceState.isUserUnlocked()) {
+ if (!LockedUserState.get(this).isUserUnlocked()) {
return;
}
@@ -1277,7 +1293,7 @@ public class TouchInteractionService extends Service
@Override
public void onConfigurationChanged(Configuration newConfig) {
- if (!mDeviceState.isUserUnlocked()) {
+ if (!LockedUserState.get(this).isUserUnlocked()) {
return;
}
final BaseActivityInterface activityInterface = mOverviewComponentObserver.getActivityInterface();
@@ -1286,7 +1302,10 @@ public class TouchInteractionService extends Service
// We only care about the existing background activity.
return;
}
- if (mOverviewComponentObserver.canHandleConfigChanges(activity.getComponentName(),
+ Configuration oldConfig = activity.getResources().getConfiguration();
+ boolean isFoldUnfold = isTablet(oldConfig) != isTablet(newConfig);
+ if (!isFoldUnfold && mOverviewComponentObserver.canHandleConfigChanges(
+ activity.getComponentName(),
activity.getResources().getConfiguration().diff(newConfig))) {
// Since navBar gestural height are different between portrait and landscape,
// can handle orientation changes and refresh navigation gestural region through
@@ -1301,82 +1320,47 @@ public class TouchInteractionService extends Service
preloadOverview(false /* fromInit */);
}
+ private static boolean isTablet(Configuration config) {
+ return config.smallestScreenWidthDp >= MIN_TABLET_WIDTH;
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] rawArgs) {
- if (rawArgs.length > 0 && Utilities.IS_DEBUG_DEVICE) {
- LinkedList args = new LinkedList(Arrays.asList(rawArgs));
- switch (args.pollFirst()) {
- case "cmd":
- if (args.peekFirst() == null) {
- printAvailableCommands(pw);
- } else {
- onCommand(pw, args);
- }
- break;
- }
- } else {
- // Dump everything
- FlagsFactory.dump(pw);
- if (mDeviceState.isUserUnlocked()) {
- PluginManagerWrapper.INSTANCE.get(getBaseContext()).dump(pw);
- }
- mDeviceState.dump(pw);
- if (mOverviewComponentObserver != null) {
- mOverviewComponentObserver.dump(pw);
- }
- if (mOverviewCommandHelper != null) {
- mOverviewCommandHelper.dump(pw);
- }
- if (mGestureState != null) {
- mGestureState.dump(pw);
- }
- pw.println("Input state:");
- pw.println(" mInputMonitorCompat=" + mInputMonitorCompat);
- pw.println(" mInputEventReceiver=" + mInputEventReceiver);
- DisplayController.INSTANCE.get(this).dump(pw);
- pw.println("TouchState:");
- BaseDraggingActivity createdOverviewActivity = mOverviewComponentObserver == null ? null
- : mOverviewComponentObserver.getActivityInterface().getCreatedActivity();
- boolean resumed = mOverviewComponentObserver != null
- && mOverviewComponentObserver.getActivityInterface().isResumed();
- pw.println(" createdOverviewActivity=" + createdOverviewActivity);
- pw.println(" resumed=" + resumed);
- pw.println(" mConsumer=" + mConsumer.getName());
- ActiveGestureLog.INSTANCE.dump("", pw);
- RecentsModel.INSTANCE.get(this).dump("", pw);
- pw.println("ProtoTrace:");
- pw.println(" file=" + ProtoTracer.INSTANCE.get(this).getTraceFile());
- if (createdOverviewActivity != null) {
- createdOverviewActivity.getDeviceProfile().dump(this, "", pw);
- }
- mTaskbarManager.dumpLogs("", pw);
+ // Dump everything
+ FlagsFactory.dump(pw);
+ if (LockedUserState.get(this).isUserUnlocked()) {
+ PluginManagerWrapper.INSTANCE.get(getBaseContext()).dump(pw);
}
- }
-
- private void printAvailableCommands(PrintWriter pw) {
- pw.println("Available commands:");
- pw.println(" clear-touch-log: Clears the touch interaction log");
- pw.println(" print-gesture-log: only prints the ActiveGestureLog dump");
- }
-
- private void onCommand(PrintWriter pw, LinkedList args) {
- String cmd = args.pollFirst();
- if (cmd == null) {
- pw.println("Command missing");
- printAvailableCommands(pw);
- return;
+ mDeviceState.dump(pw);
+ if (mOverviewComponentObserver != null) {
+ mOverviewComponentObserver.dump(pw);
}
- switch (cmd) {
- case "clear-touch-log":
- ActiveGestureLog.INSTANCE.clear();
- break;
- case "print-gesture-log":
- ActiveGestureLog.INSTANCE.dump("", pw);
- break;
- default:
- pw.println("Command does not exist: " + cmd);
- printAvailableCommands(pw);
+ if (mOverviewCommandHelper != null) {
+ mOverviewCommandHelper.dump(pw);
}
+ if (mGestureState != null) {
+ mGestureState.dump(pw);
+ }
+ pw.println("Input state:");
+ pw.println(" mInputMonitorCompat=" + mInputMonitorCompat);
+ pw.println(" mInputEventReceiver=" + mInputEventReceiver);
+ DisplayController.INSTANCE.get(this).dump(pw);
+ pw.println("TouchState:");
+ BaseDraggingActivity createdOverviewActivity = mOverviewComponentObserver == null ? null
+ : mOverviewComponentObserver.getActivityInterface().getCreatedActivity();
+ boolean resumed = mOverviewComponentObserver != null
+ && mOverviewComponentObserver.getActivityInterface().isResumed();
+ pw.println(" createdOverviewActivity=" + createdOverviewActivity);
+ pw.println(" resumed=" + resumed);
+ pw.println(" mConsumer=" + mConsumer.getName());
+ ActiveGestureLog.INSTANCE.dump("", pw);
+ RecentsModel.INSTANCE.get(this).dump("", pw);
+ if (createdOverviewActivity != null) {
+ createdOverviewActivity.getDeviceProfile().dump(this, "", pw);
+ }
+ mTaskbarManager.dumpLogs("", pw);
+ pw.println("AssistStateManager:");
+ AssistStateManager.INSTANCE.get(this).dump(" ", pw);
}
private AbsSwipeUpHandler createLauncherSwipeHandler(
@@ -1392,17 +1376,4 @@ public class TouchInteractionService extends Service
gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
mInputConsumer);
}
-
- @Override
- public void writeToProto(LauncherTraceProto.Builder proto) {
- TouchInteractionServiceProto.Builder serviceProto = TouchInteractionServiceProto.newBuilder();
- serviceProto.setServiceConnected(true);
-
- if (mOverviewComponentObserver != null) {
- mOverviewComponentObserver.writeToProto(serviceProto);
- }
- mConsumer.writeToProto(serviceProto);
-
- proto.setTouchInteractionService(serviceProto);
- }
}
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 11b1ab8ec9..8a9e04e488 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -15,9 +15,9 @@
*/
package com.android.quickstep.fallback;
-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.app.animation.Interpolators.FINAL_FRAME;
+import static com.android.app.animation.Interpolators.INSTANT;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 074aedd3c7..a8d6724782 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -35,6 +35,7 @@ import androidx.annotation.Nullable;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.util.SplitConfigurationOptions;
@@ -79,27 +80,36 @@ public class FallbackRecentsView extends RecentsView 0 ? homeTask[0] : null;
onGestureAnimationStart(homeTask, rotationTouchHelper);
}
/**
- * When the gesture ends and we're going to recents view, we also remove the temporary
- * invisible tile added for the home task. This also pushes the remaining tiles back
+ * When the gesture ends and we're going to recents view, we also remove the
+ * temporary
+ * invisible tile added for the home task. This also pushes the remaining tiles
+ * back
* to the center.
*/
@Override
@@ -112,7 +122,7 @@ public class FallbackRecentsView extends RecentsView setCurrentTask(-1));
AnimatorPlaybackController controller = pa.createPlaybackController();
controller.dispatchOnStart();
@@ -157,8 +167,10 @@ public class FallbackRecentsView extends RecentsView taskGroups) {
- // When quick-switching on 3p-launcher, we add a "stub" tile corresponding to Launcher
- // as well. This tile is never shown as we have setCurrentTaskHidden, but allows use to
- // track the index of the next task appropriately, as if we are switching on any other app.
- // TODO(b/195607777) Confirm home task info is front-most task and not mixed in with others
+ // When quick-switching on 3p-launcher, we add a "stub" tile corresponding to
+ // Launcher
+ // as well. This tile is never shown as we have setCurrentTaskHidden, but allows
+ // use to
+ // track the index of the next task appropriately, as if we are switching on any
+ // other app.
+ // TODO(b/195607777) Confirm home task info is front-most task and not mixed in
+ // with others
int runningTaskId = getTaskIdsForRunningTaskView()[0];
if (mHomeTask != null && mHomeTask.key.id == runningTaskId
&& !taskGroups.isEmpty()) {
@@ -246,12 +262,16 @@ public class FallbackRecentsView extends RecentsView
- remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true));
+ runActionOnRemoteHandles(
+ remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true));
}
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
index 6a36d9fd64..ec6efcb440 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
@@ -103,8 +103,7 @@ public class AccessibilityInputConsumer extends DelegateInputConsumer {
if (mState == STATE_INACTIVE) {
int pointerIndex = ev.getActionIndex();
if (mDeviceState.getRotationTouchHelper().isInSwipeUpTouchRegion(ev,
- pointerIndex, mGestureState.getActivityInterface())
- && mDelegate.allowInterceptByParent()) {
+ pointerIndex) && mDelegate.allowInterceptByParent()) {
setActive(ev);
mActivePointerId = ev.getPointerId(pointerIndex);
@@ -153,4 +152,9 @@ public class AccessibilityInputConsumer extends DelegateInputConsumer {
mDelegate.onMotionEvent(ev);
}
}
+
+ @Override
+ protected String getDelegatorName() {
+ return "AccessibilityInputConsumer";
+ }
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
index 162ace4965..ba012c9b64 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
@@ -42,9 +42,9 @@ import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
-import com.android.launcher3.anim.Interpolators;
import com.android.quickstep.BaseActivityInterface;
import com.android.quickstep.GestureState;
import com.android.quickstep.InputConsumer;
@@ -209,7 +209,7 @@ public class AssistantInputConsumer extends DelegateInputConsumer {
SystemUiProxy.INSTANCE.get(mContext).onAssistantProgress(0f);
}
});
- animator.setInterpolator(Interpolators.DEACCEL_2);
+ animator.setInterpolator(Interpolators.DECELERATE_2);
animator.start();
}
mPassedSlop = false;
@@ -278,4 +278,9 @@ public class AssistantInputConsumer extends DelegateInputConsumer {
return true;
}
}
+
+ @Override
+ protected String getDelegatorName() {
+ return "AssistantInputConsumer";
+ }
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
index 03f8eef077..63771f0d56 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
@@ -4,8 +4,8 @@ import android.view.MotionEvent;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.launcher3.tracing.InputConsumerProto;
import com.android.quickstep.InputConsumer;
+import com.android.quickstep.util.ActiveGestureLog;
import com.android.systemui.shared.system.InputMonitorCompat;
public abstract class DelegateInputConsumer implements InputConsumer {
@@ -43,7 +43,15 @@ public abstract class DelegateInputConsumer implements InputConsumer {
mDelegate.onConsumerAboutToBeSwitched();
}
+ /**
+ * Returns the name of this DelegateInputConsumer.
+ */
+ protected abstract String getDelegatorName();
+
protected void setActive(MotionEvent ev) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(getDelegatorName())
+ .append(" became active"));
+
mState = STATE_ACTIVE;
TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
mInputMonitor.pilferPointers();
@@ -54,9 +62,4 @@ public abstract class DelegateInputConsumer implements InputConsumer {
mDelegate.onMotionEvent(event);
event.recycle();
}
-
- @Override
- public void writeToProtoInternal(InputConsumerProto.Builder inputConsumerProto) {
- mDelegate.writeToProtoInternal(inputConsumerProto);
- }
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index 59a9582c1f..2a355848b5 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -39,9 +39,9 @@ import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
import android.view.VelocityTracker;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatedFloat;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.DisplayController;
@@ -153,8 +153,7 @@ public class DeviceLockedInputConsumer implements InputConsumer,
if (!mThresholdCrossed) {
// Cancel interaction in case of multi-touch interaction
int ptrIdx = ev.getActionIndex();
- if (!mDeviceState.getRotationTouchHelper().isInSwipeUpTouchRegion(ev, ptrIdx,
- mGestureState.getActivityInterface())) {
+ if (!mDeviceState.getRotationTouchHelper().isInSwipeUpTouchRegion(ev, ptrIdx)) {
int action = ev.getAction();
ev.setAction(ACTION_CANCEL);
finishTouchTracking(ev);
@@ -204,7 +203,7 @@ public class DeviceLockedInputConsumer implements InputConsumer,
// Animate back to fullscreen before finishing
ObjectAnimator animator = mProgress.animateToValue(mProgress.value, 0);
animator.setDuration(100);
- animator.setInterpolator(Interpolators.ACCEL);
+ animator.setInterpolator(Interpolators.ACCELERATE);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java
new file mode 100644
index 0000000000..7a2b3430ee
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java
@@ -0,0 +1,49 @@
+/*
+ * 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.quickstep.inputconsumers;
+
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.ResourceBasedOverride;
+
+/**
+ * Class for extending nav handle long press behavior
+ */
+public class NavHandleLongPressHandler implements ResourceBasedOverride {
+
+ /** Creates NavHandleLongPressHandler as specified by overrides */
+ public static NavHandleLongPressHandler newInstance(Context context) {
+ return Overrides.getObject(NavHandleLongPressHandler.class, context,
+ R.string.nav_handle_long_press_handler_class);
+ }
+
+ /**
+ * Called when nav handle is long pressed to get the Runnable that should be executed by the
+ * caller to invoke long press behavior. If null is returned that means long press couldn't be
+ * handled.
+ *
+ * A Runnable is returned here to ensure the InputConsumer can call
+ * {@link android.view.InputMonitor#pilferPointers()} before invoking the long press behavior
+ * since pilfering can break the long press behavior.
+ */
+ public @Nullable Runnable getLongPressRunnable() {
+ return null;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
new file mode 100644
index 0000000000..f824210bd8
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -0,0 +1,88 @@
+/*
+ * 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.quickstep.inputconsumers;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.content.Context;
+import android.view.GestureDetector;
+import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.MotionEvent;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.DisplayController;
+import com.android.quickstep.InputConsumer;
+import com.android.systemui.shared.system.InputMonitorCompat;
+
+/**
+ * Listens for a long press
+ */
+public class NavHandleLongPressInputConsumer extends DelegateInputConsumer {
+
+ private final GestureDetector mLongPressDetector;
+ private final NavHandleLongPressHandler mNavHandleLongPressHandler;
+ private final float mNavHandleWidth;
+ private final float mScreenWidth;
+
+ public NavHandleLongPressInputConsumer(Context context, InputConsumer delegate,
+ InputMonitorCompat inputMonitor) {
+ super(delegate, inputMonitor);
+ mNavHandleWidth = context.getResources().getDimensionPixelSize(
+ R.dimen.navigation_home_handle_width);
+ mScreenWidth = DisplayController.INSTANCE.get(context).getInfo().currentSize.x;
+
+ mNavHandleLongPressHandler = NavHandleLongPressHandler.newInstance(context);
+
+ mLongPressDetector = new GestureDetector(context, new SimpleOnGestureListener() {
+ @Override
+ public void onLongPress(MotionEvent motionEvent) {
+ if (isInArea(motionEvent.getRawX())) {
+ Runnable longPressRunnable = mNavHandleLongPressHandler.getLongPressRunnable();
+ if (longPressRunnable != null) {
+ setActive(motionEvent);
+
+ MAIN_EXECUTOR.getHandler().postDelayed(longPressRunnable, 50);
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ public int getType() {
+ return TYPE_NAV_HANDLE_LONG_PRESS | mDelegate.getType();
+ }
+
+ @Override
+ public void onMotionEvent(MotionEvent ev) {
+ mLongPressDetector.onTouchEvent(ev);
+ if (mState != STATE_ACTIVE) {
+ mDelegate.onMotionEvent(ev);
+ }
+ }
+
+ protected boolean isInArea(float x) {
+ float areaFromMiddle = mNavHandleWidth / 2.0f;
+ float distFromMiddle = Math.abs(mScreenWidth / 2.0f - x);
+
+ return distFromMiddle < areaFromMiddle;
+ }
+
+ @Override
+ protected String getDelegatorName() {
+ return "NavHandleLongPressInputConsumer";
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
index da58567768..44e1365b86 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
@@ -175,4 +175,9 @@ public class OneHandedModeInputConsumer extends DelegateInputConsumer {
final float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
return angle > ANGLE_MIN && angle < ANGLE_MAX;
}
+
+ @Override
+ protected String getDelegatorName() {
+ return "OneHandedModeInputConsumer";
+ }
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index f9cd4ee1cd..7e6116720b 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -28,7 +28,6 @@ import static com.android.launcher3.PagedView.DEBUG_FAILED_QUICKSWITCH;
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS;
import static com.android.launcher3.util.VelocityUtils.PX_PER_MS;
import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
@@ -49,7 +48,6 @@ import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.launcher3.tracing.InputConsumerProto;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.AbsSwipeUpHandler;
@@ -229,8 +227,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
// Until we detect the gesture, handle events as we receive them
mInputEventReceiver.setBatchingEnabled(false);
- Object traceToken = TraceHelper.INSTANCE.beginSection(DOWN_EVT,
- FLAG_CHECK_FOR_RACE_CONDITIONS);
+ TraceHelper.INSTANCE.beginSection(DOWN_EVT);
mActivePointerId = ev.getPointerId(0);
mDownPos.set(ev.getX(), ev.getY());
mLastPos.set(mDownPos);
@@ -241,15 +238,14 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
startTouchTrackingForWindowAnimation(ev.getEventTime());
}
- TraceHelper.INSTANCE.endSection(traceToken);
+ TraceHelper.INSTANCE.endSection();
break;
}
case ACTION_POINTER_DOWN: {
if (!mPassedPilferInputSlop) {
// Cancel interaction in case of multi-touch interaction
int ptrIdx = ev.getActionIndex();
- if (!mRotationTouchHelper.isInSwipeUpTouchRegion(ev, ptrIdx,
- mActivityInterface)) {
+ if (!mRotationTouchHelper.isInSwipeUpTouchRegion(ev, ptrIdx)) {
forceCancelGesture(ev);
}
}
@@ -282,7 +278,8 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
if (!mIsDeferredDownTarget) {
// Normal gesture, ensure we pass the drag slop before we start tracking
// the gesture
- if (Math.abs(displacement) > mTouchSlop) {
+ if (mGestureState.isTrackpadGesture() || Math.abs(displacement)
+ > mTouchSlop) {
mPassedWindowMoveSlop = true;
mStartDisplacement = Math.min(displacement, -mTouchSlop);
}
@@ -291,8 +288,8 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
float horizontalDist = Math.abs(displacementX);
float upDist = -displacement;
- boolean passedSlop = squaredHypot(displacementX, displacementY)
- >= mSquaredTouchSlop;
+ boolean passedSlop = mGestureState.isTrackpadGesture() || squaredHypot(
+ displacementX, displacementY) >= mSquaredTouchSlop;
if (!mPassedSlopOnThisGesture && passedSlop) {
mPassedSlopOnThisGesture = true;
@@ -352,7 +349,8 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
mInteractionHandler.updateDisplacement(displacement - mStartDisplacement);
}
- if (mDeviceState.isFullyGesturalNavMode()) {
+ if (mDeviceState.isFullyGesturalNavMode()
+ || mGestureState.isTrackpadGesture()) {
boolean minSwipeMet = upDist >= Math.max(mMotionPauseMinDisplacement,
mInteractionHandler.getThresholdToAllowMotionPause());
mInteractionHandler.setCanSlowSwipeGoHome(minSwipeMet);
@@ -417,11 +415,11 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
* the animation can still be running.
*/
private void finishTouchTracking(MotionEvent ev) {
- Object traceToken = TraceHelper.INSTANCE.beginSection(UP_EVT,
- FLAG_CHECK_FOR_RACE_CONDITIONS);
+ TraceHelper.INSTANCE.beginSection(UP_EVT);
+ boolean isCanceled = ev.getActionMasked() == ACTION_CANCEL;
if (mPassedWindowMoveSlop && mInteractionHandler != null) {
- if (ev.getActionMasked() == ACTION_CANCEL) {
+ if (isCanceled) {
mInteractionHandler.onGestureCancelled();
} else {
mVelocityTracker.computeCurrentVelocity(PX_PER_MS);
@@ -443,8 +441,10 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
if (mActiveCallbacks != null && mInteractionHandler != null) {
if (mTaskAnimationManager.isRecentsAnimationRunning()) {
// The animation started, but with no movement, in this case, there will be no
- // animateToProgress so we have to manually finish here.
- mTaskAnimationManager.finishRunningRecentsAnimation(false /* toHome */);
+ // animateToProgress so we have to manually finish here. In the case of
+ // ACTION_CANCEL, someone else may be doing something so finish synchronously.
+ mTaskAnimationManager.finishRunningRecentsAnimation(false /* toHome */,
+ isCanceled /* forceFinish */);
} else {
// The animation hasn't started yet, so insert a replacement handler into the
// callbacks which immediately finishes the animation after it starts.
@@ -455,7 +455,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
onInteractionGestureFinished();
}
cleanupAfterGesture();
- TraceHelper.INSTANCE.endSection(traceToken);
+ TraceHelper.INSTANCE.endSection();
}
private void cleanupAfterGesture() {
@@ -512,13 +512,6 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
return !mPassedPilferInputSlop;
}
- @Override
- public void writeToProtoInternal(InputConsumerProto.Builder inputConsumerProto) {
- if (mInteractionHandler != null) {
- mInteractionHandler.writeToProto(inputConsumerProto);
- }
- }
-
/**
* A listener which just finishes the animation immediately after starting. Replaces
* AbsSwipeUpHandler if the gesture itself finishes before the animation even starts.
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
index eac09ad7dc..c9c64b669c 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
@@ -15,7 +15,7 @@
*/
package com.android.quickstep.inputconsumers;
-import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.app.animation.Interpolators.scrollInterpolatorForVelocity;
import static com.android.launcher3.touch.BaseSwipeDetector.calculateDuration;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_POSITIVE;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
index fbe7fde236..0be933793d 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
@@ -28,6 +28,7 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.InputDevice;
import android.view.MotionEvent;
import androidx.annotation.Nullable;
@@ -36,20 +37,26 @@ import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.taskbar.TaskbarActivityContext;
+import com.android.launcher3.taskbar.TaskbarThresholdUtils;
import com.android.launcher3.taskbar.TaskbarTranslationController.TransitionCallback;
+import com.android.launcher3.taskbar.bubbles.BubbleControllers;
import com.android.launcher3.touch.OverScroll;
import com.android.launcher3.util.DisplayController;
import com.android.quickstep.InputConsumer;
+import com.android.quickstep.OverviewCommandHelper;
import com.android.systemui.shared.system.InputMonitorCompat;
/**
* Listens for touch and hover events to unstash the Taskbar.
*
- *
Cancels the current gesture if the long press causes the Taskbar to be unstashed.
+ *
+ * Cancels the current gesture if the long press causes the Taskbar to be
+ * unstashed.
*/
public class TaskbarUnstashInputConsumer extends DelegateInputConsumer {
private final TaskbarActivityContext mTaskbarActivityContext;
+ private final OverviewCommandHelper mOverviewCommandHelper;
private final GestureDetector mLongPressDetector;
private final float mSquaredTouchSlop;
@@ -62,6 +69,7 @@ public class TaskbarUnstashInputConsumer extends DelegateInputConsumer {
private final int mTaskbarNavThresholdY;
private final boolean mIsTaskbarAllAppsOpen;
private boolean mHasPassedTaskbarNavThreshold;
+ private boolean mIsInBubbleBarArea;
private final PointF mDownPos = new PointF();
private final PointF mLastPos = new PointF();
@@ -78,20 +86,22 @@ public class TaskbarUnstashInputConsumer extends DelegateInputConsumer {
private final @Nullable TransitionCallback mTransitionCallback;
public TaskbarUnstashInputConsumer(Context context, InputConsumer delegate,
- InputMonitorCompat inputMonitor, TaskbarActivityContext taskbarActivityContext) {
+ InputMonitorCompat inputMonitor, TaskbarActivityContext taskbarActivityContext,
+ OverviewCommandHelper overviewCommandHelper) {
super(delegate, inputMonitor);
mTaskbarActivityContext = taskbarActivityContext;
+ mOverviewCommandHelper = overviewCommandHelper;
// TODO(b/270395798): remove this when cleaning up old Persistent Taskbar code.
mSquaredTouchSlop = Utilities.squaredTouchSlop(context);
mScreenWidth = taskbarActivityContext.getDeviceProfile().widthPx;
Resources res = context.getResources();
mUnstashArea = res.getDimensionPixelSize(R.dimen.taskbar_unstash_input_area);
- mTaskbarNavThreshold = res.getDimensionPixelSize(R.dimen.taskbar_from_nav_threshold);
+ mTaskbarNavThreshold = TaskbarThresholdUtils.getFromNavThreshold(res,
+ taskbarActivityContext.getDeviceProfile());
mTaskbarNavThresholdY = taskbarActivityContext.getDeviceProfile().heightPx
- mTaskbarNavThreshold;
- mIsTaskbarAllAppsOpen =
- mTaskbarActivityContext != null && mTaskbarActivityContext.isTaskbarAllAppsOpen();
+ mIsTaskbarAllAppsOpen = mTaskbarActivityContext != null && mTaskbarActivityContext.isTaskbarAllAppsOpen();
mIsTransientTaskbar = DisplayController.isTransientTaskbar(context);
@@ -104,8 +114,7 @@ public class TaskbarUnstashInputConsumer extends DelegateInputConsumer {
mBottomScreenEdge = res.getDimensionPixelSize(
R.dimen.taskbar_stashed_screen_edge_hover_deadzone_height);
- mStashedTaskbarBottomEdge =
- res.getDimensionPixelSize(R.dimen.taskbar_stashed_below_hover_deadzone_height);
+ mStashedTaskbarBottomEdge = res.getDimensionPixelSize(R.dimen.taskbar_stashed_below_hover_deadzone_height);
mTransitionCallback = mIsTransientTaskbar
? taskbarActivityContext.getTranslationCallbacks()
@@ -121,7 +130,11 @@ public class TaskbarUnstashInputConsumer extends DelegateInputConsumer {
public void onMotionEvent(MotionEvent ev) {
mLongPressDetector.onTouchEvent(ev);
if (mState != STATE_ACTIVE) {
- mDelegate.onMotionEvent(ev);
+ boolean isStashedTaskbarHovered = isMouseEvent(ev)
+ && isStashedTaskbarHovered((int) ev.getX(), (int) ev.getY());
+ if (!isStashedTaskbarHovered) {
+ mDelegate.onMotionEvent(ev);
+ }
// Only show the transient task bar if the touch events are on the screen.
if (mTaskbarActivityContext != null && !isTrackpadMotionEvent(ev)) {
@@ -136,7 +149,7 @@ public class TaskbarUnstashInputConsumer extends DelegateInputConsumer {
mHasPassedTaskbarNavThreshold = false;
mTaskbarActivityContext.setAutohideSuspendFlag(
FLAG_AUTOHIDE_SUSPEND_TOUCHING, true);
- if (isInArea(x)) {
+ if (isInTaskbarArea(x)) {
if (!mIsTransientTaskbar) {
mLongPressDownX = x;
mLongPressDownY = y;
@@ -145,10 +158,12 @@ public class TaskbarUnstashInputConsumer extends DelegateInputConsumer {
mCanceledUnstashHint = false;
}
}
-
if (mTransitionCallback != null && !mIsTaskbarAllAppsOpen) {
mTransitionCallback.onActionDown();
}
+ if (mIsTransientTaskbar && isInBubbleBarArea(x)) {
+ mIsInBubbleBarArea = true;
+ }
break;
case MotionEvent.ACTION_POINTER_UP:
int ptrIdx = ev.getActionIndex();
@@ -165,8 +180,7 @@ public class TaskbarUnstashInputConsumer extends DelegateInputConsumer {
case MotionEvent.ACTION_MOVE:
if (!mIsTransientTaskbar
&& !mCanceledUnstashHint
- && squaredHypot(mLongPressDownX - x, mLongPressDownY - y)
- > mSquaredTouchSlop) {
+ && squaredHypot(mLongPressDownX - x, mLongPressDownY - y) > mSquaredTouchSlop) {
mTaskbarActivityContext.startTaskbarUnstashHint(
/* animateForward = */ false);
mCanceledUnstashHint = true;
@@ -185,7 +199,11 @@ public class TaskbarUnstashInputConsumer extends DelegateInputConsumer {
if (!mHasPassedTaskbarNavThreshold && passedTaskbarNavThreshold) {
mHasPassedTaskbarNavThreshold = true;
- mTaskbarActivityContext.onSwipeToUnstashTaskbar();
+ if (mIsInBubbleBarArea) {
+ mTaskbarActivityContext.onSwipeToOpenBubblebar();
+ } else {
+ mTaskbarActivityContext.onSwipeToUnstashTaskbar();
+ }
}
if (dY < 0) {
@@ -208,21 +226,38 @@ public class TaskbarUnstashInputConsumer extends DelegateInputConsumer {
mTransitionCallback.onActionEnd();
}
mHasPassedTaskbarNavThreshold = false;
+ mIsInBubbleBarArea = false;
+ break;
+ case MotionEvent.ACTION_BUTTON_RELEASE:
+ if (isStashedTaskbarHovered) {
+ mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_HOME);
+ }
break;
}
}
}
}
- private boolean isInArea(float x) {
+ private boolean isInTaskbarArea(float x) {
float areaFromMiddle = mUnstashArea / 2.0f;
float distFromMiddle = Math.abs(mScreenWidth / 2.0f - x);
return distFromMiddle < areaFromMiddle;
}
+ private boolean isInBubbleBarArea(float x) {
+ if (mTaskbarActivityContext != null && mIsTransientTaskbar) {
+ BubbleControllers controllers = mTaskbarActivityContext.getBubbleControllers();
+ if (controllers == null)
+ return false;
+ Rect bubbleBarBounds = controllers.bubbleBarViewController.getBubbleBarBounds();
+ return x >= bubbleBarBounds.left && x <= bubbleBarBounds.right;
+ }
+ return false;
+ }
+
private void onLongPressDetected(MotionEvent motionEvent) {
if (mTaskbarActivityContext != null
- && isInArea(motionEvent.getRawX())
+ && isInTaskbarArea(motionEvent.getRawX())
&& !mIsTransientTaskbar) {
boolean taskBarPressed = mTaskbarActivityContext.onLongPressToUnstashTaskbar();
if (taskBarPressed) {
@@ -234,9 +269,14 @@ public class TaskbarUnstashInputConsumer extends DelegateInputConsumer {
/**
* Listen for hover events for the stashed taskbar.
*
- *
When hovered over the stashed taskbar handle, show the unstash hint.
- *
When the cursor is touching the bottom edge below the stashed taskbar, unstash it.
- *
When the cursor is within a defined threshold of the screen's bottom edge outside of
+ *
+ * When hovered over the stashed taskbar handle, show the unstash hint.
+ *
+ * When the cursor is touching the bottom edge below the stashed taskbar,
+ * unstash it.
+ *
+ * When the cursor is within a defined threshold of the screen's bottom edge
+ * outside of
* the stashed taskbar, unstash it.
*/
@Override
@@ -255,19 +295,17 @@ public class TaskbarUnstashInputConsumer extends DelegateInputConsumer {
private void updateHoveredTaskbarState(int x, int y) {
DeviceProfile dp = mTaskbarActivityContext.getDeviceProfile();
- mStashedTaskbarHandleBounds.set(
+ mBottomEdgeBounds.set(
(dp.widthPx - (int) mUnstashArea) / 2,
- dp.heightPx - dp.stashedTaskbarHeight,
+ dp.heightPx - mStashedTaskbarBottomEdge,
(int) (((dp.widthPx - mUnstashArea) / 2) + mUnstashArea),
dp.heightPx);
- mBottomEdgeBounds.set(mStashedTaskbarHandleBounds);
- mBottomEdgeBounds.top = dp.heightPx - mStashedTaskbarBottomEdge;
if (mBottomEdgeBounds.contains(x, y)) {
// If hovering stashed taskbar and then hover screen bottom edge, unstash it.
mTaskbarActivityContext.onSwipeToUnstashTaskbar();
mIsStashedTaskbarHovered = false;
- } else if (!mStashedTaskbarHandleBounds.contains(x, y)) {
+ } else if (!isStashedTaskbarHovered(x, y)) {
// If exit hovering stashed taskbar, remove hint.
startStashedTaskbarHover(/* isHovered = */ false);
}
@@ -275,18 +313,13 @@ public class TaskbarUnstashInputConsumer extends DelegateInputConsumer {
private void updateUnhoveredTaskbarState(int x, int y) {
DeviceProfile dp = mTaskbarActivityContext.getDeviceProfile();
- mStashedTaskbarHandleBounds.set(
- (dp.widthPx - (int) mUnstashArea) / 2,
- dp.heightPx - dp.stashedTaskbarHeight,
- (int) (((dp.widthPx - mUnstashArea) / 2) + mUnstashArea),
- dp.heightPx);
mBottomEdgeBounds.set(
0,
dp.heightPx - mBottomScreenEdge,
dp.widthPx,
dp.heightPx);
- if (mStashedTaskbarHandleBounds.contains(x, y)) {
+ if (isStashedTaskbarHovered(x, y)) {
// If enter hovering stashed taskbar, start hint.
startStashedTaskbarHover(/* isHovered = */ true);
} else if (mBottomEdgeBounds.contains(x, y)) {
@@ -299,4 +332,28 @@ public class TaskbarUnstashInputConsumer extends DelegateInputConsumer {
mTaskbarActivityContext.startTaskbarUnstashHint(isHovered, /* forceUnstash = */ true);
mIsStashedTaskbarHovered = isHovered;
}
+
+ private boolean isStashedTaskbarHovered(int x, int y) {
+ if (!mTaskbarActivityContext.isTaskbarStashed()
+ || mTaskbarActivityContext.isTaskbarAllAppsOpen()
+ || !ENABLE_CURSOR_HOVER_STATES.get()) {
+ return false;
+ }
+ DeviceProfile dp = mTaskbarActivityContext.getDeviceProfile();
+ mStashedTaskbarHandleBounds.set(
+ (dp.widthPx - (int) mUnstashArea) / 2,
+ dp.heightPx - dp.stashedTaskbarHeight,
+ (int) (((dp.widthPx - mUnstashArea) / 2) + mUnstashArea),
+ dp.heightPx);
+ return mStashedTaskbarHandleBounds.contains(x, y);
+ }
+
+ private boolean isMouseEvent(MotionEvent event) {
+ return event.getSource() == InputDevice.SOURCE_MOUSE;
+ }
+
+ @Override
+ protected String getDelegatorName() {
+ return "TaskbarUnstashInputConsumer";
+ }
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/StatusBarInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TrackpadStatusBarInputConsumer.java
similarity index 88%
rename from quickstep/src/com/android/quickstep/inputconsumers/StatusBarInputConsumer.java
rename to quickstep/src/com/android/quickstep/inputconsumers/TrackpadStatusBarInputConsumer.java
index 898aa8640d..f3e21e1399 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/StatusBarInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TrackpadStatusBarInputConsumer.java
@@ -27,15 +27,15 @@ import com.android.quickstep.InputConsumer;
import com.android.quickstep.SystemUiProxy;
import com.android.systemui.shared.system.InputMonitorCompat;
-/** Allows the status bar to be pull down for notification shade */
-public class StatusBarInputConsumer extends DelegateInputConsumer {
+/** Allows the status bar to be pull down for notification shade using the trackpad. */
+public class TrackpadStatusBarInputConsumer extends DelegateInputConsumer {
private final SystemUiProxy mSystemUiProxy;
private final float mTouchSlop;
private final PointF mDown = new PointF();
private boolean mHasPassedTouchSlop;
- public StatusBarInputConsumer(Context context, InputConsumer delegate,
+ public TrackpadStatusBarInputConsumer(Context context, InputConsumer delegate,
InputMonitorCompat inputMonitor) {
super(delegate, inputMonitor);
@@ -79,7 +79,12 @@ public class StatusBarInputConsumer extends DelegateInputConsumer {
private void dispatchTouchEvent(MotionEvent ev) {
if (mSystemUiProxy.isActive()) {
- mSystemUiProxy.onStatusBarMotionEvent(ev);
+ mSystemUiProxy.onStatusBarTrackpadEvent(ev);
}
}
+
+ @Override
+ protected String getDelegatorName() {
+ return "TrackpadStatusBarInputConsumer";
+ }
}
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index 6619dd86f6..bc049304dd 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -15,10 +15,10 @@
*/
package com.android.quickstep.interaction;
+import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.Utilities.mapBoundToRange;
import static com.android.launcher3.Utilities.mapRange;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
import android.animation.Animator;
@@ -74,14 +74,14 @@ import java.net.URISyntaxException;
import java.util.Map;
/**
- * A page shows after SUW flow to hint users to swipe up from the bottom of the screen to go home
+ * A page shows after SUW flow to hint users to swipe up from the bottom of the
+ * screen to go home
* for the gestural system navigation.
*/
public class AllSetActivity extends Activity {
private static final String LOG_TAG = "AllSetActivity";
- private static final String URI_SYSTEM_NAVIGATION_SETTING =
- "#Intent;action=com.android.settings.SEARCH_RESULT_TRAMPOLINE;S.:settings:fragment_args_key=gesture_system_navigation_input_summary;S.:settings:show_fragment=com.android.settings.gestures.SystemNavigationGestureSettings;end";
+ private static final String URI_SYSTEM_NAVIGATION_SETTING = "#Intent;action=com.android.settings.SEARCH_RESULT_TRAMPOLINE;S.:settings:fragment_args_key=gesture_system_navigation_input_summary;S.:settings:show_fragment=com.android.settings.gestures.SystemNavigationGestureSettings;end";
private static final String EXTRA_ACCENT_COLOR_DARK_MODE = "suwColorAccentDark";
private static final String EXTRA_ACCENT_COLOR_LIGHT_MODE = "suwColorAccentLight";
private static final String EXTRA_DEVICE_NAME = "suwDeviceName";
@@ -95,16 +95,16 @@ public class AllSetActivity extends Activity {
private static final float ANIMATION_PAUSE_ALPHA_THRESHOLD = 0.1f;
- private TISBindHelper mTISBindHelper;
- private TISBinder mBinder;
- @Nullable private TaskbarManager mTaskbarManager = null;
-
private final AnimatedFloat mSwipeProgress = new AnimatedFloat(this::onSwipeProgressUpdate);
+
+ private TISBindHelper mTISBindHelper;
+
private BgDrawable mBackground;
private View mRootView;
private float mSwipeUpShift;
- @Nullable private Vibrator mVibrator;
+ @Nullable
+ private Vibrator mVibrator;
private LottieAnimationView mAnimatedBackground;
private Animator.AnimatorListener mBackgroundAnimatorListener;
@@ -138,7 +138,8 @@ public class AllSetActivity extends Activity {
subtitle.setText(getString(
R.string.allset_description_generic,
!TextUtils.isEmpty(suwDeviceName)
- ? suwDeviceName : getString(R.string.default_device_name)));
+ ? suwDeviceName
+ : getString(R.string.default_device_name)));
TextView settings = findViewById(R.id.navigation_settings);
settings.setTextColor(accentColor);
@@ -162,8 +163,10 @@ public class AllSetActivity extends Activity {
mVibrator = getSystemService(Vibrator.class);
mAnimatedBackground = findViewById(R.id.animated_background);
- // There's a bug in the currently used external Lottie library (v5.2.0), and it doesn't load
- // the correct animation from the raw resources when configuration changes, so we need to
+ // There's a bug in the currently used external Lottie library (v5.2.0), and it
+ // doesn't load
+ // the correct animation from the raw resources when configuration changes, so
+ // we need to
// manually load the resource and pass it to Lottie.
mAnimatedBackground.setAnimation(resources.openRawResource(R.raw.all_set_page_bg),
null);
@@ -174,7 +177,7 @@ public class AllSetActivity extends Activity {
LOTTIE_TERTIARY_COLOR_TOKEN, R.color.all_set_bg_tertiary),
getTheme());
- startBackgroundAnimation();
+ startBackgroundAnimation(dp.isTablet);
}
private void runOnUiHelperThread(Runnable runnable) {
@@ -185,7 +188,7 @@ public class AllSetActivity extends Activity {
Executors.UI_HELPER_EXECUTOR.execute(runnable);
}
- private void startBackgroundAnimation() {
+ private void startBackgroundAnimation(boolean forTablet) {
if (!Utilities.ATLEAST_S || mVibrator == null) {
return;
}
@@ -199,63 +202,64 @@ public class AllSetActivity extends Activity {
if (mBackgroundAnimatorListener == null) {
VibrationEffect vibrationEffect = VibrationEffect.startComposition()
.addPrimitive(supportsThud
- ? VibrationEffect.Composition.PRIMITIVE_THUD
- : VibrationEffect.Composition.PRIMITIVE_TICK,
- /* scale= */ 1.0f,
+ ? VibrationEffect.Composition.PRIMITIVE_THUD
+ : VibrationEffect.Composition.PRIMITIVE_TICK,
+ /* scale= */ forTablet ? 1.0f : 0.3f,
/* delay= */ 50)
.compose();
- mBackgroundAnimatorListener =
- new Animator.AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) {
- runOnUiHelperThread(() -> mVibrator.vibrate(vibrationEffect));
- }
+ mBackgroundAnimatorListener = new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ runOnUiHelperThread(() -> mVibrator.vibrate(vibrationEffect));
+ }
- @Override
- public void onAnimationRepeat(Animator animation) {
- runOnUiHelperThread(() -> mVibrator.vibrate(vibrationEffect));
- }
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ runOnUiHelperThread(() -> mVibrator.vibrate(vibrationEffect));
+ }
- @Override
- public void onAnimationEnd(Animator animation) {
- runOnUiHelperThread(mVibrator::cancel);
- }
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ runOnUiHelperThread(mVibrator::cancel);
+ }
- @Override
- public void onAnimationCancel(Animator animation) {
- runOnUiHelperThread(mVibrator::cancel);
- }
- };
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ runOnUiHelperThread(mVibrator::cancel);
+ }
+ };
}
mAnimatedBackground.addAnimatorListener(mBackgroundAnimatorListener);
mAnimatedBackground.playAnimation();
}
private void setSetupUIVisible(boolean visible) {
- if (mBinder == null || mTaskbarManager == null) return;
- mTaskbarManager.setSetupUIVisible(visible);
+ TaskbarManager taskbarManager = mTISBindHelper.getTaskbarManager();
+ if (taskbarManager == null)
+ return;
+ taskbarManager.setSetupUIVisible(visible);
}
@Override
protected void onResume() {
super.onResume();
maybeResumeOrPauseBackgroundAnimation();
- if (mBinder != null) {
+ TISBinder binder = mTISBindHelper.getBinder();
+ if (binder != null) {
setSetupUIVisible(true);
- mBinder.setSwipeUpProxy(this::createSwipeUpProxy);
+ binder.setSwipeUpProxy(this::createSwipeUpProxy);
}
}
private void onTISConnected(TISBinder binder) {
- mBinder = binder;
- mTaskbarManager = mBinder.getTaskbarManager();
setSetupUIVisible(isResumed());
- mBinder.setSwipeUpProxy(isResumed() ? this::createSwipeUpProxy : null);
- mBinder.setOverviewTargetChangeListener(mBinder::preloadOverviewForSUWAllSet);
- mBinder.preloadOverviewForSUWAllSet();
- if (mTaskbarManager != null) {
- mLauncherStartAnim = mTaskbarManager.createLauncherStartFromSuwAnim(MAX_SWIPE_DURATION);
+ binder.setSwipeUpProxy(isResumed() ? this::createSwipeUpProxy : null);
+ binder.setOverviewTargetChangeListener(binder::preloadOverviewForSUWAllSet);
+ binder.preloadOverviewForSUWAllSet();
+ TaskbarManager taskbarManager = binder.getTaskbarManager();
+ if (taskbarManager != null) {
+ mLauncherStartAnim = taskbarManager.createLauncherStartFromSuwAnim(MAX_SWIPE_DURATION);
}
}
@@ -271,17 +275,20 @@ public class AllSetActivity extends Activity {
}
private void clearBinderOverride() {
- if (mBinder != null) {
+ TISBinder binder = mTISBindHelper.getBinder();
+ if (binder != null) {
setSetupUIVisible(false);
- mBinder.setSwipeUpProxy(null);
- mBinder.setOverviewTargetChangeListener(null);
+ binder.setSwipeUpProxy(null);
+ binder.setOverviewTargetChangeListener(null);
}
}
/**
- * Should be called when we have successfully reached Launcher, so we dispatch to animation
- * listeners to ensure the state matches the visual animation that just occurred.
- */
+ * Should be called when we have successfully reached Launcher, so we dispatch
+ * to animation
+ * listeners to ensure the state matches the visual animation that just
+ * occurred.
+ */
private void dispatchLauncherAnimStartEnd() {
if (mLauncherStartAnim != null) {
mLauncherStartAnim.dispatchOnStart();
@@ -302,7 +309,7 @@ public class AllSetActivity extends Activity {
}
private AnimatedFloat createSwipeUpProxy(GestureState state) {
- if (state.getRunningTaskId() != getTaskId()) {
+ if (state.getTopRunningTaskId() != getTaskId()) {
return null;
}
mSwipeProgress.updateValue(0);
@@ -315,9 +322,8 @@ public class AllSetActivity extends Activity {
}
private void maybeResumeOrPauseBackgroundAnimation() {
- boolean shouldPlayAnimation =
- getContentViewAlphaForSwipeProgress() > ANIMATION_PAUSE_ALPHA_THRESHOLD
- && isResumed();
+ boolean shouldPlayAnimation = getContentViewAlphaForSwipeProgress() > ANIMATION_PAUSE_ALPHA_THRESHOLD
+ && isResumed();
if (mAnimatedBackground.isAnimating() && !shouldPlayAnimation) {
mAnimatedBackground.pauseAnimation();
} else if (!mAnimatedBackground.isAnimating() && shouldPlayAnimation) {
@@ -374,8 +380,7 @@ public class AllSetActivity extends Activity {
private final Matrix mMatrix = new Matrix();
private final ColorMatrix mColorMatrix = new ColorMatrix();
- private final ColorMatrixColorFilter mColorFilter =
- new ColorMatrixColorFilter(mColorMatrix);
+ private final ColorMatrixColorFilter mColorFilter = new ColorMatrixColorFilter(mColorMatrix);
private final int mColor;
private float mProgress = 0;
@@ -383,8 +388,8 @@ public class AllSetActivity extends Activity {
BgDrawable(Context context) {
mColor = context.getColor(R.color.all_set_page_background);
mMaskGrad = new RadialGradient(0, 0, 1,
- new int[] {ColorUtils.setAlphaComponent(mColor, 0), mColor},
- new float[]{0, 1}, TileMode.CLAMP);
+ new int[] { ColorUtils.setAlphaComponent(mColor, 0), mColor },
+ new float[] { 0, 1 }, TileMode.CLAMP);
mPaint.setShader(mMaskGrad);
mPaint.setColorFilter(mColorFilter);
@@ -406,7 +411,7 @@ public class AllSetActivity extends Activity {
float size = PointF.length(x, height);
float radius = size * mapRange(progress, START_SIZE_FACTOR, END_SIZE_FACTOR);
- float y = mapRange(progress, height + radius , height / 2);
+ float y = mapRange(progress, height + radius, height / 2);
mMatrix.setTranslate(x, y);
mMatrix.postScale(radius, radius, x, y);
mMaskGrad.setLocalMatrix(mMatrix);
@@ -432,9 +437,11 @@ public class AllSetActivity extends Activity {
}
@Override
- public void setAlpha(int i) { }
+ public void setAlpha(int i) {
+ }
@Override
- public void setColorFilter(ColorFilter colorFilter) { }
+ public void setColorFilter(ColorFilter colorFilter) {
+ }
}
}
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
index 5d2527973e..135cb72f77 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
@@ -23,9 +23,9 @@ import android.annotation.LayoutRes;
import android.graphics.PointF;
import android.view.View;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.Interpolators;
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult;
import com.android.quickstep.util.LottieAnimationColorUtils;
@@ -183,7 +183,7 @@ final class BackGestureTutorialController extends TutorialController {
/* upperBound = */ 1f,
/* toMin = */ 1f,
/* toMax = */ EXITING_APP_MIN_SIZE_PERCENTAGE,
- Interpolators.DEACCEL);
+ Interpolators.DECELERATE);
// shrink the exiting app as we progress through the back gesture
mExitingAppView.setPivotX(isLeftGesture ? mScreenWidth : 0);
@@ -197,7 +197,7 @@ final class BackGestureTutorialController extends TutorialController {
/* upperBound = */ 1f,
/* toMin = */ 0,
/* toMax = */ mExitingAppMargin,
- Interpolators.DEACCEL)
+ Interpolators.DECELERATE)
* (isLeftGesture ? -1 : 1));
// round the corners of the exiting app as we progress through the back gesture
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
index a16b239ff5..b379baabe0 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
@@ -42,6 +42,12 @@ public class BackGestureTutorialFragment extends TutorialFragment {
super(fromTutorialMenu);
}
+ @NonNull
+ @Override
+ TutorialType getDefaultTutorialType() {
+ return TutorialType.BACK_NAVIGATION;
+ }
+
@Nullable
@Override
Integer getEdgeAnimationResId() {
diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
index b4b229bd3c..f43c9ad36c 100644
--- a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
+++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
@@ -40,12 +40,15 @@ import androidx.dynamicanimation.animation.FloatPropertyCompat;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.R;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.util.VibratorWrapper;
import com.android.systemui.shared.testing.ResourceUtils;
-/** Forked from platform/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java. */
+/**
+ * Forked from
+ * platform/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java.
+ */
public class EdgeBackGesturePanel extends View {
private static final String LOG_TAG = "EdgeBackGesturePanel";
@@ -54,7 +57,8 @@ public class EdgeBackGesturePanel extends View {
private static final long DISAPPEAR_ARROW_ANIMATION_DURATION_MS = 100;
/**
- * The time required since the first vibration effect to automatically trigger a click
+ * The time required since the first vibration effect to automatically trigger a
+ * click
*/
private static final int GESTURE_DURATION_FOR_CLICK_MS = 400;
@@ -96,19 +100,19 @@ public class EdgeBackGesturePanel extends View {
/**
* The interpolator used to rubberband
*/
- private static final Interpolator RUBBER_BAND_INTERPOLATOR =
- new PathInterpolator(1.0f / 5.0f, 1.0f, 1.0f, 1.0f);
+ private static final Interpolator RUBBER_BAND_INTERPOLATOR = new PathInterpolator(1.0f / 5.0f, 1.0f, 1.0f, 1.0f);
/**
- * The amount of rubber banding we do for the translation before base translation
+ * The amount of rubber banding we do for the translation before base
+ * translation
*/
private static final int RUBBER_BAND_AMOUNT_APPEAR = 4;
/**
* The interpolator used to rubberband the appearing of the arrow.
*/
- private static final Interpolator RUBBER_BAND_INTERPOLATOR_APPEAR =
- new PathInterpolator(1.0f / RUBBER_BAND_AMOUNT_APPEAR, 1.0f, 1.0f, 1.0f);
+ private static final Interpolator RUBBER_BAND_INTERPOLATOR_APPEAR = new PathInterpolator(
+ 1.0f / RUBBER_BAND_AMOUNT_APPEAR, 1.0f, 1.0f, 1.0f);
private BackCallback mBackCallback;
@@ -123,7 +127,8 @@ public class EdgeBackGesturePanel extends View {
private final float mArrowThickness;
/**
- * The minimum delta needed in movement for the arrow to change direction / stop triggering back
+ * The minimum delta needed in movement for the arrow to change direction / stop
+ * triggering back
*/
private final float mMinDeltaForSwitch;
// The closest to y = 0 that the arrow will be displayed.
@@ -178,57 +183,56 @@ public class EdgeBackGesturePanel extends View {
private long mVibrationTime;
private int mScreenSize;
- private final DynamicAnimation.OnAnimationEndListener mSetGoneEndListener =
- new DynamicAnimation.OnAnimationEndListener() {
- @Override
- public void onAnimationEnd(
- DynamicAnimation animation, boolean canceled, float value, float velocity) {
- animation.removeEndListener(this);
- if (!canceled) {
- setVisibility(GONE);
- }
- }
- };
+ private final DynamicAnimation.OnAnimationEndListener mSetGoneEndListener = new DynamicAnimation.OnAnimationEndListener() {
+ @Override
+ public void onAnimationEnd(
+ DynamicAnimation animation, boolean canceled, float value, float velocity) {
+ animation.removeEndListener(this);
+ if (!canceled) {
+ setVisibility(GONE);
+ }
+ }
+ };
- private static final FloatPropertyCompat CURRENT_ANGLE =
- new FloatPropertyCompat("currentAngle") {
- @Override
- public void setValue(EdgeBackGesturePanel object, float value) {
- object.setCurrentAngle(value);
- }
+ private static final FloatPropertyCompat CURRENT_ANGLE = new FloatPropertyCompat(
+ "currentAngle") {
+ @Override
+ public void setValue(EdgeBackGesturePanel object, float value) {
+ object.setCurrentAngle(value);
+ }
- @Override
- public float getValue(EdgeBackGesturePanel object) {
- return object.getCurrentAngle();
- }
- };
+ @Override
+ public float getValue(EdgeBackGesturePanel object) {
+ return object.getCurrentAngle();
+ }
+ };
- private static final FloatPropertyCompat CURRENT_TRANSLATION =
- new FloatPropertyCompat("currentTranslation") {
- @Override
- public void setValue(EdgeBackGesturePanel object, float value) {
- object.setCurrentTranslation(value);
- }
+ private static final FloatPropertyCompat CURRENT_TRANSLATION = new FloatPropertyCompat(
+ "currentTranslation") {
+ @Override
+ public void setValue(EdgeBackGesturePanel object, float value) {
+ object.setCurrentTranslation(value);
+ }
- @Override
- public float getValue(EdgeBackGesturePanel object) {
- return object.getCurrentTranslation();
- }
- };
+ @Override
+ public float getValue(EdgeBackGesturePanel object) {
+ return object.getCurrentTranslation();
+ }
+ };
- private static final FloatPropertyCompat CURRENT_VERTICAL_TRANSLATION =
- new FloatPropertyCompat("verticalTranslation") {
+ private static final FloatPropertyCompat CURRENT_VERTICAL_TRANSLATION = new FloatPropertyCompat(
+ "verticalTranslation") {
- @Override
- public void setValue(EdgeBackGesturePanel object, float value) {
- object.setVerticalTranslation(value);
- }
+ @Override
+ public void setValue(EdgeBackGesturePanel object, float value) {
+ object.setVerticalTranslation(value);
+ }
- @Override
- public float getValue(EdgeBackGesturePanel object) {
- return object.getVerticalTranslation();
- }
- };
+ @Override
+ public float getValue(EdgeBackGesturePanel object) {
+ return object.getVerticalTranslation();
+ }
+ };
public EdgeBackGesturePanel(Context context, ViewGroup parent, LayoutParams layoutParams) {
super(context);
@@ -254,8 +258,7 @@ public class EdgeBackGesturePanel extends View {
invalidate();
});
- mAngleAnimation =
- new SpringAnimation(this, CURRENT_ANGLE);
+ mAngleAnimation = new SpringAnimation(this, CURRENT_ANGLE);
mAngleAppearForce = new SpringForce()
.setStiffness(500)
.setDampingRatio(0.5f);
@@ -265,8 +268,7 @@ public class EdgeBackGesturePanel extends View {
.setFinalPosition(90);
mAngleAnimation.setSpring(mAngleAppearForce).setMaxValue(90);
- mTranslationAnimation =
- new SpringAnimation(this, CURRENT_TRANSLATION);
+ mTranslationAnimation = new SpringAnimation(this, CURRENT_TRANSLATION);
mRegularTranslationSpring = new SpringForce()
.setStiffness(SpringForce.STIFFNESS_MEDIUM)
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
@@ -274,20 +276,18 @@ public class EdgeBackGesturePanel extends View {
.setStiffness(450)
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
mTranslationAnimation.setSpring(mRegularTranslationSpring);
- mVerticalTranslationAnimation =
- new SpringAnimation(this, CURRENT_VERTICAL_TRANSLATION);
+ mVerticalTranslationAnimation = new SpringAnimation(this, CURRENT_VERTICAL_TRANSLATION);
mVerticalTranslationAnimation.setSpring(
new SpringForce()
.setStiffness(SpringForce.STIFFNESS_MEDIUM)
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
- int currentNightMode =
- context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+ int currentNightMode = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
mPaint.setColor(context.getColor(R.color.gesture_tutorial_back_arrow_color));
loadDimens();
updateArrowDirection();
mSwipeThreshold = ResourceUtils.getDimenByName(
- "navigation_edge_action_drag_threshold", context.getResources(), 16 /* defaultValue */);
+ "navigation_edge_action_drag_threshold", context.getResources(), 16 /* defaultValue */);
parent.addView(this, layoutParams);
setVisibility(GONE);
}
@@ -548,7 +548,8 @@ public class EdgeBackGesturePanel extends View {
setTriggerBack(true /* triggerBack */, true /* animated */);
}
- // Let's make sure we only go to the baseextend and apply rubberbanding afterwards
+ // Let's make sure we only go to the baseextend and apply rubberbanding
+ // afterwards
if (touchTranslation > mBaseTranslation) {
float diff = touchTranslation - mBaseTranslation;
float progress = MathUtils.clamp(diff / (mScreenSize - mBaseTranslation), 0, 1);
@@ -565,7 +566,7 @@ public class EdgeBackGesturePanel extends View {
// By default we just assume the current direction is kept
boolean triggerBack = mTriggerBack;
- // First lets see if we had continuous motion in one direction for a while
+ // First lets see if we had continuous motion in one direction for a while
if (Math.abs(mTotalTouchDelta) > mMinDeltaForSwitch) {
triggerBack = mTotalTouchDelta > 0;
}
@@ -591,7 +592,8 @@ public class EdgeBackGesturePanel extends View {
touchTranslation = 0;
} else if (mIsLeftPanel && mArrowsPointLeft
|| (!mIsLeftPanel && !mArrowsPointLeft)) {
- // If we're on the left we should move less, because the arrow is facing the other
+ // If we're on the left we should move less, because the arrow is facing the
+ // other
// direction
touchTranslation -= getStaticArrowWidth();
}
@@ -599,8 +601,7 @@ public class EdgeBackGesturePanel extends View {
updateAngle(true /* animated */);
float maxYOffset = getHeight() / 2.0f - mArrowLength;
- float progress =
- MathUtils.clamp(Math.abs(yOffset) / (maxYOffset * RUBBER_BAND_AMOUNT), 0, 1);
+ float progress = MathUtils.clamp(Math.abs(yOffset) / (maxYOffset * RUBBER_BAND_AMOUNT), 0, 1);
float verticalTranslation = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress)
* maxYOffset * Math.signum(yOffset);
setDesiredVerticalTransition(verticalTranslation, true /* animated */);
@@ -656,7 +657,8 @@ public class EdgeBackGesturePanel extends View {
mTriggerBack = triggerBack;
mAngleAnimation.cancel();
updateAngle(animated);
- // Whenever the trigger back state changes the existing translation animation should be
+ // Whenever the trigger back state changes the existing translation animation
+ // should be
// cancelled
mTranslationAnimation.cancel();
}
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
index 62726a01d2..2c9029f282 100644
--- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
@@ -52,8 +52,10 @@ public class GestureSandboxActivity extends FragmentActivity {
static final String KEY_GESTURE_COMPLETE = "gesture_complete";
static final String KEY_USE_TUTORIAL_MENU = "use_tutorial_menu";
- @Nullable private TutorialType[] mTutorialSteps;
- private GestureSandboxFragment mFragment;
+ @Nullable
+ private TutorialType[] mTutorialSteps;
+ private GestureSandboxFragment mCurrentFragment;
+ private GestureSandboxFragment mPendingFragment;
private int mCurrentStep;
private int mNumSteps;
@@ -61,10 +63,7 @@ public class GestureSandboxActivity extends FragmentActivity {
private SharedPreferences mSharedPrefs;
private StatsLogManager mStatsLogManager;
-
- private View mRotationPrompt;
private TISBindHelper mTISBindHelper;
- private TISBinder mBinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -83,7 +82,7 @@ public class GestureSandboxActivity extends FragmentActivity {
&& args.getBoolean(KEY_USE_TUTORIAL_MENU, false)) {
mTutorialSteps = null;
TutorialType tutorialTypeOverride = (TutorialType) args.get(KEY_TUTORIAL_TYPE);
- mFragment = tutorialTypeOverride == null
+ mCurrentFragment = tutorialTypeOverride == null
? new MenuFragment()
: makeTutorialFragment(
tutorialTypeOverride,
@@ -91,16 +90,15 @@ public class GestureSandboxActivity extends FragmentActivity {
/* fromMenu= */ true);
} else {
mTutorialSteps = getTutorialSteps(args);
- mFragment = makeTutorialFragment(
+ mCurrentFragment = makeTutorialFragment(
mTutorialSteps[mCurrentStep - 1],
gestureComplete,
/* fromMenu= */ false);
}
getSupportFragmentManager().beginTransaction()
- .add(R.id.gesture_tutorial_fragment_container, mFragment)
+ .add(R.id.gesture_tutorial_fragment_container, mCurrentFragment)
.commit();
- mRotationPrompt = findViewById(R.id.rotation_prompt);
if (FeatureFlags.ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) {
correctUserOrientation();
}
@@ -118,44 +116,57 @@ public class GestureSandboxActivity extends FragmentActivity {
}
/**
- * Gesture animations are only in landscape for large screens and portrait for mobile. This
+ * Gesture animations are only in landscape for large screens and portrait for
+ * mobile. This
* method enforces the following flows:
- * 1) phone / two-panel closed -> lock to portrait
- * 2) two-panel open / tablet + portrait -> prompt the user to rotate the screen
- * 3) two-panel open / tablet + landscape -> hide potential rotating prompt
+ * 1) phone / two-panel closed -> lock to portrait
+ * 2) two-panel open / tablet + portrait -> prompt the user to rotate the screen
+ * 3) two-panel open / tablet + landscape -> hide potential rotating prompt
*/
private void correctUserOrientation() {
DeviceProfile deviceProfile = InvariantDeviceProfile.INSTANCE.get(
getApplicationContext()).getDeviceProfile(this);
if (deviceProfile.isTablet) {
- mShowRotationPrompt = getResources().getConfiguration().orientation
- == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
- updateVisibility(mRotationPrompt, mShowRotationPrompt ? View.VISIBLE : View.GONE);
+ boolean showRotationPrompt = getResources()
+ .getConfiguration().orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+
+ GestureSandboxFragment recreatedFragment = showRotationPrompt || mPendingFragment == null
+ ? null
+ : mPendingFragment.recreateFragment();
+ showFragment(showRotationPrompt
+ ? new RotationPromptFragment()
+ : recreatedFragment == null
+ ? mCurrentFragment
+ : recreatedFragment);
} else {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
}
- void updateVisibility(View view, int visibility) {
- if (view == null || view.getVisibility() == visibility) {
- return;
+ private void showFragment(@NonNull GestureSandboxFragment fragment) {
+ if (mCurrentFragment.recreateFragment() != null) {
+ mPendingFragment = mCurrentFragment;
}
- view.setVisibility(visibility);
+ mCurrentFragment = fragment;
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.gesture_tutorial_fragment_container, mCurrentFragment)
+ .runOnCommit(() -> mCurrentFragment.onAttachedToWindow())
+ .commit();
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
- if (mFragment.shouldDisableSystemGestures()) {
+ if (mCurrentFragment.shouldDisableSystemGestures()) {
disableSystemGestures();
}
- mFragment.onAttachedToWindow();
+ mCurrentFragment.onAttachedToWindow();
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
- mFragment.onDetachedFromWindow();
+ mCurrentFragment.onDetachedFromWindow();
}
@Override
@@ -170,7 +181,7 @@ public class GestureSandboxActivity extends FragmentActivity {
protected void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
savedInstanceState.putStringArray(KEY_TUTORIAL_STEPS, getTutorialStepNames());
savedInstanceState.putInt(KEY_CURRENT_STEP, mCurrentStep);
- mFragment.onSaveInstanceState(savedInstanceState);
+ mCurrentFragment.onSaveInstanceState(savedInstanceState);
super.onSaveInstanceState(savedInstanceState);
}
@@ -186,7 +197,9 @@ public class GestureSandboxActivity extends FragmentActivity {
return mStatsLogManager;
}
- /** Returns true iff there aren't anymore tutorial types to display to the user. */
+ /**
+ * Returns true iff there aren't anymore tutorial types to display to the user.
+ */
public boolean isTutorialComplete() {
return mCurrentStep >= mNumSteps;
}
@@ -200,13 +213,14 @@ public class GestureSandboxActivity extends FragmentActivity {
}
/**
- * Replaces the current TutorialFragment, continuing to the next tutorial step if there is one.
+ * Replaces the current TutorialFragment, continuing to the next tutorial step
+ * if there is one.
*
* If there is no following step, the tutorial is closed.
*/
public void continueTutorial() {
if (isTutorialComplete() || mTutorialSteps == null) {
- mFragment.close();
+ mCurrentFragment.close();
return;
}
launchTutorialStep(mTutorialSteps[mCurrentStep], false);
@@ -221,24 +235,17 @@ public class GestureSandboxActivity extends FragmentActivity {
/**
* Launches the given gesture nav tutorial step.
*
- * If the step is being launched from the gesture nav tutorial menu, then that step will launch
+ * If the step is being launched from the gesture nav tutorial menu, then that
+ * step will launch
* the menu when complete.
*/
public void launchTutorialStep(@NonNull TutorialType tutorialType, boolean fromMenu) {
- mFragment = makeTutorialFragment(tutorialType, false, fromMenu);
- getSupportFragmentManager().beginTransaction()
- .replace(R.id.gesture_tutorial_fragment_container, mFragment)
- .runOnCommit(() -> mFragment.onAttachedToWindow())
- .commit();
+ showFragment(makeTutorialFragment(tutorialType, false, fromMenu));
}
/** Launches the gesture nav tutorial menu page */
public void launchTutorialMenu() {
- mFragment = new MenuFragment();
- getSupportFragmentManager().beginTransaction()
- .replace(R.id.gesture_tutorial_fragment_container, mFragment)
- .runOnCommit(() -> mFragment.onAttachedToWindow())
- .commit();
+ showFragment(new MenuFragment());
}
private String[] getTutorialStepNames() {
@@ -259,7 +266,7 @@ public class GestureSandboxActivity extends FragmentActivity {
TutorialType[] defaultSteps = new TutorialType[] {
TutorialType.HOME_NAVIGATION,
TutorialType.BACK_NAVIGATION,
- TutorialType.OVERVIEW_NAVIGATION};
+ TutorialType.OVERVIEW_NAVIGATION };
mCurrentStep = 1;
mNumSteps = defaultSteps.length;
@@ -271,7 +278,8 @@ public class GestureSandboxActivity extends FragmentActivity {
Object savedSteps = extras.get(KEY_TUTORIAL_STEPS);
if (savedSteps instanceof String) {
savedStepsNames = TextUtils.isEmpty((String) savedSteps)
- ? null : ((String) savedSteps).split(",");
+ ? null
+ : ((String) savedSteps).split(",");
} else if (savedSteps instanceof String[]) {
savedStepsNames = (String[]) savedSteps;
} else {
@@ -321,7 +329,6 @@ public class GestureSandboxActivity extends FragmentActivity {
}
private void onTISConnected(TISBinder binder) {
- mBinder = binder;
updateServiceState(isResumed());
}
@@ -332,8 +339,9 @@ public class GestureSandboxActivity extends FragmentActivity {
}
private void updateServiceState(boolean isEnabled) {
- if (mBinder != null) {
- mBinder.setGestureBlockedTaskId(isEnabled ? getTaskId() : -1);
+ TISBinder binder = mTISBindHelper.getBinder();
+ if (binder != null) {
+ binder.setGestureBlockedTaskId(isEnabled ? getTaskId() : -1);
}
}
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxFragment.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxFragment.java
index d52f19a2b5..08f298965c 100644
--- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxFragment.java
@@ -17,6 +17,7 @@ package com.android.quickstep.interaction;
import android.app.Activity;
+import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
@@ -27,6 +28,11 @@ public abstract class GestureSandboxFragment extends Fragment {
void onDetachedFromWindow() {}
+ @Nullable
+ GestureSandboxFragment recreateFragment() {
+ return null;
+ }
+
boolean shouldDisableSystemGestures() {
return true;
}
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java
index bced8c4aef..3e924d7ed1 100644
--- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java
@@ -41,6 +41,12 @@ public class HomeGestureTutorialFragment extends TutorialFragment {
super(fromTutorialMenu);
}
+ @NonNull
+ @Override
+ TutorialType getDefaultTutorialType() {
+ return TutorialType.HOME_NAVIGATION;
+ }
+
@Nullable
@Override
Integer getEdgeAnimationResId() {
diff --git a/quickstep/src/com/android/quickstep/interaction/MenuFragment.java b/quickstep/src/com/android/quickstep/interaction/MenuFragment.java
index 46f79b1ce8..dbf141b987 100644
--- a/quickstep/src/com/android/quickstep/interaction/MenuFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/MenuFragment.java
@@ -19,7 +19,6 @@ import static com.android.quickstep.interaction.GestureSandboxActivity.KEY_GESTU
import static com.android.quickstep.interaction.GestureSandboxActivity.KEY_TUTORIAL_TYPE;
import static com.android.quickstep.interaction.GestureSandboxActivity.KEY_USE_TUTORIAL_MENU;
-import android.graphics.Rect;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@@ -28,33 +27,23 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.R;
/** Displays the gesture nav tutorial menu. */
public final class MenuFragment extends GestureSandboxFragment {
- @NonNull private Rect mInsets = new Rect();
-
+ @NonNull
@Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mInsets = InvariantDeviceProfile.INSTANCE.get(getContext())
- .getDeviceProfile(getContext()).getInsets();
+ GestureSandboxFragment recreateFragment() {
+ return new MenuFragment();
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
- View root = inflater.inflate(
+ final View root = inflater.inflate(
R.layout.gesture_tutorial_step_menu, container, false);
- root.setPadding(
- root.getPaddingLeft() + mInsets.left,
- root.getPaddingTop() + mInsets.top,
- root.getPaddingRight() + mInsets.right,
- root.getPaddingBottom() + mInsets.bottom);
-
root.findViewById(R.id.gesture_tutorial_menu_home_button).setOnClickListener(
v -> launchTutorialStep(TutorialController.TutorialType.HOME_NAVIGATION));
root.findViewById(R.id.gesture_tutorial_menu_back_button).setOnClickListener(
diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
index 58eda94063..5065829bd6 100644
--- a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
@@ -61,11 +61,10 @@ public class NavBarGestureHandler implements OnTouchListener,
NavBarGestureHandler(Context context) {
mContext = context;
DisplayController.Info displayInfo = DisplayController.INSTANCE.get(mContext).getInfo();
- final int displayRotation = displayInfo.rotation;
Point currentSize = displayInfo.currentSize;
mDisplaySize.set(currentSize.x, currentSize.y);
mSwipeUpTouchTracker = new TriggerSwipeUpTouchTracker(context, true /* disableHorizontalSwipe */,
- new NavBarPosition(NavigationMode.NO_BUTTON, displayRotation),
+ new NavBarPosition(NavigationMode.NO_BUTTON, displayInfo),
null /* onInterceptTouch */, this);
mMotionPauseDetector = new MotionPauseDetector(context);
diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
index 454dd17f37..75b80b31f9 100644
--- a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
@@ -15,7 +15,7 @@
*/
package com.android.quickstep.interaction;
-import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.app.animation.Interpolators.ACCELERATE;
import static com.android.launcher3.config.FeatureFlags.ENABLE_NEW_GESTURE_NAV_TUTORIAL;
import android.animation.Animator;
@@ -254,7 +254,7 @@ final class OverviewGestureTutorialController extends SwipeUpGestureTutorialCont
public void animateTaskViewToOverview(boolean animateDelayedSuccessFeedback) {
PendingAnimation anim = new PendingAnimation(TASK_VIEW_END_ANIMATION_DURATION_MILLIS);
anim.setFloat(mTaskViewSwipeUpAnimation
- .getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL);
+ .getCurrentShift(), AnimatedFloat.VALUE, 1, ACCELERATE);
if (animateDelayedSuccessFeedback) {
anim.addListener(new AnimatorListenerAdapter() {
diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java
index 01074dd276..ee1c46057e 100644
--- a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java
@@ -41,6 +41,12 @@ public class OverviewGestureTutorialFragment extends TutorialFragment {
super(fromTutorialMenu);
}
+ @NonNull
+ @Override
+ TutorialType getDefaultTutorialType() {
+ return TutorialType.OVERVIEW_NAVIGATION;
+ }
+
@Nullable
@Override
Integer getEdgeAnimationResId() {
diff --git a/quickstep/src/com/android/quickstep/interaction/RotationPromptFragment.java b/quickstep/src/com/android/quickstep/interaction/RotationPromptFragment.java
new file mode 100644
index 0000000000..89af647985
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/RotationPromptFragment.java
@@ -0,0 +1,37 @@
+/*
+ * 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.quickstep.interaction;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+
+/** Displays the prompt requesting that the user rotates their device. */
+public class RotationPromptFragment extends GestureSandboxFragment {
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.gesture_tutorial_rotation_prompt, container, false);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
index 558d5dc238..a29cbeb780 100644
--- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
@@ -18,7 +18,7 @@ package com.android.quickstep.interaction;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
-import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.app.animation.Interpolators.ACCELERATE;
import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
import static com.android.quickstep.AbsSwipeUpHandler.MAX_SWIPE_DURATION;
@@ -152,6 +152,7 @@ abstract class SwipeUpGestureTutorialController extends TutorialController {
mShowPreviousTasks = false;
mRunningWindowAnim = null;
}
+
void fadeOutFakeTaskView(boolean toOverviewFirst, @Nullable Runnable onEndRunnable) {
fadeOutFakeTaskView(
toOverviewFirst,
@@ -171,14 +172,13 @@ abstract class SwipeUpGestureTutorialController extends TutorialController {
PendingAnimation anim = new PendingAnimation(300);
if (toOverviewFirst) {
anim.setFloat(mTaskViewSwipeUpAnimation
- .getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL);
+ .getCurrentShift(), AnimatedFloat.VALUE, 1, ACCELERATE);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation, boolean isReverse) {
- PendingAnimation fadeAnim =
- new PendingAnimation(TASK_VIEW_END_ANIMATION_DURATION_MILLIS);
+ PendingAnimation fadeAnim = new PendingAnimation(TASK_VIEW_END_ANIMATION_DURATION_MILLIS);
fadeAnim.setFloat(mTaskViewSwipeUpAnimation
- .getCurrentShift(), AnimatedFloat.VALUE, 0, ACCEL);
+ .getCurrentShift(), AnimatedFloat.VALUE, 0, ACCELERATE);
if (resetViews) {
fadeAnim.addListener(mResetTaskView);
}
@@ -195,8 +195,7 @@ abstract class SwipeUpGestureTutorialController extends TutorialController {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
- Animator multiRowAnimation =
- mFakePreviousTaskView.createAnimationToMultiRowLayout();
+ Animator multiRowAnimation = mFakePreviousTaskView.createAnimationToMultiRowLayout();
if (multiRowAnimation != null) {
multiRowAnimation.setDuration(
@@ -213,7 +212,7 @@ abstract class SwipeUpGestureTutorialController extends TutorialController {
});
} else {
anim.setFloat(mTaskViewSwipeUpAnimation
- .getCurrentShift(), AnimatedFloat.VALUE, 0, ACCEL);
+ .getCurrentShift(), AnimatedFloat.VALUE, 0, ACCELERATE);
if (resetViews) {
anim.addListener(mResetTaskView);
}
@@ -239,8 +238,8 @@ abstract class SwipeUpGestureTutorialController extends TutorialController {
mFakeTaskView.setVisibility(View.VISIBLE);
PendingAnimation anim = new PendingAnimation(300);
anim.setFloat(mTaskViewSwipeUpAnimation
- .getCurrentShift(), AnimatedFloat.VALUE, 0, ACCEL);
- anim.setViewAlpha(mFakeTaskView, 1, ACCEL);
+ .getCurrentShift(), AnimatedFloat.VALUE, 0, ACCELERATE);
+ anim.setViewAlpha(mFakeTaskView, 1, ACCELERATE);
anim.addListener(mResetTaskView);
AnimatorSet animset = anim.buildAnim();
if (animateTaskbar) {
@@ -256,11 +255,10 @@ abstract class SwipeUpGestureTutorialController extends TutorialController {
mFakePreviousTaskView.setVisibility(View.INVISIBLE);
mFakeHotseatView.setVisibility(View.VISIBLE);
mShowPreviousTasks = false;
- RectFSpringAnim rectAnim =
- mTaskViewSwipeUpAnimation.handleSwipeUpToHome(finalVelocity);
+ RectFSpringAnim rectAnim = mTaskViewSwipeUpAnimation.handleSwipeUpToHome(finalVelocity);
// After home animation finishes, fade out and run onEndRunnable.
PendingAnimation fadeAnim = new PendingAnimation(300);
- fadeAnim.setViewAlpha(mFakeIconView, 0, ACCEL);
+ fadeAnim.setViewAlpha(mFakeIconView, 0, ACCELERATE);
final View hotseatIconView = mHotseatIconView;
if (hotseatIconView != null) {
hotseatIconView.setVisibility(INVISIBLE);
@@ -311,9 +309,9 @@ abstract class SwipeUpGestureTutorialController extends TutorialController {
mFakePreviousTaskView.setTranslationX(
-(2 * mFakePreviousTaskView.getWidth() + FAKE_PREVIOUS_TASK_MARGIN));
mFakePreviousTaskView.animate()
- .setDuration(300)
- .translationX(-(mFakePreviousTaskView.getWidth() + FAKE_PREVIOUS_TASK_MARGIN))
- .start();
+ .setDuration(300)
+ .translationX(-(mFakePreviousTaskView.getWidth() + FAKE_PREVIOUS_TASK_MARGIN))
+ .start();
}
mShowPreviousTasks = true;
}
@@ -322,13 +320,12 @@ abstract class SwipeUpGestureTutorialController extends TutorialController {
class ViewSwipeUpAnimation extends SwipeUpAnimationLogic {
ViewSwipeUpAnimation(Context context, RecentsAnimationDeviceState deviceState,
- GestureState gestureState) {
+ GestureState gestureState) {
super(context, deviceState, gestureState);
mRemoteTargetHandles[0] = new RemoteTargetGluer.RemoteTargetHandle(
mRemoteTargetHandles[0].getTaskViewSimulator(), new FakeTransformParams());
- for (RemoteTargetGluer.RemoteTargetHandle handle
- : mTargetGluer.getRemoteTargetHandles()) {
+ for (RemoteTargetGluer.RemoteTargetHandle handle : mTargetGluer.getRemoteTargetHandles()) {
// Override home screen rotation preference so that home and overview animations
// work properly
handle.getTaskViewSimulator()
@@ -443,20 +440,18 @@ abstract class SwipeUpGestureTutorialController extends TutorialController {
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mFakePreviousTaskView.setVisibility(View.VISIBLE);
- onMotionPaused(true /*arbitrary value*/);
+ onMotionPaused(true /* arbitrary value */);
}
});
return overviewSwipeAnimator;
}
-
private Animator createFingerDotSwipeUpAnimator(float fingerDotStartTranslationY) {
ValueAnimator swipeAnimator = ValueAnimator.ofFloat(0f, 1f);
swipeAnimator.addUpdateListener(valueAnimator -> {
- float gestureProgress =
- -fingerDotStartTranslationY * valueAnimator.getAnimatedFraction();
+ float gestureProgress = -fingerDotStartTranslationY * valueAnimator.getAnimatedFraction();
setNavBarGestureProgress(gestureProgress);
mFingerDotView.setTranslationY(fingerDotStartTranslationY + gestureProgress);
});
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index c8fa070616..d7c3fba21b 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -234,13 +234,11 @@ abstract class TutorialController implements BackGestureAttemptCallback,
return;
}
Matrix scaleMatrix = new Matrix();
- float pivotX = mScreenWidth / 2f;
- float pivotY = mScreenHeight;
float scaleFactor = mScreenWidth / animationBoundsRect.width();
+ float heightTranslate = (mScreenHeight - (scaleFactor * animationBoundsRect.height()));
- scaleMatrix.postScale(scaleFactor, scaleFactor, pivotX, pivotY);
- scaleMatrix.postTranslate(0,
- mTutorialFragment.getDeviceProfile().heightPx - animationBoundsRect.height());
+ scaleMatrix.postScale(scaleFactor, scaleFactor);
+ scaleMatrix.postTranslate(0, heightTranslate);
mAnimatedGestureDemonstration.setImageMatrix(scaleMatrix);
}
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
index 84326f5a27..f4c782eaa4 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
@@ -100,6 +100,19 @@ abstract class TutorialFragment extends GestureSandboxFragment implements OnTouc
return fragment;
}
+ @Nullable
+ @Override
+ GestureSandboxFragment recreateFragment() {
+ TutorialType tutorialType = mTutorialController == null
+ ? (mTutorialType == null
+ ? getDefaultTutorialType() : mTutorialType)
+ : mTutorialController.mTutorialType;
+ return newInstance(tutorialType, isGestureComplete(), mFromTutorialMenu);
+ }
+
+ @NonNull
+ abstract TutorialType getDefaultTutorialType();
+
TutorialFragment(boolean fromTutorialMenu) {
mFromTutorialMenu = fromTutorialMenu;
}
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 6288937912..d6f6f5e08a 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -62,14 +62,11 @@ import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.Executors;
-import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.LogConfig;
import com.android.launcher3.views.ActivityContext;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import com.android.systemui.shared.system.SysUiStatsLog;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -90,8 +87,10 @@ public class StatsLogCompatManager extends StatsLogManager {
private static final String LATENCY_TAG = "StatsLatencyLog";
private static final String IMPRESSION_TAG = "StatsImpressionLog";
private static final boolean IS_VERBOSE = Utilities.isPropertyEnabled(LogConfig.STATSLOG);
+ private static final boolean DEBUG = !Utilities.isRunningInTestHarness();
private static final InstanceId DEFAULT_INSTANCE_ID = InstanceId.fakeInstanceId(0);
- // LauncherAtom.ItemInfo.getDefaultInstance() should be used but until launcher proto migrates
+ // LauncherAtom.ItemInfo.getDefaultInstance() should be used but until launcher
+ // proto migrates
// from nano to lite, bake constant to prevent robo test failure.
private static final int DEFAULT_PAGE_INDEX = -2;
private static final int FOLDER_HIERARCHY_OFFSET = 100;
@@ -107,10 +106,7 @@ public class StatsLogCompatManager extends StatsLogManager {
private static final int SEARCH_ATTRIBUTES_ENTRY_STATE_ALL_APPS = 1 << 2;
private static final int SEARCH_ATTRIBUTES_ENTRY_STATE_QSB = 1 << 3;
- public static final CopyOnWriteArrayList LOGS_CONSUMER =
- new CopyOnWriteArrayList<>();
-
- private final Context mContext;
+ public static final CopyOnWriteArrayList LOGS_CONSUMER = new CopyOnWriteArrayList<>();
public StatsLogCompatManager(Context context) {
mContext = context;
@@ -144,7 +140,7 @@ public class StatsLogCompatManager extends StatsLogManager {
}
SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_SNAPSHOT,
LAUNCHER_WORKSPACE_SNAPSHOT.getId() /* event_id */,
- info.getItemCase().getNumber() /* target_id */,
+ info.getItemCase().getNumber() /* target_id */,
instanceId.getId() /* instance_id */,
0 /* uid */,
getPackageName(info) /* package_name */,
@@ -174,7 +170,8 @@ public class StatsLogCompatManager extends StatsLogManager {
}
/**
- * Builds {@link StatsEvent} from {@link LauncherAtom.ItemInfo}. Used for pulled atom callback
+ * Builds {@link StatsEvent} from {@link LauncherAtom.ItemInfo}. Used for pulled
+ * atom callback
* implementation.
*/
public static StatsEvent buildStatsEvent(LauncherAtom.ItemInfo info,
@@ -183,16 +180,16 @@ public class StatsLogCompatManager extends StatsLogManager {
SysUiStatsLog.LAUNCHER_LAYOUT_SNAPSHOT, // atom ID,
LAUNCHER_WORKSPACE_SNAPSHOT.getId(), // event_id = 1;
info.getItemCase().getNumber(), // item_id = 2;
- instanceId == null ? 0 : instanceId.getId(), //instance_id = 3;
- 0, //uid = 4 [(is_uid) = true];
+ instanceId == null ? 0 : instanceId.getId(), // instance_id = 3;
+ 0, // uid = 4 [(is_uid) = true];
getPackageName(info), // package_name = 5;
getComponentName(info), // component_name = 6;
- getGridX(info, false), //grid_x = 7 [default = -1];
- getGridY(info, false), //grid_y = 8 [default = -1];
+ getGridX(info, false), // grid_x = 7 [default = -1];
+ getGridY(info, false), // grid_y = 8 [default = -1];
getPageId(info), // page_id = 9 [default = -2];
- getGridX(info, true), //grid_x_parent = 10 [default = -1];
- getGridY(info, true), //grid_y_parent = 11 [default = -1];
- getParentPageId(info), //page_id_parent = 12 [default = -2];
+ getGridX(info, true), // grid_x_parent = 10 [default = -1];
+ getGridY(info, true), // grid_y_parent = 11 [default = -1];
+ getParentPageId(info), // page_id_parent = 12 [default = -2];
getHierarchy(info), // container_id = 13;
info.getIsWork(), // is_work_profile = 14;
0, // attribute_id = 15;
@@ -331,6 +328,10 @@ public class StatsLogCompatManager extends StatsLogManager {
if (!Utilities.ATLEAST_R) {
return;
}
+ if (DEBUG) {
+ String name = (event instanceof Enum) ? ((Enum) event).name() : event.getId() + "";
+ Log.d(TAG, name);
+ }
LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
if (mSlice == null && mSliceItem != null) {
@@ -341,8 +342,8 @@ public class StatsLogCompatManager extends StatsLogManager {
if (mSlice != null) {
Executors.MODEL_EXECUTOR.execute(
() -> {
- LauncherAtom.ItemInfo.Builder itemInfoBuilder =
- LauncherAtom.ItemInfo.newBuilder().setSlice(mSlice);
+ LauncherAtom.ItemInfo.Builder itemInfoBuilder = LauncherAtom.ItemInfo.newBuilder()
+ .setSlice(mSlice);
mContainerInfo.ifPresent(itemInfoBuilder::setContainerInfo);
write(event, applyOverwrites(itemInfoBuilder.build()));
});
@@ -400,8 +401,7 @@ public class StatsLogCompatManager extends StatsLogManager {
mRank.ifPresent(itemInfoBuilder::setRank);
mContainerInfo.ifPresent(itemInfoBuilder::setContainerInfo);
- mActivityContext.ifPresent(activityContext ->
- activityContext.applyOverwritesToLogItem(itemInfoBuilder));
+ mActivityContext.ifPresent(activityContext -> activityContext.applyOverwritesToLogItem(itemInfoBuilder));
if (mFromState.isPresent() || mToState.isPresent() || mEditText.isPresent()) {
FolderIcon.Builder folderIconBuilder = itemInfoBuilder
@@ -422,8 +422,7 @@ public class StatsLogCompatManager extends StatsLogManager {
int dstState = mDstState;
int inputType = mInputType;
if (IS_VERBOSE) {
- String name = (event instanceof Enum) ? ((Enum) event).name() :
- event.getId() + "";
+ String name = (event instanceof Enum) ? ((Enum) event).name() : event.getId() + "";
StringBuilder logStringBuilder = new StringBuilder("\n");
if (instanceId != DEFAULT_INSTANCE_ID) {
logStringBuilder.append(String.format("InstanceId:%s ", instanceId));
@@ -541,8 +540,7 @@ public class StatsLogCompatManager extends StatsLogManager {
@Override
public void log(EventEnum event) {
if (IS_VERBOSE) {
- String name = (event instanceof Enum) ? ((Enum) event).name() :
- event.getId() + "";
+ String name = (event instanceof Enum) ? ((Enum) event).name() : event.getId() + "";
StringBuilder logStringBuilder = new StringBuilder("\n");
logStringBuilder.append(String.format("InstanceId:%s ", mInstanceId));
logStringBuilder.append(String.format("%s=%sms", name, mLatencyInMillis));
@@ -554,7 +552,7 @@ public class StatsLogCompatManager extends StatsLogManager {
mInstanceId.getId(), // instance_id
mPackageId, // package_id
mLatencyInMillis, // latency_in_millis
- mType.getId(), //type
+ mType.getId(), // type
mQueryLength, // query_length
mSubEventType, // sub_event_type
mCardinality // cardinality
@@ -566,14 +564,16 @@ public class StatsLogCompatManager extends StatsLogManager {
* Helps to construct and log statsd compatible impression events.
*/
private static class StatsCompatImpressionLogger implements StatsImpressionLogger {
- private int[] mResultTypeList = new int[]{};
- private int[] mResultCountList = new int[]{};
- private final List mAboveKeyboardList = new ArrayList<>();
- private int[] mUidList = new int[]{};
private InstanceId mInstanceId = DEFAULT_INSTANCE_ID;
private State mLauncherState = State.UNKNOWN;
private int mQueryLength = -1;
+ // Fields used for Impression Logging V2.
+ private int mResultType;
+ private boolean mAboveKeyboard = false;
+ private int mUid;
+ private int mResultSource;
+
@Override
public StatsImpressionLogger withInstanceId(InstanceId instanceId) {
this.mInstanceId = instanceId;
@@ -593,69 +593,57 @@ public class StatsLogCompatManager extends StatsLogManager {
}
@Override
- public StatsImpressionLogger withResultType(IntArray resultType) {
- mResultTypeList = resultType.toArray();
+ public StatsImpressionLogger withResultType(int resultType) {
+ mResultType = resultType;
return this;
}
@Override
- public StatsImpressionLogger withResultCount(IntArray resultCount) {
- mResultCountList = resultCount.toArray();
+ public StatsImpressionLogger withAboveKeyboard(boolean aboveKeyboard) {
+ mAboveKeyboard = aboveKeyboard;
return this;
}
@Override
- public StatsImpressionLogger withAboveKeyboard(List aboveKeyboard) {
- mAboveKeyboardList.clear();
- this.mAboveKeyboardList.addAll(aboveKeyboard);
+ public StatsImpressionLogger withUid(int uid) {
+ mUid = uid;
return this;
}
@Override
- public StatsImpressionLogger withUids(IntArray uid) {
- mUidList = uid.toArray();
+ public StatsImpressionLogger withResultSource(int resultSource) {
+ mResultSource = resultSource;
return this;
}
@Override
public void log(EventEnum event) {
- boolean[] mAboveKeyboard = new boolean[mAboveKeyboardList.size()];
- for (int i = 0; i < mAboveKeyboardList.size(); i++) {
- mAboveKeyboard[i] = mAboveKeyboardList.get(i);
- }
if (IS_VERBOSE) {
- String name = (event instanceof Enum) ? ((Enum) event).name() :
- event.getId() + "";
+ String name = (event instanceof Enum) ? ((Enum) event).name() : event.getId() + "";
StringBuilder logStringBuilder = new StringBuilder("\n");
logStringBuilder.append(String.format("InstanceId:%s ", mInstanceId));
logStringBuilder.append(String.format("ImpressionEvent:%s ", name));
- logStringBuilder.append(String.format("LauncherState = %s ", mLauncherState));
- logStringBuilder.append(String.format("QueryLength = %s ", mQueryLength));
- for (int i = 0; i < mResultTypeList.length; i++) {
- logStringBuilder.append(String.format(
- "\n ResultType = %s with ResultCount = %s with is_above_keyboard = %s"
- + " with uid = %s",
- mResultTypeList[i], mResultCountList[i],
- mAboveKeyboard[i], mUidList[i]));
- }
+ logStringBuilder.append(String.format("\n\tLauncherState = %s ", mLauncherState));
+ logStringBuilder.append(String.format("\tQueryLength = %s ", mQueryLength));
+ logStringBuilder.append(String.format(
+ "\n\t ResultType = %s is_above_keyboard = %s"
+ + " uid = %s result_source = %s",
+ mResultType,
+ mAboveKeyboard, mUid, mResultSource));
+
Log.d(IMPRESSION_TAG, logStringBuilder.toString());
}
-
-
- SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_IMPRESSION_EVENT,
+ SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_IMPRESSION_EVENT_V2,
event.getId(), // event_id
mInstanceId.getId(), // instance_id
mLauncherState.getLauncherState(), // state
mQueryLength, // query_length
- //result type list
- mResultTypeList,
- // result count list
- mResultCountList,
- // above keyboard list
- mAboveKeyboard,
- // uid list
- mUidList
+ mResultType, // result type
+ mAboveKeyboard, // above keyboard
+ mUid, // uid
+ mResultSource // result source
+
);
}
}
@@ -769,8 +757,7 @@ public class StatsLogCompatManager extends StatsLogManager {
private static int getParentPageId(LauncherAtom.ItemInfo info) {
switch (info.getContainerInfo().getContainerCase()) {
case FOLDER:
- if (info.getContainerInfo().getFolder().getParentContainerCase()
- == ParentContainerCase.HOTSEAT) {
+ if (info.getContainerInfo().getFolder().getParentContainerCase() == ParentContainerCase.HOTSEAT) {
return info.getContainerInfo().getFolder().getHotseat().getIndex();
}
return info.getContainerInfo().getFolder().getWorkspace().getPageIndex();
@@ -831,11 +818,9 @@ public class StatsLogCompatManager extends StatsLogManager {
}
ContainerInfo containerInfo = info.getContainerInfo();
if (containerInfo.getContainerCase() == EXTENDED_CONTAINERS
- && containerInfo.getExtendedContainers().getContainerCase()
- == DEVICE_SEARCH_RESULT_CONTAINER
+ && containerInfo.getExtendedContainers().getContainerCase() == DEVICE_SEARCH_RESULT_CONTAINER
&& containerInfo.getExtendedContainers()
- .getDeviceSearchResultContainer().hasSearchAttributes()
- ) {
+ .getDeviceSearchResultContainer().hasSearchAttributes()) {
return searchAttributesToInt(containerInfo.getExtendedContainers()
.getDeviceSearchResultContainer().getSearchAttributes());
}
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
index 409bf9cd39..cca4d520c5 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
@@ -18,12 +18,9 @@ package com.android.quickstep.util;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.launcher3.config.FeatureFlags;
-
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
@@ -150,10 +147,6 @@ public class ActiveGestureLog {
lastEventEntries.add(eventEntry);
}
- public void clear() {
- Arrays.fill(logs, null);
- }
-
public void dump(String prefix, PrintWriter writer) {
writer.println(prefix + "ActiveGestureErrorDetector:");
for (int i = 0; i < logs.length; i++) {
diff --git a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
index a92ab2a417..8440b6c63e 100644
--- a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
+++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
@@ -15,9 +15,9 @@
*/
package com.android.quickstep.util;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.quickstep.AbsSwipeUpHandler.ALL_APPS_SHIFT_THRESHOLD;
+import static com.android.app.animation.Interpolators.DECELERATE;
+import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
@@ -34,6 +34,7 @@ import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -46,289 +47,307 @@ import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.quickstep.views.RecentsView;
/**
- * Controls an animation that can go beyond progress = 1, at which point resistance should be
- * applied. Internally, this is a wrapper around 2 {@link AnimatorPlaybackController}s, one that
- * runs from progress 0 to 1 like normal, then one that seamlessly continues that animation but
+ * Controls an animation that can go beyond progress = 1, at which point
+ * resistance should be
+ * applied. Internally, this is a wrapper around 2
+ * {@link AnimatorPlaybackController}s, one that
+ * runs from progress 0 to 1 like normal, then one that seamlessly continues
+ * that animation but
* starts applying resistance as well.
*/
public class AnimatorControllerWithResistance {
- private enum RecentsResistanceParams {
- FROM_APP(0.75f, 0.5f, 1f, false),
- FROM_APP_TO_ALL_APPS(0.75f, 0.5f, 0.8f, false),
- FROM_APP_TABLET(1f, 0.7f, 1f, true),
- FROM_APP_TO_ALL_APPS_TABLET(1f, 0.5f, 0.5f, false),
- FROM_OVERVIEW(1f, 0.75f, 0.5f, false);
+ private enum RecentsResistanceParams {
+ FROM_APP(0.75f, 0.5f, 1f, false),
+ FROM_APP_TO_ALL_APPS(1f, 0.6f, 0.8f, false),
+ FROM_APP_TABLET(1f, 0.7f, 1f, true),
+ FROM_APP_TO_ALL_APPS_TABLET(1f, 0.5f, 0.5f, false),
+ FROM_OVERVIEW(1f, 0.75f, 0.5f, false);
- RecentsResistanceParams(float scaleStartResist, float scaleMaxResist,
- float translationFactor, boolean stopScalingAtTop) {
- this.scaleStartResist = scaleStartResist;
- this.scaleMaxResist = scaleMaxResist;
- this.translationFactor = translationFactor;
- this.stopScalingAtTop = stopScalingAtTop;
- }
-
- /**
- * Start slowing down the rate of scaling down when recents view is smaller than this scale.
- */
- public final float scaleStartResist;
-
- /**
- * Recents view will reach this scale at the very end of the drag.
- */
- public final float scaleMaxResist;
-
- /**
- * How much translation to apply to RecentsView when the drag reaches the top of the screen,
- * where 0 will keep it centered and 1 will have it barely touch the top of the screen.
- */
- public final float translationFactor;
-
- /**
- * Whether to end scaling effect when the scaled down version of TaskView's top reaches the
- * non-scaled version of TaskView's top.
- */
- public final boolean stopScalingAtTop;
- }
-
- private static final TimeInterpolator RECENTS_SCALE_RESIST_INTERPOLATOR = DEACCEL;
- private static final TimeInterpolator RECENTS_TRANSLATE_RESIST_INTERPOLATOR = LINEAR;
-
- private static final Rect TEMP_RECT = new Rect();
-
- private final AnimatorPlaybackController mNormalController;
- private final AnimatorPlaybackController mResistanceController;
-
- // Initialize to -1 so the first 0 gets applied.
- private float mLastNormalProgress = -1;
- private float mLastResistProgress;
-
- public AnimatorControllerWithResistance(AnimatorPlaybackController normalController,
- AnimatorPlaybackController resistanceController) {
- mNormalController = normalController;
- mResistanceController = resistanceController;
- }
-
- public AnimatorPlaybackController getNormalController() {
- return mNormalController;
- }
-
- /**
- * Applies the current progress of the animation.
- * @param progress From 0 to maxProgress, where 1 is the target we are animating towards.
- * @param maxProgress > 1, this is where the resistance will be applied.
- */
- public void setProgress(float progress, float maxProgress) {
- float normalProgress = Utilities.boundToRange(progress, 0, 1);
- if (normalProgress != mLastNormalProgress) {
- mLastNormalProgress = normalProgress;
- mNormalController.setPlayFraction(normalProgress);
- }
- if (maxProgress <= 1) {
- return;
- }
- float resistProgress = progress <= 1 ? 0 : Utilities.getProgress(progress, 1, maxProgress);
- if (resistProgress != mLastResistProgress) {
- mLastResistProgress = resistProgress;
- mResistanceController.setPlayFraction(resistProgress);
- }
- }
-
- /**
- * Applies resistance to recents when swiping up past its target position.
- * @param normalController The controller to run from 0 to 1 before this resistance applies.
- * @param context Used to compute start and end values.
- * @param recentsOrientedState Used to compute start and end values.
- * @param dp Used to compute start and end values.
- * @param scaleTarget The target for the scaleProperty.
- * @param scaleProperty Animate the value to change the scale of the window/recents view.
- * @param translationTarget The target for the translationProperty.
- * @param translationProperty Animate the value to change the translation of the recents view.
- */
- public static AnimatorControllerWithResistance createForRecents(
- AnimatorPlaybackController normalController, Context context,
- RecentsOrientedState recentsOrientedState, DeviceProfile dp, SCALE scaleTarget,
- FloatProperty scaleProperty, TRANSLATION translationTarget,
- FloatProperty translationProperty) {
-
- RecentsParams params = new RecentsParams(context, recentsOrientedState, dp, scaleTarget,
- scaleProperty, translationTarget, translationProperty);
- PendingAnimation resistAnim = createRecentsResistanceAnim(params);
-
- // Apply All Apps animation during the resistance animation.
- if (recentsOrientedState.getActivityInterface().allowAllAppsFromOverview()) {
- StatefulActivity activity =
- recentsOrientedState.getActivityInterface().getCreatedActivity();
- if (activity != null) {
- StateManager stateManager = activity.getStateManager();
- if (stateManager.isInStableState(LauncherState.BACKGROUND_APP)
- && stateManager.isInTransition()) {
-
- // Calculate the resistance progress threshold where All Apps will trigger.
- float threshold = getAllAppsThreshold(context, recentsOrientedState, dp);
-
- StateAnimationConfig config = new StateAnimationConfig();
- AllAppsSwipeController.applyOverviewToAllAppsAnimConfig(dp, config, threshold);
- AnimatorSet allAppsAnimator = stateManager.createAnimationToNewWorkspace(
- LauncherState.ALL_APPS, config).getTarget();
- resistAnim.add(allAppsAnimator);
+ RecentsResistanceParams(float scaleStartResist, float scaleMaxResist,
+ float translationFactor, boolean stopScalingAtTop) {
+ this.scaleStartResist = scaleStartResist;
+ this.scaleMaxResist = scaleMaxResist;
+ this.translationFactor = translationFactor;
+ this.stopScalingAtTop = stopScalingAtTop;
}
- }
+
+ /**
+ * Start slowing down the rate of scaling down when recents view is smaller than
+ * this scale.
+ */
+ public final float scaleStartResist;
+
+ /**
+ * Recents view will reach this scale at the very end of the drag.
+ */
+ public final float scaleMaxResist;
+
+ /**
+ * How much translation to apply to RecentsView when the drag reaches the top of
+ * the screen,
+ * where 0 will keep it centered and 1 will have it barely touch the top of the
+ * screen.
+ */
+ public final float translationFactor;
+
+ /**
+ * Whether to end scaling effect when the scaled down version of TaskView's top
+ * reaches the
+ * non-scaled version of TaskView's top.
+ */
+ public final boolean stopScalingAtTop;
}
- AnimatorPlaybackController resistanceController = resistAnim.createPlaybackController();
- return new AnimatorControllerWithResistance(normalController, resistanceController);
- }
+ private static final TimeInterpolator RECENTS_SCALE_RESIST_INTERPOLATOR = DECELERATE;
+ private static final TimeInterpolator RECENTS_TRANSLATE_RESIST_INTERPOLATOR = LINEAR;
- private static float getAllAppsThreshold(Context context,
- RecentsOrientedState recentsOrientedState, DeviceProfile dp) {
- int transitionDragLength =
- recentsOrientedState.getActivityInterface().getSwipeUpDestinationAndLength(
- dp, context, TEMP_RECT,
- recentsOrientedState.getOrientationHandler());
- float dragLengthFactor = (float) dp.heightPx / transitionDragLength;
- // -1s are because 0-1 is reserved for the normal transition.
- return (ALL_APPS_SHIFT_THRESHOLD - 1) / (dragLengthFactor - 1);
- }
+ private static final Rect TEMP_RECT = new Rect();
- /**
- * Creates the resistance animation for {@link #createForRecents}, or can be used separately
- * when starting from recents, i.e. {@link #createRecentsResistanceFromOverviewAnim}.
- */
- public static PendingAnimation createRecentsResistanceAnim(
- RecentsParams params) {
- Rect startRect = new Rect();
- PagedOrientationHandler orientationHandler = params.recentsOrientedState
- .getOrientationHandler();
- params.recentsOrientedState.getActivityInterface()
- .calculateTaskSize(params.context, params.dp, startRect, orientationHandler);
- long distanceToCover = startRect.bottom;
- PendingAnimation resistAnim = params.resistAnim != null
- ? params.resistAnim
- : new PendingAnimation(distanceToCover * 2);
+ private final AnimatorPlaybackController mNormalController;
+ private final AnimatorPlaybackController mResistanceController;
- PointF pivot = new PointF();
- float fullscreenScale = params.recentsOrientedState.getFullScreenScaleAndPivot(
- startRect, params.dp, pivot);
+ // Initialize to -1 so the first 0 gets applied.
+ private float mLastNormalProgress = -1;
+ private float mLastResistProgress;
- // Compute where the task view would be based on the end scale.
- RectF endRectF = new RectF(startRect);
- Matrix temp = new Matrix();
- temp.setScale(params.resistanceParams.scaleMaxResist,
- params.resistanceParams.scaleMaxResist, pivot.x, pivot.y);
- temp.mapRect(endRectF);
- // Translate such that the task view touches the top of the screen when drag does.
- float endTranslation = endRectF.top
- * orientationHandler.getSecondaryTranslationDirectionFactor()
- * params.resistanceParams.translationFactor;
- resistAnim.addFloat(params.translationTarget, params.translationProperty,
- params.startTranslation, endTranslation, RECENTS_TRANSLATE_RESIST_INTERPOLATOR);
-
- float prevScaleRate = (fullscreenScale - params.startScale)
- / (params.dp.heightPx - startRect.bottom);
- // This is what the scale would be at the end of the drag if we didn't apply resistance.
- float endScale = params.startScale - prevScaleRate * distanceToCover;
- // Create an interpolator that resists the scale so the scale doesn't get smaller than
- // RECENTS_SCALE_MAX_RESIST.
- float startResist = Utilities.getProgress(params.resistanceParams.scaleStartResist,
- params.startScale, endScale);
- float maxResist = Utilities.getProgress(params.resistanceParams.scaleMaxResist,
- params.startScale, endScale);
- float stopResist =
- params.resistanceParams.stopScalingAtTop ? 1f - startRect.top / endRectF.top : 1f;
- final TimeInterpolator scaleInterpolator = t -> {
- if (t < startResist) {
- return t;
- }
- if (t > stopResist) {
- return maxResist;
- }
- float resistProgress = Utilities.getProgress(t, startResist, stopResist);
- resistProgress = RECENTS_SCALE_RESIST_INTERPOLATOR.getInterpolation(resistProgress);
- return startResist + resistProgress * (maxResist - startResist);
- };
- resistAnim.addFloat(params.scaleTarget, params.scaleProperty, params.startScale, endScale,
- scaleInterpolator);
-
- return resistAnim;
- }
-
- /**
- * Helper method to update or create a PendingAnimation suitable for animating
- * a RecentsView interaction that started from the overview state.
- */
- public static PendingAnimation createRecentsResistanceFromOverviewAnim(
- Launcher launcher, @Nullable PendingAnimation resistanceAnim) {
- RecentsView recentsView = launcher.getOverviewPanel();
- RecentsParams params = new RecentsParams(launcher, recentsView.getPagedViewOrientedState(),
- launcher.getDeviceProfile(), recentsView, RECENTS_SCALE_PROPERTY, recentsView,
- TASK_SECONDARY_TRANSLATION)
- .setResistAnim(resistanceAnim)
- .setResistanceParams(RecentsResistanceParams.FROM_OVERVIEW)
- .setStartScale(recentsView.getScaleX());
- return createRecentsResistanceAnim(params);
- }
-
- /**
- * Params to compute resistance when scaling/translating recents.
- */
- private static class RecentsParams {
- // These are all required and can't have default values, hence are final.
- public final Context context;
- public final RecentsOrientedState recentsOrientedState;
- public final DeviceProfile dp;
- public final SCALE scaleTarget;
- public final FloatProperty scaleProperty;
- public final TRANSLATION translationTarget;
- public final FloatProperty translationProperty;
-
- // These are not required, or can have a default value that is generally correct.
- @Nullable public PendingAnimation resistAnim = null;
- public RecentsResistanceParams resistanceParams;
- public float startScale = 1f;
- public float startTranslation = 0f;
-
- private RecentsParams(Context context, RecentsOrientedState recentsOrientedState,
- DeviceProfile dp, SCALE scaleTarget, FloatProperty scaleProperty,
- TRANSLATION translationTarget, FloatProperty translationProperty) {
- this.context = context;
- this.recentsOrientedState = recentsOrientedState;
- this.dp = dp;
- this.scaleTarget = scaleTarget;
- this.scaleProperty = scaleProperty;
- this.translationTarget = translationTarget;
- this.translationProperty = translationProperty;
- if (dp.isTablet) {
- resistanceParams =
- recentsOrientedState.getActivityInterface().allowAllAppsFromOverview()
- ? RecentsResistanceParams.FROM_APP_TO_ALL_APPS_TABLET
- : RecentsResistanceParams.FROM_APP_TABLET;
- } else {
- resistanceParams =
- recentsOrientedState.getActivityInterface().allowAllAppsFromOverview()
- ? RecentsResistanceParams.FROM_APP_TO_ALL_APPS
- : RecentsResistanceParams.FROM_APP;
- }
+ public AnimatorControllerWithResistance(AnimatorPlaybackController normalController,
+ AnimatorPlaybackController resistanceController) {
+ mNormalController = normalController;
+ mResistanceController = resistanceController;
}
- private RecentsParams setResistAnim(PendingAnimation resistAnim) {
- this.resistAnim = resistAnim;
- return this;
+ public AnimatorPlaybackController getNormalController() {
+ return mNormalController;
}
- private RecentsParams setResistanceParams(RecentsResistanceParams resistanceParams) {
- this.resistanceParams = resistanceParams;
- return this;
+ /**
+ * Applies the current progress of the animation.
+ *
+ * @param progress From 0 to maxProgress, where 1 is the target we are
+ * animating towards.
+ * @param maxProgress > 1, this is where the resistance will be applied.
+ */
+ public void setProgress(float progress, float maxProgress) {
+ float normalProgress = Utilities.boundToRange(progress, 0, 1);
+ if (normalProgress != mLastNormalProgress) {
+ mLastNormalProgress = normalProgress;
+ mNormalController.setPlayFraction(normalProgress);
+ }
+ if (maxProgress <= 1) {
+ return;
+ }
+ float resistProgress = progress <= 1 ? 0 : Utilities.getProgress(progress, 1, maxProgress);
+ if (resistProgress != mLastResistProgress) {
+ mLastResistProgress = resistProgress;
+ mResistanceController.setPlayFraction(resistProgress);
+ }
}
- private RecentsParams setStartScale(float startScale) {
- this.startScale = startScale;
- return this;
+ /**
+ * Applies resistance to recents when swiping up past its target position.
+ *
+ * @param normalController The controller to run from 0 to 1 before this
+ * resistance applies.
+ * @param context Used to compute start and end values.
+ * @param recentsOrientedState Used to compute start and end values.
+ * @param dp Used to compute start and end values.
+ * @param scaleTarget The target for the scaleProperty.
+ * @param scaleProperty Animate the value to change the scale of the
+ * window/recents view.
+ * @param translationTarget The target for the translationProperty.
+ * @param translationProperty Animate the value to change the translation of
+ * the recents view.
+ */
+ public static AnimatorControllerWithResistance createForRecents(
+ AnimatorPlaybackController normalController, Context context,
+ RecentsOrientedState recentsOrientedState, DeviceProfile dp, SCALE scaleTarget,
+ FloatProperty scaleProperty, TRANSLATION translationTarget,
+ FloatProperty translationProperty) {
+
+ RecentsParams params = new RecentsParams(context, recentsOrientedState, dp, scaleTarget,
+ scaleProperty, translationTarget, translationProperty);
+ PendingAnimation resistAnim = createRecentsResistanceAnim(params);
+
+ // Apply All Apps animation during the resistance animation.
+ if (recentsOrientedState.getActivityInterface().allowAllAppsFromOverview()) {
+ StatefulActivity activity = recentsOrientedState.getActivityInterface().getCreatedActivity();
+ if (activity != null) {
+ StateManager