instanceIds =
+ LogUtils.getShellShareableInstanceId();
+ mStatsLogManager.logger()
+ .withItemInfo(item)
+ .withInstanceId(instanceIds.second)
+ .log(getLogEventForPosition(side));
+
+ if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+ SystemUiProxy.INSTANCE.get(mContext).startShortcut(
+ info.getIntent().getPackage(),
+ info.getDeepShortcutId(),
+ side,
+ /* bundleOpts= */ null,
+ info.user,
+ instanceIds.first);
+ } else {
+ SystemUiProxy.INSTANCE.get(mContext).startIntent(
+ mLauncherApps.getMainActivityLaunchIntent(
+ item.getIntent().getComponent(),
+ /* startActivityOptions= */null,
+ item.user),
+ new Intent(), side, null, instanceIds.first);
+ }
+ return true;
+ } else if (action == DEEP_SHORTCUTS || action == SHORTCUTS_AND_NOTIFICATIONS) {
+ mContext.showPopupMenuForIcon((BubbleTextView) host);
+
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected boolean beginAccessibleDrag(View item, ItemInfo info, boolean fromKeyboard) {
+ return false;
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarSpringOnStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarSpringOnStashController.java
new file mode 100644
index 0000000000..d65b5c0f61
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarSpringOnStashController.java
@@ -0,0 +1,97 @@
+/*
+ * 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.anim.AnimatedFloat.VALUE;
+
+import android.animation.ValueAnimator;
+
+import androidx.annotation.Nullable;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.launcher3.R;
+import com.android.launcher3.anim.AnimatedFloat;
+import com.android.launcher3.anim.SpringAnimationBuilder;
+import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController;
+import com.android.launcher3.util.DisplayController;
+
+import java.io.PrintWriter;
+
+/**
+ * Manages the spring animation when stashing the transient taskbar.
+ */
+public class TaskbarSpringOnStashController implements LoggableTaskbarController {
+
+ private final TaskbarActivityContext mContext;
+ private TaskbarControllers mControllers;
+ private final AnimatedFloat mTranslationForStash = new AnimatedFloat(
+ this::updateTranslationYForStash);
+
+ private final boolean mIsTransientTaskbar;
+
+ private final float mStartVelocityPxPerS;
+
+ public TaskbarSpringOnStashController(TaskbarActivityContext context) {
+ mContext = context;
+ mIsTransientTaskbar = DisplayController.isTransientTaskbar(mContext);
+ mStartVelocityPxPerS = context.getResources()
+ .getDimension(R.dimen.transient_taskbar_stash_spring_velocity_dp_per_s);
+ }
+
+ /**
+ * Initialization method.
+ */
+ public void init(TaskbarControllers controllers) {
+ mControllers = controllers;
+ }
+
+ private void updateTranslationYForStash() {
+ if (!mIsTransientTaskbar) {
+ return;
+ }
+
+ float transY = mTranslationForStash.value;
+ mControllers.stashedHandleViewController.setTranslationYForStash(transY);
+ mControllers.taskbarViewController.setTranslationYForStash(transY);
+ mControllers.taskbarDragLayerController.setTranslationYForStash(transY);
+ }
+
+ /**
+ * Returns a spring animation to be used when stashing the transient taskbar.
+ */
+ public @Nullable ValueAnimator createSpringToStash() {
+ if (!mIsTransientTaskbar) {
+ return null;
+ }
+ return new SpringAnimationBuilder(mContext)
+ .setStartValue(mTranslationForStash.value)
+ .setEndValue(0)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
+ .setStiffness(SpringForce.STIFFNESS_LOW)
+ .setStartVelocity(mStartVelocityPxPerS)
+ .build(mTranslationForStash, VALUE);
+ }
+
+
+ @Override
+ public void dumpLogs(String prefix, PrintWriter pw) {
+ pw.println(prefix + "TaskbarSpringOnStashController:");
+
+ pw.println(prefix + "\tmTranslationForStash=" + mTranslationForStash.value);
+ pw.println(prefix + "\tmStartVelocityPxPerS=" + mStartVelocityPxPerS);
+ }
+}
+
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 7f7585000c..69ea9fd03a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -16,58 +16,124 @@
package com.android.launcher3.taskbar;
import static android.view.HapticFeedbackConstants.LONG_PRESS;
+import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
+import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
+import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
+import static com.android.launcher3.anim.Interpolators.INSTANT;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.config.FeatureFlags.FORCE_PERSISTENT_TASKBAR;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_HIDE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_SHOW;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_HIDE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_SHOW;
+import static com.android.launcher3.taskbar.TaskbarKeyguardController.MASK_ANY_SYSUI_LOCKED;
+import static com.android.launcher3.taskbar.TaskbarManager.SYSTEM_ACTION_ID_TASKBAR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
+import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
-import android.annotation.Nullable;
+import android.app.RemoteAction;
import android.content.SharedPreferences;
-import android.content.res.Resources;
+import android.graphics.drawable.Icon;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.InsetsController;
+import android.view.View;
import android.view.ViewConfiguration;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.Interpolator;
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.jank.InteractionJankMonitor;
+import com.android.launcher3.Alarm;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
-import com.android.quickstep.AnimatedFloat;
+import com.android.launcher3.anim.AnimatedFloat;
+import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
import com.android.quickstep.SystemUiProxy;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.StringJoiner;
import java.util.function.IntPredicate;
/**
* Coordinates between controllers such as TaskbarViewController and StashedHandleViewController to
* create a cohesive animation between stashed/unstashed states.
*/
-public class TaskbarStashController {
+public class TaskbarStashController implements TaskbarControllers.LoggableTaskbarController {
+ private static final String TAG = TaskbarStashController.class.getSimpleName();
+ private static final boolean DEBUG = false;
public static final int FLAG_IN_APP = 1 << 0;
public static final int FLAG_STASHED_IN_APP_MANUAL = 1 << 1; // long press, persisted
- public static final int FLAG_STASHED_IN_APP_PINNED = 1 << 2; // app pinning
- public static final int FLAG_STASHED_IN_APP_EMPTY = 1 << 3; // no hotseat icons
- public static final int FLAG_STASHED_IN_APP_SETUP = 1 << 4; // setup wizard and AllSetActivity
- public static final int FLAG_STASHED_IN_APP_IME = 1 << 5; // IME is visible
- public static final int FLAG_IN_STASHED_LAUNCHER_STATE = 1 << 6;
+ public static final int FLAG_STASHED_IN_APP_SYSUI = 1 << 2; // shade open, ...
+ public static final int FLAG_STASHED_IN_APP_SETUP = 1 << 3; // setup wizard and AllSetActivity
+ public static final int FLAG_STASHED_IN_APP_IME = 1 << 4; // IME is visible
+ public static final int FLAG_IN_STASHED_LAUNCHER_STATE = 1 << 5;
+ public static final int FLAG_STASHED_IN_TASKBAR_ALL_APPS = 1 << 6; // All apps is visible.
+ 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_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.
private static final int FLAGS_STASHED_IN_APP = FLAG_STASHED_IN_APP_MANUAL
- | FLAG_STASHED_IN_APP_PINNED | FLAG_STASHED_IN_APP_EMPTY | FLAG_STASHED_IN_APP_SETUP
- | FLAG_STASHED_IN_APP_IME;
+ | 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;
+
+ private static final int FLAGS_STASHED_IN_APP_IGNORING_IME =
+ FLAGS_STASHED_IN_APP & ~FLAG_STASHED_IN_APP_IME;
// If any of these flags are enabled, inset apps by our stashed height instead of our unstashed
// height. This way the reported insets are consistent even during transitions out of the app.
- // Currently any flag that causes us to stash in an app is included, except for IME since that
- // covers the underlying app anyway and thus the app shouldn't change insets.
+ // 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_APP_IME & ~FLAG_STASHED_IN_TASKBAR_ALL_APPS;
+
+ // If any of these flags are enabled, the taskbar must be stashed.
+ private static final int FLAGS_FORCE_STASHED = FLAG_STASHED_SYSUI | FLAG_STASHED_DEVICE_LOCKED
+ | FLAG_STASHED_IN_TASKBAR_ALL_APPS | FLAG_STASHED_SMALL_SCREEN;
/**
* How long to stash/unstash when manually invoked via long press.
+ *
+ * Use {@link #getStashDuration()} to query duration
*/
- public static final long TASKBAR_STASH_DURATION = 300;
+ private static final long TASKBAR_STASH_DURATION =
+ InsetsController.ANIMATION_DURATION_RESIZE;
+
+ /**
+ * How long to stash/unstash transient taskbar.
+ *
+ * Use {@link #getStashDuration()} to query duration.
+ */
+ private static final long TRANSIENT_TASKBAR_STASH_DURATION = 417;
/**
* How long to stash/unstash when keyboard is appearing/disappearing.
@@ -77,7 +143,7 @@ public class TaskbarStashController {
/**
* The scale TaskbarView animates to when being stashed.
*/
- private static final float STASHED_TASKBAR_SCALE = 0.5f;
+ protected static final float STASHED_TASKBAR_SCALE = 0.5f;
/**
* How long the hint animation plays, starting on motion down.
@@ -85,6 +151,21 @@ public class TaskbarStashController {
private static final long TASKBAR_HINT_STASH_DURATION =
ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT;
+ /**
+ * How long to delay the icon/stash handle alpha.
+ */
+ private static final long TASKBAR_STASH_ALPHA_START_DELAY = 33;
+
+ /**
+ * How long the icon/stash handle alpha animation plays.
+ */
+ 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.
+ */
+ 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.
*/
@@ -105,6 +186,43 @@ public class TaskbarStashController {
*/
private static final boolean DEFAULT_STASHED_PREF = false;
+ // 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
+ // asynchronously.
+ private static final long UNLOCK_TRANSITION_MEMOIZATION_MS = 200;
+
+ /**
+ * The default stash animation, morphing the taskbar into the navbar.
+ */
+ private static final int TRANSITION_DEFAULT = 0;
+ /**
+ * 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
+ * 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
+ * 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,
+ TRANSITION_HOME_TO_APP,
+ TRANSITION_HANDLE_FADE,
+ TRANSITION_UNSTASH_SUW_MANUAL,
+ })
+ private @interface StashAnimation {}
+
private final TaskbarActivityContext mActivity;
private final SharedPreferences mPrefs;
private final int mStashedHeight;
@@ -117,12 +235,13 @@ public class TaskbarStashController {
private AnimatedFloat mTaskbarBackgroundOffset;
private AnimatedFloat mTaskbarImeBgAlpha;
// TaskbarView icon properties.
- private AlphaProperty mIconAlphaForStash;
+ private MultiProperty mIconAlphaForStash;
private AnimatedFloat mIconScaleForStash;
private AnimatedFloat mIconTranslationYForStash;
// Stashed handle properties.
- private AlphaProperty mTaskbarStashedHandleAlpha;
+ private MultiProperty mTaskbarStashedHandleAlpha;
private AnimatedFloat mTaskbarStashedHandleHintScale;
+ private final AccessibilityManager mAccessibilityManager;
/** Whether we are currently visually stashed (might change based on launcher state). */
private boolean mIsStashed = false;
@@ -131,55 +250,93 @@ public class TaskbarStashController {
private @Nullable AnimatorSet mAnimator;
private boolean mIsSystemGestureInProgress;
private boolean mIsImeShowing;
+ private boolean mIsImeSwitcherShowing;
+
+ private boolean mEnableManualStashingDuringTests = false;
+
+ private final Alarm mTimeoutAlarm = new Alarm();
+ private boolean mEnableBlockingTimeoutDuringTests = false;
// Evaluate whether the handle should be stashed
private final StatePropertyHolder mStatePropertyHolder = new StatePropertyHolder(
flags -> {
- boolean inApp = hasAnyFlag(flags, FLAG_IN_APP);
+ boolean inApp = hasAnyFlag(flags, FLAGS_IN_APP);
boolean stashedInApp = hasAnyFlag(flags, FLAGS_STASHED_IN_APP);
boolean stashedLauncherState = hasAnyFlag(flags, FLAG_IN_STASHED_LAUNCHER_STATE);
- return (inApp && stashedInApp) || (!inApp && stashedLauncherState);
+ boolean forceStashed = hasAnyFlag(flags, FLAGS_FORCE_STASHED);
+ return (inApp && stashedInApp) || (!inApp && stashedLauncherState) || forceStashed;
});
+ private boolean mIsTaskbarSystemActionRegistered = false;
+ private TaskbarSharedState mTaskbarSharedState;
+
public TaskbarStashController(TaskbarActivityContext activity) {
mActivity = activity;
- mPrefs = Utilities.getPrefs(mActivity);
- final Resources resources = mActivity.getResources();
- mStashedHeight = resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size);
+ mPrefs = LauncherPrefs.getPrefs(mActivity);
mSystemUiProxy = SystemUiProxy.INSTANCE.get(activity);
- mUnstashedHeight = mActivity.getDeviceProfile().taskbarSize;
+ mAccessibilityManager = mActivity.getSystemService(AccessibilityManager.class);
+
+ mUnstashedHeight = mActivity.getDeviceProfile().taskbarHeight;
+ mStashedHeight = mActivity.getDeviceProfile().stashedTaskbarHeight;
}
- public void init(TaskbarControllers controllers, TaskbarSharedState sharedState) {
+ /**
+ * Show Taskbar upon receiving broadcast
+ */
+ public void showTaskbarFromBroadcast() {
+ // If user is in middle of taskbar education handle go to next step of education
+ if (mControllers.taskbarEduTooltipController.isBeforeTooltipFeaturesStep()) {
+ mControllers.taskbarEduTooltipController.hide();
+ mControllers.taskbarEduTooltipController.maybeShowFeaturesEdu();
+ }
+ updateAndAnimateTransientTaskbar(false);
+ }
+
+ /**
+ * Initializes the controller
+ */
+ public void init(
+ TaskbarControllers controllers,
+ boolean setupUIVisible,
+ TaskbarSharedState sharedState) {
mControllers = controllers;
+ mTaskbarSharedState = sharedState;
TaskbarDragLayerController dragLayerController = controllers.taskbarDragLayerController;
mTaskbarBackgroundOffset = dragLayerController.getTaskbarBackgroundOffset();
mTaskbarImeBgAlpha = dragLayerController.getImeBgTaskbar();
TaskbarViewController taskbarViewController = controllers.taskbarViewController;
- mIconAlphaForStash = taskbarViewController.getTaskbarIconAlpha().getProperty(
+ mIconAlphaForStash = taskbarViewController.getTaskbarIconAlpha().get(
TaskbarViewController.ALPHA_INDEX_STASH);
mIconScaleForStash = taskbarViewController.getTaskbarIconScaleForStash();
mIconTranslationYForStash = taskbarViewController.getTaskbarIconTranslationYForStash();
StashedHandleViewController stashedHandleController =
controllers.stashedHandleViewController;
- mTaskbarStashedHandleAlpha = stashedHandleController.getStashedHandleAlpha().getProperty(
+ mTaskbarStashedHandleAlpha = stashedHandleController.getStashedHandleAlpha().get(
StashedHandleViewController.ALPHA_INDEX_STASHED);
mTaskbarStashedHandleHintScale = stashedHandleController.getStashedHandleHintScale();
- boolean isManuallyStashedInApp = supportsManualStashing()
+ 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).
+ boolean isManuallyStashedInApp = supportsVisualStashing()
+ && !isTransientTaskbar
+ && !FORCE_PERSISTENT_TASKBAR.get()
&& mPrefs.getBoolean(SHARED_PREFS_STASHED_KEY, DEFAULT_STASHED_PREF);
- boolean isInSetup = !mActivity.isUserSetupComplete() || sharedState.setupUIVisible;
+ boolean isInSetup = !mActivity.isUserSetupComplete() || setupUIVisible;
updateStateForFlag(FLAG_STASHED_IN_APP_MANUAL, isManuallyStashedInApp);
- // TODO(b/204384193): Temporarily disable SUW specific logic
- // updateStateForFlag(FLAG_STASHED_IN_APP_SETUP, isInSetup);
- if (isInSetup) {
- // Update the in-app state to ensure isStashed() reflects right state during SUW
- updateStateForFlag(FLAG_IN_APP, true);
- }
- applyState();
+ updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, isTransientTaskbar);
+ updateStateForFlag(FLAG_STASHED_IN_APP_SETUP, isInSetup);
+ 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.
+ updateStateForFlag(FLAG_IN_APP, true);
+ applyState(/* duration = */ 0);
notifyStashChange(/* visible */ false, /* stashed */ isStashedInApp());
}
@@ -188,30 +345,58 @@ public class TaskbarStashController {
* Returns whether the taskbar can visually stash into a handle based on the current device
* state.
*/
- private boolean supportsVisualStashing() {
- return !mActivity.isThreeButtonNav();
+ public boolean supportsVisualStashing() {
+ return !mActivity.isThreeButtonNav() && mControllers.uiController.supportsVisualStashing();
}
/**
* Returns whether the user can manually stash the taskbar based on the current device state.
*/
- private boolean supportsManualStashing() {
+ protected boolean supportsManualStashing() {
+ if (FORCE_PERSISTENT_TASKBAR.get()) {
+ return false;
+ }
return supportsVisualStashing()
- && (!Utilities.IS_RUNNING_IN_TEST_HARNESS || supportsStashingForTests());
+ && isInApp()
+ && (!Utilities.isRunningInTestHarness() || mEnableManualStashingDuringTests)
+ && !DisplayController.isTransientTaskbar(mActivity);
}
- private boolean supportsStashingForTests() {
- // TODO: enable this for tests that specifically check stash/unstash behavior.
- return false;
+ /**
+ * Enables support for manual stashing. This should only be used to add this functionality
+ * to Launcher specific tests.
+ */
+ @VisibleForTesting
+ public void enableManualStashingDuringTests(boolean enableManualStashing) {
+ mEnableManualStashingDuringTests = enableManualStashing;
+ }
+
+ /**
+ * Enables the auto timeout for taskbar stashing. This method should only be used for taskbar
+ * testing.
+ */
+ @VisibleForTesting
+ public void enableBlockingTimeoutDuringTests(boolean enableBlockingTimeout) {
+ mEnableBlockingTimeoutDuringTests = enableBlockingTimeout;
}
/**
* Sets the flag indicating setup UI is visible
*/
protected void setSetupUIVisible(boolean isVisible) {
- updateStateForFlag(FLAG_STASHED_IN_APP_SETUP,
- isVisible || !mActivity.isUserSetupComplete());
- applyState();
+ boolean hideTaskbar = isVisible || !mActivity.isUserSetupComplete();
+ updateStateForFlag(FLAG_IN_SETUP, hideTaskbar);
+ updateStateForFlag(FLAG_STASHED_IN_APP_SETUP, hideTaskbar);
+ applyState(hideTaskbar ? 0 : getStashDuration());
+ }
+
+ /**
+ * Returns how long the stash/unstash animation should play.
+ */
+ public long getStashDuration() {
+ return DisplayController.isTransientTaskbar(mActivity)
+ ? TRANSIENT_TASKBAR_STASH_DURATION
+ : TASKBAR_STASH_DURATION;
}
/**
@@ -228,11 +413,25 @@ public class TaskbarStashController {
return hasAnyFlag(FLAGS_STASHED_IN_APP);
}
+ /**
+ * Returns whether the taskbar should be stashed in apps regardless of the IME visibility.
+ */
+ public boolean isStashedInAppIgnoringIme() {
+ return hasAnyFlag(FLAGS_STASHED_IN_APP_IGNORING_IME);
+ }
+
/**
* Returns whether the taskbar should be stashed in the current LauncherState.
*/
public boolean isInStashedLauncherState() {
- return hasAnyFlag(FLAG_IN_STASHED_LAUNCHER_STATE) && supportsVisualStashing();
+ return (hasAnyFlag(FLAG_IN_STASHED_LAUNCHER_STATE) && supportsVisualStashing());
+ }
+
+ /**
+ * @return {@code true} if we're not on a large screen AND using gesture nav
+ */
+ private boolean isPhoneMode() {
+ return TaskbarManager.isPhoneMode(mActivity.getDeviceProfile());
}
private boolean hasAnyFlag(int flagMask) {
@@ -245,28 +444,100 @@ public class TaskbarStashController {
/**
- * Returns whether the taskbar is currently visible and in an app.
+ * Returns whether the taskbar is currently visible and not in the process of being stashed.
*/
- public boolean isInAppAndNotStashed() {
- return !mIsStashed && (mState & FLAG_IN_APP) != 0;
+ public boolean isTaskbarVisibleAndNotStashing() {
+ return !mIsStashed && mControllers.taskbarViewController.areIconsVisible();
+ }
+
+ public boolean isInApp() {
+ return hasAnyFlag(FLAGS_IN_APP);
+ }
+
+ /**
+ * Returns the height that taskbar will be touchable.
+ */
+ public int getTouchableHeight() {
+ return mIsStashed
+ ? mStashedHeight
+ : (mUnstashedHeight + mActivity.getDeviceProfile().taskbarBottomMargin);
}
/**
* Returns the height that taskbar will inset when inside apps.
+ * @see android.view.WindowInsets.Type#navigationBars()
+ * @see android.view.WindowInsets.Type#systemBars()
*/
public int getContentHeightToReportToApps() {
- if (hasAnyFlag(FLAGS_REPORT_STASHED_INSETS_TO_APP)) {
- boolean isAnimating = mAnimator != null && mAnimator.isStarted();
- return mControllers.stashedHandleViewController.isStashedHandleVisible() || isAnimating
- ? mStashedHeight : 0;
+ if ((isPhoneMode() && !mActivity.isThreeButtonNav())
+ || DisplayController.isTransientTaskbar(mActivity)) {
+ return getStashedHeight();
}
+
+ if (supportsVisualStashing() && hasAnyFlag(FLAGS_REPORT_STASHED_INSETS_TO_APP)) {
+ DeviceProfile dp = mActivity.getDeviceProfile();
+ if (hasAnyFlag(FLAG_STASHED_IN_APP_SETUP) && dp.isTaskbarPresent) {
+ // We always show the back button in SUW but in portrait the SUW layout may not
+ // be wide enough to support overlapping the nav bar with its content.
+ // We're sending different res values in portrait vs landscape
+ return mActivity.getResources().getDimensionPixelSize(R.dimen.taskbar_suw_insets);
+ }
+ boolean isAnimating = mAnimator != null && mAnimator.isStarted();
+ if (!mControllers.stashedHandleViewController.isStashedHandleVisible()
+ && isInApp()
+ && !isAnimating) {
+ // 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;
+ }
+ return mStashedHeight;
+ }
+
return mUnstashedHeight;
}
+ /**
+ * Returns the height that taskbar will inset when inside apps.
+ * @see android.view.WindowInsets.Type#tappableElement()
+ */
+ public int getTappableHeightToReportToApps() {
+ int contentHeight = getContentHeightToReportToApps();
+ return contentHeight <= mStashedHeight ? 0 : contentHeight;
+ }
+
public int getStashedHeight() {
return mStashedHeight;
}
+ /**
+ * Stash or unstashes the transient taskbar, using the default TASKBAR_STASH_DURATION.
+ */
+ public void updateAndAnimateTransientTaskbar(boolean stash) {
+ updateAndAnimateTransientTaskbar(stash, TASKBAR_STASH_DURATION);
+ }
+
+ /**
+ * Stash or unstashes the transient taskbar.
+ */
+ public void updateAndAnimateTransientTaskbar(boolean stash, long duration) {
+ if (!DisplayController.isTransientTaskbar(mActivity)) {
+ return;
+ }
+
+ if (stash && mControllers.taskbarAutohideSuspendController.isSuspended()
+ && !mControllers.taskbarAutohideSuspendController
+ .isSuspendedForTransientTaskbarInOverview()) {
+ // Avoid stashing if autohide is currently suspended.
+ return;
+ }
+
+ if (hasAnyFlag(FLAG_STASHED_IN_APP_AUTO) != stash) {
+ updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, stash);
+ applyState();
+ }
+ }
+
/**
* Should be called when long pressing the nav region when taskbar is present.
* @return Whether taskbar was stashed and now is unstashed.
@@ -277,6 +548,9 @@ public class TaskbarStashController {
// taskbar, we use an OnLongClickListener on TaskbarView instead.
return false;
}
+ if (!canCurrentlyManuallyUnstash()) {
+ return false;
+ }
if (updateAndAnimateIsManuallyStashedInApp(false)) {
mControllers.taskbarActivityContext.getDragLayer().performHapticFeedback(LONG_PRESS);
return true;
@@ -284,6 +558,16 @@ public class TaskbarStashController {
return false;
}
+ /**
+ * 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);
+ }
+
/**
* 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.
@@ -301,34 +585,95 @@ public class TaskbarStashController {
return false;
}
+ /**
+ * 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
+ * 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) {
+ createAnimToIsStashed(
+ /* isStashed= */ false,
+ placeholderDuration,
+ TRANSITION_UNSTASH_SUW_MANUAL);
+ animation.play(mAnimator);
+ }
+
/**
* 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 startDelay how many milliseconds to delay the animation after starting it.
+ * @param animationType what transition type to play.
*/
- private void createAnimToIsStashed(boolean isStashed, long duration, long startDelay) {
+ private void createAnimToIsStashed(boolean isStashed, long duration,
+ @StashAnimation int animationType) {
+ if (animationType == TRANSITION_UNSTASH_SUW_MANUAL && isStashed) {
+ // The STASH_ANIMATION_SUW_MANUAL must only be used during an unstash animation.
+ Log.e(TAG, "Illegal arguments:Using TRANSITION_UNSTASH_SUW_MANUAL to stash taskbar");
+ }
+
if (mAnimator != null) {
mAnimator.cancel();
}
mAnimator = new AnimatorSet();
+ addJankMonitorListener(mAnimator, /* appearing= */ !mIsStashed);
+ boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivity);
+ final float stashTranslation = isPhoneMode() || isTransientTaskbar
+ ? 0
+ : (mUnstashedHeight - mStashedHeight);
if (!supportsVisualStashing()) {
// Just hide/show the icons and background instead of stashing into a handle.
mAnimator.play(mIconAlphaForStash.animateToValue(isStashed ? 0 : 1)
.setDuration(duration));
+ mAnimator.playTogether(mTaskbarBackgroundOffset.animateToValue(isStashed ? 1 : 0)
+ .setDuration(duration));
+ mAnimator.playTogether(mIconTranslationYForStash.animateToValue(isStashed
+ ? stashTranslation : 0)
+ .setDuration(duration));
mAnimator.play(mTaskbarImeBgAlpha.animateToValue(
hasAnyFlag(FLAG_STASHED_IN_APP_IME) ? 0 : 1).setDuration(duration));
- mAnimator.setStartDelay(startDelay);
- mAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mAnimator = null;
- }
- });
+ mAnimator.addListener(AnimatorListeners.forEndCallback(() -> mAnimator = null));
return;
}
+ if (isTransientTaskbar) {
+ createTransientAnimToIsStashed(mAnimator, isStashed, duration, animationType);
+ } else {
+ createAnimToIsStashed(mAnimator, isStashed, duration, stashTranslation, animationType);
+ }
+
+ mAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mIsStashed = isStashed;
+ onIsStashedChanged(mIsStashed);
+
+ cancelTimeoutIfExists();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimator = null;
+
+ if (!mIsStashed) {
+ tryStartTaskbarTimeout();
+ }
+
+ // only announce if we are actually animating
+ if (duration > 0 && isInApp()) {
+ mControllers.taskbarViewController.announceForAccessibility();
+ }
+ }
+ });
+ }
+
+ private void createAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration,
+ float stashTranslation, @StashAnimation int animationType) {
AnimatorSet fullLengthAnimatorSet = new AnimatorSet();
// Not exactly half and may overlap. See [first|second]HalfDurationScale below.
AnimatorSet firstHalfAnimatorSet = new AnimatorSet();
@@ -340,34 +685,50 @@ public class TaskbarStashController {
if (isStashed) {
firstHalfDurationScale = 0.75f;
secondHalfDurationScale = 0.5f;
- final float stashTranslation = (mUnstashedHeight - mStashedHeight) / 2f;
- fullLengthAnimatorSet.playTogether(
- mTaskbarBackgroundOffset.animateToValue(1),
- mIconTranslationYForStash.animateToValue(stashTranslation)
- );
+ fullLengthAnimatorSet.play(mIconTranslationYForStash.animateToValue(stashTranslation));
+ fullLengthAnimatorSet.play(mTaskbarBackgroundOffset.animateToValue(1));
+
firstHalfAnimatorSet.playTogether(
mIconAlphaForStash.animateToValue(0),
- mIconScaleForStash.animateToValue(STASHED_TASKBAR_SCALE)
+ mIconScaleForStash.animateToValue(isPhoneMode() ?
+ 0 : STASHED_TASKBAR_SCALE)
);
secondHalfAnimatorSet.playTogether(
mTaskbarStashedHandleAlpha.animateToValue(1)
);
+
+ if (animationType == TRANSITION_HANDLE_FADE) {
+ fullLengthAnimatorSet.setInterpolator(INSTANT);
+ firstHalfAnimatorSet.setInterpolator(INSTANT);
+ }
} else {
firstHalfDurationScale = 0.5f;
secondHalfDurationScale = 0.75f;
fullLengthAnimatorSet.playTogether(
- mTaskbarBackgroundOffset.animateToValue(0),
mIconScaleForStash.animateToValue(1),
- mIconTranslationYForStash.animateToValue(0)
- );
+ mIconTranslationYForStash.animateToValue(0));
+
+ final boolean animateBg = animationType != TRANSITION_UNSTASH_SUW_MANUAL;
+ if (animateBg) {
+ fullLengthAnimatorSet.play(mTaskbarBackgroundOffset.animateToValue(0));
+ } else {
+ fullLengthAnimatorSet.addListener(AnimatorListeners.forEndCallback(
+ () -> mTaskbarBackgroundOffset.updateValue(0)));
+ }
+
firstHalfAnimatorSet.playTogether(
mTaskbarStashedHandleAlpha.animateToValue(0)
);
secondHalfAnimatorSet.playTogether(
mIconAlphaForStash.animateToValue(1)
);
+
+ if (animationType == TRANSITION_HANDLE_FADE) {
+ fullLengthAnimatorSet.setInterpolator(FINAL_FRAME);
+ secondHalfAnimatorSet.setInterpolator(FINAL_FRAME);
+ }
}
fullLengthAnimatorSet.play(mControllers.stashedHandleViewController
@@ -381,23 +742,111 @@ public class TaskbarStashController {
secondHalfAnimatorSet.setDuration((long) (duration * secondHalfDurationScale));
secondHalfAnimatorSet.setStartDelay((long) (duration * (1 - secondHalfDurationScale)));
- mAnimator.playTogether(fullLengthAnimatorSet, firstHalfAnimatorSet,
+ as.playTogether(fullLengthAnimatorSet, firstHalfAnimatorSet,
secondHalfAnimatorSet);
- mAnimator.setStartDelay(startDelay);
- mAnimator.addListener(new AnimatorListenerAdapter() {
+
+ }
+
+ private void createTransientAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration,
+ @StashAnimation int animationType) {
+ // Target values of the properties this is going to set
+ final float backgroundOffsetTarget = isStashed ? 1 : 0;
+ final float iconAlphaTarget = isStashed ? 0 : 1;
+ final float stashedHandleAlphaTarget = isStashed ? 1 : 0;
+
+ // Timing for the alpha values depend on the animation played
+ long iconAlphaStartDelay = 0, iconAlphaDuration = 0, stashedHandleAlphaDelay = 0,
+ stashedHandleAlphaDuration = 0;
+ if (duration > 0) {
+ if (animationType == TRANSITION_HANDLE_FADE) {
+ // When fading, the handle fades in/out at the beginning of the transition with
+ // TASKBAR_STASH_ALPHA_DURATION.
+ stashedHandleAlphaDuration = TASKBAR_STASH_ALPHA_DURATION;
+ // The iconAlphaDuration must be set to duration for the skippable interpolators
+ // below to work.
+ iconAlphaDuration = duration;
+ } else {
+ iconAlphaStartDelay = TASKBAR_STASH_ALPHA_START_DELAY;
+ iconAlphaDuration = TASKBAR_STASH_ALPHA_DURATION;
+ stashedHandleAlphaDuration = TASKBAR_STASH_ALPHA_DURATION;
+
+ if (isStashed) {
+ if (animationType == TRANSITION_HOME_TO_APP) {
+ iconAlphaStartDelay = TASKBAR_STASH_ICON_ALPHA_HOME_TO_APP_START_DELAY;
+ }
+ stashedHandleAlphaDelay = iconAlphaStartDelay;
+ stashedHandleAlphaDuration = Math.max(0, duration - iconAlphaStartDelay);
+ }
+
+ }
+ }
+
+ play(as, mTaskbarStashedHandleAlpha.animateToValue(stashedHandleAlphaTarget),
+ stashedHandleAlphaDelay,
+ stashedHandleAlphaDuration, LINEAR);
+
+ // The rest of the animations might be "skipped" in TRANSITION_HANDLE_FADE transitions.
+ AnimatorSet skippable = as;
+ if (animationType == TRANSITION_HANDLE_FADE) {
+ skippable = new AnimatorSet();
+ as.play(skippable);
+ skippable.setInterpolator(isStashed ? INSTANT : FINAL_FRAME);
+ }
+
+ final boolean animateBg = animationType != TRANSITION_UNSTASH_SUW_MANUAL;
+ if (animateBg) {
+ play(skippable, mTaskbarBackgroundOffset.animateToValue(backgroundOffsetTarget), 0,
+ duration, EMPHASIZED);
+ } else {
+ skippable.addListener(AnimatorListeners.forEndCallback(
+ () -> mTaskbarBackgroundOffset.updateValue(backgroundOffsetTarget)));
+ }
+
+ play(skippable, mIconAlphaForStash.animateToValue(iconAlphaTarget), iconAlphaStartDelay,
+ iconAlphaDuration,
+ LINEAR);
+
+ if (isStashed) {
+ play(skippable, mControllers.taskbarSpringOnStashController.createSpringToStash(),
+ 0, duration, LINEAR);
+ }
+
+ mControllers.taskbarViewController.addRevealAnimToIsStashed(skippable, isStashed, duration,
+ EMPHASIZED);
+
+ 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.
+ skippable.play(mTaskbarStashedHandleHintScale.animateToValue(1f)
+ .setDuration(isStashed ? duration / 2 : duration));
+ }
+
+ private static void play(AnimatorSet as, Animator a, long startDelay, long duration,
+ Interpolator interpolator) {
+ a.setDuration(duration);
+ a.setStartDelay(startDelay);
+ a.setInterpolator(interpolator);
+ as.play(a);
+ }
+
+ private void addJankMonitorListener(AnimatorSet animator, boolean expanding) {
+ View v = mControllers.taskbarActivityContext.getDragLayer();
+ int action = expanding ? InteractionJankMonitor.CUJ_TASKBAR_EXPAND :
+ InteractionJankMonitor.CUJ_TASKBAR_COLLAPSE;
+ animator.addListener(new AnimatorListenerAdapter() {
@Override
- public void onAnimationStart(Animator animation) {
- mIsStashed = isStashed;
- onIsStashed(mIsStashed);
+ public void onAnimationStart(@NonNull Animator animation) {
+ InteractionJankMonitor.getInstance().begin(v, action);
}
@Override
- public void onAnimationEnd(Animator animation) {
- mAnimator = null;
+ public void onAnimationEnd(@NonNull Animator animation) {
+ InteractionJankMonitor.getInstance().end(action);
}
});
}
-
/**
* Creates and starts a partial stash animation, hinting at the new state that will trigger when
* long press is detected.
@@ -425,33 +874,49 @@ public class TaskbarStashController {
// Already unstashed, no need to hint in that direction.
return;
}
+ if (!canCurrentlyManuallyUnstash()) {
+ // If any other flags are causing us to be stashed, long press won't cause us to
+ // unstash, so don't hint that it will.
+ return;
+ }
mTaskbarStashedHandleHintScale.animateToValue(
animateForward ? UNSTASHED_TASKBAR_HANDLE_HINT_SCALE : 1)
.setDuration(TASKBAR_HINT_STASH_DURATION).start();
}
- private void onIsStashed(boolean isStashed) {
- mControllers.stashedHandleViewController.onIsStashed(isStashed);
+ private void onIsStashedChanged(boolean isStashed) {
+ mControllers.runAfterInit(() -> {
+ mControllers.stashedHandleViewController.onIsStashedChanged(isStashed);
+ mControllers.taskbarInsetsController.onTaskbarWindowHeightOrInsetsChanged();
+ });
}
public void applyState() {
- applyState(TASKBAR_STASH_DURATION);
+ applyState(hasAnyFlag(FLAG_IN_SETUP) ? 0 : TASKBAR_STASH_DURATION);
}
public void applyState(long duration) {
- mStatePropertyHolder.setState(mState, duration, true);
+ Animator animator = createApplyStateAnimator(duration);
+ if (animator != null) {
+ animator.start();
+ }
}
public void applyState(long duration, long startDelay) {
- mStatePropertyHolder.setState(mState, duration, startDelay, true);
+ Animator animator = createApplyStateAnimator(duration);
+ if (animator != null) {
+ animator.setStartDelay(startDelay);
+ animator.start();
+ }
}
- public Animator applyStateWithoutStart() {
- return applyStateWithoutStart(TASKBAR_STASH_DURATION);
- }
-
- public Animator applyStateWithoutStart(long duration) {
- return mStatePropertyHolder.setState(mState, duration, false);
+ /**
+ * Returns an animator which applies the latest state if mIsStashed is changed, or {@code null}
+ * otherwise.
+ */
+ @Nullable
+ public Animator createApplyStateAnimator(long duration) {
+ return mStatePropertyHolder.createSetStateAnimator(mState, duration);
}
/**
@@ -460,10 +925,32 @@ public class TaskbarStashController {
*/
public void setSystemGestureInProgress(boolean inProgress) {
mIsSystemGestureInProgress = inProgress;
- // Only update FLAG_STASHED_IN_APP_IME when system gesture is not in progress.
- if (!mIsSystemGestureInProgress) {
- updateStateForFlag(FLAG_STASHED_IN_APP_IME, mIsImeShowing);
+ if (mIsSystemGestureInProgress) {
+ return;
+ }
+
+ // Only update the following flags when system gesture is not in progress.
+ boolean shouldStashForIme = shouldStashForIme();
+ updateStateForFlag(FLAG_STASHED_IN_TASKBAR_ALL_APPS, false);
+ if (hasAnyFlag(FLAG_STASHED_IN_APP_IME) != shouldStashForIme) {
+ updateStateForFlag(FLAG_STASHED_IN_APP_IME, shouldStashForIme);
applyState(TASKBAR_STASH_DURATION_FOR_IME, getTaskbarStashStartDelayForIme());
+ } else {
+ applyState(mControllers.taskbarOverlayController.getCloseDuration());
+ }
+ }
+
+ /**
+ * Resets the flag if no system gesture is in progress.
+ *
+ * Otherwise, the reset should be deferred until after the gesture is finished.
+ *
+ * @see #setSystemGestureInProgress
+ */
+ public void resetFlagIfNoGestureInProgress(int flag) {
+ if (!mIsSystemGestureInProgress) {
+ updateStateForFlag(flag, false);
+ applyState(mControllers.taskbarOverlayController.getCloseDuration());
}
}
@@ -486,13 +973,21 @@ public class TaskbarStashController {
long animDuration = TASKBAR_STASH_DURATION;
long startDelay = 0;
- updateStateForFlag(FLAG_STASHED_IN_APP_PINNED,
+ updateStateForFlag(FLAG_STASHED_IN_APP_SYSUI, hasAnyFlag(systemUiStateFlags,
+ SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE));
+ updateStateForFlag(FLAG_STASHED_SYSUI,
hasAnyFlag(systemUiStateFlags, SYSUI_STATE_SCREEN_PINNING));
+ boolean isLocked = hasAnyFlag(systemUiStateFlags, MASK_ANY_SYSUI_LOCKED)
+ && !hasAnyFlag(systemUiStateFlags, SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY);
+ updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED, isLocked);
+
// Only update FLAG_STASHED_IN_APP_IME when system gesture is not in progress.
mIsImeShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SHOWING);
+ mIsImeSwitcherShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SWITCHER_SHOWING);
+
if (!mIsSystemGestureInProgress) {
- updateStateForFlag(FLAG_STASHED_IN_APP_IME, mIsImeShowing);
+ updateStateForFlag(FLAG_STASHED_IN_APP_IME, shouldStashForIme());
animDuration = TASKBAR_STASH_DURATION_FOR_IME;
startDelay = getTaskbarStashStartDelayForIme();
}
@@ -500,6 +995,22 @@ public class TaskbarStashController {
applyState(skipAnim ? 0 : animDuration, skipAnim ? 0 : startDelay);
}
+ /**
+ * We stash when IME or IME switcher is showing AND NOT
+ * * in small screen AND
+ * * 3 button nav AND
+ * * landscape (or seascape)
+ * We do not stash if taskbar is transient
+ */
+ private boolean shouldStashForIme() {
+ if (DisplayController.isTransientTaskbar(mActivity)) {
+ return false;
+ }
+ return (mIsImeShowing || mIsImeSwitcherShowing) &&
+ !(isPhoneMode() && mActivity.isThreeButtonNav()
+ && mActivity.getDeviceProfile().isLandscape);
+ }
+
/**
* Updates the proper flag to indicate whether the task bar should be stashed.
*
@@ -510,6 +1021,10 @@ public class TaskbarStashController {
* unstashed.
*/
public void updateStateForFlag(int flag, boolean enabled) {
+ if (flag == FLAG_IN_APP && TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.TASKBAR_IN_APP_STATE, String.format(
+ "setting flag FLAG_IN_APP to: %b", enabled), new Exception());
+ }
if (enabled) {
mState |= flag;
} else {
@@ -525,9 +1040,11 @@ public class TaskbarStashController {
if (hasAnyFlag(changedFlags, FLAGS_STASHED_IN_APP)) {
mControllers.uiController.onStashedInAppChanged();
}
- if (hasAnyFlag(changedFlags, FLAGS_STASHED_IN_APP | FLAG_IN_APP)) {
- notifyStashChange(/* visible */ hasAnyFlag(FLAG_IN_APP),
+ if (hasAnyFlag(changedFlags, FLAGS_STASHED_IN_APP | FLAGS_IN_APP)) {
+ notifyStashChange(/* visible */ hasAnyFlag(FLAGS_IN_APP),
/* stashed */ isStashedInApp());
+ mControllers.taskbarAutohideSuspendController.updateFlag(
+ TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_IN_LAUNCHER, !isInApp());
}
if (hasAnyFlag(changedFlags, FLAG_STASHED_IN_APP_MANUAL)) {
if (hasAnyFlag(FLAG_STASHED_IN_APP_MANUAL)) {
@@ -536,57 +1053,251 @@ public class TaskbarStashController {
mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_LONGPRESS_SHOW);
}
}
+ if (hasAnyFlag(changedFlags, FLAG_STASHED_IN_APP_AUTO)) {
+ mActivity.getStatsLogManager().logger().log(hasAnyFlag(FLAG_STASHED_IN_APP_AUTO)
+ ? LAUNCHER_TRANSIENT_TASKBAR_HIDE
+ : LAUNCHER_TRANSIENT_TASKBAR_SHOW);
+ }
}
private void notifyStashChange(boolean visible, boolean stashed) {
mSystemUiProxy.notifyTaskbarStatus(visible, stashed);
+ setUpTaskbarSystemAction(visible);
+ // If stashing taskbar is caused by IME visibility, we could just skip updating rounded
+ // corner insets since the rounded corners will be covered by IME during IME is showing and
+ // taskbar will be restored back to unstashed when IME is hidden.
+ mControllers.taskbarActivityContext.updateInsetRoundedCornerFrame(
+ visible && !isStashedInAppIgnoringIme());
mControllers.rotationButtonController.onTaskbarStateChange(visible, stashed);
}
+ /**
+ * Setup system action for showing Taskbar depending on its visibility.
+ */
+ public void setUpTaskbarSystemAction(boolean visible) {
+ UI_HELPER_EXECUTOR.execute(() -> {
+ if (!visible || !DisplayController.isTransientTaskbar(mActivity)) {
+ mAccessibilityManager.unregisterSystemAction(SYSTEM_ACTION_ID_TASKBAR);
+ mIsTaskbarSystemActionRegistered = false;
+ return;
+ }
+
+ if (!mIsTaskbarSystemActionRegistered) {
+ RemoteAction taskbarRemoteAction = new RemoteAction(
+ Icon.createWithResource(mActivity, R.drawable.ic_info_no_shadow),
+ mActivity.getString(R.string.taskbar_a11y_title),
+ mActivity.getString(R.string.taskbar_a11y_title),
+ mTaskbarSharedState.taskbarSystemActionPendingIntent);
+
+ mAccessibilityManager.registerSystemAction(taskbarRemoteAction,
+ SYSTEM_ACTION_ID_TASKBAR);
+ mIsTaskbarSystemActionRegistered = true;
+ }
+ });
+ }
+
+ /**
+ * Clean up on destroy from TaskbarControllers
+ */
+ public void onDestroy() {
+ UI_HELPER_EXECUTOR.execute(
+ () -> mAccessibilityManager.unregisterSystemAction(SYSTEM_ACTION_ID_TASKBAR));
+ }
+
+ /**
+ * Cancels a timeout if any exists.
+ */
+ public void cancelTimeoutIfExists() {
+ if (mTimeoutAlarm.alarmPending()) {
+ mTimeoutAlarm.cancelAlarm();
+ }
+ }
+
+ /**
+ * Updates the status of the taskbar timeout.
+ * @param isAutohideSuspended If true, cancels any existing timeout
+ * If false, attempts to re/start the timeout
+ */
+ public void updateTaskbarTimeout(boolean isAutohideSuspended) {
+ if (!DisplayController.isTransientTaskbar(mActivity)) {
+ return;
+ }
+ if (isAutohideSuspended) {
+ cancelTimeoutIfExists();
+ } else {
+ tryStartTaskbarTimeout();
+ }
+ }
+
+ /**
+ * Attempts to start timer to auto hide the taskbar based on time.
+ */
+ public void tryStartTaskbarTimeout() {
+ if (!DisplayController.isTransientTaskbar(mActivity)
+ || mIsStashed
+ || mEnableBlockingTimeoutDuringTests) {
+ return;
+ }
+
+ cancelTimeoutIfExists();
+
+ mTimeoutAlarm.setOnAlarmListener(this::onTaskbarTimeout);
+ mTimeoutAlarm.setAlarm(getTaskbarAutoHideTimeout());
+ }
+
+ /**
+ * 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,
+ FLAG_CONTENT_CONTROLS);
+ }
+
+ private void onTaskbarTimeout(Alarm alarm) {
+ if (mControllers.taskbarAutohideSuspendController.isSuspended()) {
+ return;
+ }
+ updateAndAnimateTransientTaskbar(true);
+ }
+
+ @Override
+ public void dumpLogs(String prefix, PrintWriter pw) {
+ pw.println(prefix + "TaskbarStashController:");
+
+ pw.println(prefix + "\tmStashedHeight=" + mStashedHeight);
+ pw.println(prefix + "\tmUnstashedHeight=" + mUnstashedHeight);
+ pw.println(prefix + "\tmIsStashed=" + mIsStashed);
+ pw.println(prefix + "\tappliedState=" + getStateString(mStatePropertyHolder.mPrevFlags));
+ pw.println(prefix + "\tmState=" + getStateString(mState));
+ pw.println(prefix + "\tmIsSystemGestureInProgress=" + mIsSystemGestureInProgress);
+ pw.println(prefix + "\tmIsImeShowing=" + mIsImeShowing);
+ pw.println(prefix + "\tmIsImeSwitcherShowing=" + mIsImeSwitcherShowing);
+ }
+
+ private static String getStateString(int flags) {
+ StringJoiner sj = new StringJoiner("|");
+ appendFlag(sj, flags, FLAGS_IN_APP, "FLAG_IN_APP");
+ appendFlag(sj, flags, FLAG_STASHED_IN_APP_MANUAL, "FLAG_STASHED_IN_APP_MANUAL");
+ appendFlag(sj, flags, FLAG_STASHED_IN_APP_SYSUI, "FLAG_STASHED_IN_APP_SYSUI");
+ appendFlag(sj, flags, FLAG_STASHED_IN_APP_SETUP, "FLAG_STASHED_IN_APP_SETUP");
+ appendFlag(sj, flags, FLAG_STASHED_IN_APP_IME, "FLAG_STASHED_IN_APP_IME");
+ appendFlag(sj, flags, FLAG_IN_STASHED_LAUNCHER_STATE, "FLAG_IN_STASHED_LAUNCHER_STATE");
+ appendFlag(sj, flags, FLAG_STASHED_IN_TASKBAR_ALL_APPS, "FLAG_STASHED_IN_TASKBAR_ALL_APPS");
+ appendFlag(sj, flags, FLAG_IN_SETUP, "FLAG_IN_SETUP");
+ appendFlag(sj, flags, FLAG_STASHED_IN_APP_AUTO, "FLAG_STASHED_IN_APP_AUTO");
+ appendFlag(sj, flags, FLAG_STASHED_SYSUI, "FLAG_STASHED_SYSUI");
+ appendFlag(sj, flags, FLAG_STASHED_DEVICE_LOCKED, "FLAG_STASHED_DEVICE_LOCKED");
+ return sj.toString();
+ }
+
private class StatePropertyHolder {
private final IntPredicate mStashCondition;
private boolean mIsStashed;
+ private @StashAnimation int mLastStartedTransitionType = TRANSITION_DEFAULT;
private int mPrevFlags;
+ private long mLastUnlockTransitionTimeout = 0;
+
StatePropertyHolder(IntPredicate stashCondition) {
mStashCondition = stashCondition;
}
/**
- * @see #setState(int, long, long, boolean) with a default startDelay = 0.
- */
- public Animator setState(int flags, long duration, boolean start) {
- return setState(flags, duration, 0 /* startDelay */, start);
- }
-
- /**
- * Applies the latest state, potentially calling onStateChangeApplied() and creating a new
- * animation (stored in mAnimator) which is started if {@param start} is true.
+ * 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 duration The length of the animation.
- * @param startDelay How long to delay the animation after calling start().
- * @param start Whether to start mAnimator immediately.
- * @return mAnimator if mIsStashed changed, else null.
+ * @return mAnimator if mIsStashed changed, or {@code null} otherwise.
*/
- public Animator setState(int flags, long duration, long startDelay, boolean start) {
+ @Nullable
+ public Animator createSetStateAnimator(int flags, long duration) {
+ boolean isStashed = mStashCondition.test(flags);
+
+ if (DEBUG) {
+ String stateString = formatFlagChange(flags, mPrevFlags,
+ TaskbarStashController::getStateString);
+ Log.d(TAG, "createSetStateAnimator: flags: " + stateString
+ + ", duration: " + duration
+ + ", isStashed: " + isStashed
+ + ", mIsStashed: " + mIsStashed);
+ }
+
int changedFlags = mPrevFlags ^ flags;
if (mPrevFlags != flags) {
onStateChangeApplied(changedFlags);
mPrevFlags = flags;
}
- boolean isStashed = mStashCondition.test(flags);
- if (mIsStashed != isStashed) {
+
+ boolean isUnlockTransition = hasAnyFlag(changedFlags, FLAG_STASHED_DEVICE_LOCKED)
+ && !hasAnyFlag(FLAG_STASHED_DEVICE_LOCKED);
+ 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,
+ // this memoizes the state transition for UNLOCK_TRANSITION_MEMOIZATION_MS.
+ mLastUnlockTransitionTimeout =
+ SystemClock.elapsedRealtime() + UNLOCK_TRANSITION_MEMOIZATION_MS;
+ }
+
+ @StashAnimation int animationType = computeTransitionType(changedFlags);
+
+ // 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
+ && animationType != TRANSITION_DEFAULT;
+
+ if (mIsStashed != isStashed || transitionTypeChanged) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.TASKBAR_IN_APP_STATE, String.format(
+ "setState: mIsStashed=%b, isStashed=%b, "
+ + "mAnimationType=%d, animationType=%d, duration=%d",
+ mIsStashed,
+ isStashed,
+ mLastStartedTransitionType,
+ animationType,
+ duration));
+ }
mIsStashed = isStashed;
+ mLastStartedTransitionType = animationType;
// This sets mAnimator.
- createAnimToIsStashed(mIsStashed, duration, startDelay);
- if (start) {
- mAnimator.start();
- }
+ createAnimToIsStashed(mIsStashed, duration, animationType);
return mAnimator;
}
return null;
}
+
+ private @StashAnimation int computeTransitionType(int changedFlags) {
+
+ 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,
+ // 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;
+ if (isUnlockTransition) {
+ // 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.
+ return TRANSITION_HANDLE_FADE;
+ }
+
+ boolean homeToApp = hasAnyFlag(changedFlags, FLAG_IN_APP) && hasAnyFlag(FLAG_IN_APP);
+ if (homeToApp) {
+ return TRANSITION_HOME_TO_APP;
+ }
+
+ return TRANSITION_DEFAULT;
+ }
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashViaTouchController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashViaTouchController.kt
new file mode 100644
index 0000000000..23add74d91
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashViaTouchController.kt
@@ -0,0 +1,133 @@
+/*
+ * 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.view.MotionEvent
+import com.android.launcher3.R
+import com.android.launcher3.Utilities
+import com.android.launcher3.anim.Interpolators.LINEAR
+import com.android.launcher3.touch.SingleAxisSwipeDetector
+import com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE
+import com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.TouchController
+import com.android.quickstep.inputconsumers.TaskbarStashInputConsumer
+import com.android.systemui.shared.testing.ResourceUtils
+
+/**
+ * A helper [TouchController] for [TaskbarDragLayerController], specifically to handle touch events
+ * to stash Transient Taskbar. There are two cases to handle:
+ * - A touch outside of Transient Taskbar bounds will immediately stash on [MotionEvent.ACTION_DOWN]
+ * or [MotionEvent.ACTION_OUTSIDE].
+ * - Touches inside Transient Taskbar bounds will stash if it is detected as a swipe down gesture.
+ *
+ * Note: touches to *unstash* Taskbar are handled by [TaskbarStashInputConsumer].
+ */
+class TaskbarStashViaTouchController(val controllers: TaskbarControllers) : TouchController {
+
+ private val activity: TaskbarActivityContext = controllers.taskbarActivityContext
+ private val enabled = DisplayController.isTransientTaskbar(activity)
+ private val swipeDownDetector: SingleAxisSwipeDetector
+ private val translationCallback = controllers.taskbarTranslationController.transitionCallback
+ /** Interpolator to apply resistance as user swipes down to the bottom of the screen. */
+ private val displacementInterpolator = LINEAR
+ /** How far we can translate the TaskbarView before it's offscreen. */
+ private val maxVisualDisplacement =
+ activity.resources.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin).toFloat()
+ /** How far the swipe could go, if user swiped from the very top of TaskbarView. */
+ private val maxTouchDisplacement = maxVisualDisplacement + activity.deviceProfile.taskbarHeight
+ private val touchDisplacementToStash =
+ activity.resources.getDimensionPixelSize(R.dimen.taskbar_to_nav_threshold).toFloat()
+
+ /** The height of the system gesture region, so we don't stash when touching down there. */
+ private var gestureHeightYThreshold = 0f
+
+ init {
+ updateGestureHeight()
+ swipeDownDetector = SingleAxisSwipeDetector(activity, createSwipeListener(), VERTICAL)
+ swipeDownDetector.setDetectableScrollConditions(DIRECTION_NEGATIVE, false)
+ }
+
+ fun updateGestureHeight() {
+ if (!enabled) return
+
+ val gestureHeight: Int =
+ ResourceUtils.getNavbarSize(
+ ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE,
+ activity.resources
+ )
+ gestureHeightYThreshold = (activity.deviceProfile.heightPx - gestureHeight).toFloat()
+ }
+
+ private fun createSwipeListener() =
+ object : SingleAxisSwipeDetector.Listener {
+ private var lastDisplacement = 0f
+
+ override fun onDragStart(start: Boolean, startDisplacement: Float) {}
+
+ override fun onDrag(displacement: Float): Boolean {
+ lastDisplacement = displacement
+ if (displacement < 0) return false
+ // Apply resistance so that the visual displacement doesn't go beyond the screen.
+ translationCallback.onActionMove(
+ Utilities.mapToRange(
+ displacement,
+ 0f,
+ maxTouchDisplacement,
+ 0f,
+ maxVisualDisplacement,
+ displacementInterpolator
+ )
+ )
+ return false
+ }
+
+ override fun onDragEnd(velocity: Float) {
+ val isFlingDown = swipeDownDetector.isFling(velocity) && velocity > 0
+ val isSignificantDistance = lastDisplacement > touchDisplacementToStash
+ if (isFlingDown || isSignificantDistance) {
+ // Successfully triggered stash.
+ controllers.taskbarStashController.updateAndAnimateTransientTaskbar(true)
+ }
+ translationCallback.onActionEnd()
+ swipeDownDetector.finishedScrolling()
+ }
+ }
+
+ override fun onControllerInterceptTouchEvent(ev: MotionEvent): Boolean {
+ if (!enabled || controllers.taskbarStashController.isStashed) {
+ return false
+ }
+
+ val screenCoordinatesEv = MotionEvent.obtain(ev)
+ screenCoordinatesEv.setLocation(ev.rawX, ev.rawY)
+ if (ev.action == MotionEvent.ACTION_OUTSIDE) {
+ controllers.taskbarStashController.updateAndAnimateTransientTaskbar(true)
+ } else if (controllers.taskbarViewController.isEventOverAnyItem(screenCoordinatesEv)) {
+ swipeDownDetector.onTouchEvent(ev)
+ if (swipeDownDetector.isDraggingState) {
+ return true
+ }
+ } else if (ev.action == MotionEvent.ACTION_DOWN) {
+ if (screenCoordinatesEv.y < gestureHeightYThreshold) {
+ controllers.taskbarStashController.updateAndAnimateTransientTaskbar(true)
+ }
+ }
+ return false
+ }
+
+ override fun onControllerTouchEvent(ev: MotionEvent) = swipeDownDetector.onTouchEvent(ev)
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java
new file mode 100644
index 0000000000..4b18bb6556
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2022 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.anim.AnimatedFloat.VALUE;
+import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+
+import androidx.annotation.Nullable;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.launcher3.anim.AnimatedFloat;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.anim.SpringAnimationBuilder;
+import com.android.launcher3.util.DisplayController;
+
+import java.io.PrintWriter;
+
+/**
+ * Class responsible for translating the transient taskbar UI during a swipe gesture.
+ *
+ * The translation is controlled, in priority order:
+ * - animation to home
+ * - a spring animation
+ * - controlled by user
+ *
+ * The spring animation will play start once the user lets go or when user pauses to go to overview.
+ * When the user goes home, the stash animation will play.
+ */
+public class TaskbarTranslationController implements TaskbarControllers.LoggableTaskbarController {
+
+ private final TaskbarActivityContext mContext;
+ private TaskbarControllers mControllers;
+ private final AnimatedFloat mTranslationYForSwipe = new AnimatedFloat(
+ this::updateTranslationYForSwipe);
+
+ private boolean mHasSprungOnceThisGesture;
+ private @Nullable ValueAnimator mSpringBounce;
+ private boolean mGestureEnded;
+ private boolean mGestureInProgress;
+ private boolean mAnimationToHomeRunning;
+
+ private final boolean mIsTransientTaskbar;
+
+ private final TransitionCallback mCallback;
+
+ public TaskbarTranslationController(TaskbarActivityContext context) {
+ mContext = context;
+ mIsTransientTaskbar = DisplayController.isTransientTaskbar(mContext);
+ mCallback = new TransitionCallback();
+ }
+
+ /**
+ * Initialization method.
+ */
+ public void init(TaskbarControllers controllers) {
+ mControllers = controllers;
+ }
+
+ /**
+ * Called to cancel any existing animations.
+ */
+ public void cancelSpringIfExists() {
+ if (mSpringBounce != null) {
+ mSpringBounce.cancel();
+ mSpringBounce = null;
+ }
+ }
+
+ private void updateTranslationYForSwipe() {
+ if (!mIsTransientTaskbar) {
+ return;
+ }
+
+ float transY = mTranslationYForSwipe.value;
+ mControllers.stashedHandleViewController.setTranslationYForSwipe(transY);
+ mControllers.taskbarViewController.setTranslationYForSwipe(transY);
+ mControllers.taskbarDragLayerController.setTranslationYForSwipe(transY);
+ }
+
+ /**
+ * Starts a spring aniamtion to set the views back to the resting state.
+ */
+ public void startSpring() {
+ if (mHasSprungOnceThisGesture || mAnimationToHomeRunning) {
+ return;
+ }
+ mSpringBounce = new SpringAnimationBuilder(mContext)
+ .setStartValue(mTranslationYForSwipe.value)
+ .setEndValue(0)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
+ .setStiffness(SpringForce.STIFFNESS_LOW)
+ .build(mTranslationYForSwipe, VALUE);
+ mSpringBounce.addListener(forEndCallback(() -> {
+ if (!mGestureEnded) {
+ return;
+ }
+ reset();
+ if (mControllers.taskbarStashController.isInApp()
+ && mControllers.taskbarStashController.isTaskbarVisibleAndNotStashing()) {
+ mControllers.taskbarEduTooltipController.maybeShowFeaturesEdu();
+ }
+ }));
+ mSpringBounce.start();
+ mHasSprungOnceThisGesture = true;
+ }
+
+ private void reset() {
+ mGestureEnded = false;
+ mGestureInProgress = false;
+ mHasSprungOnceThisGesture = false;
+ }
+
+ /**
+ * Returns a callback to help monitor the swipe gesture.
+ */
+ public TransitionCallback getTransitionCallback() {
+ return mCallback;
+ }
+
+ /**
+ * Returns {@code true} if we should reset the animation back to zero.
+ *
+ * Returns {@code false} if there is a gesture in progress, or if we are already animating
+ * to 0 within the specified duration.
+ */
+ public boolean shouldResetBackToZero(long duration) {
+ if (mGestureInProgress) {
+ return false;
+ }
+ if (mSpringBounce != null && mSpringBounce.isRunning()) {
+ long springDuration = mSpringBounce.getDuration();
+ long current = mSpringBounce.getCurrentPlayTime();
+ return (springDuration - current >= duration);
+ }
+ if (mTranslationYForSwipe.isAnimatingToValue(0)) {
+ return mTranslationYForSwipe.getRemainingTime() >= duration;
+ }
+ return true;
+ }
+
+ /**
+ * Returns an animation to reset the taskbar translation to {@code 0}.
+ */
+ public ObjectAnimator createAnimToResetTranslation(long duration) {
+ ObjectAnimator animator = mTranslationYForSwipe.animateToValue(0);
+ animator.setInterpolator(Interpolators.LINEAR);
+ animator.setDuration(duration);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ cancelSpringIfExists();
+ reset();
+ mAnimationToHomeRunning = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimationToHomeRunning = false;
+ reset();
+ }
+ });
+ return animator;
+ }
+
+ /**
+ * Helper class to communicate to/from the input consumer.
+ */
+ public class TransitionCallback {
+
+ /**
+ * Clears any existing animations so that user
+ * can take control over the movement of the taskbaer.
+ */
+ public void onActionDown() {
+ if (mAnimationToHomeRunning) {
+ mTranslationYForSwipe.cancelAnimation();
+ }
+ mAnimationToHomeRunning = false;
+ cancelSpringIfExists();
+ reset();
+ mGestureInProgress = true;
+ }
+ /**
+ * Called when there is movement to move the taskbar.
+ */
+ public void onActionMove(float dY) {
+ if (mAnimationToHomeRunning
+ || (mHasSprungOnceThisGesture && !mGestureEnded)) {
+ return;
+ }
+
+ mTranslationYForSwipe.updateValue(dY);
+ }
+
+ /**
+ * Called when swipe gesture has ended.
+ */
+ public void onActionEnd() {
+ if (mHasSprungOnceThisGesture) {
+ reset();
+ } else {
+ mGestureEnded = true;
+ startSpring();
+ }
+ mGestureInProgress = false;
+ }
+ }
+
+ @Override
+ public void dumpLogs(String prefix, PrintWriter pw) {
+ pw.println(prefix + "TaskbarTranslationController:");
+
+ pw.println(prefix + "\tmTranslationYForSwipe=" + mTranslationYForSwipe.value);
+ pw.println(prefix + "\tmHasSprungOnceThisGesture=" + mHasSprungOnceThisGesture);
+ pw.println(prefix + "\tmAnimationToHomeRunning=" + mAnimationToHomeRunning);
+ pw.println(prefix + "\tmGestureEnded=" + mGestureEnded);
+ pw.println(prefix + "\tmGestureInProgress=" + mGestureInProgress);
+ pw.println(prefix + "\tmSpringBounce is running=" + (mSpringBounce != null
+ && mSpringBounce.isRunning()));
+ }
+}
+
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index abad9060ab..1435cb0996 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -15,14 +15,32 @@
*/
package com.android.launcher3.taskbar;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
+import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
+
+import android.content.Intent;
+import android.graphics.drawable.BitmapDrawable;
+import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.CallSuper;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.SplitConfigurationOptions;
+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 java.util.stream.Stream;
+import java.io.PrintWriter;
/**
* Base class for providing different taskbar UI
@@ -48,13 +66,30 @@ public class TaskbarUIController {
return true;
}
- protected void onStashedInAppChanged() { }
-
- public Stream getAppIconsForEdu() {
- return Stream.empty();
+ /**
+ * 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.
+ */
+ public boolean supportsVisualStashing() {
+ return true;
}
- public void onTaskbarIconLaunched(WorkspaceItemInfo item) { }
+ protected void onStashedInAppChanged() { }
+
+ /**
+ * Called when taskbar icon layout bounds change.
+ */
+ 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.
+ mControllers.taskbarStashController.updateStateForFlag(FLAG_IN_APP, true);
+ mControllers.taskbarStashController.applyState();
+ }
public View getRootView() {
return mControllers.taskbarActivityContext.getDragLayer();
@@ -67,4 +102,220 @@ public class TaskbarUIController {
public void setSystemGestureInProgress(boolean inProgress) {
mControllers.taskbarStashController.setSystemGestureInProgress(inProgress);
}
+
+ /**
+ * Manually closes the overlay window.
+ */
+ public void hideOverlayWindow() {
+ if (!DisplayController.isTransientTaskbar(mControllers.taskbarActivityContext)
+ || mControllers.taskbarAllAppsController.isOpen()) {
+ mControllers.taskbarOverlayController.hideWindow();
+ }
+ }
+
+ /**
+ * User expands PiP to full-screen (or split-screen) mode, try to hide the Taskbar.
+ */
+ public void onExpandPip() {
+ if (mControllers != null) {
+ final TaskbarStashController stashController = mControllers.taskbarStashController;
+ stashController.updateStateForFlag(FLAG_IN_APP, true);
+ stashController.applyState();
+ }
+ }
+
+ /**
+ * SysUI flags updated, see QuickStepContract.SYSUI_STATE_* values.
+ */
+ public void updateStateForSysuiFlags(int sysuiFlags, boolean skipAnim){
+ }
+
+ /**
+ * Returns {@code true} iff taskbar is stashed.
+ */
+ public boolean isTaskbarStashed() {
+ return mControllers.taskbarStashController.isStashed();
+ }
+
+ /**
+ * Returns {@code true} iff taskbar All Apps is open.
+ */
+ public boolean isTaskbarAllAppsOpen() {
+ return mControllers.taskbarAllAppsController.isOpen();
+ }
+
+ /**
+ * Called at the end of the swipe gesture on Transient taskbar.
+ */
+ public void startTranslationSpring() {
+ mControllers.taskbarActivityContext.startTranslationSpring();
+ }
+
+ /*
+ * @param ev MotionEvent in screen coordinates.
+ * @return Whether any Taskbar item could handle the given MotionEvent if given the chance.
+ */
+ public boolean isEventOverAnyTaskbarItem(MotionEvent ev) {
+ return mControllers.taskbarViewController.isEventOverAnyItem(ev)
+ || mControllers.navbarButtonsViewController.isEventOverAnyItem(ev);
+ }
+
+ /**
+ * Returns true if icons should be aligned to hotseat in the current transition.
+ */
+ public boolean isIconAlignedWithHotseat() {
+ return false;
+ }
+
+ /**
+ * Returns true if hotseat icons are on top of view hierarchy when aligned in the current state.
+ */
+ public boolean isHotseatIconOnTopWhenAligned() {
+ return true;
+ }
+
+ /** Returns {@code true} if Taskbar is currently within overview. */
+ protected boolean isInOverview() {
+ return false;
+ }
+
+ @CallSuper
+ protected void dumpLogs(String prefix, PrintWriter pw) {
+ pw.println(String.format(
+ "%sTaskbarUIController: using an instance of %s",
+ prefix,
+ getClass().getSimpleName()));
+ }
+
+ /**
+ * 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.
+ */
+ public @Nullable RecentsView getRecentsView() {
+ return null;
+ }
+
+ public void startSplitSelection(SplitConfigurationOptions.SplitSelectSource splitSelectSource) {
+ RecentsView recentsView = getRecentsView();
+ if (recentsView == null) {
+ return;
+ }
+
+ ComponentKey componentToBeStaged = new ComponentKey(
+ splitSelectSource.itemInfo.getTargetComponent(),
+ splitSelectSource.itemInfo.user);
+ recentsView.getSplitSelectController().findLastActiveTaskAndRunCallback(
+ componentToBeStaged,
+ foundTask -> {
+ splitSelectSource.alreadyRunningTaskId = foundTask == null
+ ? INVALID_TASK_ID
+ : foundTask.key.id;
+ splitSelectSource.animateCurrentTaskDismissal = foundTask != null;
+ recentsView.initiateSplitSelect(splitSelectSource);
+ }
+ );
+ }
+
+ /**
+ * Uses the clicked Taskbar icon to launch a second app for splitscreen.
+ */
+ public void triggerSecondAppForSplit(ItemInfoWithIcon info, Intent intent, View startingView) {
+ RecentsView recents = getRecentsView();
+ ComponentKey secondAppComponent = new ComponentKey(info.getTargetComponent(), info.user);
+ recents.getSplitSelectController().findLastActiveTaskAndRunCallback(
+ secondAppComponent,
+ foundTask -> {
+ if (foundTask != null) {
+ TaskView foundTaskView = recents.getTaskViewByTaskId(foundTask.key.id);
+ // TODO (b/266482558): This additional null check is needed because there
+ // are times when our Tasks list doesn't match our TaskViews list (like when
+ // a tile is removed during {@link RecentsView#applyLoadPlan()}. A clearer
+ // state management system is in the works so that we don't need to rely on
+ // null checks as much. See comments at ag/21152798.
+ 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);
+ recents.confirmSplitSelect(
+ foundTaskView,
+ foundTask,
+ taskAttributes.getIconView().getDrawable(),
+ taskAttributes.getThumbnailView(),
+ taskAttributes.getThumbnailView().getThumbnail(),
+ null /* intent */,
+ null /* user */);
+ return;
+ }
+ }
+
+ // No running app of that type, create a new instance as second app.
+ recents.confirmSplitSelect(
+ null /* containerTaskView */,
+ null /* task */,
+ new BitmapDrawable(info.bitmap.icon),
+ startingView,
+ 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)
+ */
+ public void openQuickSwitchView() {
+ mControllers.keyboardQuickSwitchController.openQuickSwitchView();
+ }
+
+ /**
+ * 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.
+ *
+ * @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();
+ mControllers.keyboardQuickSwitchController.closeQuickSwitchView();
+ return focusedTaskIndex;
+ }
+
+ /**
+ * Launches the focused task in splitscreen.
+ *
+ * No-op if the view is not yet open.
+ */
+ 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) {
+ if (!(view.getTag() instanceof ItemInfo)) {
+ return null;
+ }
+ ItemInfo info = (ItemInfo) view.getTag();
+ if (info.container != CONTAINER_HOTSEAT && info.container != CONTAINER_HOTSEAT_PREDICTION) {
+ return null;
+ }
+
+ // 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) {
+ if (views[i] != null
+ && views[i].getTag() instanceof ItemInfo
+ && ((ItemInfo) views[i].getTag()).screenId == screenId) {
+ return views[i];
+ }
+ }
+ return null;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
index c785186446..280e1debb8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
@@ -21,42 +21,63 @@ import android.view.WindowManager;
import com.android.quickstep.util.LauncherViewsMoveFromCenterTranslationApplier;
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
+import com.android.systemui.unfold.updates.RotationChangeProvider;
+import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider;
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
+import java.io.PrintWriter;
+
/**
* Controls animation of taskbar icons when unfolding foldable devices
*/
-public class TaskbarUnfoldAnimationController {
+public class TaskbarUnfoldAnimationController implements
+ TaskbarControllers.LoggableTaskbarController {
- private final ScopedUnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
+ private final ScopedUnfoldTransitionProgressProvider mScopedUnfoldTransitionProgressProvider;
+ private final NaturalRotationUnfoldProgressProvider mNaturalUnfoldTransitionProgressProvider;
private final UnfoldMoveFromCenterAnimator mMoveFromCenterAnimator;
private final TransitionListener mTransitionListener = new TransitionListener();
private TaskbarViewController mTaskbarViewController;
- public TaskbarUnfoldAnimationController(ScopedUnfoldTransitionProgressProvider
- unfoldTransitionProgressProvider, WindowManager windowManager) {
- mUnfoldTransitionProgressProvider = unfoldTransitionProgressProvider;
+ public TaskbarUnfoldAnimationController(BaseTaskbarContext context,
+ ScopedUnfoldTransitionProgressProvider source,
+ WindowManager windowManager,
+ RotationChangeProvider rotationChangeProvider) {
+ mScopedUnfoldTransitionProgressProvider = source;
+ mNaturalUnfoldTransitionProgressProvider =
+ new NaturalRotationUnfoldProgressProvider(context,
+ rotationChangeProvider,
+ source);
mMoveFromCenterAnimator = new UnfoldMoveFromCenterAnimator(windowManager,
new LauncherViewsMoveFromCenterTranslationApplier());
}
/**
* Initializes the controller
+ *
* @param taskbarControllers references to all other taskbar controllers
*/
public void init(TaskbarControllers taskbarControllers) {
+ mNaturalUnfoldTransitionProgressProvider.init();
mTaskbarViewController = taskbarControllers.taskbarViewController;
mTaskbarViewController.addOneTimePreDrawListener(() ->
- mUnfoldTransitionProgressProvider.setReadyToHandleTransition(true));
- mUnfoldTransitionProgressProvider.addCallback(mTransitionListener);
+ mScopedUnfoldTransitionProgressProvider.setReadyToHandleTransition(true));
+ mNaturalUnfoldTransitionProgressProvider.addCallback(mTransitionListener);
}
/**
* Destroys the controller
*/
public void onDestroy() {
- mUnfoldTransitionProgressProvider.setReadyToHandleTransition(false);
- mUnfoldTransitionProgressProvider.removeCallback(mTransitionListener);
+ mScopedUnfoldTransitionProgressProvider.setReadyToHandleTransition(false);
+ mNaturalUnfoldTransitionProgressProvider.removeCallback(mTransitionListener);
+ mNaturalUnfoldTransitionProgressProvider.destroy();
+ mTaskbarViewController = null;
+ }
+
+ @Override
+ public void dumpLogs(String prefix, PrintWriter pw) {
+ pw.println(prefix + "TaskbarUnfoldAnimationController:");
}
private class TransitionListener implements TransitionProgressListener {
@@ -84,5 +105,9 @@ public class TaskbarUnfoldAnimationController {
public void onTransitionProgress(float progress) {
mMoveFromCenterAnimator.onTransitionProgress(progress);
}
+
+ @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 59393d7b5b..fedeec88e4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -15,13 +15,19 @@
*/
package com.android.launcher3.taskbar;
+import static android.content.pm.PackageManager.FEATURE_PC;
+import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
+
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Rect;
+import android.os.Bundle;
import android.util.AttributeSet;
+import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import androidx.annotation.LayoutRes;
@@ -29,26 +35,45 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.icons.ThemedIconDrawable;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.uioverrides.ApiWrapper;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.LauncherBindableItemsContainer;
+import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.DoubleShadowBubbleTextView;
+import com.android.launcher3.views.IconButtonView;
+import com.patrykmichalik.opto.core.PreferenceExtensionsKt;
+
+import java.util.function.Predicate;
+
+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.
*/
-public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconParent, Insettable {
+public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconParent, Insettable,
+ DeviceProfile.OnDeviceProfileChangeListener {
+ private static final String TAG = TaskbarView.class.getSimpleName();
+
+ private static final Rect sTmpRect = new Rect();
private final int[] mTempOutLocation = new int[2];
-
- private final Rect mIconLayoutBounds = new Rect();
+ private final Rect mIconLayoutBounds;
private final int mIconTouchSize;
private final int mItemMarginLeftRight;
private final int mItemPadding;
+ private final int mFolderLeaveBehindColor;
+ private final boolean mIsRtl;
private final TaskbarActivityContext mActivityContext;
@@ -57,12 +82,23 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
private View.OnClickListener mIconClickListener;
private View.OnLongClickListener mIconLongClickListener;
- // Prevents dispatching touches to children if true
- private boolean mTouchEnabled = true;
-
// Only non-null when the corresponding Folder is open.
private @Nullable FolderIcon mLeaveBehindFolderIcon;
+ // Only non-null when device supports having an All Apps button.
+ private @Nullable IconButtonView mAllAppsButton;
+
+ // Only non-null when device supports having an All Apps button.
+ private @Nullable View mTaskbarDivider;
+
+ private View mQsb;
+
+ private float mTransientTaskbarMinWidth;
+
+ private float mTransientTaskbarAllAppsButtonTranslationXOffset;
+
+ private boolean mShouldTryStartAlign;
+
public TaskbarView(@NonNull Context context) {
this(context, null);
}
@@ -72,27 +108,112 @@ 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());
mActivityContext = ActivityContext.lookupContext(context);
-
+ mIconLayoutBounds = mActivityContext.getTransientTaskbarBounds();
Resources resources = getResources();
- mIconTouchSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_touch_size);
+ boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivityContext);
+ 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);
+
+ onDeviceProfileChanged(mActivityContext.getDeviceProfile());
int actualMargin = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
- int actualIconSize = mActivityContext.getDeviceProfile().iconSizePx;
+ int actualIconSize = mActivityContext.getDeviceProfile().taskbarIconSize;
+
+ mIconTouchSize = Math.max(actualIconSize,
+ resources.getDimensionPixelSize(R.dimen.taskbar_icon_min_touch_size));
// We layout the icons to be of mIconTouchSize in width and height
mItemMarginLeftRight = actualMargin - (mIconTouchSize - actualIconSize) / 2;
mItemPadding = (mIconTouchSize - actualIconSize) / 2;
+ mFolderLeaveBehindColor = Themes.getAttrColor(mActivityContext,
+ android.R.attr.textColorTertiary);
+
// Needed to draw folder leave-behind when opening one.
setWillNotDraw(false);
+
+ if (!mActivityContext.getPackageManager().hasSystemFeature(FEATURE_PC)) {
+ mAllAppsButton = (IconButtonView) LayoutInflater.from(context)
+ .inflate(R.layout.taskbar_all_apps_button, this, false);
+ mAllAppsButton.setIconDrawable(resources.getDrawable(isTransientTaskbar
+ ? R.drawable.ic_transient_taskbar_all_apps_button
+ : R.drawable.ic_taskbar_all_apps_button));
+ mAllAppsButton.setScaleX(mIsRtl ? -1 : 1);
+ mAllAppsButton.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
+ mAllAppsButton.setForegroundTint(
+ mActivityContext.getColor(R.color.all_apps_button_color));
+
+ if (FeatureFlags.ENABLE_TASKBAR_PINNING.get()) {
+ mTaskbarDivider = LayoutInflater.from(context).inflate(R.layout.taskbar_divider,
+ this, false);
+ }
+ }
+
+ // TODO: Disable touch events on QSB otherwise it can crash.
+ 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);
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mActivityContext.addOnDeviceProfileChangeListener(this);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mActivityContext.removeOnDeviceProfileChangeListener(this);
+ }
+
+ @Override
+ public void onDeviceProfileChanged(DeviceProfile dp) {
+ mShouldTryStartAlign = mActivityContext.isThreeButtonNav() && dp.startAlignTaskbar;
+ }
+
+ @Override
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+ if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) {
+ announceForAccessibility(mContext.getString(R.string.taskbar_a11y_shown_title));
+ } else if (action == AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS) {
+ announceForAccessibility(mContext.getString(R.string.taskbar_a11y_hidden_title));
+ }
+ return super.performAccessibilityActionInternal(action, arguments);
+
+ }
+
+ protected void announceAccessibilityChanges() {
+ this.performAccessibilityAction(
+ isVisibleToUser() ? AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS
+ : AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
+
+ ActivityContext.lookupContext(getContext()).getDragLayer()
+ .sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED);
+ }
+
+ /**
+ * Returns the icon touch size.
+ */
+ public int getIconTouchSize() {
+ return mIconTouchSize;
}
protected void init(TaskbarViewController.TaskbarViewCallbacks callbacks) {
@@ -101,6 +222,13 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
mIconLongClickListener = mControllerCallbacks.getIconOnLongClickListener();
setOnLongClickListener(mControllerCallbacks.getBackgroundOnLongClickListener());
+
+ if (mAllAppsButton != null) {
+ mAllAppsButton.setOnClickListener(mControllerCallbacks.getAllAppsButtonClickListener());
+ }
+ if (mTaskbarDivider != null) {
+ //TODO(b/265434705): set long press listener
+ }
}
private void removeAndRecycle(View view) {
@@ -120,6 +248,16 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
int nextViewIndex = 0;
int numViewsAnimated = 0;
+ if (mAllAppsButton != null) {
+ removeView(mAllAppsButton);
+
+ if (mTaskbarDivider != null) {
+ removeView(mTaskbarDivider);
+ }
+ }
+ removeView(mQsb);
+
+
for (int i = 0; i < hotseatItemInfos.length; i++) {
ItemInfo hotseatItemInfo = hotseatItemInfos[i];
if (hotseatItemInfo == null) {
@@ -190,6 +328,37 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
while (nextViewIndex < getChildCount()) {
removeAndRecycle(getChildAt(nextViewIndex));
}
+
+ if (mAllAppsButton != null) {
+ mAllAppsButton.setTranslationXForTaskbarAllAppsIcon(getChildCount() > 0
+ ? mTransientTaskbarAllAppsButtonTranslationXOffset : 0f);
+ addView(mAllAppsButton, mIsRtl ? getChildCount() : 0);
+
+ // if only all apps button present, don't include divider view.
+ if (mTaskbarDivider != null && getChildCount() > 1) {
+ addView(mTaskbarDivider, mIsRtl ? (getChildCount() - 1) : 1);
+ }
+ }
+ if (mActivityContext.getDeviceProfile().isQsbInline) {
+ addView(mQsb, mIsRtl ? getChildCount() : 0);
+ // Always set QSB to invisible after re-adding.
+ mQsb.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ /**
+ * Traverse all the child views and change the background of themeIcons
+ **/
+ public void setThemedIconsBackgroundColor(int color) {
+ for (View icon : getIconViews()) {
+ if (icon instanceof DoubleShadowBubbleTextView) {
+ DoubleShadowBubbleTextView textView = ((DoubleShadowBubbleTextView) icon);
+ if (textView.getIcon() != null
+ && textView.getIcon() instanceof ThemedIconDrawable) {
+ ((ThemedIconDrawable) textView.getIcon()).canApplyTheme();
+ }
+ }
+ }
}
/**
@@ -203,48 +372,95 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int count = getChildCount();
- int spaceNeeded = count * (mItemMarginLeftRight * 2 + mIconTouchSize);
- int navSpaceNeeded = ApiWrapper.getHotseatEndOffset(getContext());
- boolean layoutRtl = isLayoutRtl();
- int iconEnd = right - (right - left - spaceNeeded) / 2;
- boolean needMoreSpaceForNav = layoutRtl ?
- navSpaceNeeded > (iconEnd - spaceNeeded) :
- iconEnd > (right - navSpaceNeeded);
- if (needMoreSpaceForNav) {
- int offset = layoutRtl ?
- navSpaceNeeded - (iconEnd - spaceNeeded) :
- (right - navSpaceNeeded) - iconEnd;
- iconEnd += offset;
+ 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;
+ int iconEnd;
+
+ if (mShouldTryStartAlign) {
+ // Taskbar is aligned to the start
+ int startSpacingPx = deviceProfile.inlineNavButtonsEndSpacingPx;
+
+ if (layoutRtl) {
+ iconEnd = right - startSpacingPx;
+ } else {
+ iconEnd = startSpacingPx + spaceNeeded;
+ }
+ } else {
+ iconEnd = centerAlignIconEnd;
+ }
+
+ boolean needMoreSpaceForNav = layoutRtl
+ ? navSpaceNeeded > (iconEnd - spaceNeeded)
+ : iconEnd > (right - navSpaceNeeded);
+ if (needMoreSpaceForNav) {
+ // Add offset to account for nav bar when taskbar is centered
+ int offset = layoutRtl
+ ? navSpaceNeeded - (centerAlignIconEnd - spaceNeeded)
+ : (right - navSpaceNeeded) - centerAlignIconEnd;
+
+ iconEnd = centerAlignIconEnd + offset;
+ }
+
+ sTmpRect.set(mIconLayoutBounds);
+
// Layout the children
mIconLayoutBounds.right = iconEnd;
mIconLayoutBounds.top = (bottom - top - mIconTouchSize) / 2;
mIconLayoutBounds.bottom = mIconLayoutBounds.top + mIconTouchSize;
for (int i = count; i > 0; i--) {
View child = getChildAt(i - 1);
- iconEnd -= mItemMarginLeftRight;
- int iconStart = iconEnd - mIconTouchSize;
- child.layout(iconStart, mIconLayoutBounds.top, iconEnd, mIconLayoutBounds.bottom);
- iconEnd = iconStart - mItemMarginLeftRight;
+ if (child == mQsb) {
+ int qsbStart;
+ int qsbEnd;
+ if (layoutRtl) {
+ qsbStart = iconEnd + mItemMarginLeftRight;
+ qsbEnd = qsbStart + deviceProfile.hotseatQsbWidth;
+ } else {
+ qsbEnd = iconEnd - mItemMarginLeftRight;
+ qsbStart = qsbEnd - deviceProfile.hotseatQsbWidth;
+ }
+ int qsbTop = (bottom - top - deviceProfile.hotseatQsbHeight) / 2;
+ int qsbBottom = qsbTop + deviceProfile.hotseatQsbHeight;
+ child.layout(qsbStart, qsbTop, qsbEnd, qsbBottom);
+ } else if (child == mTaskbarDivider) {
+ iconEnd += mItemMarginLeftRight;
+ int iconStart = iconEnd - mIconTouchSize;
+ child.layout(iconStart, mIconLayoutBounds.top, iconEnd, mIconLayoutBounds.bottom);
+ iconEnd = iconStart + mItemMarginLeftRight;
+ } else {
+ iconEnd -= mItemMarginLeftRight;
+ int iconStart = iconEnd - mIconTouchSize;
+ child.layout(iconStart, mIconLayoutBounds.top, iconEnd, mIconLayoutBounds.bottom);
+ iconEnd = iconStart - mItemMarginLeftRight;
+ }
}
- mIconLayoutBounds.left = iconEnd;
- }
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- if (!mTouchEnabled) {
- return true;
+ mIconLayoutBounds.left = iconEnd;
+
+ if (mIconLayoutBounds.right - mIconLayoutBounds.left < mTransientTaskbarMinWidth) {
+ int center = mIconLayoutBounds.centerX();
+ int distanceFromCenter = (int) mTransientTaskbarMinWidth / 2;
+ mIconLayoutBounds.right = center + distanceFromCenter;
+ mIconLayoutBounds.left = center - distanceFromCenter;
+ }
+
+ if (!sTmpRect.equals(mIconLayoutBounds)) {
+ mControllerCallbacks.notifyIconLayoutBoundsChanged();
}
- return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
- if (!mTouchEnabled) {
- return true;
- }
- if (mIconLayoutBounds.contains((int) event.getX(), (int) event.getY())) {
- // Don't allow long pressing between icons.
+ if (mIconLayoutBounds.left <= event.getX() && event.getX() <= mIconLayoutBounds.right) {
+ // Don't allow long pressing between icons, or above/below them.
return true;
}
if (mControllerCallbacks.onTouchEvent(event)) {
@@ -259,10 +475,6 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
return super.onTouchEvent(event);
}
- public void setTouchesEnabled(boolean touchEnabled) {
- this.mTouchEnabled = touchEnabled;
- }
-
/**
* Returns whether the given MotionEvent, *in screen coorindates*, is within any Taskbar item's
* touch bounds.
@@ -278,6 +490,18 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
return mIconLayoutBounds;
}
+ /**
+ * Returns the space used by the icons
+ */
+ public int getIconLayoutWidth() {
+ int countExcludingQsb = getChildCount();
+ DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
+ if (deviceProfile.isQsbInline) {
+ countExcludingQsb--;
+ }
+ return countExcludingQsb * (mItemMarginLeftRight * 2 + mIconTouchSize);
+ }
+
/**
* Returns the app icons currently shown in the taskbar.
*/
@@ -290,6 +514,21 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
return icons;
}
+ /**
+ * Returns the all apps button in the taskbar.
+ */
+ @Nullable
+ public View getAllAppsButtonView() {
+ return mAllAppsButton;
+ }
+
+ /**
+ * Returns the QSB in the taskbar.
+ */
+ public View getQsb() {
+ return mQsb;
+ }
+
// FolderIconParent implemented methods.
@Override
@@ -312,7 +551,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
if (mLeaveBehindFolderIcon != null) {
canvas.save();
canvas.translate(mLeaveBehindFolderIcon.getLeft(), mLeaveBehindFolderIcon.getTop());
- mLeaveBehindFolderIcon.getFolderBackground().drawLeaveBehind(canvas);
+ mLeaveBehindFolderIcon.getFolderBackground().drawLeaveBehind(canvas,
+ mFolderLeaveBehindColor);
canvas.restore();
}
}
@@ -330,4 +570,39 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
// Consider the overall visibility
return getVisibility() == VISIBLE;
}
+
+ /**
+ * Maps {@code op} over all the child views.
+ */
+ public void mapOverItems(LauncherBindableItemsContainer.ItemOperator op) {
+ // map over all the shortcuts on the taskbar
+ for (int i = 0; i < getChildCount(); i++) {
+ View item = getChildAt(i);
+ if (op.evaluate((ItemInfo) item.getTag(), item)) {
+ return;
+ }
+ }
+ }
+
+ /**
+ * Finds the first icon to match one of the given matchers, from highest to lowest priority.
+ *
+ * @return The first match, or All Apps button if no match was found.
+ */
+ public View getFirstMatch(Predicate... matchers) {
+ for (Predicate matcher : matchers) {
+ for (int i = 0; i < getChildCount(); i++) {
+ View item = getChildAt(i);
+ if (!(item.getTag() instanceof ItemInfo)) {
+ // Should only happen for All Apps button.
+ continue;
+ }
+ ItemInfo info = (ItemInfo) item.getTag();
+ if (matcher.test(info)) {
+ return item;
+ }
+ }
+ }
+ return mAllAppsButton;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 445a23bf3f..6eb409e35d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -16,32 +16,65 @@
package com.android.launcher3.taskbar;
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;
+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.quickstep.AnimatedFloat.VALUE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP;
+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;
+import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_REVEAL_ANIM;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
import android.graphics.Rect;
-import android.util.FloatProperty;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewTreeObserver;
-import android.view.ViewTreeObserver.OnPreDrawListener;
+import android.view.animation.Interpolator;
+
+import androidx.annotation.Nullable;
+import androidx.core.graphics.ColorUtils;
+import androidx.core.view.OneShotPreDrawListener;
-import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+import com.android.launcher3.Reorderable;
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.folder.FolderIcon;
+import com.android.launcher3.anim.RevealOutlineAnimation;
+import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.icons.ThemedIconDrawable;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.LauncherBindableItemsContainer;
+import com.android.launcher3.util.MultiPropertyFactory;
+import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.util.MultiValueAlpha;
-import com.android.quickstep.AnimatedFloat;
+
+import java.io.PrintWriter;
+import java.util.function.Predicate;
/**
* Handles properties/data collection, then passes the results to TaskbarView to render.
*/
-public class TaskbarViewController {
+public class TaskbarViewController implements TaskbarControllers.LoggableTaskbarController {
+
+ private static final String TAG = TaskbarViewController.class.getSimpleName();
+
private static final Runnable NO_OP = () -> { };
public static final int ALPHA_INDEX_HOME = 0;
@@ -49,7 +82,11 @@ public class TaskbarViewController {
public static final int ALPHA_INDEX_STASH = 2;
public static final int ALPHA_INDEX_RECENTS_DISABLED = 3;
public static final int ALPHA_INDEX_NOTIFICATION_EXPANDED = 4;
- private static final int NUM_ALPHA_CHANNELS = 5;
+ public static final int ALPHA_INDEX_ASSISTANT_INVOKED = 5;
+ public static final int ALPHA_INDEX_SMALL_SCREEN = 6;
+ private static final int NUM_ALPHA_CHANNELS = 7;
+
+ private static final float TASKBAR_DARK_THEME_ICONS_BACKGROUND_LUMINANCE = 0.30f;
private final TaskbarActivityContext mActivity;
private final TaskbarView mTaskbarView;
@@ -60,6 +97,18 @@ public class TaskbarViewController {
private final AnimatedFloat mTaskbarIconTranslationYForStash = new AnimatedFloat(
this::updateTranslationY);
private AnimatedFloat mTaskbarNavButtonTranslationY;
+ private AnimatedFloat mTaskbarNavButtonTranslationYForInAppDisplay;
+ private float mTaskbarIconTranslationYForSwipe;
+ private float mTaskbarIconTranslationYForSpringOnStash;
+
+ private final int mTaskbarBottomMargin;
+ private final int mStashedHandleHeight;
+ private final int mLauncherThemedIconsBackgroundColor;
+ private final int mTaskbarThemedIconsBackgroundColor;
+
+ /** Progress from {@code 0} for Launcher's color to {@code 1} for Taskbar's color. */
+ private final AnimatedFloat mThemedIconsBackgroundProgress = new AnimatedFloat(
+ this::updateIconsBackground);
private final TaskbarModelCallbacks mModelCallbacks;
@@ -71,18 +120,41 @@ public class TaskbarViewController {
private AnimatorPlaybackController mIconAlignControllerLazy = null;
private Runnable mOnControllerPreCreateCallback = NO_OP;
+ private boolean mIsHotseatIconOnTopWhenAligned;
+
+ private final DeviceProfile.OnDeviceProfileChangeListener mDeviceProfileChangeListener =
+ dp -> commitRunningAppsToUI();
+
+ private final boolean mIsRtl;
+
public TaskbarViewController(TaskbarActivityContext activity, TaskbarView taskbarView) {
mActivity = activity;
mTaskbarView = taskbarView;
mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, NUM_ALPHA_CHANNELS);
mTaskbarIconAlpha.setUpdateVisibility(true);
mModelCallbacks = new TaskbarModelCallbacks(activity, mTaskbarView);
+ mTaskbarBottomMargin = activity.getDeviceProfile().taskbarBottomMargin;
+ mStashedHandleHeight = activity.getResources()
+ .getDimensionPixelSize(R.dimen.taskbar_stashed_handle_height);
+ mLauncherThemedIconsBackgroundColor = ThemedIconDrawable.getColors(mActivity)[0];
+ if (!Utilities.isDarkTheme(mActivity)) {
+ mTaskbarThemedIconsBackgroundColor = mLauncherThemedIconsBackgroundColor;
+ } else {
+ // Increase luminance for dark themed icons given they are on a dark Taskbar background.
+ float[] colorHSL = new float[3];
+ ColorUtils.colorToHSL(mLauncherThemedIconsBackgroundColor, colorHSL);
+ colorHSL[2] = TASKBAR_DARK_THEME_ICONS_BACKGROUND_LUMINANCE;
+ mTaskbarThemedIconsBackgroundColor = ColorUtils.HSLToColor(colorHSL);
+ }
+ mIsRtl = Utilities.isRtl(mTaskbarView.getResources());
}
public void init(TaskbarControllers controllers) {
mControllers = controllers;
mTaskbarView.init(new TaskbarViewCallbacks());
- mTaskbarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize;
+ mTaskbarView.getLayoutParams().height = isPhoneMode(mActivity.getDeviceProfile())
+ ? mActivity.getResources().getDimensionPixelSize(R.dimen.taskbar_size)
+ : mActivity.getDeviceProfile().taskbarHeight;
mTaskbarIconScaleForStash.updateValue(1f);
@@ -93,33 +165,39 @@ public class TaskbarViewController {
}
mTaskbarNavButtonTranslationY =
controllers.navbarButtonsViewController.getTaskbarNavButtonTranslationY();
+ mTaskbarNavButtonTranslationYForInAppDisplay = controllers.navbarButtonsViewController
+ .getTaskbarNavButtonTranslationYForInAppDisplay();
+
+ mActivity.addOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
+ }
+
+ /**
+ * Announcement for Accessibility when Taskbar stashes/unstashes.
+ */
+ public void announceForAccessibility() {
+ mTaskbarView.announceAccessibilityChanges();
}
public void onDestroy() {
LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks);
+ mActivity.removeOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
+ mModelCallbacks.unregisterListeners();
}
public boolean areIconsVisible() {
return mTaskbarView.areIconsVisible();
}
- public MultiValueAlpha getTaskbarIconAlpha() {
+ public MultiPropertyFactory getTaskbarIconAlpha() {
return mTaskbarIconAlpha;
}
/**
- * Should be called when the IME visibility changes, so we can make Taskbar not steal touches.
- */
- public void setImeIsVisible(boolean isImeVisible) {
- mTaskbarView.setTouchesEnabled(!isImeVisible);
- }
-
- /**
- * Should be called when the recents button is disabled, so we can hide taskbar icons as well.
+ * Should be called when the recents button is disabled, so we can hide Taskbar icons as well.
*/
public void setRecentsButtonDisabled(boolean isDisabled) {
// TODO: check TaskbarStashController#supportsStashing(), to stash instead of setting alpha.
- mTaskbarIconAlpha.getProperty(ALPHA_INDEX_RECENTS_DISABLED).setValue(isDisabled ? 0 : 1);
+ mTaskbarIconAlpha.get(ALPHA_INDEX_RECENTS_DISABLED).setValue(isDisabled ? 0 : 1);
}
/**
@@ -130,32 +208,31 @@ public class TaskbarViewController {
}
/**
- * Adds one time pre draw listener to the taskbar view, it is called before
+ * Adds one time pre draw listener to the Taskbar view, it is called before
* drawing a frame and invoked only once
* @param listener callback that will be invoked before drawing the next frame
*/
- public void addOneTimePreDrawListener(Runnable listener) {
- mTaskbarView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- final ViewTreeObserver viewTreeObserver = mTaskbarView.getViewTreeObserver();
- if (viewTreeObserver.isAlive()) {
- listener.run();
- viewTreeObserver.removeOnPreDrawListener(this);
- }
- return true;
- }
- });
+ public void addOneTimePreDrawListener(@NonNull Runnable listener) {
+ OneShotPreDrawListener.add(mTaskbarView, listener);
}
public Rect getIconLayoutBounds() {
return mTaskbarView.getIconLayoutBounds();
}
+ public int getIconLayoutWidth() {
+ return mTaskbarView.getIconLayoutWidth();
+ }
+
public View[] getIconViews() {
return mTaskbarView.getIconViews();
}
+ @Nullable
+ public View getAllAppsButtonView() {
+ return mTaskbarView.getAllAppsButtonView();
+ }
+
public AnimatedFloat getTaskbarIconScaleForStash() {
return mTaskbarIconScaleForStash;
}
@@ -173,19 +250,169 @@ public class TaskbarViewController {
mTaskbarView.setScaleY(scale);
}
- private void updateTranslationY() {
- mTaskbarView.setTranslationY(mTaskbarIconTranslationYForHome.value
- + mTaskbarIconTranslationYForStash.value);
+ /**
+ * Sets the translation of the TaskbarView during the swipe up gesture.
+ */
+ public void setTranslationYForSwipe(float transY) {
+ mTaskbarIconTranslationYForSwipe = transY;
+ updateTranslationY();
}
/**
- * Sets the taskbar icon alignment relative to Launcher hotseat icons
+ * Sets the translation of the TaskbarView during the spring on stash animation.
+ */
+ public void setTranslationYForStash(float transY) {
+ mTaskbarIconTranslationYForSpringOnStash = transY;
+ updateTranslationY();
+ }
+
+ private void updateTranslationY() {
+ mTaskbarView.setTranslationY(mTaskbarIconTranslationYForHome.value
+ + mTaskbarIconTranslationYForStash.value
+ + mTaskbarIconTranslationYForSwipe
+ + mTaskbarIconTranslationYForSpringOnStash);
+ }
+
+ /**
+ * Updates the Taskbar's themed icons background according to the progress between in-app/home.
+ */
+ protected void updateIconsBackground() {
+ mTaskbarView.setThemedIconsBackgroundColor(
+ ColorUtils.blendARGB(
+ mLauncherThemedIconsBackgroundColor,
+ mTaskbarThemedIconsBackgroundColor,
+ mThemedIconsBackgroundProgress.value
+ ));
+ }
+
+ private ValueAnimator createRevealAnimForView(View view, boolean isStashed, float newWidth,
+ boolean isQsb) {
+ Rect viewBounds = new Rect(0, 0, view.getWidth(), view.getHeight());
+ int centerY = viewBounds.centerY();
+ int halfHandleHeight = mStashedHandleHeight / 2;
+ final int top = centerY - halfHandleHeight;
+ final int bottom = centerY + halfHandleHeight;
+
+ final int left;
+ final int right;
+ // QSB will crop from the 'start' whereas all other icons will crop from the center.
+ if (isQsb) {
+ if (mIsRtl) {
+ right = viewBounds.right;
+ left = (int) (right - newWidth);
+ } else {
+ left = viewBounds.left;
+ right = (int) (left + newWidth);
+ }
+ } else {
+ int widthDelta = (int) ((viewBounds.width() - newWidth) / 2);
+
+ left = viewBounds.left + widthDelta;
+ right = viewBounds.right - widthDelta;
+ }
+
+ Rect stashedRect = new Rect(left, top, right, bottom);
+ // QSB radius can be > 0 since it does not have any UI elements outside of it bounds.
+ float radius = isQsb
+ ? viewBounds.height() / 2f
+ : 0f;
+ float stashedRadius = stashedRect.height() / 2f;
+
+ return new RoundedRectRevealOutlineProvider(radius, stashedRadius, viewBounds, stashedRect)
+ .createRevealAnimator(view, !isStashed, 0);
+ }
+
+ /**
+ * Creates and returns a {@link RevealOutlineAnimation} Animator that updates the icon shape
+ * and size.
+ * @param as The AnimatorSet to add all animations to.
+ * @param isStashed When true, the icon crops vertically to the size of the stashed handle.
+ * When false, the reverse happens.
+ * @param duration The duration of the animation.
+ * @param interpolator The interpolator to use for all animations.
+ */
+ public void addRevealAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration,
+ Interpolator interpolator) {
+ AnimatorSet reveal = new AnimatorSet();
+
+ Rect stashedBounds = new Rect();
+ mControllers.stashedHandleViewController.getStashedHandleBounds(stashedBounds);
+
+ int numIcons = mTaskbarView.getChildCount();
+ float newChildWidth = stashedBounds.width() / (float) numIcons;
+
+ // All children move the same y-amount since they will be cropped to the same centerY.
+ float croppedTransY = mTaskbarView.getIconTouchSize() - stashedBounds.height();
+
+ for (int i = mTaskbarView.getChildCount() - 1; i >= 0; i--) {
+ View child = mTaskbarView.getChildAt(i);
+ boolean isQsb = child == mTaskbarView.getQsb();
+
+ // Crop the icons to/from the nav handle shape.
+ reveal.play(createRevealAnimForView(child, isStashed, newChildWidth, isQsb)
+ .setDuration(duration));
+
+ // Translate the icons to/from their locations as the "nav handle."
+
+ // All of the Taskbar icons will overlap the entirety of the stashed handle
+ // And the QSB, if inline, will overlap part of stashed handle as well.
+ float currentPosition = isQsb ? child.getX() : child.getLeft();
+ float newPosition = stashedBounds.left + (newChildWidth * i);
+ final float croppedTransX;
+ // We look at 'left' and 'right' values to ensure that the children stay within the
+ // bounds of the stashed handle since the new width only occurs at the end of the anim.
+ if (currentPosition > newPosition) {
+ float newRight = stashedBounds.right - (newChildWidth
+ * (numIcons - 1 - i));
+ croppedTransX = -(currentPosition + child.getWidth() - newRight);
+ } else {
+ croppedTransX = newPosition - currentPosition;
+ }
+ float[] transX = isStashed
+ ? new float[] {croppedTransX}
+ : new float[] {croppedTransX, 0};
+ float[] transY = isStashed
+ ? new float[] {croppedTransY}
+ : new float[] {croppedTransY, 0};
+
+ if (child instanceof Reorderable) {
+ MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
+
+ reveal.play(ObjectAnimator.ofFloat(mtd.getTranslationX(INDEX_TASKBAR_REVEAL_ANIM),
+ MULTI_PROPERTY_VALUE, transX)
+ .setDuration(duration));
+ reveal.play(ObjectAnimator.ofFloat(mtd.getTranslationY(INDEX_TASKBAR_REVEAL_ANIM),
+ MULTI_PROPERTY_VALUE, transY));
+ as.addListener(forEndCallback(() ->
+ mtd.setTranslation(INDEX_TASKBAR_REVEAL_ANIM, 0, 0)));
+ } else {
+ reveal.play(ObjectAnimator.ofFloat(child, VIEW_TRANSLATE_X, transX)
+ .setDuration(duration));
+ reveal.play(ObjectAnimator.ofFloat(child, VIEW_TRANSLATE_Y, transY));
+ as.addListener(forEndCallback(() -> {
+ child.setTranslationX(0);
+ child.setTranslationY(0);
+ }));
+ }
+ }
+
+ reveal.setInterpolator(interpolator);
+ as.play(reveal);
+ }
+
+ /**
+ * Sets the Taskbar icon alignment relative to Launcher hotseat icons
* @param alignmentRatio [0, 1]
* 0 => not aligned
* 1 => fully aligned
*/
public void setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp) {
- if (mIconAlignControllerLazy == null) {
+ boolean isHotseatIconOnTopWhenAligned =
+ mControllers.uiController.isHotseatIconOnTopWhenAligned();
+ // When mIsHotseatIconOnTopWhenAligned changes, animation needs to be re-created.
+ if (mIconAlignControllerLazy == null
+ || mIsHotseatIconOnTopWhenAligned != isHotseatIconOnTopWhenAligned) {
+ mIsHotseatIconOnTopWhenAligned = isHotseatIconOnTopWhenAligned;
mIconAlignControllerLazy = createIconAlignmentController(launcherDp);
}
mIconAlignControllerLazy.setPlayFraction(alignmentRatio);
@@ -196,37 +423,131 @@ public class TaskbarViewController {
}
/**
- * Creates an animation for aligning the taskbar icons with the provided Launcher device profile
+ * 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);
+ DeviceProfile taskbarDp = mActivity.getDeviceProfile();
Rect hotseatPadding = launcherDp.getHotseatLayoutPadding(mActivity);
- float scaleUp = ((float) launcherDp.iconSizePx) / mActivity.getDeviceProfile().iconSizePx;
- int hotseatCellSize =
- (launcherDp.availableWidthPx - hotseatPadding.left - hotseatPadding.right)
- / launcherDp.numShownHotseatIcons;
+ float scaleUp = ((float) launcherDp.iconSizePx) / taskbarDp.taskbarIconSize;
+ int borderSpacing = launcherDp.hotseatBorderSpace;
+ int hotseatCellSize = DeviceProfile.calculateCellWidth(
+ launcherDp.availableWidthPx - hotseatPadding.left - hotseatPadding.right,
+ borderSpacing,
+ launcherDp.numShownHotseatIcons);
+
+ boolean isToHome = mControllers.uiController.isIconAlignedWithHotseat();
+ // If Hotseat is not the top element, Taskbar should maintain in-app state as it fades out,
+ // or fade in while already in in-app state.
+ Interpolator interpolator = mIsHotseatIconOnTopWhenAligned ? LINEAR : FINAL_FRAME;
int offsetY = launcherDp.getTaskbarOffsetY();
- setter.setFloat(mTaskbarIconTranslationYForHome, VALUE, -offsetY, LINEAR);
- setter.setFloat(mTaskbarNavButtonTranslationY, VALUE, -offsetY, LINEAR);
+ setter.setFloat(mTaskbarIconTranslationYForHome, VALUE, -offsetY, interpolator);
+ setter.setFloat(mTaskbarNavButtonTranslationY, VALUE, -offsetY, interpolator);
+ setter.setFloat(mTaskbarNavButtonTranslationYForInAppDisplay, VALUE, offsetY, interpolator);
+
+ if (Utilities.isDarkTheme(mTaskbarView.getContext())) {
+ setter.addFloat(mThemedIconsBackgroundProgress, VALUE, 1f, 0f, LINEAR);
+ }
int collapsedHeight = mActivity.getDefaultTaskbarWindowHeight();
- int expandedHeight = Math.max(collapsedHeight,
- mActivity.getDeviceProfile().taskbarSize + offsetY);
+ int expandedHeight = Math.max(collapsedHeight, taskbarDp.taskbarHeight + offsetY);
setter.addOnFrameListener(anim -> mActivity.setTaskbarWindowHeight(
anim.getAnimatedFraction() > 0 ? expandedHeight : collapsedHeight));
- int count = mTaskbarView.getChildCount();
- for (int i = 0; i < count; i++) {
+ for (int i = 0; i < mTaskbarView.getChildCount(); i++) {
View child = mTaskbarView.getChildAt(i);
- ItemInfo info = (ItemInfo) child.getTag();
- setter.setFloat(child, SCALE_PROPERTY, scaleUp, LINEAR);
+ boolean isAllAppsButton = child == mTaskbarView.getAllAppsButtonView();
+ if (!mIsHotseatIconOnTopWhenAligned) {
+ // When going to home, the EMPHASIZED interpolator in TaskbarLauncherStateController
+ // plays iconAlignment to 1 really fast, therefore moving the fading towards the end
+ // to avoid icons disappearing rather than fading out visually.
+ setter.setViewAlpha(child, 0, Interpolators.clampToProgress(LINEAR, 0.8f, 1f));
+ } else if ((isAllAppsButton && !FeatureFlags.ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.get())) {
+ if (!isToHome
+ && mIsHotseatIconOnTopWhenAligned
+ && mControllers.taskbarStashController.isStashed()) {
+ // Prevent All Apps icon from appearing when going from hotseat to nav handle.
+ setter.setViewAlpha(child, 0, Interpolators.clampToProgress(LINEAR, 0f, 0f));
+ } else {
+ setter.setViewAlpha(child, 0,
+ isToHome
+ ? Interpolators.clampToProgress(LINEAR, 0f, 0.17f)
+ : Interpolators.clampToProgress(LINEAR, 0.72f, 0.84f));
+ }
+ }
- float childCenter = (child.getLeft() + child.getRight()) / 2;
- float hotseatIconCenter = hotseatPadding.left + hotseatCellSize * info.screenId
- + hotseatCellSize / 2;
- setter.setFloat(child, ICON_TRANSLATE_X, hotseatIconCenter - childCenter, LINEAR);
+ if (child == mTaskbarView.getQsb()) {
+ boolean isRtl = Utilities.isRtl(child.getResources());
+ float hotseatIconCenter = isRtl
+ ? launcherDp.widthPx - hotseatPadding.right + borderSpacing
+ + launcherDp.hotseatQsbWidth / 2f
+ : hotseatPadding.left - borderSpacing - launcherDp.hotseatQsbWidth / 2f;
+ float childCenter = (child.getLeft() + child.getRight()) / 2f;
+ float halfQsbIconWidthDiff =
+ (launcherDp.hotseatQsbWidth - taskbarDp.taskbarIconSize) / 2f;
+ float scale = ((float) taskbarDp.taskbarIconSize)
+ / launcherDp.hotseatQsbVisualHeight;
+ setter.addFloat(child, SCALE_PROPERTY, scale, 1f, interpolator);
+
+ float fromX = isRtl ? -halfQsbIconWidthDiff : halfQsbIconWidthDiff;
+ float toX = hotseatIconCenter - childCenter;
+ if (child instanceof Reorderable) {
+ MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
+
+ setter.addFloat(mtd.getTranslationX(INDEX_TASKBAR_ALIGNMENT_ANIM),
+ MULTI_PROPERTY_VALUE, fromX, toX, interpolator);
+ setter.setFloat(mtd.getTranslationY(INDEX_TASKBAR_ALIGNMENT_ANIM),
+ MULTI_PROPERTY_VALUE, mTaskbarBottomMargin, interpolator);
+ } else {
+ setter.addFloat(child, VIEW_TRANSLATE_X, fromX, toX, interpolator);
+ setter.setFloat(child, VIEW_TRANSLATE_Y, mTaskbarBottomMargin, interpolator);
+ }
+
+ if (mIsHotseatIconOnTopWhenAligned) {
+ setter.addFloat(child, VIEW_ALPHA, 0f, 1f,
+ isToHome
+ ? Interpolators.clampToProgress(LINEAR, 0f, 0.35f)
+ : mActivity.getDeviceProfile().isQsbInline
+ ? Interpolators.clampToProgress(LINEAR, 0f, 1f)
+ : Interpolators.clampToProgress(LINEAR, 0.84f, 1f));
+ }
+ setter.addOnFrameListener(animator -> AlphaUpdateListener.updateVisibility(child));
+ continue;
+ }
+
+ int positionInHotseat;
+ if (isAllAppsButton) {
+ // Note that there is no All Apps button in the hotseat, this position is only used
+ // as its convenient for animation purposes.
+ positionInHotseat = Utilities.isRtl(child.getResources())
+ ? taskbarDp.numShownHotseatIcons
+ : -1;
+ } else if (child.getTag() instanceof ItemInfo) {
+ positionInHotseat = ((ItemInfo) child.getTag()).screenId;
+ } else {
+ Log.w(TAG, "Unsupported view found in createIconAlignmentController, v=" + child);
+ continue;
+ }
+
+ float hotseatIconCenter = hotseatPadding.left
+ + (hotseatCellSize + borderSpacing) * positionInHotseat
+ + hotseatCellSize / 2f;
+ float childCenter = (child.getLeft() + child.getRight()) / 2f;
+ float toX = hotseatIconCenter - childCenter;
+ if (child instanceof Reorderable) {
+ MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
+
+ setter.setFloat(mtd.getTranslationX(INDEX_TASKBAR_ALIGNMENT_ANIM),
+ MULTI_PROPERTY_VALUE, toX, interpolator);
+ setter.setFloat(mtd.getTranslationY(INDEX_TASKBAR_ALIGNMENT_ANIM),
+ MULTI_PROPERTY_VALUE, mTaskbarBottomMargin, interpolator);
+ } else {
+ setter.setFloat(child, VIEW_TRANSLATE_X, toX, interpolator);
+ setter.setFloat(child, VIEW_TRANSLATE_Y, mTaskbarBottomMargin, interpolator);
+ }
+ setter.setFloat(child, SCALE_PROPERTY, scaleUp, interpolator);
}
AnimatorPlaybackController controller = setter.createPlaybackController();
@@ -235,13 +556,33 @@ public class TaskbarViewController {
}
public void onRotationChanged(DeviceProfile deviceProfile) {
- if (areIconsVisible()) {
- // We only translate on rotation when on home
+ if (!mControllers.uiController.isIconAlignedWithHotseat()) {
+ // We only translate on rotation when icon is aligned with hotseat
return;
}
+ mActivity.setTaskbarWindowHeight(
+ deviceProfile.taskbarHeight + deviceProfile.getTaskbarOffsetY());
mTaskbarNavButtonTranslationY.updateValue(-deviceProfile.getTaskbarOffsetY());
}
+ /**
+ * Maps the given operator to all the top-level children of TaskbarView.
+ */
+ public void mapOverItems(LauncherBindableItemsContainer.ItemOperator op) {
+ mTaskbarView.mapOverItems(op);
+ }
+
+ /**
+ * Returns the first icon to match the given parameter, in priority from:
+ * 1) Icons directly on Taskbar
+ * 2) FolderIcon of the Folder containing the given icon
+ * 3) All Apps button
+ */
+ public View getFirstIconMatch(Predicate matcher) {
+ Predicate folderMatcher = ItemInfoMatcher.forFolderMatch(matcher);
+ return mTaskbarView.getFirstMatch(matcher, folderMatcher);
+ }
+
/**
* Returns whether the given MotionEvent, *in screen coorindates*, is within any Taskbar item's
* touch bounds.
@@ -250,6 +591,36 @@ public class TaskbarViewController {
return mTaskbarView.isEventOverAnyItem(ev);
}
+ @Override
+ public void dumpLogs(String prefix, PrintWriter pw) {
+ pw.println(prefix + "TaskbarViewController:");
+
+ mTaskbarIconAlpha.dump(
+ prefix + "\t",
+ pw,
+ "mTaskbarIconAlpha",
+ "ALPHA_INDEX_HOME",
+ "ALPHA_INDEX_KEYGUARD",
+ "ALPHA_INDEX_STASH",
+ "ALPHA_INDEX_RECENTS_DISABLED",
+ "ALPHA_INDEX_NOTIFICATION_EXPANDED",
+ "ALPHA_INDEX_ASSISTANT_INVOKED",
+ "ALPHA_INDEX_IME_BUTTON_NAV",
+ "ALPHA_INDEX_SMALL_SCREEN");
+
+ mModelCallbacks.dumpLogs(prefix + "\t", pw);
+ }
+
+ /** Called when there's a change in running apps to update the UI. */
+ public void commitRunningAppsToUI() {
+ mModelCallbacks.commitRunningAppsToUI();
+ }
+
+ /** Call TaskbarModelCallbacks to update running apps. */
+ public void updateRunningApps() {
+ mModelCallbacks.updateRunningApps();
+ }
+
/**
* Callbacks for {@link TaskbarView} to interact with its controller.
*/
@@ -263,6 +634,13 @@ public class TaskbarViewController {
return mActivity.getItemOnClickListener();
}
+ public View.OnClickListener getAllAppsButtonClickListener() {
+ return v -> {
+ mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP);
+ mControllers.taskbarAllAppsController.show();
+ };
+ }
+
public View.OnLongClickListener getIconOnLongClickListener() {
return mControllers.taskbarDragController::startDragOnLongClick;
}
@@ -275,6 +653,7 @@ public class TaskbarViewController {
/**
* 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.
+ * TODO(b/270395798): We can remove this entirely once we remove the Transient Taskbar flag.
*/
public boolean onTouchEvent(MotionEvent motionEvent) {
final float x = motionEvent.getRawX();
@@ -303,33 +682,15 @@ public class TaskbarViewController {
}
break;
}
+
return false;
}
+
+ /**
+ * Notifies launcher to update icon alignment.
+ */
+ public void notifyIconLayoutBoundsChanged() {
+ mControllers.uiController.onIconLayoutBoundsChanged();
+ }
}
-
- public static final FloatProperty ICON_TRANSLATE_X =
- new FloatProperty("taskbarAligmentTranslateX") {
-
- @Override
- public void setValue(View view, float v) {
- if (view instanceof BubbleTextView) {
- ((BubbleTextView) view).setTranslationXForTaskbarAlignmentAnimation(v);
- } else if (view instanceof FolderIcon) {
- ((FolderIcon) view).setTranslationForTaskbarAlignmentAnimation(v);
- } else {
- view.setTranslationX(v);
- }
- }
-
- @Override
- public Float get(View view) {
- if (view instanceof BubbleTextView) {
- return ((BubbleTextView) view)
- .getTranslationXForTaskbarAlignmentAnimation();
- } else if (view instanceof FolderIcon) {
- return ((FolderIcon) view).getTranslationXForTaskbarAlignmentAnimation();
- }
- return view.getTranslationX();
- }
- };
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/Utilities.java b/quickstep/src/com/android/launcher3/taskbar/Utilities.java
new file mode 100644
index 0000000000..47d6684727
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/Utilities.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 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;
+
+/**
+ * Various utilities shared amongst the Taskbar's classes.
+ */
+public final class Utilities {
+
+ private Utilities() {}
+
+ /**
+ * Sets drag, long-click, and split selection behavior on 1P and 3P launchers with Taskbar
+ */
+ static void setOverviewDragState(TaskbarControllers controllers,
+ boolean disallowGlobalDrag, boolean disallowLongClick,
+ boolean allowInitialSplitSelection) {
+ controllers.taskbarDragController.setDisallowGlobalDrag(disallowGlobalDrag);
+ controllers.taskbarDragController.setDisallowLongClick(disallowLongClick);
+ controllers.taskbarAllAppsController.setDisallowGlobalDrag(disallowGlobalDrag);
+ controllers.taskbarAllAppsController.setDisallowLongClick(disallowLongClick);
+ controllers.taskbarPopupController.setAllowInitialSplitSelection(
+ allowInitialSplitSelection);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/VoiceInteractionWindowController.kt b/quickstep/src/com/android/launcher3/taskbar/VoiceInteractionWindowController.kt
new file mode 100644
index 0000000000..5a5ff8e880
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/VoiceInteractionWindowController.kt
@@ -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
+
+import android.animation.AnimatorSet
+import android.graphics.Canvas
+import android.view.View
+import android.view.ViewTreeObserver
+import android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION
+import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.views.BaseDragLayer
+import com.android.systemui.animation.ViewRootSync
+import java.io.PrintWriter
+
+private const val TASKBAR_ICONS_FADE_DURATION = 300L
+private const val STASHED_HANDLE_FADE_DURATION = 180L
+private const val TEMP_BACKGROUND_WINDOW_TITLE = "VoiceInteractionTaskbarBackground"
+
+/**
+ * Controls Taskbar behavior while Voice Interaction Window (assistant) is showing. Specifically:
+ * - We always hide the taskbar icons or stashed handle, whichever is currently showing.
+ * - For persistent taskbar, we also move the taskbar background to a new window/layer
+ * (TYPE_APPLICATION_OVERLAY) which is behind the assistant.
+ * - For transient taskbar, we hide the real taskbar background (if it's showing).
+ */
+class VoiceInteractionWindowController(val context: TaskbarActivityContext) :
+ TaskbarControllers.LoggableTaskbarController, TaskbarControllers.BackgroundRendererController {
+
+ private val isSeparateBackgroundEnabled = !DisplayController.isTransientTaskbar(context)
+ private val taskbarBackgroundRenderer = TaskbarBackgroundRenderer(context)
+ private val nonTouchableInsetsComputer =
+ ViewTreeObserver.OnComputeInternalInsetsListener {
+ it.touchableRegion.setEmpty()
+ it.setTouchableInsets(TOUCHABLE_INSETS_REGION)
+ }
+
+ // Initialized in init.
+ private lateinit var controllers: TaskbarControllers
+ // Only initialized if isSeparateBackgroundEnabled
+ private var separateWindowForTaskbarBackground: BaseDragLayer? = null
+ private var separateWindowLayoutParams: WindowManager.LayoutParams? = null
+
+ private var isVoiceInteractionWindowVisible: Boolean = false
+ private var pendingAttachedToWindowListener: View.OnAttachStateChangeListener? = null
+
+ fun init(controllers: TaskbarControllers) {
+ this.controllers = controllers
+
+ if (!isSeparateBackgroundEnabled) {
+ return
+ }
+
+ separateWindowForTaskbarBackground =
+ object : BaseDragLayer(context, null, 0) {
+ override fun recreateControllers() {
+ mControllers = emptyArray()
+ }
+
+ override fun draw(canvas: Canvas) {
+ super.draw(canvas)
+ if (controllers.taskbarStashController.isTaskbarVisibleAndNotStashing) {
+ taskbarBackgroundRenderer.draw(canvas)
+ }
+ }
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ viewTreeObserver.addOnComputeInternalInsetsListener(nonTouchableInsetsComputer)
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ viewTreeObserver.removeOnComputeInternalInsetsListener(
+ nonTouchableInsetsComputer
+ )
+ }
+ }
+ separateWindowForTaskbarBackground?.recreateControllers()
+ separateWindowForTaskbarBackground?.setWillNotDraw(false)
+
+ separateWindowLayoutParams =
+ context.createDefaultWindowLayoutParams(
+ TYPE_APPLICATION_OVERLAY,
+ TEMP_BACKGROUND_WINDOW_TITLE
+ )
+ separateWindowLayoutParams?.isSystemApplicationOverlay = true
+ }
+
+ fun onDestroy() {
+ setIsVoiceInteractionWindowVisible(visible = false, skipAnim = true)
+ separateWindowForTaskbarBackground?.removeOnAttachStateChangeListener(
+ pendingAttachedToWindowListener
+ )
+ }
+
+ fun setIsVoiceInteractionWindowVisible(visible: Boolean, skipAnim: Boolean) {
+ if (isVoiceInteractionWindowVisible == visible) {
+ return
+ }
+ isVoiceInteractionWindowVisible = visible
+
+ // Fade out taskbar icons and stashed handle.
+ val taskbarIconAlpha = if (isVoiceInteractionWindowVisible) 0f else 1f
+ val fadeTaskbarIcons =
+ controllers.taskbarViewController.taskbarIconAlpha
+ .get(TaskbarViewController.ALPHA_INDEX_ASSISTANT_INVOKED)
+ .animateToValue(taskbarIconAlpha)
+ .setDuration(TASKBAR_ICONS_FADE_DURATION)
+ val fadeStashedHandle =
+ controllers.stashedHandleViewController.stashedHandleAlpha
+ .get(StashedHandleViewController.ALPHA_INDEX_ASSISTANT_INVOKED)
+ .animateToValue(taskbarIconAlpha)
+ .setDuration(STASHED_HANDLE_FADE_DURATION)
+ val animSet = AnimatorSet()
+ animSet.play(fadeTaskbarIcons)
+ animSet.play(fadeStashedHandle)
+ if (!isSeparateBackgroundEnabled) {
+ val fadeTaskbarBackground =
+ controllers.taskbarDragLayerController.assistantBgTaskbar
+ .animateToValue(taskbarIconAlpha)
+ .setDuration(TASKBAR_ICONS_FADE_DURATION)
+ animSet.play(fadeTaskbarBackground)
+ }
+ animSet.start()
+ if (skipAnim) {
+ animSet.end()
+ }
+
+ if (isSeparateBackgroundEnabled) {
+ moveTaskbarBackgroundToAppropriateLayer(skipAnim)
+ }
+ }
+
+ /**
+ * Either:
+ *
+ * Hides the TaskbarDragLayer background and creates a new window to draw just that background.
+ *
+ * OR
+ *
+ * Removes the temporary window and show the TaskbarDragLayer background again.
+ */
+ private fun moveTaskbarBackgroundToAppropriateLayer(skipAnim: Boolean) {
+ val moveToLowerLayer = isVoiceInteractionWindowVisible
+ val onWindowsSynchronized =
+ if (moveToLowerLayer) {
+ // First add the temporary window, then hide the overlapping taskbar background.
+ context.addWindowView(
+ separateWindowForTaskbarBackground,
+ separateWindowLayoutParams
+ );
+ { controllers.taskbarDragLayerController.setIsBackgroundDrawnElsewhere(true) }
+ } else {
+ // First reapply the original taskbar background, then remove the temporary window.
+ controllers.taskbarDragLayerController.setIsBackgroundDrawnElsewhere(false);
+ { context.removeWindowView(separateWindowForTaskbarBackground) }
+ }
+
+ if (skipAnim) {
+ onWindowsSynchronized()
+ } else {
+ separateWindowForTaskbarBackground?.runWhenAttachedToWindow {
+ ViewRootSync.synchronizeNextDraw(
+ separateWindowForTaskbarBackground!!,
+ context.dragLayer,
+ onWindowsSynchronized
+ )
+ }
+ }
+ }
+
+ private fun View.runWhenAttachedToWindow(onAttachedToWindow: () -> Unit) {
+ if (isAttachedToWindow) {
+ onAttachedToWindow()
+ return
+ }
+ removeOnAttachStateChangeListener(pendingAttachedToWindowListener)
+ pendingAttachedToWindowListener =
+ object : View.OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(v: View) {
+ onAttachedToWindow()
+ removeOnAttachStateChangeListener(this)
+ pendingAttachedToWindowListener = null
+ }
+
+ override fun onViewDetachedFromWindow(v: View) {}
+ }
+ addOnAttachStateChangeListener(pendingAttachedToWindowListener)
+ }
+
+ override fun setCornerRoundness(cornerRoundness: Float) {
+ if (!isSeparateBackgroundEnabled) {
+ return
+ }
+ taskbarBackgroundRenderer.setCornerRoundness(cornerRoundness)
+ separateWindowForTaskbarBackground?.invalidate()
+ }
+
+ override fun dumpLogs(prefix: String, pw: PrintWriter) {
+ pw.println(prefix + "VoiceInteractionWindowController:")
+ pw.println("$prefix\tisSeparateBackgroundEnabled=$isSeparateBackgroundEnabled")
+ pw.println("$prefix\tisVoiceInteractionWindowVisible=$isVoiceInteractionWindowVisible")
+ pw.println(
+ "$prefix\tisSeparateTaskbarBackgroundAttachedToWindow=" +
+ "${separateWindowForTaskbarBackground?.isAttachedToWindow}"
+ )
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContainerView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContainerView.java
new file mode 100644
index 0000000000..eeca329e80
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContainerView.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 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.allapps;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.WindowInsets;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.ActivityAllAppsContainerView;
+import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
+
+/** All apps container accessible from taskbar. */
+public class TaskbarAllAppsContainerView extends
+ ActivityAllAppsContainerView {
+
+ public TaskbarAllAppsContainerView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TaskbarAllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ setInsets(insets.getInsets(WindowInsets.Type.systemBars()).toRect());
+ return super.onApplyWindowInsets(insets);
+ }
+
+ @Override
+ protected View inflateSearchBox() {
+ // 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);
+ searchView.setId(R.id.search_container_all_apps);
+ searchView.setVisibility(GONE);
+ return searchView;
+ }
+
+ @Override
+ protected boolean isSearchSupported() {
+ return false;
+ }
+
+ @Override
+ protected void updateBackground(DeviceProfile deviceProfile) {
+ super.updateBackground(deviceProfile);
+ // TODO(b/240670050): Remove this and add header protection for the taskbar entrypoint.
+ mBottomSheetBackground.setBackgroundResource(R.drawable.bg_rounded_corner_bottom_sheet);
+ }
+
+ @Override
+ public boolean isInAllApps() {
+ // All apps is always open
+ return true;
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
new file mode 100644
index 0000000000..4a95a8f718
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2022 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.allapps;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.launcher3.R;
+import com.android.launcher3.appprediction.PredictionRowView;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.taskbar.TaskbarControllers;
+import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
+
+import java.util.List;
+
+/**
+ * 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
+ * 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 models for the next all apps session.
+ */
+public final class TaskbarAllAppsController {
+
+ private TaskbarControllers mControllers;
+ private @Nullable TaskbarAllAppsSlideInView mSlideInView;
+ private @Nullable TaskbarAllAppsContainerView mAppsView;
+
+ // Application data models.
+ private AppInfo[] mApps;
+ private int mAppsModelFlags;
+ private List mPredictedApps;
+ private boolean mDisallowGlobalDrag;
+ private boolean mDisallowLongClick;
+
+ /** Initialize the controller. */
+ public void init(TaskbarControllers controllers, boolean allAppsVisible) {
+ mControllers = controllers;
+
+ /*
+ * Recreate All Apps if it was open in the previous Taskbar instance (e.g. the configuration
+ * changed).
+ */
+ if (allAppsVisible) {
+ show(false);
+ }
+ }
+
+ /** Updates the current {@link AppInfo} instances. */
+ public void setApps(AppInfo[] apps, int flags) {
+ mApps = apps;
+ mAppsModelFlags = flags;
+ if (mAppsView != null) {
+ mAppsView.getAppsStore().setApps(mApps, mAppsModelFlags);
+ }
+ }
+
+ public void setDisallowGlobalDrag(boolean disableDragForOverviewState) {
+ mDisallowGlobalDrag = disableDragForOverviewState;
+ }
+
+ public void setDisallowLongClick(boolean disallowLongClick) {
+ mDisallowLongClick = disallowLongClick;
+ }
+
+ /** Updates the current predictions. */
+ public void setPredictedApps(List predictedApps) {
+ mPredictedApps = predictedApps;
+ if (mAppsView != null) {
+ mAppsView.getFloatingHeaderView()
+ .findFixedRowByType(PredictionRowView.class)
+ .setPredictedApps(mPredictedApps);
+ }
+ }
+
+ /** Opens the {@link TaskbarAllAppsContainerView} in a new window. */
+ public void show() {
+ show(true);
+ }
+
+ /** Returns {@code true} if All Apps is open. */
+ public boolean isOpen() {
+ return mSlideInView != null && mSlideInView.isOpen();
+ }
+
+ private void show(boolean animate) {
+ if (mAppsView != null) {
+ return;
+ }
+ // 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);
+ mSlideInView.addOnCloseListener(() -> {
+ mControllers.getSharedState().allAppsVisible = false;
+ mSlideInView = null;
+ mAppsView = null;
+ });
+ TaskbarAllAppsViewController viewController = new TaskbarAllAppsViewController(
+ overlayContext, mSlideInView, mControllers);
+
+ viewController.show(animate);
+ mAppsView = overlayContext.getAppsView();
+ mAppsView.getAppsStore().setApps(mApps, mAppsModelFlags);
+ 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
+ // doesn't also close
+ overlayContext.getDragController().setDisallowGlobalDrag(mDisallowGlobalDrag);
+ overlayContext.getDragController().setDisallowLongClick(mDisallowLongClick);
+ }
+
+
+ @VisibleForTesting
+ public int getTaskbarAllAppsTopPadding() {
+ // Allow null-pointer since this should only be null if the apps view is not showing.
+ return mAppsView.getActiveRecyclerView().getClipBounds().top;
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsFallbackSearchContainer.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsFallbackSearchContainer.java
new file mode 100644
index 0000000000..53fe06d32c
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsFallbackSearchContainer.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 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.allapps;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.ExtendedEditText;
+import com.android.launcher3.allapps.ActivityAllAppsContainerView;
+import com.android.launcher3.allapps.SearchUiManager;
+
+/** Empty search container for Taskbar All Apps used as a fallback if search is not supported. */
+public class TaskbarAllAppsFallbackSearchContainer extends View implements SearchUiManager {
+ public TaskbarAllAppsFallbackSearchContainer(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TaskbarAllAppsFallbackSearchContainer(
+ Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ public void initializeSearch(ActivityAllAppsContainerView> containerView) {
+ // Do nothing.
+ }
+
+ @Override
+ public void resetSearch() {
+ // Do nothing.
+ }
+
+ @Nullable
+ @Override
+ public ExtendedEditText getEditText() {
+ return null;
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
new file mode 100644
index 0000000000..c1597b6507
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2022 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.allapps;
+
+import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
+
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.animation.Interpolator;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Insettable;
+import com.android.launcher3.R;
+import com.android.launcher3.taskbar.allapps.TaskbarAllAppsViewController.TaskbarAllAppsCallbacks;
+import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
+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 TaskbarAllAppsContainerView mAppsView;
+ private float mShiftRange;
+
+ // Initialized in init.
+ private TaskbarAllAppsCallbacks mAllAppsCallbacks;
+
+ public TaskbarAllAppsSlideInView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TaskbarAllAppsSlideInView(Context context, AttributeSet attrs,
+ int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ void init(TaskbarAllAppsCallbacks callbacks) {
+ mAllAppsCallbacks = callbacks;
+ }
+
+ /** Opens the all apps view. */
+ void show(boolean animate) {
+ if (mIsOpen || mOpenCloseAnimator.isRunning()) {
+ return;
+ }
+ mIsOpen = true;
+ attachToContainer();
+
+ if (animate) {
+ mOpenCloseAnimator.setValues(
+ PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
+ mOpenCloseAnimator.setInterpolator(EMPHASIZED);
+ mOpenCloseAnimator.setDuration(mAllAppsCallbacks.getOpenDuration()).start();
+ } else {
+ mTranslationShift = TRANSLATION_SHIFT_OPENED;
+ }
+ }
+
+ /** The apps container inside this view. */
+ TaskbarAllAppsContainerView getAppsView() {
+ return mAppsView;
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ handleClose(animate, mAllAppsCallbacks.getCloseDuration());
+ }
+
+ @Override
+ protected Interpolator getIdleInterpolator() {
+ return EMPHASIZED;
+ }
+
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_TASKBAR_ALL_APPS) != 0;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mAppsView = findViewById(R.id.apps_view);
+ mContent = mAppsView;
+
+ DeviceProfile dp = mActivityContext.getDeviceProfile();
+ setShiftRange(dp.allAppsShiftRange);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mActivityContext.addOnDeviceProfileChangeListener(this);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mActivityContext.removeOnDeviceProfileChangeListener(this);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ setTranslationShift(mTranslationShift);
+ }
+
+ @Override
+ protected int getScrimColor(Context context) {
+ return context.getColor(R.color.widgets_picker_scrim);
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mNoIntercept = !mAppsView.shouldContainerScroll(ev);
+ }
+ return super.onControllerInterceptTouchEvent(ev);
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ mAppsView.setInsets(insets);
+ }
+
+ @Override
+ public void onDeviceProfileChanged(DeviceProfile dp) {
+ setShiftRange(dp.allAppsShiftRange);
+ setTranslationShift(TRANSLATION_SHIFT_OPENED);
+ }
+
+ private void setShiftRange(float shiftRange) {
+ mShiftRange = shiftRange;
+ }
+
+ @Override
+ protected float getShiftRange() {
+ return mShiftRange;
+ }
+
+ @Override
+ protected boolean isEventOverContent(MotionEvent ev) {
+ return getPopupContainer().isEventOverView(mAppsView.getVisibleContainerView(), ev);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
new file mode 100644
index 0000000000..7a3b3e8c7b
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2022 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.allapps;
+
+import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_IN_TASKBAR_ALL_APPS;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.appprediction.AppsDividerView;
+import com.android.launcher3.appprediction.PredictionRowView;
+import com.android.launcher3.taskbar.NavbarButtonsViewController;
+import com.android.launcher3.taskbar.TaskbarControllers;
+import com.android.launcher3.taskbar.TaskbarStashController;
+import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
+import com.android.launcher3.taskbar.overlay.TaskbarOverlayController;
+import com.android.launcher3.util.DisplayController;
+
+/**
+ * Handles the {@link TaskbarAllAppsContainerView} behavior and synchronizes its transitions with
+ * taskbar stashing.
+ */
+final class TaskbarAllAppsViewController {
+
+ private final TaskbarOverlayContext mContext;
+ private final TaskbarAllAppsSlideInView mSlideInView;
+ private final TaskbarAllAppsContainerView mAppsView;
+ private final TaskbarStashController mTaskbarStashController;
+ private final NavbarButtonsViewController mNavbarButtonsViewController;
+ private final TaskbarOverlayController mOverlayController;
+
+ TaskbarAllAppsViewController(
+ TaskbarOverlayContext context,
+ TaskbarAllAppsSlideInView slideInView,
+ TaskbarControllers taskbarControllers) {
+
+ mContext = context;
+ mSlideInView = slideInView;
+ mAppsView = mSlideInView.getAppsView();
+ mTaskbarStashController = taskbarControllers.taskbarStashController;
+ mNavbarButtonsViewController = taskbarControllers.navbarButtonsViewController;
+ mOverlayController = taskbarControllers.taskbarOverlayController;
+
+ mSlideInView.init(new TaskbarAllAppsCallbacks());
+ setUpIconLongClick();
+ setUpAppDivider();
+ setUpTaskbarStashing();
+ }
+
+ /** Starts the {@link TaskbarAllAppsSlideInView} enter transition. */
+ void show(boolean animate) {
+ mSlideInView.show(animate);
+ }
+
+ /** Closes the {@link TaskbarAllAppsSlideInView}. */
+ void close(boolean animate) {
+ mSlideInView.close(animate);
+ }
+
+ private void setUpIconLongClick() {
+ mAppsView.setOnIconLongClickListener(
+ mContext.getDragController()::startDragOnLongClick);
+ mAppsView.getFloatingHeaderView()
+ .findFixedRowByType(PredictionRowView.class)
+ .setOnIconLongClickListener(
+ mContext.getDragController()::startDragOnLongClick);
+ }
+
+ private void setUpAppDivider() {
+ mAppsView.getFloatingHeaderView()
+ .findFixedRowByType(AppsDividerView.class)
+ .setShowAllAppsLabel(!mContext.getOnboardingPrefs().hasReachedMaxCount(
+ ALL_APPS_VISITED_COUNT));
+ mContext.getOnboardingPrefs().incrementEventCount(ALL_APPS_VISITED_COUNT);
+ }
+
+ private void setUpTaskbarStashing() {
+ mTaskbarStashController.updateStateForFlag(FLAG_STASHED_IN_TASKBAR_ALL_APPS, true);
+ mTaskbarStashController.applyState(mOverlayController.getOpenDuration());
+
+ mNavbarButtonsViewController.setSlideInViewVisible(true);
+ mSlideInView.setOnCloseBeginListener(() -> {
+ mNavbarButtonsViewController.setSlideInViewVisible(false);
+ AbstractFloatingView.closeOpenContainer(
+ mContext, AbstractFloatingView.TYPE_ACTION_POPUP);
+
+ if (DisplayController.isTransientTaskbar(mContext)) {
+ mTaskbarStashController.updateStateForFlag(FLAG_STASHED_IN_TASKBAR_ALL_APPS, false);
+ mTaskbarStashController.applyState(mOverlayController.getCloseDuration());
+ } else {
+ // Post in case view is closing due to gesture navigation. If a gesture is in
+ // progress, wait to unstash until after the gesture is finished.
+ MAIN_EXECUTOR.post(() -> mTaskbarStashController.resetFlagIfNoGestureInProgress(
+ FLAG_STASHED_IN_TASKBAR_ALL_APPS));
+ }
+ });
+ }
+
+ class TaskbarAllAppsCallbacks {
+ int getOpenDuration() {
+ return mOverlayController.getOpenDuration();
+ }
+
+ int getCloseDuration() {
+ return mOverlayController.getCloseDuration();
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
new file mode 100644
index 0000000000..e704e5116b
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 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.ImageView
+import android.widget.LinearLayout
+import com.android.launcher3.R
+import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter
+
+/**
+ * Meant to be a simple container for data subclasses will need
+ *
+ * Assumes that the 3 navigation buttons (back/home/recents) have already been added to
+ * [navButtonContainer]
+ *
+ * @property navButtonContainer ViewGroup that holds the 3 navigation buttons.
+ * @property endContextualContainer ViewGroup that holds the end contextual button (ex, IME
+ * dismiss).
+ * @property startContextualContainer ViewGroup that holds the start contextual button (ex, A11y).
+ */
+abstract class AbstractNavButtonLayoutter(
+ val resources: Resources,
+ val navButtonContainer: LinearLayout,
+ 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)
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
new file mode 100644
index 0000000000..c093c9240a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 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.graphics.Color
+import android.graphics.drawable.PaintDrawable
+import android.view.Gravity
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.ImageView
+import android.widget.LinearLayout
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.DIMEN_TASKBAR_BACK_BUTTON_LEFT_MARGIN_KIDS
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.DIMEN_TASKBAR_HOME_BUTTON_LEFT_MARGIN_KIDS
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.DIMEN_TASKBAR_ICON_SIZE_KIDS
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.DIMEN_TASKBAR_NAV_BUTTONS_CORNER_RADIUS_KIDS
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.DIMEN_TASKBAR_NAV_BUTTONS_HEIGHT_KIDS
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.DIMEN_TASKBAR_NAV_BUTTONS_WIDTH_KIDS
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.DRAWABLE_SYSBAR_BACK_KIDS
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.DRAWABLE_SYSBAR_HOME_KIDS
+
+class KidsNavLayoutter(
+ resources: Resources,
+ navBarContainer: LinearLayout,
+ endContextualContainer: ViewGroup,
+ startContextualContainer: ViewGroup
+) :
+ AbstractNavButtonLayoutter(
+ resources,
+ navBarContainer,
+ endContextualContainer,
+ startContextualContainer
+ ) {
+
+ override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
+ val iconSize: Int = resources.getDimensionPixelSize(DIMEN_TASKBAR_ICON_SIZE_KIDS)
+ val buttonWidth: Int = resources.getDimensionPixelSize(DIMEN_TASKBAR_NAV_BUTTONS_WIDTH_KIDS)
+ val buttonHeight: Int =
+ resources.getDimensionPixelSize(DIMEN_TASKBAR_NAV_BUTTONS_HEIGHT_KIDS)
+ val buttonRadius: Int =
+ resources.getDimensionPixelSize(DIMEN_TASKBAR_NAV_BUTTONS_CORNER_RADIUS_KIDS)
+ val paddingLeft = (buttonWidth - iconSize) / 2
+ val paddingTop = (buttonHeight - iconSize) / 2
+
+ // Update icons
+ 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.scaleType = ImageView.ScaleType.FIT_CENTER
+ homeButton.setPadding(paddingLeft, paddingTop, paddingLeft, paddingTop)
+
+ // Home button layout
+ val homeLayoutparams = LinearLayout.LayoutParams(buttonWidth, buttonHeight)
+ val homeButtonLeftMargin: Int =
+ resources.getDimensionPixelSize(DIMEN_TASKBAR_HOME_BUTTON_LEFT_MARGIN_KIDS)
+ homeLayoutparams.setMargins(homeButtonLeftMargin, 0, 0, 0)
+ homeButton.layoutParams = homeLayoutparams
+
+ // Back button layout
+ val backLayoutParams = LinearLayout.LayoutParams(buttonWidth, buttonHeight)
+ val backButtonLeftMargin: Int =
+ resources.getDimensionPixelSize(DIMEN_TASKBAR_BACK_BUTTON_LEFT_MARGIN_KIDS)
+ backLayoutParams.setMargins(backButtonLeftMargin, 0, 0, 0)
+ backButton.layoutParams = backLayoutParams
+
+ // Button backgrounds
+ val whiteWith10PctAlpha = Color.argb(0.1f, 1f, 1f, 1f)
+ val buttonBackground = PaintDrawable(whiteWith10PctAlpha)
+ buttonBackground.setCornerRadius(buttonRadius.toFloat())
+ homeButton.background = buttonBackground
+ backButton.background = buttonBackground
+
+ // Update alignment within taskbar
+ val navButtonsLayoutParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
+ navButtonsLayoutParams.apply {
+ marginStart = navButtonsLayoutParams.marginEnd / 2
+ marginEnd = navButtonsLayoutParams.marginStart
+ gravity = Gravity.CENTER
+ }
+ navButtonContainer.requestLayout()
+
+ homeButton.onLongClickListener = null
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/LayoutResourceHelper.java b/quickstep/src/com/android/launcher3/taskbar/navbutton/LayoutResourceHelper.java
new file mode 100644
index 0000000000..0d9b855c91
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/LayoutResourceHelper.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 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.annotation.DimenRes;
+import android.annotation.DrawableRes;
+import android.annotation.IdRes;
+
+import com.android.launcher3.R;
+
+/**
+ * A class for retrieving resources in Kotlin.
+ *
+ * This class should be removed once the build system supports resources loading in Kotlin.
+ */
+public final class LayoutResourceHelper {
+
+ // --------------------------
+ // Kids Nav Layout
+ @DimenRes
+ public static final int DIMEN_TASKBAR_ICON_SIZE_KIDS = R.dimen.taskbar_icon_size_kids;
+ @DrawableRes
+ public static final int DRAWABLE_SYSBAR_BACK_KIDS = R.drawable.ic_sysbar_back_kids;
+ @DrawableRes
+ public static final int DRAWABLE_SYSBAR_HOME_KIDS = R.drawable.ic_sysbar_home_kids;
+ @DimenRes
+ public static final int DIMEN_TASKBAR_HOME_BUTTON_LEFT_MARGIN_KIDS =
+ R.dimen.taskbar_home_button_left_margin_kids;
+ @DimenRes
+ public static final int DIMEN_TASKBAR_BACK_BUTTON_LEFT_MARGIN_KIDS =
+ R.dimen.taskbar_back_button_left_margin_kids;
+ @DimenRes
+ public static final int DIMEN_TASKBAR_NAV_BUTTONS_WIDTH_KIDS =
+ R.dimen.taskbar_nav_buttons_width_kids;
+ @DimenRes
+ public static final int DIMEN_TASKBAR_NAV_BUTTONS_HEIGHT_KIDS =
+ R.dimen.taskbar_nav_buttons_height_kids;
+ @DimenRes
+ public static final int DIMEN_TASKBAR_NAV_BUTTONS_CORNER_RADIUS_KIDS =
+ R.dimen.taskbar_nav_buttons_corner_radius_kids;
+
+ // --------------------------
+ // Nav Layout Factory
+ @IdRes
+ public static final int ID_START_CONTEXTUAL_BUTTONS = R.id.start_contextual_buttons;
+ @IdRes
+ public static final int ID_END_CONTEXTUAL_BUTTONS = R.id.end_contextual_buttons;
+ @IdRes
+ public static final int ID_END_NAV_BUTTONS = R.id.end_nav_buttons;
+
+ private LayoutResourceHelper() {
+
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
new file mode 100644
index 0000000000..b730b215cd
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2022 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.FrameLayout
+import android.widget.LinearLayout
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.ID_END_CONTEXTUAL_BUTTONS
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.ID_END_NAV_BUTTONS
+import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.ID_START_CONTEXTUAL_BUTTONS
+import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.Companion
+import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter
+
+/**
+ * Select the correct layout for nav buttons
+ *
+ * Since layouts are done dynamically for the nav buttons on Taskbar, this class returns a
+ * corresponding [NavButtonLayoutter] via [Companion.getUiLayoutter] that can help position the
+ * buttons based on the current [DeviceProfile]
+ */
+class NavButtonLayoutFactory {
+ companion object {
+ /**
+ * Get the correct instance of [NavButtonLayoutter]
+ *
+ * No layouts supported for configurations where:
+ * * taskbar isn't showing AND
+ * * the device is not in [phoneMode] OR
+ * * phone is showing
+ * * device is using gesture navigation
+ *
+ * @param navButtonsView ViewGroup that contains start, end, nav button ViewGroups
+ * @param isKidsMode no-op when taskbar is hidden/not showing
+ * @param isInSetup no-op when taskbar is hidden/not showing
+ * @param phoneMode refers to the device using the taskbar window on phones
+ * @param isThreeButtonNav are no-ops when taskbar is present/showing
+ */
+ fun getUiLayoutter(
+ deviceProfile: DeviceProfile,
+ navButtonsView: FrameLayout,
+ resources: Resources,
+ isKidsMode: Boolean,
+ isInSetup: Boolean,
+ isThreeButtonNav: Boolean,
+ phoneMode: Boolean
+ ): NavButtonLayoutter {
+ val navButtonContainer =
+ navButtonsView.requireViewById(ID_END_NAV_BUTTONS)
+ val endContextualContainer =
+ navButtonsView.requireViewById(ID_END_CONTEXTUAL_BUTTONS)
+ val startContextualContainer =
+ navButtonsView.requireViewById(ID_START_CONTEXTUAL_BUTTONS)
+ val isPhoneNavMode = phoneMode && isThreeButtonNav
+ return when {
+ isPhoneNavMode -> {
+ if (!deviceProfile.isLandscape) {
+ PhonePortraitNavLayoutter(
+ resources,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer
+ )
+ } else {
+ PhoneLandscapeNavLayoutter(
+ resources,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer
+ )
+ }
+ }
+ deviceProfile.isTaskbarPresent -> {
+ return when {
+ isInSetup -> {
+ SetupNavLayoutter(
+ resources,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer
+ )
+ }
+ isKidsMode -> {
+ KidsNavLayoutter(
+ resources,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer
+ )
+ }
+ else ->
+ TaskbarNavLayoutter(
+ resources,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer
+ )
+ }
+ }
+ else -> error("No layoutter found")
+ }
+ }
+ }
+
+ /** Lays out and provides access to the home, recents, and back buttons for various mischief */
+ interface NavButtonLayoutter {
+ fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean)
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
new file mode 100644
index 0000000000..201895fc67
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2022 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.Gravity
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import androidx.core.view.children
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.R
+import com.android.launcher3.taskbar.TaskbarManager
+import com.android.launcher3.util.DimensionUtils
+
+class PhoneLandscapeNavLayoutter(
+ resources: Resources,
+ navBarContainer: LinearLayout,
+ endContextualContainer: ViewGroup,
+ startContextualContainer: ViewGroup
+) :
+ AbstractNavButtonLayoutter(
+ resources,
+ navBarContainer,
+ endContextualContainer,
+ startContextualContainer
+ ) {
+
+ 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))
+ navButtonContainer.removeAllViews()
+ navButtonContainer.orientation = LinearLayout.VERTICAL
+
+ navContainerParams.apply {
+ width = taskbarDimensions.x
+ height = ViewGroup.LayoutParams.MATCH_PARENT
+ gravity = Gravity.CENTER
+ topMargin = endStartMargins
+ bottomMargin = endStartMargins
+ marginEnd = 0
+ marginStart = 0
+ }
+
+ // Swap recents and back button
+ navButtonContainer.addView(recentsButton)
+ navButtonContainer.addView(homeButton)
+ navButtonContainer.addView(backButton)
+
+ navButtonContainer.layoutParams = navContainerParams
+
+ // Add the spaces in between the nav buttons
+ val spaceInBetween: Int =
+ resources.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween_phone)
+ navButtonContainer.children.forEachIndexed { i, navButton ->
+ val buttonLayoutParams = navButton.layoutParams as LinearLayout.LayoutParams
+ buttonLayoutParams.weight = 1f
+ when (i) {
+ 0 -> {
+ buttonLayoutParams.bottomMargin = spaceInBetween / 2
+ }
+ navButtonContainer.childCount - 1 -> {
+ buttonLayoutParams.topMargin = spaceInBetween / 2
+ }
+ else -> {
+ buttonLayoutParams.bottomMargin = spaceInBetween / 2
+ buttonLayoutParams.topMargin = spaceInBetween / 2
+ }
+ }
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
new file mode 100644
index 0000000000..f7ac9745f0
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2022 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.Gravity
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.R
+import com.android.launcher3.taskbar.TaskbarManager
+import com.android.launcher3.util.DimensionUtils
+
+class PhonePortraitNavLayoutter(
+ resources: Resources,
+ navBarContainer: LinearLayout,
+ endContextualContainer: ViewGroup,
+ startContextualContainer: ViewGroup
+) :
+ AbstractNavButtonLayoutter(
+ resources,
+ navBarContainer,
+ endContextualContainer,
+ startContextualContainer
+ ) {
+
+ 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))
+ 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
+ // 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
+
+ // Add the spaces in between the nav buttons
+ val spaceInBetween =
+ resources.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween_phone)
+ for (i in 0 until navButtonContainer.childCount) {
+ val navButton = navButtonContainer.getChildAt(i)
+ val buttonLayoutParams = navButton.layoutParams as LinearLayout.LayoutParams
+ buttonLayoutParams.weight = 1f
+ when (i) {
+ 0 -> {
+ // First button
+ buttonLayoutParams.marginEnd = spaceInBetween / 2
+ }
+ navButtonContainer.childCount - 1 -> {
+ // Last button
+ buttonLayoutParams.marginStart = spaceInBetween / 2
+ }
+ else -> {
+ // other buttons
+ buttonLayoutParams.marginStart = spaceInBetween / 2
+ buttonLayoutParams.marginEnd = spaceInBetween / 2
+ }
+ }
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
new file mode 100644
index 0000000000..a24002c44d
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 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.Gravity
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import com.android.launcher3.DeviceProfile
+
+class SetupNavLayoutter(
+ resources: Resources,
+ navButtonContainer: LinearLayout,
+ endContextualContainer: ViewGroup,
+ startContextualContainer: ViewGroup
+) :
+ AbstractNavButtonLayoutter(
+ resources,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer
+ ) {
+
+ override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
+ // Since setup wizard only has back button enabled, it looks strange to be
+ // end-aligned, so start-align instead.
+ val navButtonsLayoutParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
+ navButtonsLayoutParams.apply {
+ marginStart = navButtonsLayoutParams.marginEnd
+ marginEnd = 0
+ gravity = Gravity.START
+ }
+ navButtonContainer.requestLayout()
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
new file mode 100644
index 0000000000..5ec7ca0e2e
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 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.Gravity
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.R
+
+/** Layoutter for showing 3 button navigation on large screen */
+class TaskbarNavLayoutter(
+ resources: Resources,
+ navBarContainer: LinearLayout,
+ endContextualContainer: ViewGroup,
+ startContextualContainer: ViewGroup
+) :
+ AbstractNavButtonLayoutter(
+ resources,
+ navBarContainer,
+ endContextualContainer,
+ startContextualContainer
+ ) {
+
+ 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
+ // contextual button to be showing - if not, move the nav buttons over a smidge
+ if (isContextualButtonShowing && navMarginEnd < contextualWidth) {
+ // Additional spacing, eat up half of space between last icon and nav button
+ navMarginEnd += resources.getDimensionPixelSize(R.dimen.taskbar_hotseat_nav_spacing) / 2
+ }
+
+ navButtonParams.apply {
+ gravity = Gravity.END
+ width = FrameLayout.LayoutParams.WRAP_CONTENT
+ height = ViewGroup.LayoutParams.MATCH_PARENT
+ marginEnd = navMarginEnd
+ }
+ navButtonContainer.orientation = LinearLayout.HORIZONTAL
+ navButtonContainer.layoutParams = navButtonParams
+
+ // Add the spaces in between the nav buttons
+ val spaceInBetween = resources.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween)
+ for (i in 0 until navButtonContainer.childCount) {
+ val navButton = navButtonContainer.getChildAt(i)
+ val buttonLayoutParams = navButton.layoutParams as LinearLayout.LayoutParams
+ buttonLayoutParams.weight = 0f
+ when (i) {
+ 0 -> {
+ buttonLayoutParams.marginEnd = spaceInBetween / 2
+ }
+ navButtonContainer.childCount - 1 -> {
+ buttonLayoutParams.marginStart = spaceInBetween / 2
+ }
+ else -> {
+ buttonLayoutParams.marginStart = spaceInBetween / 2
+ buttonLayoutParams.marginEnd = spaceInBetween / 2
+ }
+ }
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
new file mode 100644
index 0000000000..66d591889b
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2022 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.overlay;
+
+import android.content.Context;
+import android.view.View;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.dot.DotInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.popup.PopupDataProvider;
+import com.android.launcher3.taskbar.BaseTaskbarContext;
+import com.android.launcher3.taskbar.TaskbarActivityContext;
+import com.android.launcher3.taskbar.TaskbarControllers;
+import com.android.launcher3.taskbar.TaskbarDragController;
+import com.android.launcher3.taskbar.TaskbarStashController;
+import com.android.launcher3.taskbar.TaskbarUIController;
+import com.android.launcher3.taskbar.allapps.TaskbarAllAppsContainerView;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
+
+/**
+ * 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
+ * {@link TaskbarActivityContext} such as {@link PopupDataProvider}.
+ */
+public class TaskbarOverlayContext extends BaseTaskbarContext {
+ private final TaskbarActivityContext mTaskbarContext;
+
+ private final TaskbarOverlayController mOverlayController;
+ private final TaskbarDragController mDragController;
+ private final TaskbarOverlayDragLayer mDragLayer;
+
+ // We automatically stash taskbar when All Apps is opened in gesture navigation mode.
+ private final boolean mWillTaskbarBeVisuallyStashed;
+ private final int mStashedTaskbarHeight;
+ private final TaskbarUIController mUiController;
+
+ public TaskbarOverlayContext(
+ Context windowContext,
+ TaskbarActivityContext taskbarContext,
+ TaskbarControllers controllers) {
+ super(windowContext);
+ mTaskbarContext = taskbarContext;
+ mOverlayController = controllers.taskbarOverlayController;
+ mDragController = new TaskbarDragController(this);
+ mDragController.init(controllers);
+ mDragLayer = new TaskbarOverlayDragLayer(this);
+
+ TaskbarStashController taskbarStashController = controllers.taskbarStashController;
+ mWillTaskbarBeVisuallyStashed = taskbarStashController.supportsVisualStashing();
+ mStashedTaskbarHeight = taskbarStashController.getStashedHeight();
+
+ mUiController = controllers.uiController;
+ }
+
+ boolean willTaskbarBeVisuallyStashed() {
+ return mWillTaskbarBeVisuallyStashed;
+ }
+
+ int getStashedTaskbarHeight() {
+ return mStashedTaskbarHeight;
+ }
+
+ public TaskbarOverlayController getOverlayController() {
+ return mOverlayController;
+ }
+
+ @Override
+ public DeviceProfile getDeviceProfile() {
+ return mOverlayController.getLauncherDeviceProfile();
+ }
+
+ @Override
+ public TaskbarDragController getDragController() {
+ return mDragController;
+ }
+
+ @Override
+ public TaskbarOverlayDragLayer getDragLayer() {
+ return mDragLayer;
+ }
+
+ @Override
+ public TaskbarAllAppsContainerView getAppsView() {
+ return mDragLayer.findViewById(R.id.apps_view);
+ }
+
+ @Override
+ public boolean isBindingItems() {
+ return mTaskbarContext.isBindingItems();
+ }
+
+ @Override
+ public View.OnClickListener getItemOnClickListener() {
+ return mTaskbarContext.getItemOnClickListener();
+ }
+
+ @Override
+ public PopupDataProvider getPopupDataProvider() {
+ return mTaskbarContext.getPopupDataProvider();
+ }
+
+ @Override
+ public void startSplitSelection(SplitSelectSource splitSelectSource) {
+ mUiController.startSplitSelection(splitSelectSource);
+ }
+
+ @Override
+ public DotInfo getDotInfoForItem(ItemInfo info) {
+ return mTaskbarContext.getDotInfoForItem(info);
+ }
+
+ @Override
+ public void onDragStart() {}
+
+ @Override
+ public void onDragEnd() {
+ mOverlayController.maybeCloseWindow();
+ }
+
+ @Override
+ public void onPopupVisibilityChanged(boolean isVisible) {}
+
+ @Override
+ public void onSplitScreenMenuButtonClicked() {
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java
new file mode 100644
index 0000000000..476e0a8bab
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2022 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.overlay;
+
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
+import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
+import static com.android.launcher3.LauncherState.ALL_APPS;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.taskbar.TaskbarActivityContext;
+import com.android.launcher3.taskbar.TaskbarControllers;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
+
+import java.util.Optional;
+
+/**
+ * Handles the Taskbar overlay window lifecycle.
+ *
+ * Overlays need to be inflated in a separate window so that have the correct hierarchy. For
+ * instance, they need to be below the notification tray. If there are multiple overlays open, the
+ * same window is used.
+ */
+public final class TaskbarOverlayController {
+
+ private static final String WINDOW_TITLE = "Taskbar Overlay";
+
+ private final TaskbarActivityContext mTaskbarContext;
+ private final Context mWindowContext;
+ private final TaskbarOverlayProxyView mProxyView;
+ private final LayoutParams mLayoutParams;
+
+ private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
+ @Override
+ public void onTaskStackChanged() {
+ mProxyView.close(false);
+ }
+
+ @Override
+ public void onTaskMovedToFront(int taskId) {
+ mProxyView.close(false);
+ }
+ };
+
+ private DeviceProfile mLauncherDeviceProfile;
+ private @Nullable TaskbarOverlayContext mOverlayContext;
+ private TaskbarControllers mControllers; // Initialized in init.
+
+ public TaskbarOverlayController(
+ TaskbarActivityContext taskbarContext, DeviceProfile launcherDeviceProfile) {
+ mTaskbarContext = taskbarContext;
+ mWindowContext = mTaskbarContext.createWindowContext(TYPE_APPLICATION_OVERLAY, null);
+ mProxyView = new TaskbarOverlayProxyView();
+ mLayoutParams = createLayoutParams();
+ mLauncherDeviceProfile = launcherDeviceProfile;
+ }
+
+ /** Initialize the controller. */
+ public void init(TaskbarControllers controllers) {
+ mControllers = controllers;
+ }
+
+ /**
+ * Creates a window for Taskbar overlays, if it does not already exist. Returns the window
+ * context for the current overlay window.
+ */
+ public TaskbarOverlayContext requestWindow() {
+ if (mOverlayContext == null) {
+ mOverlayContext = new TaskbarOverlayContext(
+ mWindowContext, mTaskbarContext, mControllers);
+ }
+
+ if (!mProxyView.isOpen()) {
+ mProxyView.show();
+ Optional.ofNullable(mOverlayContext.getSystemService(WindowManager.class))
+ .ifPresent(m -> m.addView(mOverlayContext.getDragLayer(), mLayoutParams));
+ TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
+ }
+
+ return mOverlayContext;
+ }
+
+ /** Hides the current overlay window with animation. */
+ public void hideWindow() {
+ mProxyView.close(true);
+ }
+
+ /**
+ * Removes the overlay window from the hierarchy, if all floating views are closed and there is
+ * no system drag operation in progress.
+ *
+ * This method should be called after an exit animation finishes, if applicable.
+ */
+ @SuppressLint("WrongConstant")
+ void maybeCloseWindow() {
+ if (mOverlayContext != null && (AbstractFloatingView.hasOpenView(mOverlayContext, TYPE_ALL)
+ || mOverlayContext.getDragController().isSystemDragInProgress())) {
+ return;
+ }
+ mProxyView.close(false);
+ onDestroy();
+ }
+
+ /** Destroys the controller and any overlay window if present. */
+ public void onDestroy() {
+ TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
+ Optional.ofNullable(mOverlayContext)
+ .map(c -> c.getSystemService(WindowManager.class))
+ .ifPresent(m -> m.removeViewImmediate(mOverlayContext.getDragLayer()));
+ mOverlayContext = null;
+ }
+
+ /** The current device profile for the overlay window. */
+ public DeviceProfile getLauncherDeviceProfile() {
+ return mLauncherDeviceProfile;
+ }
+
+ /** Updates {@link DeviceProfile} instance for Taskbar's overlay window. */
+ public void updateLauncherDeviceProfile(DeviceProfile dp) {
+ mLauncherDeviceProfile = dp;
+ Optional.ofNullable(mOverlayContext).ifPresent(c -> {
+ AbstractFloatingView.closeAllOpenViewsExcept(c, false, TYPE_REBIND_SAFE);
+ c.dispatchDeviceProfileChanged();
+ });
+ }
+
+ /** The default open duration for overlays. */
+ public int getOpenDuration() {
+ return ALL_APPS.getTransitionDuration(mTaskbarContext, true);
+ }
+
+ /** The default close duration for overlays. */
+ public int getCloseDuration() {
+ return ALL_APPS.getTransitionDuration(mTaskbarContext, false);
+ }
+
+ @SuppressLint("WrongConstant")
+ private LayoutParams createLayoutParams() {
+ LayoutParams layoutParams = new LayoutParams(
+ TYPE_APPLICATION_OVERLAY,
+ LayoutParams.FLAG_SPLIT_TOUCH,
+ PixelFormat.TRANSLUCENT);
+ layoutParams.setTitle(WINDOW_TITLE);
+ layoutParams.gravity = Gravity.BOTTOM;
+ layoutParams.packageName = mTaskbarContext.getPackageName();
+ layoutParams.setFitInsetsTypes(0); // Handled by container view.
+ layoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ layoutParams.setSystemApplicationOverlay(true);
+ return layoutParams;
+ }
+
+ /**
+ * Proxy view connecting taskbar drag layer to the overlay window.
+ *
+ * Overlays are in a separate window and has its own drag layer, but this proxy lets its views
+ * behave as though they are in the taskbar drag layer. For instance, when the taskbar closes
+ * all {@link AbstractFloatingView} instances, the overlay window will also close.
+ */
+ private class TaskbarOverlayProxyView extends AbstractFloatingView {
+
+ private TaskbarOverlayProxyView() {
+ super(mTaskbarContext, null);
+ }
+
+ private void show() {
+ mIsOpen = true;
+ mTaskbarContext.getDragLayer().addView(this);
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ mTaskbarContext.getDragLayer().removeView(this);
+ Optional.ofNullable(mOverlayContext).ifPresent(c -> closeAllOpenViews(c, animate));
+ }
+
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_TASKBAR_OVERLAY_PROXY) != 0;
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ return false;
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java
new file mode 100644
index 0000000000..ec64128c91
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2022 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.overlay;
+
+import static android.view.KeyEvent.ACTION_UP;
+import static android.view.KeyEvent.KEYCODE_BACK;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
+
+import android.content.Context;
+import android.graphics.Insets;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.util.TouchController;
+import com.android.launcher3.views.BaseDragLayer;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/** Root drag layer for the Taskbar overlay window. */
+public class TaskbarOverlayDragLayer extends
+ BaseDragLayer implements
+ ViewTreeObserver.OnComputeInternalInsetsListener {
+
+ private final List mOnClickListeners = new CopyOnWriteArrayList<>();
+ private final TouchController mClickListenerTouchController = new TouchController() {
+ @Override
+ public boolean onControllerTouchEvent(MotionEvent ev) {
+ if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
+ for (OnClickListener listener : mOnClickListeners) {
+ listener.onClick(TaskbarOverlayDragLayer.this);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ for (int i = 0; i < getChildCount(); i++) {
+ if (isEventOverView(getChildAt(i), ev)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ };
+
+ TaskbarOverlayDragLayer(Context context) {
+ super(context, null, 1);
+ setClipChildren(false);
+ recreateControllers();
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+ }
+
+ @Override
+ public void recreateControllers() {
+ mControllers = mOnClickListeners.isEmpty()
+ ? new TouchController[]{mActivity.getDragController()}
+ : new TouchController[] {
+ mActivity.getDragController(), mClickListenerTouchController};
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev);
+ return super.dispatchTouchEvent(ev);
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (event.getAction() == ACTION_UP && event.getKeyCode() == KEYCODE_BACK) {
+ AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
+ if (topView != null && topView.canHandleBack()) {
+ topView.onBackInvoked();
+ return true;
+ }
+ }
+ return super.dispatchKeyEvent(event);
+ }
+
+ @Override
+ public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
+ if (mActivity.getDragController().isSystemDragInProgress()) {
+ inoutInfo.touchableRegion.setEmpty();
+ inoutInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
+ }
+ }
+
+ @Override
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ return updateInsetsDueToStashing(insets);
+ }
+
+ @Override
+ public void onViewRemoved(View child) {
+ super.onViewRemoved(child);
+ mActivity.getOverlayController().maybeCloseWindow();
+ }
+
+ /**
+ * Adds the given callback to clicks to this drag layer.
+ *
+ * Clicks are only accepted on this drag layer if they fall within this drag layer's bounds and
+ * outside the bounds of all child views.
+ *
+ * If the click falls within the bounds of a child view, then this callback does not run and
+ * that child can optionally handle it.
+ */
+ private void addOnClickListener(@NonNull OnClickListener listener) {
+ boolean wasEmpty = mOnClickListeners.isEmpty();
+ mOnClickListeners.add(listener);
+ if (wasEmpty) {
+ recreateControllers();
+ }
+ }
+
+ /**
+ * Removes the given on click callback.
+ *
+ * No-op if the callback was never added.
+ */
+ private void removeOnClickListener(@NonNull OnClickListener listener) {
+ boolean wasEmpty = mOnClickListeners.isEmpty();
+ mOnClickListeners.remove(listener);
+ if (!wasEmpty && mOnClickListeners.isEmpty()) {
+ recreateControllers();
+ }
+ }
+
+ /**
+ * Queues the given callback on the next click on this drag layer.
+ *
+ * Once run, this callback is immediately removed.
+ */
+ public void runOnClickOnce(@NonNull OnClickListener listener) {
+ addOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ listener.onClick(v);
+ removeOnClickListener(this);
+ }
+ });
+ }
+
+ /**
+ * Taskbar automatically stashes when opening all apps, but we don't report the insets as
+ * changing to avoid moving the underlying app. But internally, the apps view should still
+ * layout according to the stashed insets rather than the unstashed insets. So this method
+ * does two things:
+ * 1) Sets navigationBars bottom inset to stashedHeight.
+ * 2) Sets tappableInsets bottom inset to 0.
+ */
+ private WindowInsets updateInsetsDueToStashing(WindowInsets oldInsets) {
+ if (!mActivity.willTaskbarBeVisuallyStashed()) {
+ return oldInsets;
+ }
+ WindowInsets.Builder updatedInsetsBuilder = new WindowInsets.Builder(oldInsets);
+
+ Insets oldNavInsets = oldInsets.getInsets(WindowInsets.Type.navigationBars());
+ Insets newNavInsets = Insets.of(oldNavInsets.left, oldNavInsets.top, oldNavInsets.right,
+ mActivity.getStashedTaskbarHeight());
+ updatedInsetsBuilder.setInsets(WindowInsets.Type.navigationBars(), newNavInsets);
+
+ Insets oldTappableInsets = oldInsets.getInsets(WindowInsets.Type.tappableElement());
+ Insets newTappableInsets = Insets.of(oldTappableInsets.left, oldTappableInsets.top,
+ oldTappableInsets.right, 0);
+ updatedInsetsBuilder.setInsets(WindowInsets.Type.tappableElement(), newTappableInsets);
+
+ return updatedInsetsBuilder.build();
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/unfold/NonDestroyableScopedUnfoldTransitionProgressProvider.java b/quickstep/src/com/android/launcher3/taskbar/unfold/NonDestroyableScopedUnfoldTransitionProgressProvider.java
new file mode 100644
index 0000000000..f9da4e4e74
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/unfold/NonDestroyableScopedUnfoldTransitionProgressProvider.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 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.unfold;
+
+import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
+
+/**
+ * ScopedUnfoldTransitionProgressProvider that doesn't propagate destroy method
+ */
+public class NonDestroyableScopedUnfoldTransitionProgressProvider extends
+ ScopedUnfoldTransitionProgressProvider {
+
+ @Override
+ public void destroy() {
+ // no-op
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
index afb06f1fe0..d22b32a66a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -17,61 +17,21 @@
package com.android.launcher3.uioverrides;
import android.app.Person;
-import android.content.Context;
import android.content.pm.ShortcutInfo;
-import android.content.res.Resources;
-import android.view.Display;
-import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.quickstep.SysUINavigationMode;
-import com.android.quickstep.SysUINavigationMode.Mode;
+/**
+ * A wrapper for the hidden API calls
+ */
public class ApiWrapper {
public static final boolean TASKBAR_DRAWN_IN_PROCESS = true;
public static Person[] getPersons(ShortcutInfo si) {
- if (!Utilities.ATLEAST_Q) return Utilities.EMPTY_PERSON_ARRAY;
+ if (!Utilities.ATLEAST_Q)
+ return Utilities.EMPTY_PERSON_ARRAY;
Person[] persons = si.getPersons();
return persons == null ? Utilities.EMPTY_PERSON_ARRAY : persons;
}
-
- /**
- * Returns true if the display is an internal displays
- */
- public static boolean isInternalDisplay(Display display) {
- return display.getType() == Display.TYPE_INTERNAL;
- }
-
- /**
- * Returns a unique ID representing the display
- */
- public static String getUniqueId(Display display) {
- try {
- return display.getUniqueId();
- } catch (Throwable t) {
- return "" + display.getDisplayId();
- }
- }
-
- /**
- * Returns the minimum space that should be left empty at the end of hotseat
- */
- public static int getHotseatEndOffset(Context context) {
- if (SysUINavigationMode.INSTANCE.get(context).getMode() == Mode.THREE_BUTTONS) {
- Resources res = context.getResources();
- /*
- * 3 nav buttons +
- * Little space at the end for contextual buttons +
- * Little space between icons and nav buttons
- */
- return 3 * res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
- + res.getDimensionPixelSize(R.dimen.taskbar_contextual_button_margin)
- + res.getDimensionPixelSize(R.dimen.taskbar_hotseat_nav_spacing);
- } else {
- return 0;
- }
-
- }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 84b3839825..955440b49a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -16,6 +16,7 @@
package com.android.launcher3.uioverrides;
+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;
@@ -23,25 +24,32 @@ 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.launcher3.testing.TestProtocol.BAD_STATE;
+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.util.Log;
+import android.view.animation.Interpolator;
import androidx.annotation.NonNull;
-import com.android.launcher3.BaseQuickstepLauncher;
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.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
+import com.android.quickstep.views.FloatingTaskView;
import com.android.quickstep.views.RecentsView;
/**
@@ -53,9 +61,9 @@ import com.android.quickstep.views.RecentsView;
public abstract class BaseRecentsViewStateController
implements StateHandler {
protected final T mRecentsView;
- protected final BaseQuickstepLauncher mLauncher;
+ protected final QuickstepLauncher mLauncher;
- public BaseRecentsViewStateController(@NonNull BaseQuickstepLauncher launcher) {
+ public BaseRecentsViewStateController(@NonNull QuickstepLauncher launcher) {
mLauncher = launcher;
mRecentsView = launcher.getOverviewPanel();
}
@@ -67,24 +75,25 @@ public abstract class BaseRecentsViewStateController
ADJACENT_PAGE_HORIZONTAL_OFFSET.set(mRecentsView, scaleAndOffset[1]);
TASK_SECONDARY_TRANSLATION.set(mRecentsView, 0f);
- float recentsAlpha = state.overviewUi ? 1f : 0;
- Log.d(BAD_STATE, "BaseRecentsViewStateController setState state=" + state
- + ", alpha=" + recentsAlpha);
- getContentAlphaProperty().set(mRecentsView, recentsAlpha);
+ getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0);
getTaskModalnessProperty().set(mRecentsView, state.getOverviewModalness());
RECENTS_GRID_PROGRESS.set(mRecentsView,
state.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f);
+ TASK_THUMBNAIL_SPLASH_ALPHA.set(mRecentsView, state.showTaskThumbnailSplash() ? 1f : 0f);
}
@Override
public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
PendingAnimation builder) {
- Log.d(BAD_STATE, "BaseRecentsViewStateController setStateWithAnimation state=" + toState
- + ", config.skipOverview=" + config.hasAnimationFlag(SKIP_OVERVIEW));
if (config.hasAnimationFlag(SKIP_OVERVIEW)) {
return;
}
setStateWithAnimationInternal(toState, config, builder);
+ builder.addEndListener(success -> {
+ if (!success) {
+ mRecentsView.reset();
+ }
+ });
}
/**
@@ -104,19 +113,70 @@ public abstract class BaseRecentsViewStateController
setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f,
config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
- float recentsAlpha = toState.overviewUi ? 1 : 0;
- Log.d(BAD_STATE, "BaseRecentsViewStateController setStateWithAnimationInternal toState="
- + toState + ", alpha=" + recentsAlpha);
- setter.setFloat(mRecentsView, getContentAlphaProperty(), recentsAlpha,
+ 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
+ )
+ );
+ }
+ }
+
+ setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
setter.setFloat(
mRecentsView, getTaskModalnessProperty(),
toState.getOverviewModalness(),
config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR));
+
+ LauncherState fromState = mLauncher.getStateManager().getState();
+ setter.setFloat(mRecentsView, TASK_THUMBNAIL_SPLASH_ALPHA,
+ toState.showTaskThumbnailSplash() ? 1f : 0f,
+ !toState.showTaskThumbnailSplash() && fromState == QUICK_SWITCH_FROM_HOME
+ ? LINEAR : INSTANT);
+
boolean showAsGrid = toState.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile());
+ Interpolator gridProgressInterpolator = showAsGrid
+ ? fromState == QUICK_SWITCH_FROM_HOME ? LINEAR : INSTANT
+ : FINAL_FRAME;
setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS, showAsGrid ? 1f : 0f,
- showAsGrid ? INSTANT : FINAL_FRAME);
+ gridProgressInterpolator);
}
abstract FloatProperty getTaskModalnessProperty();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index ee6e8cee9b..f3663cd19c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -16,6 +16,7 @@
package com.android.launcher3.uioverrides;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
+import static com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -48,10 +49,12 @@ import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.GraphicsUtils;
import com.android.launcher3.icons.IconNormalizer;
import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.touch.ItemLongClickListener;
@@ -115,14 +118,16 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
this(context, attrs, 0);
}
+ Context mContext;
public PredictedAppIcon(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+ mContext = context;
mDeviceProfile = ActivityContext.lookupContext(context).getDeviceProfile();
mNormalizedIconSize = IconNormalizer.getNormalizedCircleSize(getIconSize());
int shadowSize = context.getResources().getDimensionPixelSize(
R.dimen.blur_size_thin_outline);
mShadowFilter = new BlurMaskFilter(shadowSize, BlurMaskFilter.Blur.OUTER);
- mShapePath = GraphicsUtils.getShapePath(mNormalizedIconSize);
+ mShapePath = GraphicsUtils.getShapePath(context, mNormalizedIconSize);
}
@Override
@@ -179,7 +184,7 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
: null;
super.applyFromWorkspaceItem(info, animate, staggerIndex);
int oldPlateColor = mPlateColor;
- int newPlateColor = ColorUtils.setAlphaComponent(mDotParams.color, 200);
+ int newPlateColor = ColorUtils.setAlphaComponent(mDotParams.appColor, 200);
if (!animate) {
mPlateColor = newPlateColor;
}
@@ -236,7 +241,7 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
mSlotMachineIcons = new ArrayList<>(iconsToAnimate.size() + 2);
mSlotMachineIcons.add(getIcon());
iconsToAnimate.stream()
- .map(iconInfo -> iconInfo.newThemedIcon(mContext))
+ .map(iconInfo -> iconInfo.newIcon(mContext))
.forEach(mSlotMachineIcons::add);
if (endWithOriginalIcon) {
mSlotMachineIcons.add(getIcon());
@@ -270,7 +275,7 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
mIsPinned = true;
applyFromWorkspaceItem(info);
setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
- ((CellLayout.LayoutParams) getLayoutParams()).canReorder = true;
+ ((CellLayoutLayoutParams) getLayoutParams()).canReorder = true;
invalidate();
}
@@ -279,7 +284,7 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
*/
public void finishBinding(OnLongClickListener longClickListener) {
setOnLongClickListener(longClickListener);
- ((CellLayout.LayoutParams) getLayoutParams()).canReorder = false;
+ ((CellLayoutLayoutParams) getLayoutParams()).canReorder = false;
setTextVisibility(false);
verifyHighRes();
}
@@ -358,6 +363,19 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
canvas.drawPath(mRingPath, mIconRingPaint);
}
+ @Override
+ public void setIconDisabled(boolean isDisabled) {
+ super.setIconDisabled(isDisabled);
+ mIconRingPaint.setColorFilter(isDisabled ? getDisabledColorFilter() : null);
+ invalidate();
+ }
+
+ @Override
+ protected void setItemInfo(ItemInfoWithIcon itemInfo) {
+ super.setItemInfo(itemInfo);
+ setIconDisabled(itemInfo.isDisabled());
+ }
+
@Override
public void getSourceVisualDragBounds(Rect bounds) {
super.getSourceVisualDragBounds(bounds);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepAppWidgetHost.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepAppWidgetHost.java
new file mode 100644
index 0000000000..6659fa0ee7
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepAppWidgetHost.java
@@ -0,0 +1,70 @@
+/**
+ * Copyright (C) 2022 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 com.android.launcher3.widget.LauncherWidgetHolder.APPWIDGET_HOST_ID;
+
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+import android.os.Looper;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.LauncherWidgetHolder;
+
+import java.util.function.IntConsumer;
+
+/**
+ * {@link AppWidgetHost} that is used to receive the changes to the widgets without
+ * storing any {@code Activity} info like that of the launcher.
+ */
+final class QuickstepAppWidgetHost extends AppWidgetHost {
+ private final @NonNull Context mContext;
+ private final @NonNull IntConsumer mAppWidgetRemovedCallback;
+ private final @NonNull LauncherWidgetHolder.ProviderChangedListener mProvidersChangedListener;
+
+ QuickstepAppWidgetHost(@NonNull Context context, @NonNull IntConsumer appWidgetRemovedCallback,
+ @NonNull LauncherWidgetHolder.ProviderChangedListener listener,
+ @NonNull Looper looper) {
+ super(context, APPWIDGET_HOST_ID, null, looper);
+ mContext = context;
+ mAppWidgetRemovedCallback = appWidgetRemovedCallback;
+ mProvidersChangedListener = listener;
+ }
+
+ @Override
+ protected void onProvidersChanged() {
+ mProvidersChangedListener.notifyWidgetProvidersChanged();
+ }
+
+ @Override
+ public void onAppWidgetRemoved(int appWidgetId) {
+ mAppWidgetRemovedCallback.accept(appWidgetId);
+ }
+
+ @Override
+ protected void onProviderChanged(int appWidgetId, @NonNull AppWidgetProviderInfo appWidget) {
+ LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo.fromProviderInfo(
+ mContext, appWidget);
+ super.onProviderChanged(appWidgetId, info);
+ // The super method updates the dimensions of the providerInfo. Update the
+ // launcher spans accordingly.
+ info.initSpans(mContext, LauncherAppState.getIDP(mContext));
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
index 757bb9c9d9..df0f204135 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
@@ -36,6 +36,7 @@ import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.ActivityOptionsWrapper;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
+import app.lawnchair.LawnchairApp;
import dev.rikka.tools.refine.Refine;
/** Provides a Quickstep specific animation when launching an activity from an app widget. */
@@ -68,7 +69,7 @@ class QuickstepInteractionHandler implements RemoteViews.InteractionHandler {
launchCookie = mLauncher.getLaunchCookie((ItemInfo) itemInfo);
activityOptions.options.setLaunchCookie(launchCookie);
}
- if (Utilities.ATLEAST_S && !pendingIntent.isActivity()) {
+ if (Utilities.ATLEAST_S && !pendingIntent.isActivity() && LawnchairApp.isRecentsEnabled ()) {
// In the event this pending intent eventually launches an activity, i.e. a trampoline,
// use the Quickstep transition animation.
try {
@@ -86,9 +87,10 @@ class QuickstepInteractionHandler implements RemoteViews.InteractionHandler {
} catch (RemoteException e) {
// Do nothing.
}
+ activityOptions.options.setPendingIntentLaunchFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ activityOptions.options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR);
}
- activityOptions.options.setPendingIntentLaunchFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- activityOptions.options.setSplashscreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_EMPTY);
+
options = Pair.create(options.first, activityOptions.options);
if (pendingIntent.isActivity()) {
logAppLaunch(itemInfo);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index b0924faf85..143f1a9ecb 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,50 +14,116 @@
* limitations under the License.
*/
package com.android.launcher3.uioverrides;
-
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.os.Trace.TRACE_TAG_APP;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
-
+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_QUICKSTEP_WIDGET_APP_START;
+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.testing.TestProtocol.HINT_STATE_ORDINAL;
-import static com.android.launcher3.testing.TestProtocol.HINT_STATE_TWO_BUTTON_ORDINAL;
-import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
-import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
+import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
+import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition;
+import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
+import static com.android.launcher3.popup.SystemShortcut.INSTALL;
+import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
+import static com.android.launcher3.taskbar.LauncherTaskbarUIController.ALL_APPS_PAGE_PROGRESS_INDEX;
+import static com.android.launcher3.taskbar.LauncherTaskbarUIController.MINUS_ONE_PAGE_PROGRESS_INDEX;
+import static com.android.launcher3.taskbar.LauncherTaskbarUIController.WIDGETS_PAGE_PROGRESS_INDEX;
+import static com.android.launcher3.testing.shared.TestProtocol.HINT_STATE_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.HINT_STATE_TWO_BUTTON_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
+import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
+import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO;
+import static com.android.quickstep.util.SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
-
+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;
+import android.content.IntentSender;
import android.content.SharedPreferences;
import android.content.res.Configuration;
+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.Bundle;
+import android.os.CancellationSignal;
+import android.os.IBinder;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.view.Display;
import android.view.HapticFeedbackConstants;
+import android.view.RemoteAnimationTarget;
import android.view.View;
-
-import com.android.launcher3.BaseQuickstepLauncher;
+import android.window.BackEvent;
+import android.window.OnBackAnimationCallback;
+import android.window.OnBackInvokedDispatcher;
+import android.window.SplashScreen;
+import androidx.annotation.BinderThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+//import com.android.app.viewcapture.SettingsAwareViewCapture;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.OnBackPressedHandler;
import com.android.launcher3.QuickstepAccessibilityDelegate;
+import com.android.launcher3.QuickstepTransitionManager;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
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;
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.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;
+import com.android.launcher3.statemanager.StateManager.StateHandler;
+import com.android.launcher3.taskbar.LauncherTaskbarUIController;
+import com.android.launcher3.taskbar.TaskbarManager;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.uioverrides.QuickstepWidgetHolder.QuickstepHolderFactory;
import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory;
import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.NoButtonNavbarToOverviewTouchController;
@@ -68,59 +134,125 @@ import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchControll
import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarTouchController;
-import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.OnboardingPrefs;
+import com.android.launcher3.util.ActivityOptionsWrapper;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.IntSet;
+import com.android.launcher3.util.NavigationMode;
+import com.android.launcher3.util.ObjectWrapper;
import com.android.launcher3.util.PendingRequestArgs;
+import com.android.launcher3.util.PendingSplitSelectInfo;
+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.TouchController;
-import com.android.launcher3.util.UiThreadHelper;
-import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
-import com.android.launcher3.widget.LauncherAppWidgetHost;
-import com.android.quickstep.SysUINavigationMode;
-import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.launcher3.widget.LauncherWidgetHolder;
+import com.android.quickstep.OverviewCommandHelper;
+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.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;
+import com.android.quickstep.util.TISBindHelper;
+import com.android.quickstep.views.DesktopTaskView;
+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.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;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
import java.util.Objects;
+import java.util.function.Predicate;
import java.util.stream.Stream;
-
-import app.lawnchair.LawnchairApp;
-
-public class QuickstepLauncher extends BaseQuickstepLauncher {
-
+public class QuickstepLauncher extends Launcher {
+ public static final boolean ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
+ SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", true);
public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
- /**
- * Reusable command for applying the shelf height on the background thread.
- */
- public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) ->
- SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2);
-
private FixedContainerItems mAllAppsPredictions;
private HotseatPredictionController mHotseatPredictionController;
-
+ private DepthController mDepthController;
+ private DesktopVisibilityController mDesktopVisibilityController;
+ 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;
+ /**
+ * If Launcher restarted while in the middle of an Overview split select, it needs this data to
+ * recover. In all other cases this will remain null.
+ */
+ private PendingSplitSelectInfo mPendingSplitSelectInfo = null;
+ private SafeCloseable mViewCapture;
+ private boolean mEnableWidgetDepth;
@Override
protected void setupViews() {
super.setupViews();
+ mActionsView = findViewById(R.id.overview_actions_view);
+ RecentsView overviewPanel = (RecentsView) getOverviewPanel();
+ mSplitSelectStateController =
+ new SplitSelectStateController(this, mHandler, getStateManager(),
+ getDepthController(), getStatsLogManager(),
+ SystemUiProxy.INSTANCE.get(this), RecentsModel.INSTANCE.get(this));
+ overviewPanel.init(mActionsView, mSplitSelectStateController);
+ mSplitWithKeyboardShortcutController = new SplitWithKeyboardShortcutController(this,
+ mSplitSelectStateController);
+ mSplitToWorkspaceController = new SplitToWorkspaceController(this,
+ mSplitSelectStateController);
+ mActionsView.updateDimension(getDeviceProfile(), overviewPanel.getLastComputedTaskSize());
+ mActionsView.updateVerticalMargin(DisplayController.getNavigationMode(this));
+ mAppTransitionManager = buildAppTransitionManager();
+ mAppTransitionManager.registerRemoteAnimations();
+ mAppTransitionManager.registerRemoteTransitions();
+ mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
+ mDepthController = new DepthController(this);
+ mDesktopVisibilityController = new DesktopVisibilityController(this);
mHotseatPredictionController = new HotseatPredictionController(this);
+ mEnableWidgetDepth = SystemProperties.getBoolean("ro.launcher.depth.widget", true);
+ getWorkspace().addOverlayCallback(progress ->
+ onTaskbarInAppDisplayProgressUpdate(progress, MINUS_ONE_PAGE_PROGRESS_INDEX));
}
-
@Override
public void logAppLaunch(StatsLogManager statsLogManager, ItemInfo info,
- InstanceId instanceId) {
+ InstanceId instanceId) {
// If the app launch is from any of the surfaces in AllApps then add the InstanceId from
// LiveSearchManager to recreate the AllApps session on the server side.
if (mAllAppsSessionLogId != null && ALL_APPS.equals(
getStateManager().getCurrentStableState())) {
instanceId = mAllAppsSessionLogId;
}
-
StatsLogger logger = statsLogManager.logger().withItemInfo(info).withInstanceId(instanceId);
-
if (mAllAppsPredictions != null
&& (info.itemType == ITEM_TYPE_APPLICATION
|| info.itemType == ITEM_TYPE_SHORTCUT
@@ -134,46 +266,49 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
logger.withRank(i);
break;
}
-
}
}
logger.log(LAUNCHER_APP_LAUNCH_TAP);
-
mHotseatPredictionController.logLaunchedAppRankingInfo(info, instanceId);
}
-
@Override
protected void completeAddShortcut(Intent data, int container, int screenId, int cellX,
- int cellY, PendingRequestArgs args) {
+ int cellY, PendingRequestArgs args) {
if (container == CONTAINER_HOTSEAT) {
mHotseatPredictionController.onDeferredDrop(cellX, cellY);
}
super.completeAddShortcut(data, container, screenId, cellX, cellY, args);
}
-
@Override
protected LauncherAccessibilityDelegate createAccessibilityDelegate() {
return new QuickstepAccessibilityDelegate(this);
}
-
/**
* Returns Prediction controller for hybrid hotseat
*/
public HotseatPredictionController getHotseatPredictionController() {
return mHotseatPredictionController;
}
-
@Override
- protected OnboardingPrefs createOnboardingPrefs(SharedPreferences sharedPrefs) {
+ public void enableHotseatEdu(boolean enable) {
+ super.enableHotseatEdu(enable);
+ mHotseatPredictionController.enableHotseatEdu(enable);
+ }
+ /**
+ * Builds the {@link QuickstepTransitionManager} instance to use for managing transitions.
+ */
+ protected QuickstepTransitionManager buildAppTransitionManager() {
+ return new QuickstepTransitionManager(this);
+ }
+ @Override
+ protected QuickstepOnboardingPrefs createOnboardingPrefs(SharedPreferences sharedPrefs) {
return new QuickstepOnboardingPrefs(this, sharedPrefs);
}
-
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
onStateOrResumeChanging(false /* inTransition */);
}
-
@Override
public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
// Only pause is taskbar controller is not present
@@ -184,33 +319,65 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
}
return started;
}
-
@Override
protected void onActivityFlagsChanged(int changeBits) {
+ if ((changeBits & ACTIVITY_STATE_STARTED) != 0) {
+ mDepthController.setActivityStarted(isStarted());
+ }
+ if ((changeBits & ACTIVITY_STATE_RESUMED) != 0) {
+ if (mTaskbarUIController != null) {
+ mTaskbarUIController.onLauncherResumedOrPaused(hasBeenResumed());
+ }
+ }
super.onActivityFlagsChanged(changeBits);
if ((changeBits & (ACTIVITY_STATE_DEFERRED_RESUMED | ACTIVITY_STATE_STARTED
| ACTIVITY_STATE_USER_ACTIVE | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0) {
onStateOrResumeChanging((getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0);
}
-
if (((changeBits & ACTIVITY_STATE_STARTED) != 0
|| (changeBits & getActivityFlags() & ACTIVITY_STATE_DEFERRED_RESUMED) != 0)) {
mHotseatPredictionController.setPauseUIUpdate(false);
}
}
-
@Override
protected void showAllAppsFromIntent(boolean alreadyOnHome) {
TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
super.showAllAppsFromIntent(alreadyOnHome);
}
-
+ protected void onItemClicked(View view) {
+ if (!mSplitToWorkspaceController.handleSecondAppSelectionForSplit(view)) {
+ QuickstepLauncher.super.getItemOnClickListener().onClick(view);
+ }
+ }
+ @Override
+ public View.OnClickListener getItemOnClickListener() {
+ return this::onItemClicked;
+ }
@Override
public Stream getSupportedShortcuts() {
- return Stream.concat(
- Stream.of(mHotseatPredictionController), super.getSupportedShortcuts());
+ // Order matters as it affects order of appearance in popup container
+ List shortcuts = new ArrayList(Arrays.asList(
+ APP_INFO, WellbeingModel.SHORTCUT_FACTORY, mHotseatPredictionController));
+ shortcuts.addAll(getSplitShortcuts());
+ shortcuts.add(WIDGETS);
+ shortcuts.add(INSTALL);
+ return shortcuts.stream();
+ }
+ private List> getSplitShortcuts() {
+ if (!ENABLE_SPLIT_FROM_WORKSPACE.get() || !mDeviceProfile.isTablet) {
+ return Collections.emptyList();
+ }
+ RecentsView recentsView = (RecentsView) getOverviewPanel();
+ // TODO(b/266482558): Pull it out of PagedOrentationHandler for split from workspace.
+ List positions =
+ recentsView.getPagedOrientationHandler().getSplitPositionOptions(
+ mDeviceProfile);
+ List> splitShortcuts = new ArrayList<>();
+ for (SplitPositionOption position : positions) {
+ splitShortcuts.add(getSplitSelectShortcutByPosition(position));
+ }
+ return splitShortcuts;
}
-
/**
* Recents logic that triggers when launcher state changes or launcher activity stops/resumes.
*/
@@ -223,53 +390,65 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
(getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0;
boolean visible = (state == NORMAL || state == OVERVIEW)
&& (willUserBeActive || isUserActive())
- && !profile.isVerticalBarLayout()
- && profile.isPhone && !profile.isLandscape;
- UiThreadHelper.runAsyncCommand(this, SET_SHELF_HEIGHT, visible ? 1 : 0,
- profile.hotseatBarSizePx);
+ && !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);
+ }
}
if (state == NORMAL && !inTransition) {
((RecentsView) getOverviewPanel()).setSwipeDownShouldLaunchApp(false);
}
}
-
@Override
public void bindExtraContainerItems(FixedContainerItems item) {
if (item.containerId == Favorites.CONTAINER_PREDICTION) {
mAllAppsPredictions = item;
- getAppsView().getFloatingHeaderView().findFixedRowByType(PredictionRowView.class)
- .setPredictedApps(item.items);
+ PredictionRowView> predictionRowView =
+ getAppsView().getFloatingHeaderView().findFixedRowByType(
+ PredictionRowView.class);
+ predictionRowView.setPredictedApps(item.items);
} else if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) {
mHotseatPredictionController.setPredictedItems(item);
} else if (item.containerId == Favorites.CONTAINER_WIDGETS_PREDICTION) {
getPopupDataProvider().setRecommendedWidgets(item.items);
}
}
-
@Override
- public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) {
+ public void bindWorkspaceComponentsRemoved(Predicate matcher) {
super.bindWorkspaceComponentsRemoved(matcher);
mHotseatPredictionController.onModelItemsRemoved(matcher);
}
-
@Override
public void onDestroy() {
+ mAppTransitionManager.onActivityDestroyed();
+ if (mUnfoldTransitionProgressProvider != null) {
+ mUnfoldTransitionProgressProvider.destroy();
+ }
+ mTISBindHelper.onDestroy();
+ if (mTaskbarManager != null) {
+ mTaskbarManager.clearActivity(this);
+ }
+ if (mLauncherUnfoldAnimationController != null) {
+ mLauncherUnfoldAnimationController.onDestroy();
+ }
super.onDestroy();
mHotseatPredictionController.destroy();
+ mSplitWithKeyboardShortcutController.onDestroy();
+ if (mViewCapture != null) mViewCapture.close();
}
-
@Override
public void onStateSetEnd(LauncherState state) {
super.onStateSetEnd(state);
-
+ handlePendingActivityRequest();
switch (state.ordinal) {
case HINT_STATE_ORDINAL: {
- Workspace workspace = getWorkspace();
+ Workspace> workspace = getWorkspace();
getStateManager().goToState(NORMAL);
if (workspace.getNextPage() != Workspace.DEFAULT_PAGE) {
workspace.post(workspace::moveToDefaultScreen);
- } else {
- handleHomeTap();
}
break;
}
@@ -279,13 +458,13 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
break;
}
case OVERVIEW_STATE_ORDINAL: {
- RecentsView rv = getOverviewPanel();
+ RecentsView rv = (RecentsView) getOverviewPanel();
sendCustomAccessibilityEvent(
rv.getPageAt(rv.getCurrentPage()), TYPE_VIEW_FOCUSED, null);
break;
}
case QUICK_SWITCH_STATE_ORDINAL: {
- RecentsView rv = getOverviewPanel();
+ RecentsView rv = (RecentsView) getOverviewPanel();
TaskView tasktolaunch = rv.getTaskViewAt(0);
if (tasktolaunch != null) {
tasktolaunch.launchTask(success -> {
@@ -300,14 +479,11 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
}
break;
}
-
}
}
-
@Override
public TouchController[] createTouchControllers() {
- Mode mode = SysUINavigationMode.getMode(this);
-
+ NavigationMode mode = DisplayController.getNavigationMode(this);
ArrayList list = new ArrayList<>();
list.add(getDragController());
switch (mode) {
@@ -327,57 +503,660 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
default:
list.add(new PortraitStatesTouchController(this));
}
-
if (!getDeviceProfile().isMultiWindowMode) {
list.add(new StatusBarTouchController(this));
}
-
list.add(new LauncherTaskViewController(this));
return list.toArray(new TouchController[list.size()]);
}
-
@Override
public AtomicAnimationFactory createAtomicAnimationFactory() {
return new QuickstepAtomicAnimationFactory(this);
}
-
- protected LauncherAppWidgetHost createAppWidgetHost() {
- LauncherAppWidgetHost appWidgetHost = super.createAppWidgetHost();
- if (ENABLE_QUICKSTEP_WIDGET_APP_START.get() && LawnchairApp.isRecentsEnabled()) {
- appWidgetHost.setInteractionHandler(new QuickstepInteractionHandler(this));
- }
- return appWidgetHost;
+ @Override
+ protected LauncherWidgetHolder createAppWidgetHolder() {
+ final QuickstepHolderFactory factory =
+ (QuickstepHolderFactory) LauncherWidgetHolder.HolderFactory.newFactory(this);
+ return factory.newInstance(this,
+ appWidgetId -> getWorkspace().removeWidget(appWidgetId),
+ new QuickstepInteractionHandler(this));
+ }
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (savedInstanceState != null) {
+ mPendingSplitSelectInfo = ObjectWrapper.unwrap(
+ savedInstanceState.getIBinder(PENDING_SPLIT_SELECT_INFO));
+ }
+ addMultiWindowModeChangedListener(mDepthController);
+ initUnfoldTransitionProgressProvider();
+ if (FeatureFlags.CONTINUOUS_VIEW_TREE_CAPTURE.get()) {
+// mViewCapture = SettingsAwareViewCapture.getInstance(this).startCapture(getWindow());
+ }
+ getWindow().addPrivateFlags(PRIVATE_FLAG_OPTIMIZE_MEASURE);
+ }
+ @Override
+ public void startSplitSelection(SplitSelectSource splitSelectSource) {
+ RecentsView recentsView = getOverviewPanel();
+ ComponentKey componentToBeStaged = new ComponentKey(
+ splitSelectSource.itemInfo.getTargetComponent(),
+ splitSelectSource.itemInfo.user);
+ // Check if there is already an instance of this app running, if so, initiate the split
+ // using that.
+ mSplitSelectStateController.findLastActiveTaskAndRunCallback(
+ componentToBeStaged,
+ foundTask -> {
+ splitSelectSource.alreadyRunningTaskId = foundTask == null
+ ? INVALID_TASK_ID
+ : foundTask.key.id;
+ if (ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+ startSplitToHome(splitSelectSource);
+ } else {
+ recentsView.initiateSplitSelect(splitSelectSource);
+ }
+ }
+ );
+ }
+ /** TODO(b/266482558) Migrate into SplitSelectStateController or someplace split specific. */
+ private void startSplitToHome(SplitSelectSource source) {
+ AbstractFloatingView.closeAllOpenViews(this);
+ int splitPlaceholderSize = getResources().getDimensionPixelSize(
+ R.dimen.split_placeholder_size);
+ int splitPlaceholderInset = getResources().getDimensionPixelSize(
+ R.dimen.split_placeholder_inset);
+ Rect tempRect = new Rect();
+ mSplitSelectStateController.setInitialTaskSelect(source.intent,
+ source.position.stagePosition, source.itemInfo, source.splitEvent,
+ source.alreadyRunningTaskId);
+ RecentsView recentsView = getOverviewPanel();
+ recentsView.getPagedOrientationHandler().getInitialSplitPlaceholderBounds(
+ splitPlaceholderSize, splitPlaceholderInset, getDeviceProfile(),
+ mSplitSelectStateController.getActiveSplitStagePosition(), tempRect);
+ PendingAnimation anim = new PendingAnimation(TABLET_HOME_TO_SPLIT.getDuration());
+ RectF startingTaskRect = new RectF();
+ final FloatingTaskView floatingTaskView = FloatingTaskView.getFloatingTaskView(this,
+ source.getView(), null /* thumbnail */, source.getDrawable(), startingTaskRect);
+ floatingTaskView.setAlpha(1);
+ floatingTaskView.addStagingAnimation(anim, startingTaskRect, tempRect,
+ false /* fadeWithThumbnail */, true /* isStagedTask */);
+ mSplitSelectStateController.setFirstFloatingTaskView(floatingTaskView);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ getDragLayer().removeView(floatingTaskView);
+ mSplitSelectStateController.resetState();
+ }
+ });
+ anim.buildAnim().start();
+ }
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (mLauncherUnfoldAnimationController != null) {
+ mLauncherUnfoldAnimationController.onResume();
+ }
+ }
+ @Override
+ protected void onPause() {
+ if (mLauncherUnfoldAnimationController != null) {
+ mLauncherUnfoldAnimationController.onPause();
+ }
+ super.onPause();
+ }
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ if (mOverviewCommandHelper != null) {
+ mOverviewCommandHelper.clearPendingCommands();
+ }
+ }
+ public QuickstepTransitionManager getAppTransitionManager() {
+ return mAppTransitionManager;
+ }
+ @Override
+ public void onEnterAnimationComplete() {
+ super.onEnterAnimationComplete();
+ // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled
+ // as a part of quickstep, so that high-res thumbnails can load the next time we enter
+ // overview
+ RecentsModel.INSTANCE.get(this).getThumbnailCache()
+ .getHighResLoadingState().setVisible(true);
+ }
+ @Override
+ protected void handleGestureContract(Intent intent) {
+ if (FeatureFlags.SEPARATE_RECENTS_ACTIVITY.get()) {
+ super.handleGestureContract(intent);
+ }
+ }
+ @Override
+ public void onTrimMemory(int level) {
+ super.onTrimMemory(level);
+ RecentsModel.INSTANCE.get(this).onTrimMemory(level);
+ }
+ @Override
+ public void onUiChangedWhileSleeping() {
+ // Remove the snapshot because the content view may have obvious changes.
+ UI_HELPER_EXECUTOR.execute(
+ () -> ActivityManagerWrapper.getInstance().invalidateHomeTaskSnapshot(this));
+ }
+ @Override
+ protected void onScreenOnChanged(boolean isOn) {
+ super.onScreenOnChanged(isOn);
+ if (!isOn) {
+ RecentsView recentsView = getOverviewPanel();
+ recentsView.finishRecentsAnimation(true /* toRecents */, null);
+ }
+ }
+ @Override
+ public void onAllAppsTransition(float progress) {
+ super.onAllAppsTransition(progress);
+ onTaskbarInAppDisplayProgressUpdate(progress, ALL_APPS_PAGE_PROGRESS_INDEX);
+ }
+ @Override
+ public void onWidgetsTransition(float progress) {
+ super.onWidgetsTransition(progress);
+ onTaskbarInAppDisplayProgressUpdate(progress, WIDGETS_PAGE_PROGRESS_INDEX);
+ if (mEnableWidgetDepth) {
+ getDepthController().widgetDepth.setValue(Utilities.mapToRange(
+ progress, 0f, 1f, 0f, getDeviceProfile().bottomSheetDepth, EMPHASIZED));
+ }
+ }
+ @Override
+ protected void registerBackDispatcher() {
+ getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+ new OnBackAnimationCallback() {
+ @Nullable OnBackPressedHandler mActiveOnBackPressedHandler;
+ @Override
+ public void onBackStarted(@NonNull BackEvent backEvent) {
+ if (mActiveOnBackPressedHandler != null) {
+ mActiveOnBackPressedHandler.onBackCancelled();
+ }
+ mActiveOnBackPressedHandler = getOnBackPressedHandler();
+ mActiveOnBackPressedHandler.onBackStarted();
+ }
+ @Override
+ public void onBackInvoked() {
+ // Recreate mActiveOnBackPressedHandler if necessary to avoid NPE because:
+ // 1. b/260636433: In 3-button-navigation mode, onBackStarted() is not
+ // called on ACTION_DOWN before onBackInvoked() is called in ACTION_UP.
+ // 2. Launcher#onBackPressed() will call onBackInvoked() without calling
+ // onBackInvoked() beforehand.
+ if (mActiveOnBackPressedHandler == null) {
+ mActiveOnBackPressedHandler = getOnBackPressedHandler();
+ }
+ mActiveOnBackPressedHandler.onBackInvoked();
+ mActiveOnBackPressedHandler = null;
+ TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onBackInvoked");
+ }
+ @Override
+ public void onBackProgressed(@NonNull BackEvent backEvent) {
+ if (!FeatureFlags.IS_STUDIO_BUILD && mActiveOnBackPressedHandler == null) {
+ return;
+ }
+ mActiveOnBackPressedHandler
+ .onBackProgressed(backEvent.getProgress());
+ }
+ @Override
+ public void onBackCancelled() {
+ if (!FeatureFlags.IS_STUDIO_BUILD && mActiveOnBackPressedHandler == null) {
+ return;
+ }
+ mActiveOnBackPressedHandler.onBackCancelled();
+ mActiveOnBackPressedHandler = null;
+ }
+ });
+ }
+ private void onTaskbarInAppDisplayProgressUpdate(float progress, int flag) {
+ if (mTaskbarManager == null
+ || mTaskbarManager.getCurrentActivityContext() == null
+ || mTaskbarUIController == null) {
+ return;
+ }
+ mTaskbarUIController.onTaskbarInAppDisplayProgressUpdate(progress, flag);
+ }
+ @Override
+ public void startIntentSenderForResult(IntentSender intent, int requestCode,
+ Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) {
+ if (requestCode != -1) {
+ mPendingActivityRequestCode = requestCode;
+ StartActivityParams params = new StartActivityParams(this, requestCode);
+ params.intentSender = intent;
+ params.fillInIntent = fillInIntent;
+ params.flagsMask = flagsMask;
+ params.flagsValues = flagsValues;
+ params.extraFlags = extraFlags;
+ params.options = options;
+ startActivity(ProxyActivityStarter.getLaunchIntent(this, params));
+ } else {
+ super.startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask,
+ flagsValues, extraFlags, options);
+ }
+ }
+ @Override
+ public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
+ if (requestCode != -1) {
+ mPendingActivityRequestCode = requestCode;
+ StartActivityParams params = new StartActivityParams(this, requestCode);
+ params.intent = intent;
+ params.options = options;
+ startActivity(ProxyActivityStarter.getLaunchIntent(this, params));
+ } else {
+ super.startActivityForResult(intent, requestCode, options);
+ }
+ }
+ @Override
+ public void setResumed() {
+ if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+ DesktopVisibilityController controller = mDesktopVisibilityController;
+ if (controller != null && controller.areFreeformTasksVisible()
+ && !controller.isGestureInProgress()) {
+ // Return early to skip setting activity to appear as resumed
+ // TODO(b/255649902): shouldn't be needed when we have a separate launcher state
+ // for desktop that we can use to control other parts of launcher
+ return;
+ }
+ }
+ super.setResumed();
+ }
+ @Override
+ protected void onDeferredResumed() {
+ super.onDeferredResumed();
+ handlePendingActivityRequest();
+ }
+ private void handlePendingActivityRequest() {
+ if (mPendingActivityRequestCode != -1 && isInState(NORMAL)
+ && ((getActivityFlags() & ACTIVITY_STATE_DEFERRED_RESUMED) != 0)) {
+ // Remove any active ProxyActivityStarter task and send RESULT_CANCELED to Launcher.
+ onActivityResult(mPendingActivityRequestCode, RESULT_CANCELED, null);
+ // ProxyActivityStarter is started with clear task to reset the task after which it
+ // removes the task itself.
+ startActivity(ProxyActivityStarter.getLaunchIntent(this, null));
+ }
+ }
+ private void onTISConnected(TISBinder binder) {
+ mTaskbarManager = binder.getTaskbarManager();
+ mTaskbarManager.setActivity(this);
+ mOverviewCommandHelper = binder.getOverviewCommandHelper();
+ }
+ @Override
+ public void runOnBindToTouchInteractionService(Runnable r) {
+ mTISBindHelper.runOnBindToTouchInteractionService(r);
+ }
+ private void initUnfoldTransitionProgressProvider() {
+ final UnfoldTransitionConfig config = new ResourceUnfoldTransitionConfig();
+ if (config.isEnabled()) {
+ if (RECEIVE_UNFOLD_EVENTS_FROM_SYSUI.get()) {
+ initRemotelyCalculatedUnfoldAnimation(config);
+ } else {
+ initLocallyCalculatedUnfoldAnimation(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(
+ /* context= */ this,
+ config,
+ getMainExecutor(),
+ getMainThreadHandler(),
+ /* backgroundExecutor= */ UI_HELPER_EXECUTOR,
+ /* tracingTagPrefix= */ "launcher",
+ getSystemService(DisplayManager.class)
+ );
+ final RemoteUnfoldTransitionReceiver remoteUnfoldTransitionProgressProvider =
+ unfoldComponent.getRemoteTransitionProgress().orElseThrow(
+ () -> new IllegalStateException(
+ "Trying to create getRemoteTransitionProgress when the transition "
+ + "is disabled"));
+ mUnfoldTransitionProgressProvider = remoteUnfoldTransitionProgressProvider;
+ SystemUiProxy.INSTANCE.get(this).setUnfoldAnimationListener(
+ remoteUnfoldTransitionProgressProvider);
+ initUnfoldAnimationController(mUnfoldTransitionProgressProvider,
+ unfoldComponent.getRotationChangeProvider());
+ }
+ private void initUnfoldAnimationController(UnfoldTransitionProgressProvider progressProvider,
+ RotationChangeProvider rotationChangeProvider) {
+ mLauncherUnfoldAnimationController = new LauncherUnfoldAnimationController(
+ /* launcher= */ this,
+ getWindowManager(),
+ progressProvider,
+ rotationChangeProvider
+ );
+ }
+ public void setTaskbarUIController(LauncherTaskbarUIController taskbarUIController) {
+ mTaskbarUIController = taskbarUIController;
+ }
+ public @Nullable LauncherTaskbarUIController getTaskbarUIController() {
+ return mTaskbarUIController;
+ }
+ public SplitSelectStateController getSplitSelectStateController() {
+ return mSplitSelectStateController;
+ }
+ public T getActionsView() {
+ return (T) mActionsView;
+ }
+ @Override
+ protected void closeOpenViews(boolean animate) {
+ super.closeOpenViews(animate);
+ TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
+ }
+ @Override
+ protected void collectStateHandlers(List out) {
+ super.collectStateHandlers(out);
+ out.add(getDepthController());
+ out.add(new RecentsViewStateController(this));
+ }
+ public DepthController getDepthController() {
+ return mDepthController;
+ }
+ public DesktopVisibilityController getDesktopVisibilityController() {
+ return mDesktopVisibilityController;
+ }
+ @Nullable
+ public UnfoldTransitionProgressProvider getUnfoldTransitionProgressProvider() {
+ return mUnfoldTransitionProgressProvider;
+ }
+ @Override
+ public boolean supportsAdaptiveIconAnimation(View clickedView) {
+ return mAppTransitionManager.hasControlRemoteAppTransitionPermission();
+ }
+ @Override
+ public DragOptions getDefaultWorkspaceDragOptions() {
+ if (mNextWorkspaceDragOptions != null) {
+ 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
+ ? new float[] {1, 1} : new float[] {1.1f, NO_OFFSET};
+ }
+ @Override
+ public void finishBindingItems(IntSet pagesBoundFirst) {
+ super.finishBindingItems(pagesBoundFirst);
+ // Instantiate and initialize WellbeingModel now that its loading won't interfere with
+ // populating workspace.
+ // TODO: Find a better place for this
+ WellbeingModel.INSTANCE.get(this);
+ }
+ @Override
+ public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) {
+ pendingTasks.add(() -> {
+ // This is added in pending task as we need to wait for views to be positioned
+ // correctly before registering them for the animation.
+ if (mLauncherUnfoldAnimationController != null) {
+ // This is needed in case items are rebound while the unfold animation is in
+ // progress.
+ mLauncherUnfoldAnimationController.updateRegisteredViewsIfNeeded();
+ }
+ });
+ super.onInitialBindComplete(boundPages, pendingTasks);
+ }
+ @Override
+ public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
+ ActivityOptionsWrapper activityOptions =
+ mAppTransitionManager.hasControlRemoteAppTransitionPermission()
+ ? mAppTransitionManager.getActivityLaunchOptions(v)
+ : super.getActivityLaunchOptions(v, item);
+ if (mLastTouchUpTime > 0) {
+ activityOptions.options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_LAUNCHER,
+ mLastTouchUpTime);
+ }
+ if (item != null && (item.animationType == DEFAULT_NO_ICON
+ || item.animationType == VIEW_BACKGROUND)) {
+ activityOptions.options.setSplashScreenStyle(
+ SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR);
+ } else {
+ activityOptions.options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
+ }
+ activityOptions.options.setLaunchDisplayId(
+ (v != null && v.getDisplay() != null) ? v.getDisplay().getDisplayId()
+ : Display.DEFAULT_DISPLAY);
+ addLaunchCookie(item, activityOptions.options);
+ return activityOptions;
+ }
+ @Override
+ @BinderThread
+ public void enterStageSplitFromRunningApp(boolean leftOrTop) {
+ mSplitWithKeyboardShortcutController.enterStageSplit(leftOrTop);
+ }
+ /**
+ * Adds a new launch cookie for the activity launch if supported.
+ *
+ * @param info the item info for the launch
+ * @param opts the options to set the launchCookie on.
+ */
+ public void addLaunchCookie(ItemInfo info, ActivityOptions opts) {
+ IBinder launchCookie = getLaunchCookie(info);
+ if (launchCookie != null) {
+ opts.setLaunchCookie(launchCookie);
+ }
+ }
+ /**
+ * Return a new launch cookie for the activity launch if supported.
+ *
+ * @param info the item info for the launch
+ */
+ public IBinder getLaunchCookie(ItemInfo info) {
+ if (info == null) {
+ return null;
+ }
+ switch (info.container) {
+ case Favorites.CONTAINER_DESKTOP:
+ case Favorites.CONTAINER_HOTSEAT:
+ // Fall through and continue it's on the workspace (we don't support swiping back
+ // to other containers like all apps or the hotseat predictions (which can change)
+ break;
+ default:
+ if (info.container >= 0) {
+ // Also allow swiping to folders
+ break;
+ }
+ // Reset any existing launch cookies associated with the cookie
+ return ObjectWrapper.wrap(NO_MATCHING_ID);
+ }
+ 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
+ break;
+ default:
+ // Reset any existing launch cookies associated with the cookie
+ return ObjectWrapper.wrap(NO_MATCHING_ID);
+ }
+ return ObjectWrapper.wrap(new Integer(info.id));
+ }
+ public void setHintUserWillBeActive() {
+ addActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE);
+ }
+ @Override
+ public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
+ super.onDisplayInfoChanged(context, info, flags);
+ // When changing screens, force moving to rest state similar to StatefulActivity.onStop, as
+ // StatefulActivity isn't called consistently.
+ if ((flags & CHANGE_ACTIVE_SCREEN) != 0) {
+ // Do not animate moving to rest state, as it can clash with Launcher#onIdpChanged
+ // where reapplyUi calls StateManager's reapplyState during the state change animation,
+ // and cancel the state change unexpectedly. The screen will be off during screen
+ // transition, hiding the unanimated transition.
+ getStateManager().moveToRestState(/* isAnimated = */false);
+ }
+ if ((flags & CHANGE_NAVIGATION_MODE) != 0) {
+ getDragLayer().recreateControllers();
+ if (mActionsView != null) {
+ mActionsView.updateVerticalMargin(info.navigationMode);
+ }
+ }
+ }
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ // If Launcher shuts downs during split select, we save some extra data in the recovery
+ // bundle to allow graceful recovery. The normal LauncherState restore mechanism doesn't
+ // work in this case because restoring straight to OverviewSplitSelect without staging data,
+ // or before the tasks themselves have loaded into Overview, causes a crash. So we tell
+ // Launcher to first restore into Overview state, wait for the relevant tasks and icons to
+ // load in, and then proceed to OverviewSplitSelect.
+ if (isInState(OVERVIEW_SPLIT_SELECT)) {
+ // Launcher will restart in Overview and then transition to OverviewSplitSelect.
+ outState.putIBinder(PENDING_SPLIT_SELECT_INFO, ObjectWrapper.wrap(
+ new PendingSplitSelectInfo(
+ mSplitSelectStateController.getInitialTaskId(),
+ mSplitSelectStateController.getActiveSplitStagePosition(),
+ mSplitSelectStateController.getSplitEvent())
+ ));
+ outState.putInt(RUNTIME_STATE, OVERVIEW.ordinal);
+ }
+ }
+ /**
+ * When Launcher restarts, it sometimes needs to recover to a split selection state.
+ * This function checks if such a recovery is needed.
+ * @return a boolean representing whether the launcher is waiting to recover to
+ * OverviewSplitSelect state.
+ */
+ public boolean hasPendingSplitSelectInfo() {
+ return mPendingSplitSelectInfo != null;
+ }
+ /**
+ * See {@link #hasPendingSplitSelectInfo()}
+ */
+ public @Nullable PendingSplitSelectInfo getPendingSplitSelectInfo() {
+ return mPendingSplitSelectInfo;
+ }
+ /**
+ * When the launcher has successfully recovered to OverviewSplitSelect state, this function
+ * deletes the recovery data, returning it to a null state.
+ */
+ public void finishSplitSelectRecovery() {
+ mPendingSplitSelectInfo = null;
+ }
+ @Override
+ public boolean areFreeformTasksVisible() {
+ if (mDesktopVisibilityController != null) {
+ return mDesktopVisibilityController.areFreeformTasksVisible();
+ }
+ return false;
+ }
+ @Override
+ protected void onDeviceProfileInitiated() {
+ super.onDeviceProfileInitiated();
+ SystemUiProxy.INSTANCE.get(this).setLauncherAppIconSize(mDeviceProfile.iconSizePx);
+ }
+ @Override
+ public void dispatchDeviceProfileChanged() {
+ super.dispatchDeviceProfileChanged();
+ Trace.instantForTrack(TRACE_TAG_APP, "QuickstepLauncher#DeviceProfileChanged",
+ getDeviceProfile().toSmallString());
+ SystemUiProxy.INSTANCE.get(this).setLauncherAppIconSize(mDeviceProfile.iconSizePx);
+ if (mTaskbarManager != null) {
+ mTaskbarManager.debugWhyTaskbarNotDestroyed("QuickstepLauncher#onDeviceProfileChanged");
+ }
+ }
+ /**
+ * Launches the given {@link GroupTask} in splitscreen.
+ *
+ * If the second split task is missing, launches the first task normally.
+ */
+ public void launchSplitTasks(@NonNull View taskView, @NonNull GroupTask groupTask) {
+ if (groupTask.task2 == null) {
+ UI_HELPER_EXECUTOR.execute(() ->
+ ActivityManagerWrapper.getInstance().startActivityFromRecents(
+ groupTask.task1.key,
+ getActivityLaunchOptions(taskView, null).options));
+ return;
+ }
+ mSplitSelectStateController.launchTasks(
+ groupTask.task1.key.id,
+ groupTask.task2.key.id,
+ SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
+ /* callback= */ success -> {},
+ /* freezeTaskList= */ true,
+ groupTask.mSplitBounds == null
+ ? DEFAULT_SPLIT_RATIO
+ : groupTask.mSplitBounds.appsStackedVertically
+ ? groupTask.mSplitBounds.topTaskPercent
+ : groupTask.mSplitBounds.leftTaskPercent);
}
-
private static final class LauncherTaskViewController extends
TaskViewTouchController {
-
LauncherTaskViewController(Launcher activity) {
super(activity);
}
-
@Override
protected boolean isRecentsInteractive() {
return mActivity.isInState(OVERVIEW) || mActivity.isInState(OVERVIEW_MODAL_TASK);
}
-
@Override
protected boolean isRecentsModal() {
return mActivity.isInState(OVERVIEW_MODAL_TASK);
}
-
@Override
protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
mActivity.getStateManager().setCurrentUserControlledAnimation(animController);
}
}
-
@Override
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
super.dump(prefix, fd, writer, args);
+ if (mDepthController != null) {
+ mDepthController.dump(prefix, writer);
+ }
RecentsView recentsView = getOverviewPanel();
writer.println("\nQuickstepLauncher:");
writer.println(prefix + "\tmOrientationState: " + (recentsView == null ? "recentsNull" :
recentsView.getPagedViewOrientedState()));
}
-}
+}
\ 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
new file mode 100644
index 0000000000..b318100205
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
@@ -0,0 +1,365 @@
+/**
+ * Copyright (C) 2022 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 com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+import android.util.Log;
+import android.util.SparseArray;
+import android.widget.RemoteViews;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+import androidx.annotation.WorkerThread;
+
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.util.IntSet;
+import com.android.launcher3.widget.LauncherAppWidgetHostView;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.LauncherWidgetHolder;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.function.BiConsumer;
+import java.util.function.IntConsumer;
+
+/**
+ * {@link LauncherWidgetHolder} that puts the app widget host in the background
+ */
+public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
+
+ private static final String TAG = "QuickstepWidgetHolder";
+
+ private static final UpdateKey KEY_PROVIDER_UPDATE =
+ AppWidgetHostView::onUpdateProviderInfo;
+ private static final UpdateKey KEY_VIEWS_UPDATE =
+ AppWidgetHostView::updateAppWidget;
+ private static final UpdateKey KEY_VIEW_DATA_CHANGED =
+ AppWidgetHostView::onViewDataChanged;
+
+ private static final List sHolders = new ArrayList<>();
+ private static final SparseArray sListeners =
+ new SparseArray<>();
+
+ private static AppWidgetHost sWidgetHost = null;
+
+ private final SparseArray mViews = new SparseArray<>();
+
+ private final @Nullable RemoteViews.InteractionHandler mInteractionHandler;
+
+ private final @NonNull IntConsumer mAppWidgetRemovedCallback;
+
+ private final ArrayList mProviderChangedListeners = new ArrayList<>();
+ // Map to all pending updated keyed with appWidgetId;
+ private final SparseArray mPendingUpdateMap = new SparseArray<>();
+
+ private QuickstepWidgetHolder(@NonNull Context context,
+ @Nullable IntConsumer appWidgetRemovedCallback,
+ @Nullable RemoteViews.InteractionHandler interactionHandler) {
+ super(context, appWidgetRemovedCallback);
+ mAppWidgetRemovedCallback = appWidgetRemovedCallback != null ? appWidgetRemovedCallback
+ : i -> {};
+ mInteractionHandler = interactionHandler;
+ MAIN_EXECUTOR.execute(() -> sHolders.add(this));
+ }
+
+ @Override
+ @NonNull
+ protected AppWidgetHost createHost(@NonNull Context context,
+ @Nullable IntConsumer appWidgetRemovedCallback) {
+ if (sWidgetHost == null) {
+ sWidgetHost = new QuickstepAppWidgetHost(context.getApplicationContext(),
+ i -> MAIN_EXECUTOR.execute(() ->
+ sHolders.forEach(h -> h.mAppWidgetRemovedCallback.accept(i))),
+ () -> MAIN_EXECUTOR.execute(() ->
+ sHolders.forEach(h -> h.mProviderChangedListeners.forEach(
+ ProviderChangedListener::notifyWidgetProvidersChanged))),
+ UI_HELPER_EXECUTOR.getLooper());
+ if (!WidgetsModel.GO_DISABLE_WIDGETS) {
+ sWidgetHost.startListening();
+ }
+ }
+ return sWidgetHost;
+ }
+
+ @Override
+ protected void updateDeferredView() {
+ super.updateDeferredView();
+ int count = mPendingUpdateMap.size();
+ for (int i = 0; i < count; i++) {
+ int widgetId = mPendingUpdateMap.keyAt(i);
+ AppWidgetHostView view = mViews.get(widgetId);
+ if (view == null) {
+ continue;
+ }
+ PendingUpdate pendingUpdate = mPendingUpdateMap.valueAt(i);
+ if (pendingUpdate == null) {
+ continue;
+ }
+ if (pendingUpdate.providerInfo != null) {
+ KEY_PROVIDER_UPDATE.accept(view, pendingUpdate.providerInfo);
+ }
+ if (pendingUpdate.remoteViews != null) {
+ KEY_VIEWS_UPDATE.accept(view, pendingUpdate.remoteViews);
+ }
+ pendingUpdate.changedViews.forEach(
+ viewId -> KEY_VIEW_DATA_CHANGED.accept(view, viewId));
+ }
+ mPendingUpdateMap.clear();
+ }
+
+ private void onWidgetUpdate(int widgetId, UpdateKey key, T data) {
+ if (isListening()) {
+ AppWidgetHostView view = mViews.get(widgetId);
+ if (view == null) {
+ return;
+ }
+ key.accept(view, data);
+ return;
+ }
+
+ PendingUpdate pendingUpdate = mPendingUpdateMap.get(widgetId);
+ if (pendingUpdate == null) {
+ pendingUpdate = new PendingUpdate();
+ mPendingUpdateMap.put(widgetId, pendingUpdate);
+ }
+
+ if (KEY_PROVIDER_UPDATE.equals(key)) {
+ // For provider change, remove all updates
+ pendingUpdate.providerInfo = (AppWidgetProviderInfo) data;
+ pendingUpdate.remoteViews = null;
+ pendingUpdate.changedViews.clear();
+ } else if (KEY_VIEWS_UPDATE.equals(key)) {
+ // For views update, remove all previous updates, except the provider
+ pendingUpdate.remoteViews = (RemoteViews) data;
+ pendingUpdate.changedViews.clear();
+ } else if (KEY_VIEW_DATA_CHANGED.equals(key)) {
+ pendingUpdate.changedViews.add((Integer) data);
+ }
+ }
+
+ /**
+ * Delete the specified app widget from the host
+ * @param appWidgetId The ID of the app widget to be deleted
+ */
+ @Override
+ public void deleteAppWidgetId(int appWidgetId) {
+ super.deleteAppWidgetId(appWidgetId);
+ mViews.remove(appWidgetId);
+ sListeners.remove(appWidgetId);
+ }
+
+ /**
+ * Called when the launcher is destroyed
+ */
+ @Override
+ public void destroy() {
+ try {
+ MAIN_EXECUTOR.submit(() -> sHolders.remove(this)).get();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to remove self from holder list", e);
+ }
+ }
+
+ @Override
+ protected boolean shouldListen(int flags) {
+ return (flags & (FLAG_STATE_IS_NORMAL | FLAG_ACTIVITY_STARTED))
+ == (FLAG_STATE_IS_NORMAL | FLAG_ACTIVITY_STARTED);
+ }
+
+ /**
+ * Add a listener that is triggered when the providers of the widgets are changed
+ * @param listener The listener that notifies when the providers changed
+ */
+ @Override
+ public void addProviderChangeListener(
+ @NonNull LauncherWidgetHolder.ProviderChangedListener listener) {
+ mProviderChangedListeners.add(listener);
+ }
+
+ /**
+ * Remove the specified listener from the host
+ * @param listener The listener that is to be removed from the host
+ */
+ @Override
+ public void removeProviderChangeListener(
+ LauncherWidgetHolder.ProviderChangedListener listener) {
+ mProviderChangedListeners.remove(listener);
+ }
+
+ /**
+ * Stop the host from updating the widget views
+ */
+ @Override
+ public void stopListening() {
+ if (WidgetsModel.GO_DISABLE_WIDGETS) {
+ return;
+ }
+
+ sWidgetHost.setAppWidgetHidden();
+ setListeningFlag(false);
+ }
+
+ /**
+ * Create a view for the specified app widget
+ * @param context The activity context for which the view is created
+ * @param appWidgetId The ID of the widget
+ * @param appWidget The {@link LauncherAppWidgetProviderInfo} of the widget
+ * @return A view for the widget
+ */
+ @NonNull
+ @Override
+ public LauncherAppWidgetHostView createView(@NonNull Context context, int appWidgetId,
+ @NonNull LauncherAppWidgetProviderInfo appWidget) {
+ LauncherAppWidgetHostView widgetView = getPendingView(appWidgetId);
+ if (widgetView != null) {
+ removePendingView(appWidgetId);
+ } else {
+ widgetView = new LauncherAppWidgetHostView(context);
+ }
+ widgetView.setInteractionHandler(mInteractionHandler);
+ widgetView.setAppWidget(appWidgetId, appWidget);
+ mViews.put(appWidgetId, widgetView);
+
+ QuickstepWidgetHolderListener listener = sListeners.get(appWidgetId);
+ if (listener == null) {
+ listener = new QuickstepWidgetHolderListener(appWidgetId);
+ sWidgetHost.setListener(appWidgetId, listener);
+ sListeners.put(appWidgetId, listener);
+ }
+ RemoteViews remoteViews = listener.addHolder(this);
+ widgetView.updateAppWidget(remoteViews);
+
+ return widgetView;
+ }
+
+ /**
+ * Clears all the views from the host
+ */
+ @Override
+ public void clearViews() {
+ mViews.clear();
+ for (int i = sListeners.size() - 1; i >= 0; i--) {
+ sListeners.valueAt(i).mListeningHolders.remove(this);
+ }
+ }
+
+ private static class QuickstepWidgetHolderListener
+ implements AppWidgetHost.AppWidgetHostListener {
+
+ // Static listeners should use a set that is backed by WeakHashMap to avoid memory leak
+ private final Set mListeningHolders = Collections.newSetFromMap(
+ new WeakHashMap<>());
+
+ private final int mWidgetId;
+
+ private @Nullable RemoteViews mRemoteViews;
+
+ QuickstepWidgetHolderListener(int widgetId) {
+ mWidgetId = widgetId;
+ }
+
+ @UiThread
+ @Nullable
+ public RemoteViews addHolder(@NonNull QuickstepWidgetHolder holder) {
+ mListeningHolders.add(holder);
+ return mRemoteViews;
+ }
+
+ @Override
+ @WorkerThread
+ public void onUpdateProviderInfo(@Nullable AppWidgetProviderInfo info) {
+ mRemoteViews = null;
+ executeOnMainExecutor(KEY_PROVIDER_UPDATE, info);
+ }
+
+ @Override
+ @WorkerThread
+ public void updateAppWidget(@Nullable RemoteViews views) {
+ mRemoteViews = views;
+ executeOnMainExecutor(KEY_VIEWS_UPDATE, mRemoteViews);
+ }
+
+ @Override
+ @WorkerThread
+ public void onViewDataChanged(int viewId) {
+ executeOnMainExecutor(KEY_VIEW_DATA_CHANGED, viewId);
+ }
+
+ private void executeOnMainExecutor(UpdateKey key, T data) {
+ MAIN_EXECUTOR.execute(() -> mListeningHolders.forEach(holder ->
+ holder.onWidgetUpdate(mWidgetId, key, data)));
+ }
+ }
+
+ /**
+ * {@code HolderFactory} subclass that takes an interaction handler as one of the parameters
+ * when creating a new instance.
+ */
+ public static class QuickstepHolderFactory extends HolderFactory {
+
+ @SuppressWarnings("unused")
+ public QuickstepHolderFactory(Context context) { }
+
+ @Override
+ public LauncherWidgetHolder newInstance(@NonNull Context context,
+ @Nullable IntConsumer appWidgetRemovedCallback) {
+ return newInstance(context, appWidgetRemovedCallback, null);
+ }
+
+ /**
+ * @param context The context of the caller
+ * @param appWidgetRemovedCallback The callback that is called when widgets are removed
+ * @param interactionHandler The interaction handler when the widgets are clicked
+ * @return A new {@link LauncherWidgetHolder} instance
+ */
+ public LauncherWidgetHolder newInstance(@NonNull Context context,
+ @Nullable IntConsumer appWidgetRemovedCallback,
+ @Nullable RemoteViews.InteractionHandler interactionHandler) {
+
+ if (!FeatureFlags.ENABLE_WIDGET_HOST_IN_BACKGROUND.get()) {
+ return new LauncherWidgetHolder(context, appWidgetRemovedCallback) {
+ @Override
+ protected AppWidgetHost createHost(Context context,
+ @Nullable IntConsumer appWidgetRemovedCallback) {
+ AppWidgetHost host = super.createHost(context, appWidgetRemovedCallback);
+ host.setInteractionHandler(interactionHandler);
+ return host;
+ }
+ };
+ }
+ return new QuickstepWidgetHolder(context, appWidgetRemovedCallback, interactionHandler);
+ }
+ }
+
+ private static class PendingUpdate {
+ public final IntSet changedViews = new IntSet();
+ public AppWidgetProviderInfo providerInfo;
+ public RemoteViews remoteViews;
+ }
+
+ private interface UpdateKey extends BiConsumer { }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 32ce1c40d7..f16b43df5e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -20,6 +20,7 @@ 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;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
@@ -27,22 +28,24 @@ import static com.android.quickstep.views.RecentsView.TASK_PRIMARY_SPLIT_TRANSLA
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_SPLIT_TRANSLATION;
import static com.android.quickstep.views.TaskView.FLAG_UPDATE_ALL;
+import android.animation.AnimatorSet;
import android.annotation.TargetApi;
import android.os.Build;
import android.util.FloatProperty;
+import android.util.Log;
import android.util.Pair;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.util.MultiValueAlpha;
+import com.android.quickstep.util.AnimUtils;
+import com.android.quickstep.util.SplitAnimationTimings;
import com.android.quickstep.views.ClearAllButton;
import com.android.quickstep.views.LauncherRecentsView;
import com.android.quickstep.views.RecentsView;
@@ -55,7 +58,7 @@ import com.android.quickstep.views.RecentsView;
public final class RecentsViewStateController extends
BaseRecentsViewStateController {
- public RecentsViewStateController(BaseQuickstepLauncher launcher) {
+ public RecentsViewStateController(QuickstepLauncher launcher) {
super(launcher);
}
@@ -72,7 +75,10 @@ public final class RecentsViewStateController extends
// DepthController to prevent optimizations which might occlude the layers behind
mLauncher.getDepthController().setHasContentBehindLauncher(state.overviewUi);
- handleSplitSelectionState(state, null);
+ PendingAnimation builder =
+ new PendingAnimation(state.getTransitionDuration(mLauncher, true));
+
+ handleSplitSelectionState(state, builder, /* animate */false);
}
@Override
@@ -84,6 +90,13 @@ public final class RecentsViewStateController extends
// While animating into recents, update the visible task data as needed
builder.addOnFrameCallback(() -> mRecentsView.loadVisibleTaskData(FLAG_UPDATE_ALL));
mRecentsView.updateEmptyMessage();
+ // TODO(b/246283207): Remove logging once root cause of flake detected.
+ if (Utilities.isRunningInTestHarness()) {
+ Log.d("b/246283207", "RecentsView#setStateWithAnimationInternal getCurrentPage(): "
+ + mRecentsView.getCurrentPage()
+ + ", getScrollForPage(getCurrentPage())): "
+ + mRecentsView.getScrollForPage(mRecentsView.getCurrentPage()));
+ }
} else {
builder.addListener(
AnimatorListeners.forSuccessCallback(mRecentsView::resetTaskVisuals));
@@ -93,7 +106,7 @@ public final class RecentsViewStateController extends
builder.addListener(AnimatorListeners.forSuccessCallback(() ->
mLauncher.getDepthController().setHasContentBehindLauncher(toState.overviewUi)));
- handleSplitSelectionState(toState, builder);
+ handleSplitSelectionState(toState, builder, /* animate */true);
setAlphas(builder, config, toState);
builder.setFloat(mRecentsView, FULLSCREEN_PROGRESS,
@@ -106,9 +119,13 @@ public final class RecentsViewStateController extends
* will add animations to builder.
*/
private void handleSplitSelectionState(@NonNull LauncherState toState,
- @Nullable PendingAnimation builder) {
- LauncherState currentState = mLauncher.getStateManager().getState();
- boolean animate = builder != null;
+ @NonNull PendingAnimation builder, boolean animate) {
+ if (toState != OVERVIEW_SPLIT_SELECT) {
+ // Not going to split
+ return;
+ }
+
+ // Create transition animations to split select
PagedOrientationHandler orientationHandler =
((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler();
Pair taskViewsFloat =
@@ -116,37 +133,25 @@ public final class RecentsViewStateController extends
TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
mLauncher.getDeviceProfile());
- if (isSplitSelectionState(currentState, toState)) {
- // Animation to "dismiss" selected taskView
- PendingAnimation splitSelectInitAnimation =
- mRecentsView.createSplitSelectInitAnimation();
- // Add properties to shift remaining taskViews to get out of placeholder view
- splitSelectInitAnimation.setFloat(mRecentsView, taskViewsFloat.first,
- toState.getSplitSelectTranslation(mLauncher), LINEAR);
- splitSelectInitAnimation.setFloat(mRecentsView, taskViewsFloat.second, 0, LINEAR);
+ SplitAnimationTimings timings =
+ AnimUtils.getDeviceOverviewToSplitTimings(mLauncher.getDeviceProfile().isTablet);
- if (!animate && isSplitSelectionState(currentState, toState)) {
- splitSelectInitAnimation.buildAnim().start();
- } else if (animate &&
- isSplitSelectionState(currentState, toState)) {
- builder.add(splitSelectInitAnimation.buildAnim());
- }
+ mRecentsView.createSplitSelectInitAnimation(builder,
+ toState.getTransitionDuration(mLauncher, true /* isToState */));
+ // Shift tasks vertically downward to get out of placeholder view
+ builder.setFloat(mRecentsView, taskViewsFloat.first,
+ toState.getSplitSelectTranslation(mLauncher),
+ timings.getGridSlidePrimaryInterpolator());
+ // Zero out horizontal translation
+ builder.setFloat(mRecentsView, taskViewsFloat.second,
+ 0,
+ timings.getGridSlideSecondaryInterpolator());
+
+ if (!animate) {
+ AnimatorSet as = builder.buildAnim();
+ as.start();
+ as.end();
}
-
- if (isSplitSelectionState(currentState, toState)) {
- mRecentsView.applySplitPrimaryScrollOffset();
- } else {
- mRecentsView.resetSplitPrimaryScrollOffset();
- }
- }
-
- /**
- * @return true if {@param toState} is {@link LauncherState#OVERVIEW_SPLIT_SELECT}
- * and {@param fromState} is not {@link LauncherState#OVERVIEW_SPLIT_SELECT}
- */
- private boolean isSplitSelectionState(@NonNull LauncherState fromState,
- @NonNull LauncherState toState) {
- return fromState != OVERVIEW_SPLIT_SELECT && toState == OVERVIEW_SPLIT_SELECT;
}
private void setAlphas(PropertySetter propertySetter, StateAnimationConfig config,
@@ -156,7 +161,7 @@ public final class RecentsViewStateController extends
clearAllButtonAlpha, LINEAR);
float overviewButtonAlpha = state.areElementsVisible(mLauncher, OVERVIEW_ACTIONS) ? 1 : 0;
propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlpha(),
- MultiValueAlpha.VALUE, overviewButtonAlpha, config.getInterpolator(
+ MULTI_PROPERTY_VALUE, overviewButtonAlpha, config.getInterpolator(
ANIM_OVERVIEW_ACTIONS_FADE, LINEAR));
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DebugFlag.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DebugFlag.java
new file mode 100644
index 0000000000..d4944d0315
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DebugFlag.java
@@ -0,0 +1,38 @@
+/*
+ * 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.uioverrides.flags;
+
+import com.android.launcher3.config.FeatureFlags.BooleanFlag;
+
+class DebugFlag extends BooleanFlag {
+
+ public final String key;
+ public final String description;
+
+ public final boolean defaultValue;
+
+ public DebugFlag(String key, String description, boolean defaultValue, boolean currentValue) {
+ super(currentValue);
+ this.key = key;
+ this.defaultValue = defaultValue;
+ this.description = description;
+ }
+
+ @Override
+ public String toString() {
+ return key + ": defaultValue=" + defaultValue + ", mCurrentValue=" + get();
+ }
+}
diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsFragment.java
similarity index 84%
rename from src/com/android/launcher3/settings/DeveloperOptionsFragment.java
rename to quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsFragment.java
index b06b8a10bf..67ea1af056 100644
--- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsFragment.java
@@ -13,8 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.launcher3.settings;
+package com.android.launcher3.uioverrides.flags;
+import static android.content.Intent.ACTION_PACKAGE_ADDED;
+import static android.content.Intent.ACTION_PACKAGE_CHANGED;
+import static android.content.Intent.ACTION_PACKAGE_REMOVED;
import static android.content.pm.PackageManager.GET_RESOLVED_FILTER;
import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
import static android.view.View.GONE;
@@ -25,11 +28,9 @@ import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.PLU
import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.pluginEnabledKey;
import android.annotation.TargetApi;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -59,12 +60,13 @@ import androidx.preference.PreferenceScreen;
import androidx.preference.PreferenceViewHolder;
import androidx.preference.SwitchPreference;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.config.FlagTogglerPrefUi;
+import com.android.launcher3.secondarydisplay.SecondaryDisplayLauncher;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.OnboardingPrefs;
+import com.android.launcher3.util.SimpleBroadcastReceiver;
import java.util.ArrayList;
import java.util.List;
@@ -82,12 +84,8 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
private static final String ACTION_PLUGIN_SETTINGS = "com.android.systemui.action.PLUGIN_SETTINGS";
private static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN";
- private final BroadcastReceiver mPluginReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- loadPluginPrefs();
- }
- };
+ private final SimpleBroadcastReceiver mPluginReceiver =
+ new SimpleBroadcastReceiver(i -> loadPluginPrefs());
private PreferenceScreen mPreferenceScreen;
@@ -96,13 +94,9 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
- IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
- filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- filter.addDataScheme("package");
- getContext().registerReceiver(mPluginReceiver, filter);
- getContext().registerReceiver(mPluginReceiver,
- new IntentFilter(Intent.ACTION_USER_UNLOCKED));
+ mPluginReceiver.registerPkgActions(getContext(), null,
+ ACTION_PACKAGE_ADDED, ACTION_PACKAGE_CHANGED, ACTION_PACKAGE_REMOVED);
+ mPluginReceiver.register(getContext(), Intent.ACTION_USER_UNLOCKED);
mPreferenceScreen = getPreferenceManager().createPreferenceScreen(getContext());
setPreferenceScreen(mPreferenceScreen);
@@ -184,7 +178,7 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
@Override
public void onDestroy() {
super.onDestroy();
- getContext().unregisterReceiver(mPluginReceiver);
+ mPluginReceiver.unregisterReceiverSafely(getContext());
}
private PreferenceCategory newCategory(String title) {
@@ -300,17 +294,27 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
}
PreferenceCategory sandboxCategory = newCategory("Gesture Navigation Sandbox");
sandboxCategory.setSummary("Learn and practice navigation gestures");
+ Preference launchTutorialStepMenuPreference = new Preference(context);
+ launchTutorialStepMenuPreference.setKey("launchTutorialStepMenu");
+ launchTutorialStepMenuPreference.setTitle("Launch Gesture Tutorial Steps menu");
+ launchTutorialStepMenuPreference.setSummary("Select a gesture tutorial step.");
+ launchTutorialStepMenuPreference.setOnPreferenceClickListener(preference -> {
+ startActivity(launchSandboxIntent.putExtra("use_tutorial_menu", true));
+ return true;
+ });
+ sandboxCategory.addPreference(launchTutorialStepMenuPreference);
Preference launchOnboardingTutorialPreference = new Preference(context);
launchOnboardingTutorialPreference.setKey("launchOnboardingTutorial");
launchOnboardingTutorialPreference.setTitle("Launch Onboarding Tutorial");
launchOnboardingTutorialPreference.setSummary("Learn the basic navigation gestures.");
launchOnboardingTutorialPreference.setOnPreferenceClickListener(preference -> {
- startActivity(launchSandboxIntent.putExtra(
- "tutorial_steps",
- new String[] {
- "HOME_NAVIGATION",
- "BACK_NAVIGATION",
- "OVERVIEW_NAVIGATION"}));
+ startActivity(launchSandboxIntent
+ .putExtra("use_tutorial_menu", false)
+ .putExtra("tutorial_steps",
+ new String[] {
+ "HOME_NAVIGATION",
+ "BACK_NAVIGATION",
+ "OVERVIEW_NAVIGATION"}));
return true;
});
sandboxCategory.addPreference(launchOnboardingTutorialPreference);
@@ -319,9 +323,9 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
launchBackTutorialPreference.setTitle("Launch Back Tutorial");
launchBackTutorialPreference.setSummary("Learn how to use the Back gesture");
launchBackTutorialPreference.setOnPreferenceClickListener(preference -> {
- startActivity(launchSandboxIntent.putExtra(
- "tutorial_steps",
- new String[] {"BACK_NAVIGATION"}));
+ startActivity(launchSandboxIntent
+ .putExtra("use_tutorial_menu", false)
+ .putExtra("tutorial_steps", new String[] {"BACK_NAVIGATION"}));
return true;
});
sandboxCategory.addPreference(launchBackTutorialPreference);
@@ -330,9 +334,9 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
launchHomeTutorialPreference.setTitle("Launch Home Tutorial");
launchHomeTutorialPreference.setSummary("Learn how to use the Home gesture");
launchHomeTutorialPreference.setOnPreferenceClickListener(preference -> {
- startActivity(launchSandboxIntent.putExtra(
- "tutorial_steps",
- new String[] {"HOME_NAVIGATION"}));
+ startActivity(launchSandboxIntent
+ .putExtra("use_tutorial_menu", false)
+ .putExtra("tutorial_steps", new String[] {"HOME_NAVIGATION"}));
return true;
});
sandboxCategory.addPreference(launchHomeTutorialPreference);
@@ -341,9 +345,9 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
launchOverviewTutorialPreference.setTitle("Launch Overview Tutorial");
launchOverviewTutorialPreference.setSummary("Learn how to use the Overview gesture");
launchOverviewTutorialPreference.setOnPreferenceClickListener(preference -> {
- startActivity(launchSandboxIntent.putExtra(
- "tutorial_steps",
- new String[] {"OVERVIEW_NAVIGATION"}));
+ startActivity(launchSandboxIntent
+ .putExtra("use_tutorial_menu", false)
+ .putExtra("tutorial_steps", new String[] {"OVERVIEW_NAVIGATION"}));
return true;
});
sandboxCategory.addPreference(launchOverviewTutorialPreference);
@@ -352,9 +356,9 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
launchAssistantTutorialPreference.setTitle("Launch Assistant Tutorial");
launchAssistantTutorialPreference.setSummary("Learn how to use the Assistant gesture");
launchAssistantTutorialPreference.setOnPreferenceClickListener(preference -> {
- startActivity(launchSandboxIntent.putExtra(
- "tutorial_steps",
- new String[] {"ASSISTANT"}));
+ startActivity(launchSandboxIntent
+ .putExtra("use_tutorial_menu", false)
+ .putExtra("tutorial_steps", new String[] {"ASSISTANT"}));
return true;
});
sandboxCategory.addPreference(launchAssistantTutorialPreference);
@@ -363,12 +367,22 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
launchSandboxModeTutorialPreference.setTitle("Launch Sandbox Mode");
launchSandboxModeTutorialPreference.setSummary("Practice navigation gestures");
launchSandboxModeTutorialPreference.setOnPreferenceClickListener(preference -> {
- startActivity(launchSandboxIntent.putExtra(
- "tutorial_steps",
- new String[] {"SANDBOX_MODE"}));
+ startActivity(launchSandboxIntent
+ .putExtra("use_tutorial_menu", false)
+ .putExtra("tutorial_steps", new String[] {"SANDBOX_MODE"}));
return true;
});
sandboxCategory.addPreference(launchSandboxModeTutorialPreference);
+
+ Preference launchSecondaryDisplayPreference = new Preference(context);
+ launchSecondaryDisplayPreference.setKey("launchSecondaryDisplay");
+ launchSecondaryDisplayPreference.setTitle("Launch Secondary Display");
+ launchSecondaryDisplayPreference.setSummary("Launch secondary display activity");
+ launchSecondaryDisplayPreference.setOnPreferenceClickListener(preference -> {
+ startActivity(new Intent(context, SecondaryDisplayLauncher.class));
+ return true;
+ });
+ sandboxCategory.addPreference(launchSecondaryDisplayPreference);
}
private void addOnboardingPrefsCatergory() {
@@ -381,7 +395,8 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
onboardingPref.setTitle(title);
onboardingPref.setSummary("Tap to reset");
onboardingPref.setOnPreferenceClickListener(preference -> {
- SharedPreferences.Editor sharedPrefsEdit = Utilities.getPrefs(getContext()).edit();
+ SharedPreferences.Editor sharedPrefsEdit = LauncherPrefs.getPrefs(getContext())
+ .edit();
for (String key : keys) {
sharedPrefsEdit.remove(key);
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DeviceFlag.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DeviceFlag.java
new file mode 100644
index 0000000000..3900ebb549
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DeviceFlag.java
@@ -0,0 +1,33 @@
+/*
+ * 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.flags;
+
+class DeviceFlag extends DebugFlag {
+
+ private final boolean mDefaultValueInCode;
+
+ public DeviceFlag(String key, String description, boolean defaultValue,
+ boolean currentValue, boolean defaultValueInCode) {
+ super(key, description, defaultValue, currentValue);
+ mDefaultValueInCode = defaultValueInCode;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + ", mDefaultValueInCode=" + mDefaultValueInCode;
+ }
+}
diff --git a/src/com/android/launcher3/config/FlagTogglerPrefUi.java b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java
similarity index 82%
rename from src/com/android/launcher3/config/FlagTogglerPrefUi.java
rename to quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java
index 6729f7434f..b7fb2ed827 100644
--- a/src/com/android/launcher3/config/FlagTogglerPrefUi.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.launcher3.config;
+package com.android.launcher3.uioverrides.flags;
import static com.android.launcher3.config.FeatureFlags.FLAGS_PREF_NAME;
@@ -33,7 +33,10 @@ import androidx.preference.PreferenceGroup;
import androidx.preference.SwitchPreference;
import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags.DebugFlag;
+import com.android.launcher3.config.FeatureFlags;
+
+import java.util.List;
+import java.util.Set;
/**
* Dev-build only UI allowing developers to toggle flag settings. See {@link FeatureFlags}.
@@ -50,26 +53,15 @@ public final class FlagTogglerPrefUi {
@Override
public void putBoolean(String key, boolean value) {
- for (DebugFlag flag : FeatureFlags.getDebugFlags()) {
- if (flag.key.equals(key)) {
- SharedPreferences.Editor editor = mContext.getSharedPreferences(
- FLAGS_PREF_NAME, Context.MODE_PRIVATE).edit();
- if (value == flag.defaultValue) {
- editor.remove(key).apply();
- } else {
- editor.putBoolean(key, value).apply();
- }
- updateMenu();
- }
- }
+ mSharedPreferences.edit().putBoolean(key, value).apply();
+ updateMenu();
}
@Override
public boolean getBoolean(String key, boolean defaultValue) {
- for (DebugFlag flag : FeatureFlags.getDebugFlags()) {
+ for (DebugFlag flag : FlagsFactory.getDebugFlags()) {
if (flag.key.equals(key)) {
- return mContext.getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE)
- .getBoolean(key, flag.defaultValue);
+ return mSharedPreferences.getBoolean(key, flag.defaultValue);
}
}
return defaultValue;
@@ -84,11 +76,22 @@ public final class FlagTogglerPrefUi {
}
public void applyTo(PreferenceGroup parent) {
+ Set modifiedPrefs = mSharedPreferences.getAll().keySet();
+ List flags = FlagsFactory.getDebugFlags();
+ flags.sort((f1, f2) -> {
+ // Sort first by any prefs that the user has changed, then alphabetically.
+ int changeComparison = Boolean.compare(
+ modifiedPrefs.contains(f2.key), modifiedPrefs.contains(f1.key));
+ return changeComparison != 0
+ ? changeComparison
+ : f1.key.compareToIgnoreCase(f2.key);
+ });
+
// For flag overrides we only want to store when the engineer chose to override the
// flag with a different value than the default. That way, when we flip flags in
// future, engineers will pick up the new value immediately. To accomplish this, we use a
// custom preference data store.
- for (DebugFlag flag : FeatureFlags.getDebugFlags()) {
+ for (DebugFlag flag : flags) {
SwitchPreference switchPreference = new SwitchPreference(mContext);
switchPreference.setKey(flag.key);
switchPreference.setDefaultValue(flag.defaultValue);
@@ -144,11 +147,11 @@ public final class FlagTogglerPrefUi {
}
private boolean anyChanged() {
- for (DebugFlag flag : FeatureFlags.getDebugFlags()) {
+ for (DebugFlag flag : FlagsFactory.getDebugFlags()) {
if (getFlagStateFromSharedPrefs(flag) != flag.get()) {
return true;
}
}
return false;
}
-}
\ No newline at end of file
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
new file mode 100644
index 0000000000..2758f032e0
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
@@ -0,0 +1,170 @@
+/*
+ * 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.uioverrides.flags;
+
+import static android.app.ActivityThread.currentApplication;
+
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.Properties;
+import android.util.Log;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags.BooleanFlag;
+import com.android.launcher3.config.FeatureFlags.IntFlag;
+import com.android.launcher3.util.ScreenOnTracker;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Helper class to create various flags for system build
+ */
+public class FlagsFactory {
+
+ private static final String TAG = "FlagsFactory";
+
+ private static final FlagsFactory INSTANCE = new FlagsFactory();
+ private static final boolean FLAG_AUTO_APPLY_ENABLED = false;
+
+ public static final String FLAGS_PREF_NAME = "featureFlags";
+ public static final String NAMESPACE_LAUNCHER = "launcher";
+
+ private static final List sDebugFlags = new ArrayList<>();
+
+ private final Set mKeySet = new HashSet<>();
+ private boolean mRestartRequested = false;
+
+ private FlagsFactory() {
+ if (!FLAG_AUTO_APPLY_ENABLED) {
+ return;
+ }
+// DeviceConfig.addOnPropertiesChangedListener(
+// NAMESPACE_LAUNCHER, UI_HELPER_EXECUTOR, this::onPropertiesChanged);
+ }
+
+ /**
+ * Creates a new debug flag
+ */
+ public static BooleanFlag getDebugFlag(
+ int bugId, String key, boolean defaultValue, String description) {
+ if (Utilities.IS_DEBUG_DEVICE) {
+ SharedPreferences prefs = currentApplication()
+ .getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE);
+ boolean currentValue = prefs.getBoolean(key, defaultValue);
+ DebugFlag flag = new DebugFlag(key, description, defaultValue, currentValue);
+ sDebugFlags.add(flag);
+ return flag;
+ } else {
+ return new BooleanFlag(defaultValue);
+ }
+ }
+
+ /**
+ * Creates a new release flag
+ */
+ public static BooleanFlag getReleaseFlag(
+ int bugId, String key, boolean defaultValueInCode, String description) {
+ INSTANCE.mKeySet.add(key);
+// boolean defaultValue = DeviceConfig.getBoolean(NAMESPACE_LAUNCHER, key, defaultValueInCode);
+ if (Utilities.IS_DEBUG_DEVICE) {
+ SharedPreferences prefs = currentApplication()
+ .getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE);
+ boolean currentValue = prefs.getBoolean(key, defaultValueInCode);
+// DebugFlag flag = new DeviceFlag(key, description, defaultValue, currentValue,
+// defaultValueInCode);
+// sDebugFlags.add(flag);
+ return new BooleanFlag(defaultValueInCode);
+ } else {
+ return new BooleanFlag(defaultValueInCode);
+ }
+ }
+
+ /**
+ * Creates a new integer flag. Integer flags are always release flags
+ */
+ public static IntFlag getIntFlag(
+ int bugId, String key, int defaultValueInCode, String description) {
+ INSTANCE.mKeySet.add(key);
+ return new IntFlag(DeviceConfig.getInt(NAMESPACE_LAUNCHER, key, defaultValueInCode));
+ }
+
+ static List getDebugFlags() {
+ if (!Utilities.IS_DEBUG_DEVICE) {
+ return Collections.emptyList();
+ }
+ synchronized (sDebugFlags) {
+ return new ArrayList<>(sDebugFlags);
+ }
+ }
+
+ /**
+ * Dumps the current flags state to the print writer
+ */
+ public static void dump(PrintWriter pw) {
+ if (!Utilities.IS_DEBUG_DEVICE) {
+ return;
+ }
+ pw.println("DeviceFlags:");
+ synchronized (sDebugFlags) {
+ for (DebugFlag flag : sDebugFlags) {
+ if (flag instanceof DeviceFlag) {
+ pw.println(" " + flag);
+ }
+ }
+ }
+ pw.println("DebugFlags:");
+ synchronized (sDebugFlags) {
+ for (DebugFlag flag : sDebugFlags) {
+ if (!(flag instanceof DeviceFlag)) {
+ pw.println(" " + flag);
+ }
+ }
+ }
+ }
+
+ private void onPropertiesChanged(Properties properties) {
+ if (!Collections.disjoint(properties.getKeyset(), mKeySet)) {
+ // Schedule a restart
+ if (mRestartRequested) {
+ return;
+ }
+ Log.e(TAG, "Flag changed, scheduling restart");
+ mRestartRequested = true;
+ ScreenOnTracker sot = ScreenOnTracker.INSTANCE.get(currentApplication());
+ if (sot.isScreenOn()) {
+ sot.addListener(this::onScreenOnChanged);
+ } else {
+ onScreenOnChanged(false);
+ }
+ }
+ }
+
+ private void onScreenOnChanged(boolean isOn) {
+ if (mRestartRequested && !isOn) {
+ Log.e(TAG, "Restart requested, killing process");
+ System.exit(0);
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginEnablerImpl.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginEnablerImpl.java
index 5afeca7e3b..faa900b714 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginEnablerImpl.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginEnablerImpl.java
@@ -18,11 +18,11 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
-import com.android.launcher3.Utilities;
-import com.android.systemui.shared.plugins.PluginEnabler;
-
import androidx.preference.PreferenceDataStore;
+import com.android.launcher3.LauncherPrefs;
+import com.android.systemui.shared.plugins.PluginEnabler;
+
public class PluginEnablerImpl extends PreferenceDataStore implements PluginEnabler {
private static final String PREFIX_PLUGIN_ENABLED = "PLUGIN_ENABLED_";
@@ -30,7 +30,7 @@ public class PluginEnablerImpl extends PreferenceDataStore implements PluginEnab
final private SharedPreferences mSharedPrefs;
public PluginEnablerImpl(Context context) {
- mSharedPrefs = Utilities.getDevicePrefs(context);
+ mSharedPrefs = LauncherPrefs.getDevicePrefs(context);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
index 8268e594e3..d26df6acf8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
@@ -30,17 +30,17 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.shared.plugins.PluginActionManager;
import com.android.systemui.shared.plugins.PluginInstance;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.plugins.PluginManagerImpl;
import com.android.systemui.shared.plugins.PluginPrefs;
+import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Optional;
import java.util.Set;
public class PluginManagerWrapper {
@@ -50,6 +50,9 @@ public class PluginManagerWrapper {
public static final String PLUGIN_CHANGED = PluginManager.PLUGIN_CHANGED;
+ private static final UncaughtExceptionPreHandlerManager UNCAUGHT_EXCEPTION_PRE_HANDLER_MANAGER =
+ new UncaughtExceptionPreHandlerManager();
+
private final Context mContext;
private final PluginManager mPluginManager;
private final PluginEnablerImpl mPluginEnabler;
@@ -69,7 +72,7 @@ public class PluginManagerWrapper {
mPluginManager = new PluginManagerImpl(c, instanceManagerFactory,
Utilities.IS_DEBUG_DEVICE,
- Optional.ofNullable(Thread.getDefaultUncaughtExceptionHandler()), mPluginEnabler,
+ UNCAUGHT_EXCEPTION_PRE_HANDLER_MANAGER, mPluginEnabler,
new PluginPrefs(c), privilegedPlugins);
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index 899e5a4da2..c8fb2a2890 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -23,8 +23,8 @@ import android.content.Context;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
-import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.ActivityContext;
import app.lawnchair.LawnchairLauncher;
import app.lawnchair.util.LawnchairUtilsKt;
@@ -34,28 +34,23 @@ import app.lawnchair.util.LawnchairUtilsKt;
*/
public class AllAppsState extends LauncherState {
- private static final int STATE_FLAGS = FLAG_WORKSPACE_INACCESSIBLE | FLAG_CLOSE_POPUPS;
-
- private static final PageAlphaProvider PAGE_ALPHA_PROVIDER = new PageAlphaProvider(DEACCEL_2) {
- @Override
- public float getPageAlpha(int pageIndex) {
- return 0;
- }
- };
+ private static final int STATE_FLAGS = FLAG_WORKSPACE_INACCESSIBLE | FLAG_CLOSE_POPUPS | FLAG_HOTSEAT_INACCESSIBLE;
public AllAppsState(int id) {
super(id, LAUNCHER_STATE_ALLAPPS, STATE_FLAGS);
}
@Override
- public int getTransitionDuration(Context context) {
- return 320;
+ public int getTransitionDuration(
+ DEVICE_PROFILE_CONTEXT context, boolean isToState) {
+ return isToState
+ ? context.getDeviceProfile().allAppsOpenDuration
+ : context.getDeviceProfile().allAppsCloseDuration;
}
@Override
public String getDescription(Launcher launcher) {
- AllAppsContainerView appsView = launcher.getAppsView();
- return appsView.getDescription();
+ return launcher.getAppsView().getDescription();
}
@Override
@@ -65,32 +60,55 @@ public class AllAppsState extends LauncherState {
@Override
public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
- ScaleAndTranslation scaleAndTranslation = LauncherState.OVERVIEW
- .getWorkspaceScaleAndTranslation(launcher);
- scaleAndTranslation.scale = 1;
- return scaleAndTranslation;
+ return new ScaleAndTranslation(launcher.getDeviceProfile().workspaceContentScale, NO_OFFSET,
+ NO_OFFSET);
}
@Override
- public boolean isTaskbarStashed(Launcher launcher) {
- return true;
+ public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
+ if (launcher.getDeviceProfile().isTablet) {
+ return getWorkspaceScaleAndTranslation(launcher);
+ } else {
+ ScaleAndTranslation overviewScaleAndTranslation = LauncherState.OVERVIEW
+ .getWorkspaceScaleAndTranslation(launcher);
+ return new ScaleAndTranslation(
+ launcher.getDeviceProfile().workspaceContentScale,
+ overviewScaleAndTranslation.translationX,
+ overviewScaleAndTranslation.translationY);
+ }
}
@Override
- protected float getDepthUnchecked(Context context) {
- // The scrim fades in at approximately 50% of the swipe gesture.
- // This means that the depth should be greater than 1, in order to fully zoom out.
- return 2f;
+ protected float getDepthUnchecked(
+ DEVICE_PROFILE_CONTEXT context) {
+ if (context.getDeviceProfile().isTablet) {
+ return context.getDeviceProfile().bottomSheetDepth;
+ } else {
+ // The scrim fades in at approximately 50% of the swipe gesture.
+ // This means that the depth should be greater than 1, in order to fully zoom
+ // out.
+ return 2f;
+ }
}
@Override
public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
- return PAGE_ALPHA_PROVIDER;
+ PageAlphaProvider superPageAlphaProvider = super.getWorkspacePageAlphaProvider(launcher);
+ return new PageAlphaProvider(DEACCEL_2) {
+ @Override
+ public float getPageAlpha(int pageIndex) {
+ return launcher.getDeviceProfile().isTablet
+ ? superPageAlphaProvider.getPageAlpha(pageIndex)
+ : 0;
+ }
+ };
}
@Override
public int getVisibleElements(Launcher launcher) {
- return ALL_APPS_CONTENT;
+ // Don't add HOTSEAT_ICONS for non-tablets in ALL_APPS state.
+ return launcher.getDeviceProfile().isTablet ? ALL_APPS_CONTENT | HOTSEAT_ICONS
+ : ALL_APPS_CONTENT;
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index b7330072d4..e5787209d2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -16,6 +16,7 @@
package com.android.launcher3.uioverrides.states;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
+import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
import android.content.Context;
import android.graphics.Color;
@@ -23,9 +24,9 @@ import android.graphics.Color;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.RecentsView;
/**
@@ -82,20 +83,45 @@ public class BackgroundAppState extends OverviewState {
return false;
}
+ @Override
+ public boolean showTaskThumbnailSplash() {
+ return true;
+ }
+
@Override
protected float getDepthUnchecked(Context context) {
+ if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+ if (Launcher.getLauncher(context).areFreeformTasksVisible()) {
+ // Don't blur the background while freeform tasks are visible
+ return 0;
+ }
+ }
return 1;
}
@Override
public int getWorkspaceScrimColor(Launcher launcher) {
- DeviceProfile dp = launcher.getDeviceProfile();
- if (dp.isTaskbarPresentInApps) {
- return launcher.getColor(R.color.taskbar_background);
- }
return Color.TRANSPARENT;
}
+ @Override
+ public boolean isTaskbarAlignedWithHotseat(Launcher launcher) {
+ if (ENABLE_SHELL_TRANSITIONS) return false;
+ return super.isTaskbarAlignedWithHotseat(launcher);
+ }
+
+ @Override
+ public boolean disallowTaskbarGlobalDrag() {
+ // Enable global drag in overview
+ return false;
+ }
+
+ @Override
+ public boolean allowTaskbarInitialSplitSelection() {
+ // Disallow split select from taskbar items in overview
+ return false;
+ }
+
public static float[] getOverviewScaleAndOffsetForBackgroundState(
BaseDraggingActivity activity) {
return new float[] {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
index 6f084a1f97..b9221eef9d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
@@ -18,12 +18,12 @@ package com.android.launcher3.uioverrides.states;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
import android.content.Context;
-import android.graphics.Point;
import android.graphics.Rect;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.config.FeatureFlags;
import com.android.quickstep.views.RecentsView;
/**
@@ -40,7 +40,7 @@ public class OverviewModalTaskState extends OverviewState {
}
@Override
- public int getTransitionDuration(Context launcher) {
+ public int getTransitionDuration(Context launcher, boolean isToState) {
return 300;
}
@@ -70,13 +70,22 @@ public class OverviewModalTaskState extends OverviewState {
}
}
- public static float[] getOverviewScaleAndOffsetForModalState(BaseDraggingActivity activity) {
- Point taskSize = activity.getOverviewPanel().getSelectedTaskSize();
- Rect modalTaskSize = new Rect();
- activity.getOverviewPanel().getModalTaskSize(modalTaskSize);
+ @Override
+ public boolean isTaskbarStashed(Launcher launcher) {
+ if (FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get()) {
+ return true;
+ }
+ return super.isTaskbarStashed(launcher);
+ }
- float scale = Math.min((float) modalTaskSize.height() / taskSize.y,
- (float) modalTaskSize.width() / taskSize.x);
+ public static float[] getOverviewScaleAndOffsetForModalState(BaseDraggingActivity activity) {
+ RecentsView recentsView = activity.getOverviewPanel();
+ Rect taskSize = recentsView.getSelectedTaskBounds();
+ Rect modalTaskSize = new Rect();
+ recentsView.getModalTaskSize(modalTaskSize);
+
+ float scale = Math.min((float) modalTaskSize.height() / taskSize.height(),
+ (float) modalTaskSize.width() / taskSize.width());
return new float[] {scale, NO_OFFSET};
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 8c0d1c46a3..6f17221d5b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -26,7 +26,8 @@ import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
-import com.android.quickstep.SysUINavigationMode;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.Themes;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -39,6 +40,10 @@ import app.lawnchair.theme.color.ColorTokens;
*/
public class OverviewState extends LauncherState {
+ private static final int OVERVIEW_SLIDE_IN_DURATION = 380;
+ private static final int OVERVIEW_POP_IN_DURATION = 250;
+ private static final int OVERVIEW_EXIT_DURATION = 250;
+
protected static final Rect sTempRect = new Rect();
private static final int STATE_FLAGS = FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED
@@ -58,24 +63,41 @@ public class OverviewState extends LauncherState {
}
@Override
- public int getTransitionDuration(Context context) {
- // In gesture modes, overview comes in all the way from the side, so give it more time.
- return SysUINavigationMode.INSTANCE.get(context).getMode().hasGestures ? 380 : 250;
+ public int getTransitionDuration(Context context, boolean isToState) {
+ if (isToState) {
+ // In gesture modes, overview comes in all the way from the side, so give it
+ // more time.
+ return DisplayController.getNavigationMode(context).hasGestures
+ ? OVERVIEW_SLIDE_IN_DURATION
+ : OVERVIEW_POP_IN_DURATION;
+ } else {
+ // When exiting Overview, exit quickly.
+ return OVERVIEW_EXIT_DURATION;
+ }
}
@Override
public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
RecentsView recentsView = launcher.getOverviewPanel();
- float workspacePageWidth = launcher.getDeviceProfile().getWorkspaceWidth();
recentsView.getTaskSize(sTempRect);
- float scale = (float) sTempRect.width() / workspacePageWidth;
+ float scale;
+ DeviceProfile deviceProfile = launcher.getDeviceProfile();
+ if (deviceProfile.isTwoPanels) {
+ // In two panel layout, width does not include both panels or space between
+ // them, so
+ // use height instead. We do not use height for handheld, as cell layout can be
+ // shorter than a task and we want the workspace to scale down to task size.
+ scale = (float) sTempRect.height() / deviceProfile.getCellLayoutHeight();
+ } else {
+ scale = (float) sTempRect.width() / deviceProfile.getCellLayoutWidth();
+ }
float parallaxFactor = 0.5f;
return new ScaleAndTranslation(scale, 0, -getDefaultSwipeHeight(launcher) * parallaxFactor);
}
@Override
public float[] getOverviewScaleAndOffset(Launcher launcher) {
- return new float[] {NO_SCALE, NO_OFFSET};
+ return new float[] { NO_SCALE, NO_OFFSET };
}
@Override
@@ -97,8 +119,8 @@ public class OverviewState extends LauncherState {
}
@Override
- public boolean isTaskbarStashed(Launcher launcher) {
- return true;
+ public boolean isTaskbarAlignedWithHotseat(Launcher launcher) {
+ return false;
}
@Override
@@ -108,7 +130,19 @@ public class OverviewState extends LauncherState {
@Override
public boolean displayOverviewTasksAsGrid(DeviceProfile deviceProfile) {
- return deviceProfile.overviewShowAsGrid;
+ return deviceProfile.isTablet;
+ }
+
+ @Override
+ public boolean disallowTaskbarGlobalDrag() {
+ // Disable global drag in overview
+ return true;
+ }
+
+ @Override
+ public boolean allowTaskbarInitialSplitSelection() {
+ // Allow split select from taskbar items in overview
+ return true;
}
@Override
@@ -122,15 +156,20 @@ public class OverviewState extends LauncherState {
@Override
protected float getDepthUnchecked(Context context) {
- //TODO revert when b/178661709 is fixed
+ // TODO revert when b/178661709 is fixed
return SystemProperties.getBoolean("ro.launcher.depth.overview", true) ? 1 : 0;
}
@Override
public void onBackPressed(Launcher launcher) {
- TaskView taskView = launcher.getOverviewPanel().getRunningTaskView();
+ RecentsView recentsView = launcher.getOverviewPanel();
+ TaskView taskView = recentsView.getRunningTaskView();
if (taskView != null) {
- taskView.launchTaskAnimated();
+ if (recentsView.isTaskViewFullyVisible(taskView)) {
+ taskView.launchTasks();
+ } else {
+ recentsView.snapToPage(recentsView.indexOfChild(taskView));
+ }
} else {
super.onBackPressed(launcher);
}
@@ -145,14 +184,16 @@ public class OverviewState extends LauncherState {
}
/**
- * New Overview substate that represents the overview in modal mode (one task shown on its own)
+ * New Overview substate that represents the overview in modal mode (one task
+ * shown on its own)
*/
public static OverviewState newModalTaskState(int id) {
return new OverviewModalTaskState(id);
}
/**
- * New Overview substate representing state where 1 app for split screen has been selected and
+ * New Overview substate representing state where 1 app for split screen has
+ * been selected and
* pinned and user is selecting the second one
*/
public static OverviewState newSplitSelectState(int id) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
index 24467e194e..10d5257e9c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
@@ -17,15 +17,22 @@ package com.android.launcher3.uioverrides.states;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
+import android.graphics.Color;
+
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import app.lawnchair.theme.color.ColorTokens;
+import com.android.launcher3.util.Themes;
+import com.android.quickstep.views.DesktopTaskView;
/**
- * State to indicate we are about to launch a recent task. Note that this state is only used when
- * quick switching from launcher; quick switching from an app uses LauncherSwipeHandler.
+ * State to indicate we are about to launch a recent task. Note that this state
+ * is only used when
+ * quick switching from launcher; quick switching from an app uses
+ * LauncherSwipeHandler.
+ *
* @see com.android.quickstep.GestureState.GestureEndTarget#NEW_TASK
*/
public class QuickSwitchState extends BackgroundAppState {
@@ -44,6 +51,12 @@ public class QuickSwitchState extends BackgroundAppState {
@Override
public int getWorkspaceScrimColor(Launcher launcher) {
+ if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+ if (launcher.areFreeformTasksVisible()) {
+ // No scrim while freeform tasks are visible
+ return Color.TRANSPARENT;
+ }
+ }
DeviceProfile dp = launcher.getDeviceProfile();
if (dp.isTaskbarPresentInApps) {
return launcher.getColor(R.color.taskbar_background);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 75cf5cb3a5..c7cd39c100 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -22,16 +22,21 @@ import static com.android.launcher3.LauncherState.HINT_STATE;
import static com.android.launcher3.LauncherState.HINT_STATE_TWO_BUTTON;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.WorkspaceStateTransitionAnimation.getSpringScaleAnimator;
+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;
@@ -39,16 +44,14 @@ import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
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.ANIM_SCRIM_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
-import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD;
-import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD;
-import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.ALL_APPS_SCRIM_OPAQUE_THRESHOLD;
-import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.ALL_APPS_SCRIM_VISIBLE_THRESHOLD;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
import android.animation.ValueAnimator;
@@ -57,11 +60,12 @@ import com.android.launcher3.CellLayout;
import com.android.launcher3.Hotseat;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Workspace;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.touch.AllAppsSwipeController;
import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.quickstep.SysUINavigationMode;
+import com.android.launcher3.util.DisplayController;
import com.android.quickstep.util.RecentsAtomicAnimationFactory;
+import com.android.quickstep.util.SplitAnimationTimings;
import com.android.quickstep.views.RecentsView;
/**
@@ -91,34 +95,52 @@ public class QuickstepAtomicAnimationFactory extends
public void prepareForAtomicAnimation(LauncherState fromState, LauncherState toState,
StateAnimationConfig config) {
RecentsView overview = mActivity.getOverviewPanel();
- if (toState == NORMAL && fromState == OVERVIEW) {
+ if ((fromState == OVERVIEW || fromState == OVERVIEW_SPLIT_SELECT) && toState == NORMAL) {
+ if (fromState == OVERVIEW_SPLIT_SELECT) {
+ config.setInterpolator(ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN,
+ clampToProgress(EMPHASIZED_ACCELERATE, 0, 0.4f));
+ config.setInterpolator(ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE,
+ clampToProgress(LINEAR, 0, 0.33f));
+ }
+
config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, clampToProgress(LINEAR, 0, 0.25f));
- config.setInterpolator(ANIM_SCRIM_FADE, LINEAR);
+ config.setInterpolator(ANIM_SCRIM_FADE,
+ fromState == OVERVIEW_SPLIT_SELECT
+ ? clampToProgress(LINEAR, 0.33f, 1)
+ : LINEAR);
config.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL);
config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
- if (SysUINavigationMode.getMode(mActivity).hasGestures
+ if (DisplayController.getNavigationMode(mActivity).hasGestures
&& overview.getTaskViewCount() > 0) {
// Overview is going offscreen, so keep it at its current scale and opacity.
config.setInterpolator(ANIM_OVERVIEW_SCALE, FINAL_FRAME);
config.setInterpolator(ANIM_OVERVIEW_FADE, FINAL_FRAME);
config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X,
- clampToProgress(FAST_OUT_SLOW_IN, 0, 0.75f));
+ fromState == OVERVIEW_SPLIT_SELECT
+ ? EMPHASIZED_DECELERATE
+ : clampToProgress(FAST_OUT_SLOW_IN, 0, 0.75f));
config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, FINAL_FRAME);
+
+ // Scroll RecentsView to page 0 as it goes offscreen, if necessary.
+ int numPagesToScroll = overview.getNextPage() - DEFAULT_PAGE;
+ long scrollDuration = Math.min(MAX_PAGE_SCROLL_DURATION,
+ 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.
+ 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);
}
- // Scroll RecentsView to page 0 as it goes offscreen, if necessary.
- int numPagesToScroll = overview.getNextPage() - DEFAULT_PAGE;
- long scrollDuration = Math.min(MAX_PAGE_SCROLL_DURATION,
- numPagesToScroll * PER_PAGE_SCROLL_DURATION);
- config.duration = Math.max(config.duration, scrollDuration);
- overview.snapToPage(DEFAULT_PAGE, Math.toIntExact(config.duration));
-
- Workspace workspace = mActivity.getWorkspace();
+ Workspace> workspace = mActivity.getWorkspace();
// Start from a higher workspace scale, but only if we're invisible so we don't jump.
boolean isWorkspaceVisible = workspace.getVisibility() == VISIBLE;
if (isWorkspaceVisible) {
@@ -139,7 +161,7 @@ public class QuickstepAtomicAnimationFactory extends
}
} else if ((fromState == NORMAL || fromState == HINT_STATE
|| fromState == HINT_STATE_TWO_BUTTON) && toState == OVERVIEW) {
- if (SysUINavigationMode.getMode(mActivity).hasGestures) {
+ if (DisplayController.getNavigationMode(mActivity).hasGestures) {
config.setInterpolator(ANIM_WORKSPACE_SCALE,
fromState == NORMAL ? ACCEL : OVERSHOOT_1_2);
config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL);
@@ -172,18 +194,32 @@ public class QuickstepAtomicAnimationFactory extends
} else if (fromState == HINT_STATE && toState == NORMAL) {
config.setInterpolator(ANIM_DEPTH, DEACCEL_3);
if (mHintToNormalDuration == -1) {
- ValueAnimator va = getSpringScaleAnimator(mActivity, mActivity.getWorkspace(),
+ ValueAnimator va = getWorkspaceSpringScaleAnimator(mActivity,
+ mActivity.getWorkspace(),
toState.getWorkspaceScaleAndTranslation(mActivity).scale);
mHintToNormalDuration = (int) va.getDuration();
}
config.duration = Math.max(config.duration, mHintToNormalDuration);
} else if (fromState == ALL_APPS && toState == NORMAL) {
- config.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(DEACCEL,
- 1 - ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD,
- 1 - ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD));
- config.setInterpolator(ANIM_SCRIM_FADE, Interpolators.clampToProgress(DEACCEL,
- 1 - ALL_APPS_SCRIM_OPAQUE_THRESHOLD,
- 1 - ALL_APPS_SCRIM_VISIBLE_THRESHOLD));
+ AllAppsSwipeController.applyAllAppsToNormalConfig(mActivity, config);
+ } else if (fromState == NORMAL && toState == ALL_APPS) {
+ AllAppsSwipeController.applyNormalToAllAppsAnimConfig(mActivity, config);
+ } else if (fromState == OVERVIEW && toState == OVERVIEW_SPLIT_SELECT) {
+ SplitAnimationTimings timings = mActivity.getDeviceProfile().isTablet
+ ? SplitAnimationTimings.TABLET_OVERVIEW_TO_SPLIT
+ : SplitAnimationTimings.PHONE_OVERVIEW_TO_SPLIT;
+ config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, clampToProgress(LINEAR,
+ timings.getActionsFadeStartOffset(),
+ timings.getActionsFadeEndOffset()));
+ } else if ((fromState == NORMAL || fromState == ALL_APPS)
+ && toState == OVERVIEW_SPLIT_SELECT) {
+ // Splitting from Home is currently only available on tablets
+ SplitAnimationTimings timings = SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
+ config.setInterpolator(ANIM_SCRIM_FADE, clampToProgress(LINEAR,
+ timings.getScrimFadeInStartOffset(),
+ timings.getScrimFadeInEndOffset()));
+ config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_0_75);
+ config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, OVERSHOOT_0_75);
}
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
index e79d56b631..3ae221bbeb 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
@@ -16,7 +16,10 @@
package com.android.launcher3.uioverrides.states;
+import android.content.Context;
+
import com.android.launcher3.Launcher;
+import com.android.quickstep.util.SplitAnimationTimings;
import com.android.quickstep.views.RecentsView;
/**
@@ -38,4 +41,21 @@ public class SplitScreenSelectState extends OverviewState {
RecentsView recentsView = launcher.getOverviewPanel();
return recentsView.getSplitSelectTranslation();
}
+
+ @Override
+ public int getTransitionDuration(Context context, boolean isToState) {
+ boolean isTablet = ((Launcher) context).getDeviceProfile().isTablet;
+ if (isToState && isTablet) {
+ return SplitAnimationTimings.TABLET_ENTER_DURATION;
+ } else if (isToState && !isTablet) {
+ return SplitAnimationTimings.PHONE_ENTER_DURATION;
+ } else {
+ return SplitAnimationTimings.ABORT_DURATION;
+ }
+ }
+
+ @Override
+ public boolean shouldPreserveDataStateOnReapply() {
+ return true;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index 86c42caa7b..40dfd82f5d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -21,11 +21,10 @@ import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRES
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.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
+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.config.FeatureFlags.ENABLE_ALL_APPS_EDU;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
@@ -40,16 +39,14 @@ import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsTransitionController;
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.config.FeatureFlags;
-import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.util.TouchController;
import com.android.quickstep.TaskUtils;
+import com.android.quickstep.TopTaskTracker;
import com.android.quickstep.util.AnimatorControllerWithResistance;
-import com.android.quickstep.util.AssistantUtilities;
import com.android.quickstep.util.OverviewToHomeAnim;
import com.android.quickstep.views.RecentsView;
@@ -107,12 +104,13 @@ public class NavBarToHomeTouchController implements TouchController,
if (mStartState.overviewUi || mStartState == ALL_APPS) {
return true;
}
- int typeToClose = ENABLE_ALL_APPS_EDU.get() ? TYPE_ALL & ~TYPE_ALL_APPS_EDU : TYPE_ALL;
+ int typeToClose = TYPE_ALL & ~TYPE_ALL_APPS_EDU;
if (AbstractFloatingView.getTopOpenViewWithType(mLauncher, typeToClose) != null) {
return true;
}
if (FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS.get()
- && AssistantUtilities.isExcludedAssistantRunning()) {
+ && TopTaskTracker.INSTANCE.get(mLauncher).getCachedTopTask(false)
+ .isExcludedAssistant()) {
return true;
}
return false;
@@ -140,23 +138,15 @@ public class NavBarToHomeTouchController implements TouchController,
AnimatorControllerWithResistance.createRecentsResistanceFromOverviewAnim(mLauncher,
builder);
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- builder.addOnFrameCallback(recentsView::redrawLiveTile);
- }
+ builder.addOnFrameCallback(recentsView::redrawLiveTile);
AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_TASK_MENU);
} else if (mStartState == ALL_APPS) {
AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
- builder.setFloat(allAppsController, ALL_APPS_PROGRESS,
- -mPullbackDistance / allAppsController.getShiftRange(), PULLBACK_INTERPOLATOR);
-
- // Slightly fade out all apps content to further distinguish from scrolling.
- StateAnimationConfig config = new StateAnimationConfig();
- config.duration = accuracy;
- config.setInterpolator(StateAnimationConfig.ANIM_ALL_APPS_FADE, Interpolators
- .mapToProgress(PULLBACK_INTERPOLATOR, 0, 0.5f));
-
- allAppsController.setAlphas(mEndState, config, builder);
+ builder.setFloat(allAppsController, ALL_APPS_PULL_BACK_TRANSLATION,
+ -mPullbackDistance, PULLBACK_INTERPOLATOR);
+ builder.setFloat(allAppsController, ALL_APPS_PULL_BACK_ALPHA,
+ 0.5f, PULLBACK_INTERPOLATOR);
}
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mLauncher);
if (topView != null) {
@@ -189,11 +179,9 @@ public class NavBarToHomeTouchController implements TouchController,
boolean success = interpolatedProgress >= SUCCESS_TRANSITION_PROGRESS
|| (velocity < 0 && fling);
if (success) {
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- RecentsView recentsView = mLauncher.getOverviewPanel();
- recentsView.switchToScreenshot(null,
- () -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
- }
+ RecentsView recentsView = mLauncher.getOverviewPanel();
+ recentsView.switchToScreenshot(null,
+ () -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
if (mStartState.overviewUi) {
new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(mEndState))
.animateWithVelocity(velocity);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index ef6f53e8f5..b7bafd8969 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -18,13 +18,15 @@ package com.android.launcher3.uioverrides.touchcontrollers;
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.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.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
+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.ObjectAnimator;
@@ -37,12 +39,15 @@ import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
+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.VibratorWrapper;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.OverviewToHomeAnim;
-import com.android.quickstep.util.VibratorWrapper;
import com.android.quickstep.views.RecentsView;
/**
@@ -58,6 +63,7 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
private static final long TRANSLATION_ANIM_MIN_DURATION_MS = 80;
private static final float TRANSLATION_ANIM_VELOCITY_DP_PER_MS = 0.8f;
+ private final VibratorWrapper mVibratorWrapper;
private final RecentsView mRecentsView;
private final MotionPauseDetector mMotionPauseDetector;
private final float mMotionPauseMinDisplacement;
@@ -78,12 +84,18 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
mRecentsView = l.getOverviewPanel();
mMotionPauseDetector = new MotionPauseDetector(l);
mMotionPauseMinDisplacement = ViewConfiguration.get(l).getScaledTouchSlop();
+ mVibratorWrapper = VibratorWrapper.INSTANCE.get(l.getApplicationContext());
}
@Override
protected boolean canInterceptTouch(MotionEvent ev) {
mDidTouchStartInNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
- return super.canInterceptTouch(ev);
+ boolean isOneHandedModeActive = (SystemUiProxy.INSTANCE.get(mLauncher)
+ .getLastSystemUiStateFlags() & SYSUI_STATE_ONE_HANDED_ACTIVE) != 0;
+ // Reset touch slop multiplier to default 1.0f if one-handed-mode is not active
+ mDetector.setTouchSlopMultiplier(
+ isOneHandedModeActive ? ONE_HANDED_ACTIVATED_SLOP_MULTIPLIER : 1f /* default */);
+ return super.canInterceptTouch(ev) && !mLauncher.isInState(HINT_STATE);
}
@Override
@@ -109,6 +121,14 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
@Override
public void onDragStart(boolean start, float startDisplacement) {
+ if (mLauncher.isInState(ALL_APPS)) {
+ LauncherTaskbarUIController controller =
+ ((QuickstepLauncher) mLauncher).getTaskbarUIController();
+ if (controller != null) {
+ controller.setShouldDelayLauncherStateAnim(true);
+ }
+ }
+
super.onDragStart(start, startDisplacement);
mMotionPauseDetector.clear();
@@ -139,6 +159,12 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
@Override
public void onDragEnd(float velocity) {
+ LauncherTaskbarUIController controller =
+ ((QuickstepLauncher) mLauncher).getTaskbarUIController();
+ if (controller != null) {
+ controller.setShouldDelayLauncherStateAnim(false);
+ }
+
if (mStartedOverview) {
goToOverviewOrHomeOnDragEnd(velocity);
} else {
@@ -163,7 +189,12 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
// Normally we compute the duration based on the velocity and distance to the given
// 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));
+ animator.setDuration(HINT_STATE.getTransitionDuration(mLauncher, true /* isToState */));
+ }
+ if (FeatureFlags.ENABLE_PREMIUM_HAPTICS_ALL_APPS.get() &&
+ ((mFromState == NORMAL && mToState == ALL_APPS)
+ || (mFromState == ALL_APPS && mToState == NORMAL)) && isFling) {
+ mVibratorWrapper.vibrateForDragBump();
}
}
@@ -260,14 +291,4 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
private float dpiFromPx(float pixels) {
return Utilities.dpiFromPx(pixels, mLauncher.getResources().getDisplayMetrics().densityDpi);
}
-
- @Override
- public void onOneHandedModeStateChanged(boolean activated) {
- if (activated) {
- mDetector.setTouchSlopMultiplier(ONE_HANDED_ACTIVATED_SLOP_MULTIPLIER);
- } else {
- // Reset touch slop multiplier to default 1.0f
- mDetector.setTouchSlopMultiplier(1f /* default */);
- }
- }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index dadc706cf0..847114a960 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -19,7 +19,7 @@ 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;
+import static com.android.launcher3.LauncherState.QUICK_SWITCH_FROM_HOME;
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;
@@ -27,6 +27,7 @@ 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;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_UNKNOWN_SWIPEUP;
import static com.android.launcher3.logging.StatsLogManager.getLauncherAtomEvent;
@@ -38,16 +39,16 @@ import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_S
import static com.android.launcher3.states.StateAnimationConfig.SKIP_ALL_ANIMATIONS;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_SCRIM;
-import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT;
import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP;
-import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
-import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
+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;
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_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 static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import android.animation.Animator;
@@ -55,27 +56,27 @@ import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.graphics.PointF;
-import android.util.Log;
import android.view.MotionEvent;
import android.view.animation.Interpolator;
-import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
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.TouchController;
-import com.android.quickstep.AnimatedFloat;
+import com.android.launcher3.util.VibratorWrapper;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.MotionPauseDetector;
-import com.android.quickstep.util.VibratorWrapper;
import com.android.quickstep.util.WorkspaceRevealAnim;
+import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.LauncherRecentsView;
import com.android.quickstep.views.RecentsView;
@@ -92,7 +93,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
private static final Interpolator SCALE_DOWN_INTERPOLATOR = LINEAR;
private static final long ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW = 300;
- private final BaseQuickstepLauncher mLauncher;
+ private final QuickstepLauncher mLauncher;
private final BothAxesSwipeDetector mSwipeDetector;
private final float mXRange;
private final float mYRange;
@@ -114,7 +115,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
private AnimatorPlaybackController mXOverviewAnim;
private AnimatedFloat mYOverviewAnim;
- public NoButtonQuickSwitchTouchController(BaseQuickstepLauncher launcher) {
+ public NoButtonQuickSwitchTouchController(QuickstepLauncher launcher) {
mLauncher = launcher;
mSwipeDetector = new BothAxesSwipeDetector(mLauncher, this);
mRecentsView = mLauncher.getOverviewPanel();
@@ -164,6 +165,10 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) {
return false;
}
+ if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+ // TODO(b/268075592): add support for quickswitch to/from desktop
+ return false;
+ }
return true;
}
@@ -195,7 +200,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
nonOverviewBuilder.setInterpolator(ANIM_WORKSPACE_SCALE, FADE_OUT_INTERPOLATOR);
nonOverviewBuilder.setInterpolator(ANIM_DEPTH, FADE_OUT_INTERPOLATOR);
nonOverviewBuilder.setInterpolator(ANIM_VERTICAL_PROGRESS, TRANSLATE_OUT_INTERPOLATOR);
- updateNonOverviewAnim(QUICK_SWITCH, nonOverviewBuilder);
+ updateNonOverviewAnim(QUICK_SWITCH_FROM_HOME, nonOverviewBuilder);
mNonOverviewAnim.dispatchOnStart();
if (mRecentsView.getTaskViewCount() == 0) {
@@ -220,17 +225,18 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
}
private void setupOverviewAnimators() {
- final LauncherState fromState = QUICK_SWITCH;
+ final LauncherState fromState = QUICK_SWITCH_FROM_HOME;
final LauncherState toState = OVERVIEW;
// Set RecentView's initial properties.
RECENTS_SCALE_PROPERTY.set(mRecentsView, fromState.getOverviewScaleAndOffset(mLauncher)[0]);
ADJACENT_PAGE_HORIZONTAL_OFFSET.set(mRecentsView, 1f);
- Log.d(BAD_STATE, "NBQSTC setupOverviewAnimators setContentAlpha=1");
+ TASK_THUMBNAIL_SPLASH_ALPHA.set(mRecentsView, fromState.showTaskThumbnailSplash() ? 1f : 0);
mRecentsView.setContentAlpha(1);
mRecentsView.setFullscreenProgress(fromState.getOverviewFullscreenProgress());
mLauncher.getActionsView().getVisibilityAlpha().setValue(
(fromState.getVisibleElements(mLauncher) & OVERVIEW_ACTIONS) != 0 ? 1f : 0f);
+ mRecentsView.setTaskIconScaledDown(true);
float[] scaleAndOffset = toState.getOverviewScaleAndOffset(mLauncher);
// As we drag right, animate the following properties:
@@ -242,27 +248,9 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
// Use QuickSwitchState instead of OverviewState to determine scrim color,
// since we need to take potential taskbar into account.
xAnim.setViewBackgroundColor(mLauncher.getScrimView(),
- QUICK_SWITCH.getWorkspaceScrimColor(mLauncher), LINEAR);
+ QUICK_SWITCH_FROM_HOME.getWorkspaceScrimColor(mLauncher), LINEAR);
if (mRecentsView.getTaskViewCount() == 0) {
xAnim.addFloat(mRecentsView, CONTENT_ALPHA, 0f, 1f, LINEAR);
- Log.d(BAD_STATE, "NBQSTC setupOverviewAnimators from: 0 to: 1");
- xAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- Log.d(BAD_STATE, "NBQSTC setupOverviewAnimators onStart");
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- float alpha = mRecentsView == null ? -1 : CONTENT_ALPHA.get(mRecentsView);
- Log.d(BAD_STATE, "NBQSTC setupOverviewAnimators onCancel, alpha=" + alpha);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- Log.d(BAD_STATE, "NBQSTC setupOverviewAnimators onEnd");
- }
- });
}
mXOverviewAnim = xAnim.createPlaybackController();
mXOverviewAnim.dispatchOnStart();
@@ -320,6 +308,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
boolean verticalFling = mSwipeDetector.isFling(velocity.y);
boolean noFling = !horizontalFling && !verticalFling;
if (mMotionPauseDetector.isPaused() && noFling) {
+ // Going to Overview.
cancelAnimations();
StateAnimationConfig config = new StateAnimationConfig();
@@ -330,6 +319,8 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
@Override
public void onAnimationEnd(Animator animation) {
onAnimationToStateCompleted(OVERVIEW);
+ // Animate the icon after onAnimationToStateCompleted() so it doesn't clobber.
+ mRecentsView.animateUpTaskIconScale();
}
});
overviewAnim.start();
@@ -349,24 +340,24 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
} else {
if (velocity.y > 0) {
// Flinging right and down goes to quick switch.
- targetState = QUICK_SWITCH;
+ targetState = QUICK_SWITCH_FROM_HOME;
} else {
// 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 : NORMAL;
+ ? QUICK_SWITCH_FROM_HOME : NORMAL;
}
}
} else if (horizontalFling) {
- targetState = velocity.x > 0 ? QUICK_SWITCH : NORMAL;
+ targetState = velocity.x > 0 ? QUICK_SWITCH_FROM_HOME : NORMAL;
} else if (verticalFling) {
- targetState = velocity.y > 0 ? QUICK_SWITCH : NORMAL;
+ targetState = velocity.y > 0 ? QUICK_SWITCH_FROM_HOME : NORMAL;
} else {
// If user isn't flinging, just snap to the closest state.
boolean passedHorizontalThreshold = mXOverviewAnim.getInterpolatedProgress() > 0.5f;
boolean passedVerticalThreshold = mYOverviewAnim.value > 1f;
targetState = passedHorizontalThreshold && !passedVerticalThreshold
- ? QUICK_SWITCH : NORMAL;
+ ? QUICK_SWITCH_FROM_HOME : NORMAL;
}
// Animate the various components to the target state.
@@ -428,7 +419,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
nonOverviewAnim.setFloatValues(startProgress, endProgress);
mNonOverviewAnim.dispatchOnStart();
}
- if (targetState == QUICK_SWITCH) {
+ if (targetState == QUICK_SWITCH_FROM_HOME) {
// Navigating to quick switch, add scroll feedback since the first time is not
// considered a scroll by the RecentsView.
VibratorWrapper.INSTANCE.get(mLauncher).vibrate(
@@ -451,9 +442,11 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
.withSrcState(LAUNCHER_STATE_HOME)
.withDstState(targetState.statsLogOrdinal)
.log(getLauncherAtomEvent(mStartState.statsLogOrdinal, targetState.statsLogOrdinal,
- targetState.ordinal > mStartState.ordinal
- ? LAUNCHER_UNKNOWN_SWIPEUP
- : LAUNCHER_UNKNOWN_SWIPEDOWN));
+ targetState == QUICK_SWITCH_FROM_HOME
+ ? LAUNCHER_QUICKSWITCH_RIGHT
+ : targetState.ordinal > mStartState.ordinal
+ ? LAUNCHER_UNKNOWN_SWIPEUP
+ : LAUNCHER_UNKNOWN_SWIPEDOWN));
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 59ade49761..8368f9ca03 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -21,10 +21,6 @@ import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.anim.Interpolators.ACCEL;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_SCRIM_FADE;
import android.view.MotionEvent;
@@ -35,6 +31,7 @@ import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
+import com.android.launcher3.touch.AllAppsSwipeController;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.uioverrides.states.OverviewState;
import com.android.quickstep.SystemUiProxy;
@@ -49,26 +46,6 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
private static final String TAG = "PortraitStatesTouchCtrl";
- /**
- * The progress at which all apps content will be fully visible.
- */
- public static final float ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD = 0.8f;
-
- /**
- * Minimum clamping progress for fading in all apps content
- */
- public static final float ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD = 0.5f;
-
- /**
- * Minimum clamping progress for fading in all apps scrim
- */
- public static final float ALL_APPS_SCRIM_VISIBLE_THRESHOLD = .1f;
-
- /**
- * Maximum clamping progress for opaque all apps scrim
- */
- public static final float ALL_APPS_SCRIM_OPAQUE_THRESHOLD = .5f;
-
private final PortraitOverviewStateTouchHelper mOverviewPortraitStateTouchHelper;
public PortraitStatesTouchController(Launcher l) {
@@ -124,38 +101,15 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
return fromState;
}
- private StateAnimationConfig getNormalToAllAppsAnimation() {
- StateAnimationConfig builder = new StateAnimationConfig();
- builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(ACCEL,
- ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD,
- ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD));
- builder.setInterpolator(ANIM_SCRIM_FADE, Interpolators.clampToProgress(ACCEL,
- ALL_APPS_SCRIM_VISIBLE_THRESHOLD,
- ALL_APPS_SCRIM_OPAQUE_THRESHOLD));
- return builder;
- }
-
- private StateAnimationConfig getAllAppsToNormalAnimation() {
- StateAnimationConfig builder = new StateAnimationConfig();
- builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(DEACCEL,
- 1 - ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD,
- 1 - ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD));
- builder.setInterpolator(ANIM_SCRIM_FADE, Interpolators.clampToProgress(DEACCEL,
- 1 - ALL_APPS_SCRIM_OPAQUE_THRESHOLD,
- 1 - ALL_APPS_SCRIM_VISIBLE_THRESHOLD));
- return builder;
- }
-
@Override
protected StateAnimationConfig getConfigForStates(
LauncherState fromState, LauncherState toState) {
- final StateAnimationConfig config;
+ final StateAnimationConfig config = new StateAnimationConfig();
+ config.userControlled = true;
if (fromState == NORMAL && toState == ALL_APPS) {
- config = getNormalToAllAppsAnimation();
+ AllAppsSwipeController.applyNormalToAllAppsAnimConfig(mLauncher, config);
} else if (fromState == ALL_APPS && toState == NORMAL) {
- config = getAllAppsToNormalAnimation();
- } else {
- config = new StateAnimationConfig();
+ AllAppsSwipeController.applyAllAppsToNormalConfig(mLauncher, config);
}
return config;
}
@@ -221,13 +175,9 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
* @return true if the event is over the hotseat
*/
static boolean isTouchOverHotseat(Launcher launcher, MotionEvent ev) {
- return (ev.getY() >= getHotseatTop(launcher));
- }
-
- public static int getHotseatTop(Launcher launcher) {
DeviceProfile dp = launcher.getDeviceProfile();
int hotseatHeight = dp.hotseatBarSizePx + dp.getInsets().bottom;
- return launcher.getDragLayer().getHeight() - hotseatHeight;
+ return (ev.getY() >= (launcher.getDragLayer().getHeight() - hotseatHeight));
}
@Override
@@ -236,12 +186,17 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
case MotionEvent.ACTION_DOWN:
InteractionJankMonitorWrapper.begin(
mLauncher.getRootView(), InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS);
+ InteractionJankMonitorWrapper.begin(
+ mLauncher.getRootView(),
+ InteractionJankMonitorWrapper.CUJ_CLOSE_ALL_APPS_SWIPE);
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
InteractionJankMonitorWrapper.cancel(
InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS);
+ InteractionJankMonitorWrapper.cancel(
+ InteractionJankMonitorWrapper.CUJ_CLOSE_ALL_APPS_SWIPE);
break;
}
return super.onControllerInterceptTouchEvent(ev);
@@ -254,13 +209,20 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
if (newToState != ALL_APPS) {
InteractionJankMonitorWrapper.cancel(InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS);
}
+ if (newToState != NORMAL) {
+ InteractionJankMonitorWrapper.cancel(
+ InteractionJankMonitorWrapper.CUJ_CLOSE_ALL_APPS_SWIPE);
+ }
}
@Override
protected void onReachedFinalState(LauncherState toState) {
- super.onReinitToState(toState);
+ super.onReachedFinalState(toState);
if (toState == ALL_APPS) {
InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS);
+ } else if (toState == NORMAL) {
+ InteractionJankMonitorWrapper.end(
+ InteractionJankMonitorWrapper.CUJ_CLOSE_ALL_APPS_SWIPE);
}
}
@@ -268,5 +230,7 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
protected void clearState() {
super.clearState();
InteractionJankMonitorWrapper.cancel(InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS);
+ InteractionJankMonitorWrapper.cancel(
+ InteractionJankMonitorWrapper.CUJ_CLOSE_ALL_APPS_SWIPE);
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index 59c2859d12..f941b02065 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -16,7 +16,7 @@
package com.android.launcher3.uioverrides.touchcontrollers;
import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.QUICK_SWITCH;
+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;
@@ -29,7 +29,6 @@ import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TR
import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
-import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
@@ -37,7 +36,6 @@ import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHO
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
-import android.util.Log;
import android.view.MotionEvent;
import com.android.launcher3.Launcher;
@@ -46,10 +44,11 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
-import com.android.quickstep.SysUINavigationMode;
-import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.NavigationMode;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskUtils;
+import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -80,6 +79,10 @@ public class QuickSwitchTouchController extends AbstractStateChangeTouchControll
if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) == 0) {
return false;
}
+ if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+ // TODO(b/268075592): add support for quickswitch to/from desktop
+ return false;
+ }
return true;
}
@@ -89,7 +92,7 @@ public class QuickSwitchTouchController extends AbstractStateChangeTouchControll
if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) {
return NORMAL;
}
- return isDragTowardPositive ? QUICK_SWITCH : NORMAL;
+ return isDragTowardPositive ? QUICK_SWITCH_FROM_HOME : NORMAL;
}
@Override
@@ -112,9 +115,8 @@ public class QuickSwitchTouchController extends AbstractStateChangeTouchControll
// Set RecentView's initial properties for coming in from the side.
RECENTS_SCALE_PROPERTY.set(mOverviewPanel,
- QUICK_SWITCH.getOverviewScaleAndOffset(mLauncher)[0] * 0.85f);
+ QUICK_SWITCH_FROM_HOME.getOverviewScaleAndOffset(mLauncher)[0] * 0.85f);
ADJACENT_PAGE_HORIZONTAL_OFFSET.set(mOverviewPanel, 1f);
- Log.d(BAD_STATE, "QuickSwitchTouchController initCurrentAnimation setContentAlpha=1");
mOverviewPanel.setContentAlpha(1);
mCurrentAnimation = mLauncher.getStateManager()
@@ -128,7 +130,7 @@ 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);
- if (SysUINavigationMode.getMode(mLauncher) == Mode.NO_BUTTON) {
+ 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);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
index c14b6c4dfc..fd99b97004 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
@@ -38,8 +38,8 @@ import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.util.TouchController;
+import com.android.launcher3.util.VibratorWrapper;
import com.android.quickstep.SystemUiProxy;
-import com.android.quickstep.util.VibratorWrapper;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 308bca62e4..eddc50c64f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -38,11 +38,12 @@ import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.touch.BaseSwipeDetector;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
+import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.FlingBlockCheck;
import com.android.launcher3.util.TouchController;
+import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.SysUINavigationMode;
-import com.android.quickstep.util.VibratorWrapper;
+import com.android.quickstep.util.VibrationConstants;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -61,7 +62,7 @@ public abstract class TaskViewTouchController
Utilities.ATLEAST_R ? VibrationEffect.Composition.PRIMITIVE_TICK : -1;
public static final float TASK_DISMISS_VIBRATION_PRIMITIVE_SCALE = 1f;
public static final VibrationEffect TASK_DISMISS_VIBRATION_FALLBACK =
- VibratorWrapper.EFFECT_TEXTURE_TICK;
+ VibrationConstants.EFFECT_TEXTURE_TICK;
protected final T mActivity;
private final SingleAxisSwipeDetector mDetector;
@@ -177,7 +178,7 @@ public abstract class TaskViewTouchController
// - It's the focused task if in grid view
// - The task is snapped
mAllowGoingDown = i == mRecentsView.getCurrentPage()
- && SysUINavigationMode.getMode(mActivity).hasGestures
+ && DisplayController.getNavigationMode(mActivity).hasGestures
&& (!mRecentsView.showAsGrid() || mTaskBeingDragged.isFocusedTask())
&& mRecentsView.isTaskInExpectedScrollPosition(i);
@@ -236,7 +237,8 @@ public abstract class TaskViewTouchController
PendingAnimation pa;
if (goingUp) {
currentInterpolator = Interpolators.LINEAR;
- pa = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged,
+ pa = new PendingAnimation(maxDuration);
+ mRecentsView.createTaskDismissAnimation(pa, mTaskBeingDragged,
true /* animateTaskView */, true /* removeTask */, maxDuration,
false /* dismissingForSplitSelection*/);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java
index e2747dfcbb..9f2c1d479f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java
@@ -24,13 +24,11 @@ import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import android.animation.ValueAnimator;
import android.os.SystemClock;
-import android.util.Log;
import android.view.MotionEvent;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.quickstep.SystemUiProxy;
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index ee790b23fb..9f56760be4 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -15,6 +15,7 @@
*/
package com.android.quickstep;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
@@ -22,20 +23,22 @@ import static android.widget.Toast.LENGTH_SHORT;
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.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.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
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.util.DisplayController.getSingleFrameMs;
+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;
+import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
+import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
@@ -45,10 +48,12 @@ import static com.android.quickstep.GestureState.STATE_END_TARGET_SET;
import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_CANCELED;
import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
-import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.CANCEL_RECENTS_ANIMATION;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.EXPECTING_TASK_APPEARED;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.LAUNCHER_DESTROYED;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_SETTLED_ON_END_TARGET;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -57,8 +62,10 @@ import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.WindowConfiguration;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Resources;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -66,9 +73,12 @@ import android.graphics.RectF;
import android.os.Build;
import android.os.IBinder;
import android.os.SystemClock;
+import android.util.Log;
import android.view.MotionEvent;
+import android.view.RemoteAnimationTarget;
import android.view.View;
import android.view.View.OnApplyWindowInsetsListener;
+import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnDrawListener;
import android.view.ViewTreeObserver.OnScrollChangedListener;
import android.view.WindowInsets;
@@ -79,54 +89,62 @@ import android.window.PictureInPictureSurfaceTransaction;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
+import com.android.internal.util.LatencyTracker;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
+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.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.taskbar.TaskbarUIController;
import com.android.launcher3.tracing.InputConsumerProto;
import com.android.launcher3.tracing.SwipeHandlerProto;
import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
+import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.TraceHelper;
+import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.util.WindowBounds;
import com.android.quickstep.BaseActivityInterface.AnimationFactory;
import com.android.quickstep.GestureState.GestureEndTarget;
import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
+import com.android.quickstep.util.ActiveGestureErrorDetector;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.InputConsumerProxy;
import com.android.quickstep.util.InputProxyHandlerFactory;
-import com.android.quickstep.util.LauncherSplitScreenListener;
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;
+import com.android.quickstep.util.SurfaceTransaction;
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.SwipePipToHomeAnimator;
import com.android.quickstep.util.TaskViewSimulator;
-import com.android.quickstep.util.VibratorWrapper;
+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.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
-import com.android.systemui.shared.system.LatencyTrackerCompat;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.startingsurface.SplashScreenExitAnimationUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.Optional;
import java.util.function.Consumer;
import app.lawnchair.util.CompatibilityKt;
@@ -135,13 +153,12 @@ import app.lawnchair.util.CompatibilityKt;
* Handles the navigation gestures when Launcher is the default home activity.
*/
@TargetApi(Build.VERSION_CODES.R)
-public abstract class AbsSwipeUpHandler,
- Q extends RecentsView, S extends BaseState>
+public abstract class AbsSwipeUpHandler, Q extends RecentsView, S extends BaseState>
extends SwipeUpAnimationLogic implements OnApplyWindowInsetsListener,
RecentsAnimationCallbacks.RecentsAnimationListener {
private static final String TAG = "AbsSwipeUpHandler";
- private static final String[] STATE_NAMES = DEBUG_STATES ? new String[17] : null;
+ private static final ArrayList STATE_NAMES = new ArrayList<>();
protected final BaseActivityInterface mActivityInterface;
protected final InputConsumerProxy mInputConsumerProxy;
@@ -150,8 +167,10 @@ public abstract class AbsSwipeUpHandler,
private final ArrayList