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);
@@ -58,7 +86,10 @@ public class TaskbarOverlayDragLayer extends
@Override
public void recreateControllers() {
- mControllers = new TouchController[]{mActivity.getDragController()};
+ mControllers = mOnClickListeners.isEmpty()
+ ? new TouchController[]{mActivity.getDragController()}
+ : new TouchController[] {
+ mActivity.getDragController(), mClickListenerTouchController};
}
@Override
@@ -71,7 +102,8 @@ public class TaskbarOverlayDragLayer extends
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getAction() == ACTION_UP && event.getKeyCode() == KEYCODE_BACK) {
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
- if (topView != null && topView.onBackPressed()) {
+ if (topView != null && topView.canHandleBack()) {
+ topView.onBackInvoked();
return true;
}
}
@@ -97,6 +129,51 @@ public class TaskbarOverlayDragLayer extends
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
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 8fb70300f6..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;
@@ -167,12 +168,12 @@ public abstract class BaseRecentsViewStateController
LauncherState fromState = mLauncher.getStateManager().getState();
setter.setFloat(mRecentsView, TASK_THUMBNAIL_SPLASH_ALPHA,
toState.showTaskThumbnailSplash() ? 1f : 0f,
- !toState.showTaskThumbnailSplash() && fromState == LauncherState.QUICK_SWITCH
+ !toState.showTaskThumbnailSplash() && fromState == QUICK_SWITCH_FROM_HOME
? LINEAR : INSTANT);
boolean showAsGrid = toState.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile());
Interpolator gridProgressInterpolator = showAsGrid
- ? fromState == LauncherState.QUICK_SWITCH ? LINEAR : INSTANT
+ ? fromState == QUICK_SWITCH_FROM_HOME ? LINEAR : INSTANT
: FINAL_FRAME;
setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS, showAsGrid ? 1f : 0f,
gridProgressInterpolator);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java b/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java
deleted file mode 100644
index c46809afc6..0000000000
--- a/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.uioverrides;
-
-import android.annotation.TargetApi;
-import android.os.Build;
-import android.provider.DeviceConfig;
-
-import com.android.launcher3.config.FeatureFlags.DebugFlag;
-
-@TargetApi(Build.VERSION_CODES.P)
-public class DeviceFlag extends DebugFlag {
-
- public static final String NAMESPACE_LAUNCHER = "launcher";
-
- private final boolean mDefaultValueInCode;
-
- public DeviceFlag(String key, boolean defaultValue, String description) {
- super(key, getDeviceValue(key, defaultValue), description);
- mDefaultValueInCode = defaultValue;
- }
-
- @Override
- protected StringBuilder appendProps(StringBuilder src) {
- return super.appendProps(src).append(", mDefaultValueInCode=").append(mDefaultValueInCode);
- }
-
- @Override
- public boolean get() {
- // Override this method in order to let Robolectric ShadowDeviceFlag to stub it.
- return super.get();
- }
-
- protected static boolean getDeviceValue(String key, boolean defaultValue) {
- return DeviceConfig.getBoolean(NAMESPACE_LAUNCHER, key, defaultValue);
- }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index bf0f8f72e9..3990dade70 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -17,6 +17,7 @@ package com.android.launcher3.uioverrides;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED;
+import static com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -54,6 +55,7 @@ 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;
@@ -124,7 +126,7 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
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
@@ -360,6 +362,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/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 9be569e895..243ed5c2ee 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -15,8 +15,13 @@
*/
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;
@@ -30,10 +35,14 @@ import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_WIDGET_PICKER_DEPTH;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE;
+import static com.android.launcher3.config.FeatureFlags.RECEIVE_UNFOLD_EVENTS_FROM_SYSUI;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition;
+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;
@@ -43,10 +52,13 @@ import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_O
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.THREAD_POOL_EXECUTOR;
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;
@@ -56,29 +68,37 @@ 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.util.Log;
+import android.os.Trace;
import android.view.Display;
import android.view.HapticFeedbackConstants;
import android.view.RemoteAnimationTarget;
import android.view.View;
-import android.view.WindowManagerGlobal;
+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.ViewCapture;
+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;
@@ -86,6 +106,7 @@ 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;
@@ -105,6 +126,8 @@ 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;
@@ -117,6 +140,7 @@ import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchControlle
import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarTouchController;
import com.android.launcher3.util.ActivityOptionsWrapper;
+import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.NavigationMode;
@@ -124,7 +148,9 @@ 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.widget.LauncherWidgetHolder;
import com.android.quickstep.OverviewCommandHelper;
@@ -132,6 +158,7 @@ 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;
@@ -142,15 +169,18 @@ 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;
@@ -158,6 +188,8 @@ 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;
@@ -166,7 +198,7 @@ import java.util.stream.Stream;
public class QuickstepLauncher extends Launcher {
public static final boolean ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
- SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", false);
+ SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", true);
public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
@@ -183,7 +215,6 @@ public class QuickstepLauncher extends Launcher {
// Will be updated when dragging from taskbar.
private @Nullable DragOptions mNextWorkspaceDragOptions = null;
private @Nullable UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
- private @Nullable RotationChangeProvider mRotationChangeProvider;
private @Nullable LauncherUnfoldAnimationController mLauncherUnfoldAnimationController;
private SplitSelectStateController mSplitSelectStateController;
@@ -208,7 +239,8 @@ public class QuickstepLauncher extends Launcher {
RecentsView overviewPanel = getOverviewPanel();
mSplitSelectStateController =
new SplitSelectStateController(this, mHandler, getStateManager(),
- getDepthController(), getStatsLogManager());
+ getDepthController(), getStatsLogManager(),
+ SystemUiProxy.INSTANCE.get(this), RecentsModel.INSTANCE.get(this));
overviewPanel.init(mActionsView, mSplitSelectStateController);
mSplitWithKeyboardShortcutController = new SplitWithKeyboardShortcutController(this,
mSplitSelectStateController);
@@ -217,7 +249,7 @@ public class QuickstepLauncher extends Launcher {
mActionsView.updateDimension(getDeviceProfile(), overviewPanel.getLastComputedTaskSize());
mActionsView.updateVerticalMargin(DisplayController.getNavigationMode(this));
- mAppTransitionManager = new QuickstepTransitionManager(this);
+ mAppTransitionManager = buildAppTransitionManager();
mAppTransitionManager.registerRemoteAnimations();
mAppTransitionManager.registerRemoteTransitions();
@@ -226,8 +258,7 @@ public class QuickstepLauncher extends Launcher {
mDesktopVisibilityController = new DesktopVisibilityController(this);
mHotseatPredictionController = new HotseatPredictionController(this);
- mEnableWidgetDepth = ENABLE_WIDGET_PICKER_DEPTH.get()
- && SystemProperties.getBoolean("ro.launcher.depth.widget", true);
+ mEnableWidgetDepth = SystemProperties.getBoolean("ro.launcher.depth.widget", true);
getWorkspace().addOverlayCallback(progress ->
onTaskbarInAppDisplayProgressUpdate(progress, MINUS_ONE_PAGE_PROGRESS_INDEX));
}
@@ -286,6 +317,19 @@ public class QuickstepLauncher extends Launcher {
return mHotseatPredictionController;
}
+ @Override
+ 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);
@@ -351,22 +395,30 @@ public class QuickstepLauncher extends Launcher {
@Override
public Stream getSupportedShortcuts() {
- Stream base = Stream.of(WellbeingModel.SHORTCUT_FACTORY);
- if (ENABLE_SPLIT_FROM_WORKSPACE.get() && mDeviceProfile.isTablet) {
- RecentsView recentsView = getOverviewPanel();
- // TODO: 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));
- }
- base = Stream.concat(base, splitShortcuts.stream());
+ // 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();
}
- return Stream.concat(
- Stream.of(mHotseatPredictionController),
- Stream.concat(base, super.getSupportedShortcuts()));
+ 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;
}
/**
@@ -374,15 +426,18 @@ public class QuickstepLauncher extends Launcher {
*/
private void onStateOrResumeChanging(boolean inTransition) {
LauncherState state = getStateManager().getState();
- if (!ENABLE_PIP_KEEP_CLEAR_ALGORITHM) {
- boolean started = ((getActivityFlags() & ACTIVITY_STATE_STARTED)) != 0;
- if (started) {
- DeviceProfile profile = getDeviceProfile();
- boolean willUserBeActive =
- (getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0;
- boolean visible = (state == NORMAL || state == OVERVIEW)
- && (willUserBeActive || isUserActive())
- && !profile.isVerticalBarLayout();
+ boolean started = ((getActivityFlags() & ACTIVITY_STATE_STARTED)) != 0;
+ if (started) {
+ DeviceProfile profile = getDeviceProfile();
+ boolean willUserBeActive =
+ (getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0;
+ boolean visible = (state == NORMAL || state == OVERVIEW)
+ && (willUserBeActive || isUserActive())
+ && !profile.isVerticalBarLayout();
+ if (ENABLE_PIP_KEEP_CLEAR_ALGORITHM) {
+ SystemUiProxy.INSTANCE.get(this)
+ .setLauncherKeepClearAreaHeight(visible, profile.hotseatBarSizePx);
+ } else {
SystemUiProxy.INSTANCE.get(this).setShelfHeight(visible, profile.hotseatBarSizePx);
}
}
@@ -535,10 +590,71 @@ public class QuickstepLauncher extends Launcher {
addMultiWindowModeChangedListener(mDepthController);
initUnfoldTransitionProgressProvider();
if (FeatureFlags.CONTINUOUS_VIEW_TREE_CAPTURE.get()) {
- mViewCapture = ViewCapture.getInstance().startCapture(getWindow());
+ 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();
@@ -601,10 +717,12 @@ public class QuickstepLauncher extends Launcher {
}
@Override
- protected void onScreenOff() {
- super.onScreenOff();
- RecentsView recentsView = getOverviewPanel();
- recentsView.finishRecentsAnimation(true /* toRecents */, null);
+ protected void onScreenOnChanged(boolean isOn) {
+ super.onScreenOnChanged(isOn);
+ if (!isOn) {
+ RecentsView recentsView = getOverviewPanel();
+ recentsView.finishRecentsAnimation(true /* toRecents */, null);
+ }
}
@Override
@@ -623,6 +741,58 @@ public class QuickstepLauncher extends Launcher {
}
}
+ @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
@@ -666,9 +836,10 @@ public class QuickstepLauncher extends Launcher {
@Override
public void setResumed() {
- if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
+ if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
DesktopVisibilityController controller = mDesktopVisibilityController;
- if (controller != null && controller.areFreeformTasksVisible()) {
+ 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
@@ -709,39 +880,80 @@ public class QuickstepLauncher extends Launcher {
private void initUnfoldTransitionProgressProvider() {
final UnfoldTransitionConfig config = new ResourceUnfoldTransitionConfig();
if (config.isEnabled()) {
- 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= */ THREAD_POOL_EXECUTOR,
- /* tracingTagPrefix= */ "launcher",
- WindowManagerGlobal.getWindowManagerService()
- );
+ if (RECEIVE_UNFOLD_EVENTS_FROM_SYSUI.get()) {
+ initRemotelyCalculatedUnfoldAnimation(config);
+ } else {
+ initLocallyCalculatedUnfoldAnimation(config);
+ }
- mUnfoldTransitionProgressProvider = unfoldComponent.getUnfoldTransitionProvider()
- .orElseThrow(() -> new IllegalStateException(
- "Trying to create UnfoldTransitionProgressProvider when the "
- + "transition is disabled"));
-
- mRotationChangeProvider = unfoldComponent.getRotationChangeProvider();
- mLauncherUnfoldAnimationController = new LauncherUnfoldAnimationController(
- /* launcher= */ this,
- getWindowManager(),
- mUnfoldTransitionProgressProvider,
- mRotationChangeProvider
- );
- Log.d("b/261320823", "initUnfoldTransitionProgressProvider completed");
}
}
+ /** 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;
}
@@ -863,7 +1075,13 @@ public class QuickstepLauncher extends Launcher {
activityOptions.options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_LAUNCHER,
mLastTouchUpTime);
}
- activityOptions.options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
+ 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);
@@ -963,14 +1181,12 @@ public class QuickstepLauncher extends Launcher {
// 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)) {
- SplitSelectStateController splitSelectStateController =
- ((RecentsView) getOverviewPanel()).getSplitSelectController();
// Launcher will restart in Overview and then transition to OverviewSplitSelect.
outState.putIBinder(PENDING_SPLIT_SELECT_INFO, ObjectWrapper.wrap(
new PendingSplitSelectInfo(
- splitSelectStateController.getInitialTaskId(),
- splitSelectStateController.getActiveSplitStagePosition(),
- splitSelectStateController.getSplitEvent())
+ mSplitSelectStateController.getInitialTaskId(),
+ mSplitSelectStateController.getActiveSplitStagePosition(),
+ mSplitSelectStateController.getSplitEvent())
));
outState.putInt(RUNTIME_STATE, OVERVIEW.ordinal);
}
@@ -1001,6 +1217,57 @@ public class QuickstepLauncher extends Launcher {
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 {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
index a8edd511f6..b318100205 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
@@ -22,6 +22,7 @@ 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;
@@ -32,43 +33,57 @@ import androidx.annotation.WorkerThread;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.WidgetsModel;
-import com.android.launcher3.util.Thunk;
+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.Map;
+import java.util.Set;
import java.util.WeakHashMap;
-import java.util.function.Consumer;
+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<>();
- @Thunk
- QuickstepWidgetHolder(@NonNull Context context,
+ private QuickstepWidgetHolder(@NonNull Context context,
@Nullable IntConsumer appWidgetRemovedCallback,
@Nullable RemoteViews.InteractionHandler interactionHandler) {
super(context, appWidgetRemovedCallback);
mAppWidgetRemovedCallback = appWidgetRemovedCallback != null ? appWidgetRemovedCallback
: i -> {};
mInteractionHandler = interactionHandler;
- sHolders.add(this);
+ MAIN_EXECUTOR.execute(() -> sHolders.add(this));
}
@Override
@@ -81,7 +96,7 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
sHolders.forEach(h -> h.mAppWidgetRemovedCallback.accept(i))),
() -> MAIN_EXECUTOR.execute(() ->
sHolders.forEach(h -> h.mProviderChangedListeners.forEach(
- ProviderChangedListener::notifyWidgetProvidersChanged))),
+ ProviderChangedListener::notifyWidgetProvidersChanged))),
UI_HELPER_EXECUTOR.getLooper());
if (!WidgetsModel.GO_DISABLE_WIDGETS) {
sWidgetHost.startListening();
@@ -90,6 +105,62 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
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
@@ -97,6 +168,7 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
@Override
public void deleteAppWidgetId(int appWidgetId) {
super.deleteAppWidgetId(appWidgetId);
+ mViews.remove(appWidgetId);
sListeners.remove(appWidgetId);
}
@@ -105,7 +177,17 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
*/
@Override
public void destroy() {
- sHolders.remove(this);
+ 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);
}
/**
@@ -160,15 +242,16 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
}
widgetView.setInteractionHandler(mInteractionHandler);
widgetView.setAppWidget(appWidgetId, appWidget);
+ mViews.put(appWidgetId, widgetView);
QuickstepWidgetHolderListener listener = sListeners.get(appWidgetId);
if (listener == null) {
- listener = new QuickstepWidgetHolderListener(this, widgetView);
+ listener = new QuickstepWidgetHolderListener(appWidgetId);
sWidgetHost.setListener(appWidgetId, listener);
sListeners.put(appWidgetId, listener);
- } else {
- listener.resetView(this, widgetView);
}
+ RemoteViews remoteViews = listener.addHolder(this);
+ widgetView.updateAppWidget(remoteViews);
return widgetView;
}
@@ -178,53 +261,57 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
*/
@Override
public void clearViews() {
+ mViews.clear();
for (int i = sListeners.size() - 1; i >= 0; i--) {
- sListeners.valueAt(i).mView.remove(this);
+ sListeners.valueAt(i).mListeningHolders.remove(this);
}
}
private static class QuickstepWidgetHolderListener
implements AppWidgetHost.AppWidgetHostListener {
- @NonNull
- private final Map mView = new WeakHashMap<>();
- @Nullable
- private RemoteViews mRemoteViews = null;
+ // Static listeners should use a set that is backed by WeakHashMap to avoid memory leak
+ private final Set mListeningHolders = Collections.newSetFromMap(
+ new WeakHashMap<>());
- QuickstepWidgetHolderListener(@NonNull QuickstepWidgetHolder holder,
- @NonNull LauncherAppWidgetHostView view) {
- mView.put(holder, view);
+ private final int mWidgetId;
+
+ private @Nullable RemoteViews mRemoteViews;
+
+ QuickstepWidgetHolderListener(int widgetId) {
+ mWidgetId = widgetId;
}
@UiThread
- public void resetView(@NonNull QuickstepWidgetHolder holder,
- @NonNull AppWidgetHostView view) {
- mView.put(holder, view);
- view.updateAppWidget(mRemoteViews);
+ @Nullable
+ public RemoteViews addHolder(@NonNull QuickstepWidgetHolder holder) {
+ mListeningHolders.add(holder);
+ return mRemoteViews;
}
@Override
@WorkerThread
public void onUpdateProviderInfo(@Nullable AppWidgetProviderInfo info) {
mRemoteViews = null;
- executeOnMainExecutor(v -> v.onUpdateProviderInfo(info));
+ executeOnMainExecutor(KEY_PROVIDER_UPDATE, info);
}
@Override
@WorkerThread
public void updateAppWidget(@Nullable RemoteViews views) {
mRemoteViews = views;
- executeOnMainExecutor(v -> v.updateAppWidget(mRemoteViews));
+ executeOnMainExecutor(KEY_VIEWS_UPDATE, mRemoteViews);
}
@Override
@WorkerThread
public void onViewDataChanged(int viewId) {
- executeOnMainExecutor(v -> v.onViewDataChanged(viewId));
+ executeOnMainExecutor(KEY_VIEW_DATA_CHANGED, viewId);
}
- private void executeOnMainExecutor(Consumer consumer) {
- MAIN_EXECUTOR.execute(() -> mView.values().forEach(consumer));
+ private void executeOnMainExecutor(UpdateKey key, T data) {
+ MAIN_EXECUTOR.execute(() -> mListeningHolders.forEach(holder ->
+ holder.onWidgetUpdate(mWidgetId, key, data)));
}
}
@@ -267,4 +354,12 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
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 e8e83288cd..f16b43df5e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -91,7 +91,7 @@ public final class RecentsViewStateController extends
builder.addOnFrameCallback(() -> mRecentsView.loadVisibleTaskData(FLAG_UPDATE_ALL));
mRecentsView.updateEmptyMessage();
// TODO(b/246283207): Remove logging once root cause of flake detected.
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ if (Utilities.isRunningInTestHarness()) {
Log.d("b/246283207", "RecentsView#setStateWithAnimationInternal getCurrentPage(): "
+ mRecentsView.getCurrentPage()
+ ", getScrollForPage(getCurrentPage())): "
@@ -121,8 +121,7 @@ public final class RecentsViewStateController extends
private void handleSplitSelectionState(@NonNull LauncherState toState,
@NonNull PendingAnimation builder, boolean animate) {
if (toState != OVERVIEW_SPLIT_SELECT) {
- // Not going to split, nothing to do but ensure taskviews are at correct offset
- mRecentsView.resetSplitPrimaryScrollOffset();
+ // Not going to split
return;
}
@@ -153,8 +152,6 @@ public final class RecentsViewStateController extends
as.start();
as.end();
}
-
- mRecentsView.applySplitPrimaryScrollOffset();
}
private void setAlphas(PropertySetter propertySetter, StateAnimationConfig config,
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 88%
rename from src/com/android/launcher3/settings/DeveloperOptionsFragment.java
rename to quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsFragment.java
index c81214e67b..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;
@@ -62,10 +63,10 @@ import androidx.preference.SwitchPreference;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
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;
@@ -83,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;
@@ -97,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);
@@ -185,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) {
@@ -301,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);
@@ -320,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);
@@ -331,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);
@@ -342,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);
@@ -353,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);
@@ -364,9 +367,9 @@ 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);
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderHolder.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DeviceFlag.java
similarity index 50%
rename from src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderHolder.java
rename to quickstep/src/com/android/launcher3/uioverrides/flags/DeviceFlag.java
index 9562af3d50..3900ebb549 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderHolder.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DeviceFlag.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -13,20 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.launcher3.widget.picker;
-import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+package com.android.launcher3.uioverrides.flags;
-/**
- * A {@link ViewHolder} for {@link WidgetsListHeader} of an app, which renders the app icon, the app
- * name, label and a button for showing / hiding widgets.
- */
-public final class WidgetsListSearchHeaderHolder extends ViewHolder {
- final WidgetsListHeader mWidgetsListHeader;
+class DeviceFlag extends DebugFlag {
- public WidgetsListSearchHeaderHolder(WidgetsListHeader view) {
- super(view);
+ private final boolean mDefaultValueInCode;
- mWidgetsListHeader = view;
+ 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 77%
rename from src/com/android/launcher3/config/FlagTogglerPrefUi.java
rename to quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java
index 2eb6e6df9a..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,31 +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 prefs = mContext.getSharedPreferences(
- FLAGS_PREF_NAME, Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = prefs.edit();
- // We keep the key in the prefs even if it has the default value, because it's a
- // signal that it has been changed at one point.
- if (!prefs.contains(key) && value == flag.defaultValue) {
- editor.remove(key).apply();
- flag.mHasBeenChangedAtLeastOnce = false;
- } else {
- editor.putBoolean(key, value).apply();
- flag.mHasBeenChangedAtLeastOnce = true;
- }
- 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;
@@ -89,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);
@@ -149,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..888cc9d63a
--- /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, defaultValue);
+ DebugFlag flag = new DeviceFlag(key, description, defaultValue, currentValue,
+ defaultValueInCode);
+ sDebugFlags.add(flag);
+ return flag;
+ } else {
+ return new BooleanFlag(defaultValue);
+ }
+ }
+
+ /**
+ * 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/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 733c6a8b48..e5787209d2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -24,10 +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.launcher3.config.FeatureFlags;
import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.RecentsView;
/**
@@ -91,15 +90,17 @@ public class BackgroundAppState extends OverviewState {
@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 && !FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get()) {
- return launcher.getColor(R.color.taskbar_background);
- }
return Color.TRANSPARENT;
}
@@ -109,6 +110,18 @@ public class BackgroundAppState extends OverviewState {
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 0c49e5fc69..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;
/**
@@ -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 d075750330..214679acbe 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -26,7 +26,6 @@ import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.Themes;
import com.android.quickstep.util.LayoutUtils;
@@ -76,9 +75,17 @@ public class OverviewState extends LauncherState {
@Override
public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
RecentsView recentsView = launcher.getOverviewPanel();
- float workspacePageHeight = launcher.getDeviceProfile().getCellLayoutHeight();
recentsView.getTaskSize(sTempRect);
- float scale = (float) sTempRect.height() / workspacePageHeight;
+ 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);
}
@@ -103,14 +110,9 @@ public class OverviewState extends LauncherState {
return CLEAR_ALL_BUTTON | OVERVIEW_ACTIONS;
}
- @Override
- public boolean isTaskbarStashed(Launcher launcher) {
- return !FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get();
- }
-
@Override
public boolean isTaskbarAlignedWithHotseat(Launcher launcher) {
- return !FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get();
+ return false;
}
@Override
@@ -123,6 +125,18 @@ public class OverviewState extends LauncherState {
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
public String getDescription(Launcher launcher) {
return launcher.getString(R.string.accessibility_recent_apps);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
index 969abc2804..739246992c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
@@ -17,10 +17,13 @@ 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 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
@@ -43,6 +46,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 5eeeb36981..c7cd39c100 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -23,6 +23,7 @@ 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.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;
@@ -31,6 +32,7 @@ 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;
@@ -102,7 +104,10 @@ public class QuickstepAtomicAnimationFactory extends
}
config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, clampToProgress(LINEAR, 0, 0.25f));
- config.setInterpolator(ANIM_SCRIM_FADE, clampToProgress(LINEAR, 0.33f, 1));
+ 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);
@@ -111,7 +116,10 @@ public class QuickstepAtomicAnimationFactory extends
// 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, EMPHASIZED_DECELERATE);
+ config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X,
+ 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.
@@ -119,6 +127,12 @@ public class QuickstepAtomicAnimationFactory extends
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);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
index 8babd3470b..3ae221bbeb 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
@@ -53,4 +53,9 @@ public class SplitScreenSelectState extends OverviewState {
return SplitAnimationTimings.ABORT_DURATION;
}
}
+
+ @Override
+ public boolean shouldPreserveDataStateOnReapply() {
+ return true;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index b5afda388a..b7bafd8969 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -39,6 +39,7 @@ 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;
@@ -62,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;
@@ -82,6 +84,7 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
mRecentsView = l.getOverviewPanel();
mMotionPauseDetector = new MotionPauseDetector(l);
mMotionPauseMinDisplacement = ViewConfiguration.get(l).getScaledTouchSlop();
+ mVibratorWrapper = VibratorWrapper.INSTANCE.get(l.getApplicationContext());
}
@Override
@@ -188,6 +191,11 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
// need to manually set the duration to a reasonable value.
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();
+ }
}
private void onMotionPauseDetected() {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index e0cb0b4ddb..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;
@@ -76,6 +76,7 @@ import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.WorkspaceRevealAnim;
+import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.LauncherRecentsView;
import com.android.quickstep.views.RecentsView;
@@ -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,7 +225,7 @@ 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.
@@ -243,7 +248,7 @@ 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);
}
@@ -335,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.
@@ -414,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(
@@ -437,7 +442,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
.withSrcState(LAUNCHER_STATE_HOME)
.withDstState(targetState.statsLogOrdinal)
.log(getLauncherAtomEvent(mStartState.statsLogOrdinal, targetState.statsLogOrdinal,
- targetState == QUICK_SWITCH
+ targetState == QUICK_SWITCH_FROM_HOME
? LAUNCHER_QUICKSWITCH_RIGHT
: targetState.ordinal > mStartState.ordinal
? LAUNCHER_UNKNOWN_SWIPEUP
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index 56ac4c5b3a..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;
@@ -48,6 +48,7 @@ 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;
@@ -78,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;
}
@@ -87,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
@@ -110,7 +115,7 @@ 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);
mOverviewPanel.setContentAlpha(1);
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 8409475e4c..f3fb259aa5 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -27,7 +27,6 @@ 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_TASKBAR_REVISED_THRESHOLDS;
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;
@@ -51,6 +50,7 @@ import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHE
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
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;
@@ -127,6 +127,7 @@ 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.views.DesktopTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
@@ -181,6 +182,7 @@ public abstract class AbsSwipeUpHandler,
if (mActivity != activity) {
return;
}
+ ActiveGestureLog.INSTANCE.addLog("Launcher destroyed", LAUNCHER_DESTROYED);
mRecentsView = null;
mActivity = null;
}
@@ -276,8 +278,6 @@ public abstract class AbsSwipeUpHandler,
private RunningWindowAnim[] mRunningWindowAnim;
// Possible second animation running at the same time as mRunningWindowAnim
private Animator mParallelRunningAnim;
- // Current running divider animation
- private ValueAnimator mDividerAnimator;
private boolean mIsMotionPaused;
private boolean mHasMotionEverBeenPaused;
@@ -323,6 +323,8 @@ public abstract class AbsSwipeUpHandler,
private final boolean mIsTransientTaskbar;
// May be set to false when mIsTransientTaskbar is true.
private boolean mCanSlowSwipeGoHome = true;
+ // Indicates whether the divider is shown, only used when split screen is activated.
+ private boolean mIsDividerShown = true;
@Nullable
private RemoteAnimationTargets.ReleaseCheck mSwipePipToHomeReleaseCheck = null;
@@ -364,14 +366,12 @@ public abstract class AbsSwipeUpHandler,
TaskbarUIController controller = mActivityInterface.getTaskbarController();
mTaskbarAlreadyOpen = controller != null && !controller.isTaskbarStashed();
mIsTaskbarAllAppsOpen = controller != null && controller.isTaskbarAllAppsOpen();
- mTaskbarAppWindowThreshold = res
- .getDimensionPixelSize(ENABLE_TASKBAR_REVISED_THRESHOLDS.get()
- ? R.dimen.taskbar_app_window_threshold_v2
- : R.dimen.taskbar_app_window_threshold);
- mTaskbarHomeOverviewThreshold = res.getDimensionPixelSize(
- ENABLE_TASKBAR_REVISED_THRESHOLDS.get()
- ? R.dimen.taskbar_home_overview_threshold_v2
- : R.dimen.taskbar_home_overview_threshold);
+ mTaskbarAppWindowThreshold =
+ res.getDimensionPixelSize(R.dimen.taskbar_app_window_threshold);
+ boolean swipeWillNotShowTaskbar = mTaskbarAlreadyOpen;
+ mTaskbarHomeOverviewThreshold = swipeWillNotShowTaskbar
+ ? 0
+ : res.getDimensionPixelSize(R.dimen.taskbar_home_overview_threshold);
mTaskbarCatchUpThreshold = res.getDimensionPixelSize(R.dimen.taskbar_catch_up_threshold);
}
@@ -676,7 +676,7 @@ public abstract class AbsSwipeUpHandler,
@Override
public void onMotionPauseDetected() {
mHasMotionEverBeenPaused = true;
- maybeUpdateRecentsAttachedState(true/* animate */, true/* moveFocusedTask */);
+ maybeUpdateRecentsAttachedState(true/* animate */, true/* moveRunningTask */);
Optional.ofNullable(mActivityInterface.getTaskbarController())
.ifPresent(TaskbarUIController::startTranslationSpring);
performHapticFeedback();
@@ -694,7 +694,7 @@ public abstract class AbsSwipeUpHandler,
}
private void maybeUpdateRecentsAttachedState(boolean animate) {
- maybeUpdateRecentsAttachedState(animate, false /* moveFocusedTask */);
+ maybeUpdateRecentsAttachedState(animate, false /* moveRunningTask */);
}
/**
@@ -704,9 +704,9 @@ public abstract class AbsSwipeUpHandler,
*
* Note this method has no effect unless the navigation mode is NO_BUTTON.
* @param animate whether to animate when attaching RecentsView
- * @param moveFocusedTask whether to move focused task to front when attaching
+ * @param moveRunningTask whether to move running task to front when attaching
*/
- private void maybeUpdateRecentsAttachedState(boolean animate, boolean moveFocusedTask) {
+ private void maybeUpdateRecentsAttachedState(boolean animate, boolean moveRunningTask) {
if (!mDeviceState.isFullyGesturalNavMode() || mRecentsView == null) {
return;
}
@@ -725,11 +725,11 @@ public abstract class AbsSwipeUpHandler,
} else {
recentsAttachedToAppWindow = mHasMotionEverBeenPaused || mIsLikelyToStartNewTask;
}
- if (moveFocusedTask && !mAnimationFactory.hasRecentsEverAttachedToAppWindow()
+ if (moveRunningTask && !mAnimationFactory.hasRecentsEverAttachedToAppWindow()
&& recentsAttachedToAppWindow) {
- // Only move focused task if RecentsView has never been attached before, to avoid
+ // Only move running task if RecentsView has never been attached before, to avoid
// TaskView jumping to new position as we move the tasks.
- mRecentsView.moveFocusedTaskToFront();
+ mRecentsView.moveRunningTaskToFront();
}
mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
@@ -869,7 +869,11 @@ public abstract class AbsSwipeUpHandler,
public void onRecentsAnimationStart(RecentsAnimationController controller,
RecentsAnimationTargets targets) {
super.onRecentsAnimationStart(controller, targets);
- mRemoteTargetHandles = mTargetGluer.assignTargetsForSplitScreen(mContext, targets);
+ if (DesktopTaskView.DESKTOP_MODE_SUPPORTED && targets.hasDesktopTasks()) {
+ mRemoteTargetHandles = mTargetGluer.assignTargetsForDesktop(targets);
+ } else {
+ mRemoteTargetHandles = mTargetGluer.assignTargetsForSplitScreen(targets);
+ }
mRecentsAnimationController = controller;
mRecentsAnimationTargets = targets;
mSwipePipToHomeReleaseCheck = new RemoteAnimationTargets.ReleaseCheck();
@@ -1100,9 +1104,8 @@ public abstract class AbsSwipeUpHandler,
} else {
mStateCallback.setState(STATE_RESUME_LAST_TASK);
}
- if (mRecentsAnimationTargets != null) {
- setDividerShown(true /* shown */, true /* immediate */);
- }
+ // Restore the divider as it resumes the last top-tasks.
+ setDividerShown(true);
break;
}
ActiveGestureLog.INSTANCE.addLog(
@@ -1157,6 +1160,11 @@ public abstract class AbsSwipeUpHandler,
return LAST_TASK;
}
+ if (DesktopTaskView.DESKTOP_MODE_SUPPORTED && endTarget == NEW_TASK) {
+ // TODO(b/268075592): add support for quickswitch to/from desktop
+ return LAST_TASK;
+ }
+
return endTarget;
}
@@ -1583,7 +1591,8 @@ public abstract class AbsSwipeUpHandler,
final SwipePipToHomeAnimator.Builder builder = new SwipePipToHomeAnimator.Builder()
.setContext(mContext)
.setTaskId(runningTaskTarget.taskId)
- .setComponentName(taskInfo.topActivity)
+ .setActivityInfo(taskInfo.topActivityInfo)
+ .setAppIconSizePx(mDp.iconSizePx)
.setLeash(runningTaskTarget.leash)
.setSourceRectHint(
runningTaskTarget.taskInfo.pictureInPictureParams.getSourceRectHint())
@@ -1667,9 +1676,6 @@ public abstract class AbsSwipeUpHandler,
}
mRecentsAnimationController.enableInputConsumer();
-
- // Start hiding the divider
- setDividerShown(false /* shown */, true /* immediate */);
}
private void computeRecentsScrollIfInvisible() {
@@ -2133,9 +2139,6 @@ public abstract class AbsSwipeUpHandler,
@Override
public void onRecentsAnimationFinished(RecentsAnimationController controller) {
- if (!controller.getFinishTargetIsLauncher()) {
- setDividerShown(true /* shown */, false /* immediate */);
- }
mRecentsAnimationController = null;
mRecentsAnimationTargets = null;
if (mRecentsView != null) {
@@ -2235,18 +2238,23 @@ public abstract class AbsSwipeUpHandler,
boolean notSwipingToHome = mRecentsAnimationTargets != null
&& mGestureState.getEndTarget() != HOME;
boolean setRecentsScroll = mRecentsViewScrollLinked && mRecentsView != null;
+ float progress = Math.max(mCurrentShift.value, getScaleProgressDueToScroll());
+ int scrollOffset = setRecentsScroll ? mRecentsView.getScrollOffset() : 0;
+ if (progress > 0 || scrollOffset != 0) {
+ // Hide the divider as the tasks start moving.
+ setDividerShown(false);
+ }
for (RemoteTargetHandle remoteHandle : mRemoteTargetHandles) {
AnimatorControllerWithResistance playbackController =
remoteHandle.getPlaybackController();
if (playbackController != null) {
- playbackController.setProgress(Math.max(mCurrentShift.value,
- getScaleProgressDueToScroll()), mDragLengthFactor);
+ playbackController.setProgress(progress, mDragLengthFactor);
}
if (notSwipingToHome) {
TaskViewSimulator taskViewSimulator = remoteHandle.getTaskViewSimulator();
if (setRecentsScroll) {
- taskViewSimulator.setScroll(mRecentsView.getScrollOffset());
+ taskViewSimulator.setScroll(scrollOffset);
}
taskViewSimulator.apply(remoteHandle.getTransformParams());
}
@@ -2312,17 +2320,13 @@ public abstract class AbsSwipeUpHandler,
return displacement;
}
- private void setDividerShown(boolean shown, boolean immediate) {
- if (mDividerAnimator != null) {
- mDividerAnimator.cancel();
+ private void setDividerShown(boolean shown) {
+ if (mRecentsAnimationTargets == null || mIsDividerShown == shown) {
+ return;
}
- mDividerAnimator = TaskViewUtils.createSplitAuxiliarySurfacesAnimator(
- mRecentsAnimationTargets.nonApps, shown, (dividerAnimator) -> {
- dividerAnimator.start();
- if (immediate) {
- dividerAnimator.end();
- }
- });
+ mIsDividerShown = shown;
+ TaskViewUtils.createSplitAuxiliarySurfacesAnimator(
+ mRecentsAnimationTargets.nonApps, shown, null /* animatorHandler */);
}
/**
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 274b686ea4..ce41c60e89 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -20,6 +20,7 @@ import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.AbsSwipeUpHandler.RECENTS_ATTACH_DURATION;
+import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM;
import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_TRANSLATE_X_ANIM;
@@ -51,6 +52,7 @@ import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.statemanager.BaseState;
@@ -62,6 +64,7 @@ import com.android.launcher3.util.NavigationMode;
import com.android.launcher3.views.ScrimView;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.AnimatorControllerWithResistance;
+import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -107,6 +110,20 @@ public abstract class BaseActivityInterface {
+ /* No-Op */
+ }
+ lp.isStartupDataMigrated -> {
+ Log.d(TAG, "preloading start up data")
+ LauncherAppState.INSTANCE.get(context)
+ }
+ else -> {
+ Log.d(TAG, "queuing start up data migration to boot aware prefs")
+ LockedUserState.get(context).runOnUserUnlocked {
+ lp.migrateStartupDataToDeviceProtectedStorage()
+ }
+ }
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index ae9fb0b385..8bb189a887 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -61,7 +61,7 @@ public final class FallbackActivityInterface extends
@Override
public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect,
PagedOrientationHandler orientationHandler) {
- calculateTaskSize(context, dp, outRect);
+ calculateTaskSize(context, dp, outRect, orientationHandler);
if (dp.isVerticalBarLayout() && DisplayController.getNavigationMode(context) != NO_BUTTON) {
return dp.isSeascape() ? outRect.left : (dp.widthPx - outRect.right);
} else {
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 9ff941671b..ea9f032000 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -18,7 +18,6 @@ package com.android.quickstep;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherState.QUICK_SWITCH;
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -73,7 +72,7 @@ public final class LauncherActivityInterface extends
@Override
public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect,
PagedOrientationHandler orientationHandler) {
- calculateTaskSize(context, dp, outRect);
+ calculateTaskSize(context, dp, outRect, orientationHandler);
if (dp.isVerticalBarLayout()
&& DisplayController.getNavigationMode(context) != NavigationMode.NO_BUTTON) {
return dp.isSeascape() ? outRect.left : (dp.widthPx - outRect.right);
@@ -347,7 +346,7 @@ public final class LauncherActivityInterface extends
return OVERVIEW;
case NEW_TASK:
case LAST_TASK:
- return QUICK_SWITCH;
+ return BACKGROUND_APP;
case HOME:
default:
return NORMAL;
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index 27417516a4..03042c95fb 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -36,6 +36,7 @@ import android.view.SurfaceControl;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.window.BackEvent;
+import android.window.BackMotionEvent;
import android.window.BackProgressAnimator;
import android.window.IOnBackInvokedCallback;
@@ -134,14 +135,14 @@ public class LauncherBackAnimationController {
}
@Override
- public void onBackProgressed(BackEvent backEvent) {
+ public void onBackProgressed(BackMotionEvent backEvent) {
handler.post(() -> {
mProgressAnimator.onBackProgressed(backEvent);
});
}
@Override
- public void onBackStarted(BackEvent backEvent) {
+ public void onBackStarted(BackMotionEvent backEvent) {
handler.post(() -> {
startBack(backEvent);
mProgressAnimator.onBackStarted(backEvent, event -> {
@@ -185,7 +186,7 @@ public class LauncherBackAnimationController {
mBackCallback = null;
}
- private void startBack(BackEvent backEvent) {
+ private void startBack(BackMotionEvent backEvent) {
mBackInProgress = true;
RemoteAnimationTarget appTarget = backEvent.getDepartingAnimationTarget();
@@ -289,7 +290,8 @@ public class LauncherBackAnimationController {
new RemoteAnimationTarget[0],
false /* fromUnlock */,
mCurrentRect,
- cornerRadius);
+ cornerRadius,
+ mBackInProgress /* fromPredictiveBack */);
startTransitionAnimations(pair.first, pair.second);
mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
}
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index bb781c82b7..43ad175b6f 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -38,6 +38,7 @@ import androidx.annotation.Nullable;
import com.android.launcher3.LauncherState;
import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.ObjectWrapper;
@@ -104,7 +105,10 @@ public class LauncherSwipeHandlerV2 extends
private HomeAnimationFactory createIconHomeAnimationFactory(View workspaceView) {
RectF iconLocation = new RectF();
- FloatingIconView floatingIconView = getFloatingIconView(mActivity, workspaceView,
+ FloatingIconView floatingIconView = getFloatingIconView(mActivity, workspaceView, null,
+ mActivity.getTaskbarUIController() == null
+ ? null
+ : mActivity.getTaskbarUIController().findMatchingView(workspaceView),
true /* hideOriginal */, iconLocation, false /* isOpening */);
// We want the window alpha to be 0 once this threshold is met, so that the
@@ -119,6 +123,12 @@ public class LauncherSwipeHandlerV2 extends
return workspaceView;
}
+ @Override
+ public boolean isInHotseat() {
+ return workspaceView.getTag() instanceof ItemInfo
+ && ((ItemInfo) workspaceView.getTag()).isInHotseat();
+ }
+
@NonNull
@Override
public RectF getWindowTargetRect() {
@@ -136,8 +146,8 @@ public class LauncherSwipeHandlerV2 extends
@Override
public void update(RectF currentRect, float progress, float radius) {
super.update(currentRect, progress, radius);
- floatingIconView.update(1f /* alpha */, 255 /* fgAlpha */, currentRect, progress,
- windowAlphaThreshold, radius, false);
+ floatingIconView.update(1f /* alpha */, currentRect, progress, windowAlphaThreshold,
+ radius, false);
}
};
}
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index 1b05fd272d..84f6b5514f 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -33,6 +33,7 @@ import android.view.Surface;
import com.android.launcher3.R;
import com.android.launcher3.testing.shared.ResourceUtils;
+import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.NavigationMode;
import com.android.launcher3.util.window.CachedDisplayInfo;
@@ -119,7 +120,7 @@ class OrientationTouchTransformer {
}
void setNavigationMode(NavigationMode newMode, Info info, Resources newRes) {
- if (DEBUG) {
+ if (enableLog()) {
Log.d(TAG, "setNavigationMode new: " + newMode + " oldMode: " + mMode + " " + this);
}
if (mMode == newMode) {
@@ -206,7 +207,7 @@ class OrientationTouchTransformer {
* Ok to call multiple times.
*/
private void resetSwipeRegions(Info region) {
- if (DEBUG) {
+ if (enableLog()) {
Log.d(TAG, "clearing all regions except rotation: " + mCachedDisplayInfo.rotation);
}
@@ -230,9 +231,11 @@ class OrientationTouchTransformer {
}
private OrientationRectF createRegionForDisplay(Info display) {
- if (DEBUG) {
+ if (enableLog()) {
Log.d(TAG, "creating rotation region for: " + mCachedDisplayInfo.rotation
- + " with mode: " + mMode + " displayRotation: " + display.rotation);
+ + " with mode: " + mMode + " displayRotation: " + display.rotation +
+ " displaySize: " + display.currentSize +
+ " navBarHeight: " + mNavBarGesturalHeight);
}
Point size = display.currentSize;
@@ -296,9 +299,8 @@ class OrientationTouchTransformer {
}
boolean touchInValidSwipeRegions(float x, float y) {
- if (DEBUG) {
- Log.d(TAG, "touchInValidSwipeRegions " + x + "," + y + " in "
- + mLastRectTouched + " this: " + this);
+ if (enableLog()) {
+ Log.d(TAG, "touchInValidSwipeRegions " + x + "," + y + " in " + mLastRectTouched);
}
if (mLastRectTouched != null) {
return mLastRectTouched.contains(x, y);
@@ -357,11 +359,17 @@ class OrientationTouchTransformer {
}
case ACTION_POINTER_DOWN:
case ACTION_DOWN: {
+ if (enableLog()) {
+ Log.d(TAG, "ACTION_DOWN mLastRectTouched: " + mLastRectTouched);
+ }
if (mLastRectTouched != null) {
return;
}
for (OrientationRectF rect : mSwipeTouchRegions.values()) {
+ if (enableLog()) {
+ Log.d(TAG, "ACTION_DOWN rect: " + rect);
+ }
if (rect == null) {
continue;
}
@@ -376,7 +384,7 @@ class OrientationTouchTransformer {
mQuickStepStartingRotation = mLastRectTouched.getRotation();
resetSwipeRegions();
}
- if (DEBUG) {
+ if (enableLog()) {
Log.d(TAG, "set active region: " + rect);
}
return;
@@ -387,6 +395,10 @@ class OrientationTouchTransformer {
}
}
+ private boolean enableLog() {
+ return DEBUG || TestProtocol.sDebugTracing;
+ }
+
public void dump(PrintWriter pw) {
pw.println("OrientationTouchTransformerState: ");
pw.println(" currentActiveRotation=" + getCurrentActiveRotation());
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 5a09e021b2..5b85249dfa 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -24,14 +24,20 @@ import android.graphics.PointF;
import android.os.Build;
import android.os.SystemClock;
import android.os.Trace;
+import android.view.View;
import androidx.annotation.BinderThread;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.taskbar.TaskbarUIController;
import com.android.launcher3.util.RunnableList;
import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
+import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -48,7 +54,7 @@ import java.util.HashMap;
public class OverviewCommandHelper {
public static final int TYPE_SHOW = 1;
- public static final int TYPE_SHOW_NEXT_FOCUS = 2;
+ public static final int TYPE_KEYBOARD_INPUT = 2;
public static final int TYPE_HIDE = 3;
public static final int TYPE_TOGGLE = 4;
public static final int TYPE_HOME = 5;
@@ -66,6 +72,13 @@ public class OverviewCommandHelper {
private final TaskAnimationManager mTaskAnimationManager;
private final ArrayList mPendingCommands = new ArrayList<>();
+ /**
+ * Index of the TaskView that should be focused when launching Overview. Persisted so that we
+ * do not lose the focus across multiple calls of
+ * {@link OverviewCommandHelper#executeCommand(CommandInfo)} for the same command
+ */
+ private int mTaskFocusIndexOverride = -1;
+
public OverviewCommandHelper(TouchInteractionService service,
OverviewComponentObserver observer,
TaskAnimationManager taskAnimationManager) {
@@ -165,8 +178,30 @@ public class OverviewCommandHelper {
mOverviewComponentObserver.getActivityInterface();
RecentsView recents = activityInterface.getVisibleRecentsView();
if (recents == null) {
+ T activity = activityInterface.getCreatedActivity();
+ DeviceProfile dp = activity == null ? null : activity.getDeviceProfile();
+ TaskbarUIController uiController = activityInterface.getTaskbarController();
+ boolean allowQuickSwitch = FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get()
+ && uiController != null
+ && dp != null
+ && (dp.isTablet || dp.isTwoPanels);
+
+ if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+ // TODO(b/268075592): add support for quickswitch to/from desktop
+ allowQuickSwitch = false;
+ }
+
if (cmd.type == TYPE_HIDE) {
- // already hidden
+ if (!allowQuickSwitch) {
+ return true;
+ }
+ mTaskFocusIndexOverride = uiController.launchFocusedTask();
+ if (mTaskFocusIndexOverride == -1) {
+ return true;
+ }
+ }
+ if (cmd.type == TYPE_KEYBOARD_INPUT && allowQuickSwitch) {
+ uiController.openQuickSwitchView();
return true;
}
if (cmd.type == TYPE_HOME) {
@@ -179,6 +214,7 @@ public class OverviewCommandHelper {
// already visible
return true;
case TYPE_HIDE: {
+ mTaskFocusIndexOverride = -1;
int currentPage = recents.getNextPage();
TaskView tv = (currentPage >= 0 && currentPage < recents.getTaskViewCount())
? (TaskView) recents.getPageAt(currentPage)
@@ -194,15 +230,9 @@ public class OverviewCommandHelper {
}
final Runnable completeCallback = () -> {
- if (cmd.type == TYPE_SHOW_NEXT_FOCUS) {
- RecentsView rv = activityInterface.getVisibleRecentsView();
- // When the overview is launched via alt tab (cmd type is TYPE_SHOW_NEXT_FOCUS),
- // the touch mode somehow is not change to false by the Android framework.
- // The subsequent tab to go through tasks in overview can only be dispatched to
- // focuses views, while focus can only be requested in
- // {@link View#requestFocusNoSearch(int, Rect)} when touch mode is false. To note,
- // here we launch overview from home.
- rv.getViewRootImpl().touchModeChanged(false);
+ RecentsView rv = activityInterface.getVisibleRecentsView();
+ if (rv != null && (cmd.type == TYPE_KEYBOARD_INPUT || cmd.type == TYPE_HIDE)) {
+ updateRecentsViewFocus(rv);
}
scheduleNextTask(cmd);
};
@@ -253,7 +283,7 @@ public class OverviewCommandHelper {
RecentsView, ?> visibleRecentsView = activityInterface.getVisibleRecentsView();
if (visibleRecentsView != null) {
- visibleRecentsView.moveFocusedTaskToFront();
+ visibleRecentsView.moveRunningTaskToFront();
}
if (mTaskAnimationManager.isRecentsAnimationRunning()) {
cmd.mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(gestureState);
@@ -280,40 +310,55 @@ public class OverviewCommandHelper {
cmd.removeListener(handler);
Trace.endAsyncSection(TRANSITION_NAME, 0);
- if (cmd.type == TYPE_SHOW_NEXT_FOCUS) {
- RecentsView rv =
- mOverviewComponentObserver.getActivityInterface().getVisibleRecentsView();
- if (rv != null) {
- // When the overview is launched via alt tab (cmd type is TYPE_SHOW_NEXT_FOCUS),
- // the touch mode somehow is not change to false by the Android framework.
- // The subsequent tab to go through tasks in overview can only be dispatched to
- // focuses views, while focus can only be requested in
- // {@link View#requestFocusNoSearch(int, Rect)} when touch mode is false. To note,
- // here we launch overview with live tile.
- rv.getViewRootImpl().touchModeChanged(false);
- // Ensure that recents view has focus so that it receives the followup key inputs
- TaskView taskView = rv.getNextTaskView();
- if (taskView == null) {
- taskView = rv.getTaskViewAt(0);
- if (taskView != null) {
- taskView.requestFocus();
- } else {
- rv.requestFocus();
- }
- } else {
- taskView.requestFocus();
- }
- }
+ RecentsView rv =
+ mOverviewComponentObserver.getActivityInterface().getVisibleRecentsView();
+ if (rv != null && (cmd.type == TYPE_KEYBOARD_INPUT || cmd.type == TYPE_HIDE)) {
+ updateRecentsViewFocus(rv);
}
scheduleNextTask(cmd);
}
+ private void updateRecentsViewFocus(@NonNull RecentsView rv) {
+ // When the overview is launched via alt tab (cmd type is TYPE_KEYBOARD_INPUT),
+ // the touch mode somehow is not change to false by the Android framework.
+ // The subsequent tab to go through tasks in overview can only be dispatched to
+ // focuses views, while focus can only be requested in
+ // {@link View#requestFocusNoSearch(int, Rect)} when touch mode is false. To note,
+ // here we launch overview with live tile.
+ rv.getViewRootImpl().touchModeChanged(false);
+ // Ensure that recents view has focus so that it receives the followup key inputs
+ TaskView taskView = rv.getTaskViewAt(mTaskFocusIndexOverride);
+ if (taskView != null) {
+ requestFocus(taskView);
+ return;
+ }
+ taskView = rv.getNextTaskView();
+ if (taskView != null) {
+ requestFocus(taskView);
+ return;
+ }
+ taskView = rv.getTaskViewAt(0);
+ if (taskView != null) {
+ requestFocus(taskView);
+ return;
+ }
+ requestFocus(rv);
+ }
+
+ private void requestFocus(@NonNull View view) {
+ view.post(() -> {
+ view.requestFocus();
+ view.requestAccessibilityFocus();
+ });
+ }
+
public void dump(PrintWriter pw) {
pw.println("OverviewCommandHelper:");
pw.println(" mPendingCommands=" + mPendingCommands.size());
if (!mPendingCommands.isEmpty()) {
pw.println(" pendingCommandType=" + mPendingCommands.get(0).type);
}
+ pw.println(" mTaskFocusIndexOverride=" + mTaskFocusIndexOverride);
}
private static class CommandInfo {
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 83f2a0a114..a8f3c3af46 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -21,15 +21,12 @@ import static android.content.Intent.ACTION_PACKAGE_CHANGED;
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
-import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
import static com.android.systemui.shared.system.PackageManagerWrapper.ACTION_PREFERRED_ACTIVITY_CHANGED;
import android.content.ActivityNotFoundException;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -40,6 +37,7 @@ import android.util.SparseIntArray;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.launcher3.R;
import com.android.launcher3.tracing.OverviewComponentObserverProto;
import com.android.launcher3.tracing.TouchInteractionServiceProto;
import com.android.launcher3.util.SimpleBroadcastReceiver;
@@ -57,9 +55,9 @@ import java.util.function.Consumer;
public final class OverviewComponentObserver {
private static final String TAG = "OverviewComponentObserver";
- private final BroadcastReceiver mUserPreferenceChangeReceiver =
+ private final SimpleBroadcastReceiver mUserPreferenceChangeReceiver =
new SimpleBroadcastReceiver(this::updateOverviewTargets);
- private final BroadcastReceiver mOtherHomeAppUpdateReceiver =
+ private final SimpleBroadcastReceiver mOtherHomeAppUpdateReceiver =
new SimpleBroadcastReceiver(this::updateOverviewTargets);
private final Context mContext;
@@ -68,6 +66,7 @@ public final class OverviewComponentObserver {
private final Intent mMyHomeIntent;
private final Intent mFallbackIntent;
private final SparseIntArray mConfigChangesMap = new SparseIntArray();
+ private final String mSetupWizardPkg;
private Consumer mOverviewChangeListener = b -> { };
@@ -89,6 +88,7 @@ public final class OverviewComponentObserver {
new ComponentName(context.getPackageName(), info.activityInfo.name);
mMyHomeIntent.setComponent(myHomeComponent);
mConfigChangesMap.append(myHomeComponent.hashCode(), info.activityInfo.configChanges);
+ mSetupWizardPkg = context.getString(R.string.setup_wizard_pkg);
ComponentName fallbackComponent = new ComponentName(mContext, RecentsActivity.class);
mFallbackIntent = new Intent(Intent.ACTION_MAIN)
@@ -102,8 +102,7 @@ public final class OverviewComponentObserver {
mConfigChangesMap.append(fallbackComponent.hashCode(), fallbackInfo.configChanges);
} catch (PackageManager.NameNotFoundException ignored) { /* Impossible */ }
- mContext.registerReceiver(mUserPreferenceChangeReceiver,
- new IntentFilter(ACTION_PREFERRED_ACTIVITY_CHANGED));
+ mUserPreferenceChangeReceiver.register(mContext, ACTION_PREFERRED_ACTIVITY_CHANGED);
updateOverviewTargets();
}
@@ -131,6 +130,12 @@ public final class OverviewComponentObserver {
private void updateOverviewTargets() {
ComponentName defaultHome = PackageManagerWrapper.getInstance()
.getHomeActivities(new ArrayList<>());
+ if (defaultHome != null && defaultHome.getPackageName().equals(mSetupWizardPkg)) {
+ // Treat setup wizard as null default home, because there is a period between setup and
+ // launcher being default home where it is briefly null. Otherwise, it would appear as
+ // if overview targets are changing twice, giving the listener an incorrect signal.
+ defaultHome = null;
+ }
mIsHomeDisabled = mDeviceState.isHomeDisabled();
mIsDefaultHome = Objects.equals(mMyHomeIntent.getComponent(), defaultHome);
@@ -181,9 +186,8 @@ public final class OverviewComponentObserver {
unregisterOtherHomeAppUpdateReceiver();
mUpdateRegisteredPackage = defaultHome.getPackageName();
- mContext.registerReceiver(mOtherHomeAppUpdateReceiver, getPackageFilter(
- mUpdateRegisteredPackage, ACTION_PACKAGE_ADDED, ACTION_PACKAGE_CHANGED,
- ACTION_PACKAGE_REMOVED));
+ mOtherHomeAppUpdateReceiver.registerPkgActions(mContext, mUpdateRegisteredPackage,
+ ACTION_PACKAGE_ADDED, ACTION_PACKAGE_CHANGED, ACTION_PACKAGE_REMOVED);
}
}
mOverviewChangeListener.accept(mIsHomeAndOverviewSame);
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 54e4a0d3f1..5391f4d46c 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -59,7 +59,7 @@ public class QuickstepTestInformationHandler extends TestInformationHandler {
}
Rect focusedTaskRect = new Rect();
LauncherActivityInterface.INSTANCE.calculateTaskSize(mContext, mDeviceProfile,
- focusedTaskRect);
+ focusedTaskRect, PagedOrientationHandler.PORTRAIT);
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, focusedTaskRect.height());
return response;
}
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index b33ceca838..38ac5bb9e2 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -17,7 +17,7 @@
package com.android.quickstep;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import static com.android.quickstep.views.DesktopTaskView.DESKTOP_MODE_SUPPORTED;
+import static com.android.quickstep.views.DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED;
import static com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_FREEFORM;
import android.annotation.TargetApi;
@@ -270,7 +270,7 @@ public class RecentTasksList {
TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size());
for (GroupedRecentTaskInfo rawTask : rawTasks) {
- if (DESKTOP_MODE_SUPPORTED && rawTask.getType() == TYPE_FREEFORM) {
+ if (DESKTOP_IS_PROTO2_ENABLED && rawTask.getType() == TYPE_FREEFORM) {
GroupTask desktopTask = createDesktopTask(rawTask);
allTasks.add(desktopTask);
continue;
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index dc405ff8ba..3f8da56de4 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -15,6 +15,7 @@
*/
package com.android.quickstep;
+import static android.os.Trace.TRACE_TAG_APP;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
@@ -36,6 +37,7 @@ import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
+import android.os.Trace;
import android.view.Display;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
@@ -132,7 +134,8 @@ public final class RecentsActivity extends StatefulActivity {
SplitSelectStateController controller =
new SplitSelectStateController(this, mHandler, getStateManager(),
- /* depthController */ null, getStatsLogManager());
+ null /* depthController */, getStatsLogManager(),
+ SystemUiProxy.INSTANCE.get(this), RecentsModel.INSTANCE.get(this));
mDragLayer.recreateControllers();
mFallbackRecentsView.init(mActionsView, controller);
@@ -240,7 +243,7 @@ public final class RecentsActivity extends StatefulActivity {
mActivityLaunchAnimationRunner = new RemoteAnimationFactory() {
@Override
- public void onCreateAnimation(int transit, RemoteAnimationTarget[] appTargets,
+ public void onAnimationStart(int transit, RemoteAnimationTarget[] appTargets,
RemoteAnimationTarget[] wallpaperTargets,
RemoteAnimationTarget[] nonAppTargets, AnimationResult result) {
mHandler.removeCallbacks(mAnimationStartTimeoutRunnable);
@@ -407,28 +410,24 @@ public final class RecentsActivity extends StatefulActivity {
}
private final RemoteAnimationFactory mAnimationToHomeFactory =
- new RemoteAnimationFactory() {
- @Override
- public void onCreateAnimation(int transit, RemoteAnimationTarget[] appTargets,
- RemoteAnimationTarget[] wallpaperTargets,
- RemoteAnimationTarget[] nonAppTargets, AnimationResult result) {
- AnimatorPlaybackController controller = getStateManager()
- .createAnimationToNewWorkspace(RecentsState.BG_LAUNCHER, HOME_APPEAR_DURATION);
- controller.dispatchOnStart();
+ (transit, appTargets, wallpaperTargets, nonAppTargets, result) -> {
+ AnimatorPlaybackController controller =
+ getStateManager().createAnimationToNewWorkspace(
+ RecentsState.BG_LAUNCHER, HOME_APPEAR_DURATION);
+ controller.dispatchOnStart();
- RemoteAnimationTargets targets = new RemoteAnimationTargets(
- appTargets, wallpaperTargets, nonAppTargets, MODE_OPENING);
- for (RemoteAnimationTarget app : targets.apps) {
- new Transaction().setAlpha(app.leash, 1).apply();
- }
- AnimatorSet anim = new AnimatorSet();
- anim.play(controller.getAnimationPlayer());
- anim.setDuration(HOME_APPEAR_DURATION);
- result.setAnimation(anim, RecentsActivity.this,
- () -> getStateManager().goToState(RecentsState.HOME, false),
- true /* skipFirstFrame */);
- }
- };
+ RemoteAnimationTargets targets = new RemoteAnimationTargets(
+ appTargets, wallpaperTargets, nonAppTargets, MODE_OPENING);
+ for (RemoteAnimationTarget app : targets.apps) {
+ new Transaction().setAlpha(app.leash, 1).apply();
+ }
+ AnimatorSet anim = new AnimatorSet();
+ anim.play(controller.getAnimationPlayer());
+ anim.setDuration(HOME_APPEAR_DURATION);
+ result.setAnimation(anim, RecentsActivity.this,
+ () -> getStateManager().goToState(RecentsState.HOME, false),
+ true /* skipFirstFrame */);
+ };
@Override
protected void collectStateHandlers(List out) {
@@ -452,6 +451,13 @@ public final class RecentsActivity extends StatefulActivity {
return new RecentsAtomicAnimationFactory<>(this);
}
+ @Override
+ public void dispatchDeviceProfileChanged() {
+ super.dispatchDeviceProfileChanged();
+ Trace.instantForTrack(TRACE_TAG_APP, "RecentsActivity#DeviceProfileChanged",
+ getDeviceProfile().toSmallString());
+ }
+
private AnimatorListenerAdapter resetStateListener() {
return new AnimatorListenerAdapter() {
@Override
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
index 388e1256d8..15e1365998 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
@@ -15,11 +15,15 @@
*/
package com.android.quickstep;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import android.app.WindowConfiguration;
import android.graphics.Rect;
import android.view.RemoteAnimationTarget;
+import com.android.quickstep.views.DesktopTaskView;
+
/**
* Extension of {@link RemoteAnimationTargets} with additional information about swipe
* up animation
@@ -40,4 +44,22 @@ public class RecentsAnimationTargets extends RemoteAnimationTargets {
public boolean hasTargets() {
return unfilteredApps.length != 0;
}
+
+ /**
+ * Check if target apps contain desktop tasks which have windowing mode set to {@link
+ * WindowConfiguration#WINDOWING_MODE_FREEFORM}
+ *
+ * @return {@code true} if at least one target app is a desktop task
+ */
+ public boolean hasDesktopTasks() {
+ if (!DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+ return false;
+ }
+ for (RemoteAnimationTarget target : apps) {
+ if (target.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
index 4c41bef26a..f30d3f14cc 100644
--- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -26,6 +26,7 @@ import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams;
+import com.android.quickstep.views.DesktopTaskView;
import java.util.ArrayList;
@@ -41,8 +42,8 @@ public class RemoteTargetGluer {
* Use this constructor if remote targets are split-screen independent
*/
public RemoteTargetGluer(Context context, BaseActivityInterface sizingStrategy,
- RemoteAnimationTargets targets) {
- mRemoteTargetHandles = createHandles(context, sizingStrategy, targets.apps.length);
+ RemoteAnimationTargets targets, boolean forDesktop) {
+ init(context, sizingStrategy, targets.apps.length, forDesktop);
}
/**
@@ -50,15 +51,31 @@ public class RemoteTargetGluer {
* running tasks
*/
public RemoteTargetGluer(Context context, BaseActivityInterface sizingStrategy) {
+ if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+ // TODO: binder call, only for prototyping. Creating the gluer should be postponed so
+ // we can create it when we have the remote animation targets ready.
+ int desktopTasks = SystemUiProxy.INSTANCE.get(context).getVisibleDesktopTaskCount();
+ if (desktopTasks > 0) {
+ init(context, sizingStrategy, desktopTasks, true /* forDesktop */);
+ return;
+ }
+ }
+
int[] splitIds = TopTaskTracker.INSTANCE.get(context).getRunningSplitTaskIds();
- mRemoteTargetHandles = createHandles(context, sizingStrategy, splitIds.length == 2 ? 2 : 1);
+ init(context, sizingStrategy, splitIds.length == 2 ? 2 : 1, false /* forDesktop */);
+ }
+
+ private void init(Context context, BaseActivityInterface sizingStrategy, int numHandles,
+ boolean forDesktop) {
+ mRemoteTargetHandles = createHandles(context, sizingStrategy, numHandles, forDesktop);
}
private RemoteTargetHandle[] createHandles(Context context,
- BaseActivityInterface sizingStrategy, int numHandles) {
+ BaseActivityInterface sizingStrategy, int numHandles, boolean forDesktop) {
RemoteTargetHandle[] handles = new RemoteTargetHandle[numHandles];
for (int i = 0; i < numHandles; i++) {
TaskViewSimulator tvs = new TaskViewSimulator(context, sizingStrategy);
+ tvs.setIsDesktopTask(forDesktop);
TransformParams transformParams = new TransformParams();
handles[i] = new RemoteTargetHandle(tvs, transformParams);
}
@@ -72,7 +89,7 @@ public class RemoteTargetGluer {
* Length of targets.apps should match that of {@link #mRemoteTargetHandles}.
*
* If split screen may be active when this is called, you might want to use
- * {@link #assignTargetsForSplitScreen(Context, RemoteAnimationTargets)}
+ * {@link #assignTargetsForSplitScreen(RemoteAnimationTargets)}
*/
public RemoteTargetHandle[] assignTargets(RemoteAnimationTargets targets) {
for (int i = 0; i < mRemoteTargetHandles.length; i++) {
@@ -85,43 +102,45 @@ public class RemoteTargetGluer {
}
/**
- * Similar to {@link #assignTargets(RemoteAnimationTargets)}, except this matches the
- * apps in targets.apps to that of the _active_ split screened tasks.
- * See {@link #assignTargetsForSplitScreen(RemoteAnimationTargets, int[])}
+ * Similar to {@link #assignTargets(RemoteAnimationTargets)}, except this assigns the
+ * apps in {@code targets.apps} to the {@link #mRemoteTargetHandles} with index 0 will being
+ * the left/top task, index 1 right/bottom.
*/
- public RemoteTargetHandle[] assignTargetsForSplitScreen(
- Context context, RemoteAnimationTargets targets) {
- int[] splitIds = TopTaskTracker.INSTANCE.get(context).getRunningSplitTaskIds();
- return assignTargetsForSplitScreen(targets, splitIds);
- }
-
- /**
- * Assigns the provided splitIDs to the {@link #mRemoteTargetHandles}, with index 0 will being
- * the left/top task, index 1 right/bottom
- */
- public RemoteTargetHandle[] assignTargetsForSplitScreen(RemoteAnimationTargets targets,
- int[] splitIds) {
- RemoteAnimationTarget topLeftTarget; // only one set if single/fullscreen task
- RemoteAnimationTarget bottomRightTarget;
+ public RemoteTargetHandle[] assignTargetsForSplitScreen(RemoteAnimationTargets targets) {
if (mRemoteTargetHandles.length == 1) {
// If we're not in split screen, the splitIds count doesn't really matter since we
// should always hit this case.
mRemoteTargetHandles[0].mTransformParams.setTargetSet(targets);
if (targets.apps.length > 0) {
// Unclear why/when target.apps length == 0, but it sure does happen :(
- topLeftTarget = targets.apps[0];
- mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(topLeftTarget, null);
+ mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(targets.apps[0], null);
}
} else {
- // split screen
- topLeftTarget = targets.findTask(splitIds[0]);
- bottomRightTarget = targets.findTask(splitIds[1]);
+ RemoteAnimationTarget topLeftTarget = targets.apps[0];
+
+ // Fetch the adjacent target for split screen.
+ RemoteAnimationTarget bottomRightTarget = null;
+ for (int i = 1; i < targets.apps.length; i++) {
+ final RemoteAnimationTarget target = targets.apps[i];
+ Rect topLeftBounds = getStartBounds(topLeftTarget);
+ Rect bounds = getStartBounds(target);
+ if (topLeftBounds.left > bounds.right || topLeftBounds.top > bounds.bottom) {
+ bottomRightTarget = topLeftTarget;
+ topLeftTarget = target;
+ break;
+ } else if (topLeftBounds.right < bounds.left || topLeftBounds.bottom < bounds.top) {
+ bottomRightTarget = target;
+ break;
+ }
+ }
// remoteTargetHandle[0] denotes topLeft task, so we pass in the bottomRight to exclude,
// vice versa
mSplitBounds = new SplitBounds(
getStartBounds(topLeftTarget),
- getStartBounds(bottomRightTarget), splitIds[0], splitIds[1]);
+ getStartBounds(bottomRightTarget),
+ topLeftTarget.taskId,
+ bottomRightTarget.taskId);
mRemoteTargetHandles[0].mTransformParams.setTargetSet(
createRemoteAnimationTargetsForTarget(targets, bottomRightTarget));
mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(topLeftTarget,
@@ -135,6 +154,20 @@ public class RemoteTargetGluer {
return mRemoteTargetHandles;
}
+ /**
+ * Similar to {@link #assignTargets(RemoteAnimationTargets)}, except this creates distinct
+ * transform params per app in {@code targets.apps} list.
+ */
+ public RemoteTargetHandle[] assignTargetsForDesktop(RemoteAnimationTargets targets) {
+ for (int i = 0; i < mRemoteTargetHandles.length; i++) {
+ RemoteAnimationTarget primaryTaskTarget = targets.apps[i];
+ mRemoteTargetHandles[i].mTransformParams.setTargetSet(
+ createRemoteAnimationTargetsForTaskId(targets, primaryTaskTarget.taskId));
+ mRemoteTargetHandles[i].mTaskViewSimulator.setPreview(primaryTaskTarget, null);
+ }
+ return mRemoteTargetHandles;
+ }
+
private Rect getStartBounds(RemoteAnimationTarget target) {
return target.startBounds == null ? target.screenSpaceBounds : target.startBounds;
}
@@ -172,6 +205,33 @@ public class RemoteTargetGluer {
filteredApps, targets.wallpapers, targets.nonApps, targets.targetMode);
}
+ /**
+ * Ensures that we only animate one specific app target. Includes ancillary targets such as
+ * home/recents
+ *
+ * @param targets remote animation targets to filter
+ * @param taskId id for a task that we want this remote animation to apply to
+ * @return {@link RemoteAnimationTargets} where app target only includes the app that has the
+ * {@code taskId} that was passed in
+ */
+ private RemoteAnimationTargets createRemoteAnimationTargetsForTaskId(
+ RemoteAnimationTargets targets, int taskId) {
+ RemoteAnimationTarget[] targetApp = null;
+ for (RemoteAnimationTarget targetCompat : targets.unfilteredApps) {
+ if (targetCompat.taskId == taskId) {
+ targetApp = new RemoteAnimationTarget[]{targetCompat};
+ break;
+ }
+ }
+
+ if (targetApp == null) {
+ targetApp = new RemoteAnimationTarget[0];
+ }
+
+ return new RemoteAnimationTargets(targetApp, targets.wallpapers, targets.nonApps,
+ targets.targetMode);
+ }
+
public RemoteTargetHandle[] getRemoteTargetHandles() {
return mRemoteTargetHandles;
}
diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
index f8b69666fe..4c66dbb66d 100644
--- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -321,9 +321,9 @@ public class RotationTouchHelper implements DisplayInfoChangeListener {
if (enable && !mInOverview && !TestProtocol.sDisableSensorRotation) {
// Clear any previous state from sensor manager
mSensorRotation = mCurrentAppRotation;
- mOrientationListener.enable();
+ UI_HELPER_EXECUTOR.execute(mOrientationListener::enable);
} else {
- mOrientationListener.disable();
+ UI_HELPER_EXECUTOR.execute(mOrientationListener::disable);
}
}
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index 291f8354d2..f913aff167 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -39,6 +39,8 @@ import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.RectFSpringAnim;
+import com.android.quickstep.util.RectFSpringAnim.DefaultSpringConfig;
+import com.android.quickstep.util.RectFSpringAnim.TaskbarHotseatSpringConfig;
import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams;
@@ -157,6 +159,13 @@ public abstract class SwipeUpAnimationLogic implements
protected abstract class HomeAnimationFactory {
protected float mSwipeVelocity;
+ /**
+ * Returns true if we know the home animation involves an item in the hotseat.
+ */
+ public boolean isInHotseat() {
+ return false;
+ }
+
public @NonNull RectF getWindowTargetRect() {
PagedOrientationHandler orientationHandler = getOrientationHandler();
DeviceProfile dp = mDp;
@@ -288,7 +297,11 @@ public abstract class SwipeUpAnimationLogic implements
homeToWindowPositionMap.invert(windowToHomePositionMap);
windowToHomePositionMap.mapRect(startRect);
- RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mContext, mDp);
+ boolean useTaskbarHotseatParams = mDp.isTaskbarPresent
+ && homeAnimationFactory.isInHotseat();
+ RectFSpringAnim anim = new RectFSpringAnim(useTaskbarHotseatParams
+ ? new TaskbarHotseatSpringConfig(mContext, startRect, targetRect)
+ : new DefaultSpringConfig(mContext, mDp, startRect, targetRect));
homeAnimationFactory.setAnimation(anim);
SpringAnimationRunner runner = new SpringAnimationRunner(
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index bb973342d5..b3bee6cd16 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -29,7 +29,6 @@ import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
-import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
@@ -51,13 +50,15 @@ import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.android.internal.logging.InstanceId;
+import com.android.internal.util.ScreenshotRequest;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.systemui.shared.recents.ISystemUiProxy;
-import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController;
import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController;
import com.android.systemui.shared.system.smartspace.SmartspaceState;
+import com.android.systemui.unfold.progress.IUnfoldAnimation;
+import com.android.systemui.unfold.progress.IUnfoldTransitionListener;
import com.android.wm.shell.back.IBackAnimation;
import com.android.wm.shell.desktopmode.IDesktopMode;
import com.android.wm.shell.onehanded.IOneHanded;
@@ -86,6 +87,7 @@ public class SystemUiProxy implements ISystemUiProxy {
new MainThreadInitializedObject<>(SystemUiProxy::new);
private static final int MSG_SET_SHELF_HEIGHT = 1;
+ private static final int MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT = 2;
private ISystemUiProxy mSystemUiProxy;
private IPip mPip;
@@ -97,6 +99,7 @@ public class SystemUiProxy implements ISystemUiProxy {
private IRecentTasks mRecentTasks;
private IBackAnimation mBackAnimation;
private IDesktopMode mDesktopMode;
+ private IUnfoldAnimation mUnfoldAnimation;
private final DeathRecipient mSystemUiProxyDeathRecipient = () -> {
MAIN_EXECUTOR.execute(() -> clearProxy());
};
@@ -110,6 +113,7 @@ public class SystemUiProxy implements ISystemUiProxy {
private IStartingWindowListener mStartingWindowListener;
private ILauncherUnlockAnimationController mLauncherUnlockAnimationController;
private IRecentTasksListener mRecentTasksListener;
+ private IUnfoldTransitionListener mUnfoldAnimationListener;
private final LinkedHashMap mRemoteTransitions =
new LinkedHashMap<>();
private IOnBackInvokedCallback mBackToLauncherCallback;
@@ -118,6 +122,10 @@ public class SystemUiProxy implements ISystemUiProxy {
private int mLastShelfHeight;
private boolean mLastShelfVisible;
+ // Used to dedupe calls to SystemUI
+ private int mLastLauncherKeepClearAreaHeight;
+ private boolean mLastLauncherKeepClearAreaHeightVisible;
+
private final Context mContext;
private final Handler mAsyncHandler;
@@ -172,7 +180,8 @@ public class SystemUiProxy implements ISystemUiProxy {
IOneHanded oneHanded, IShellTransitions shellTransitions,
IStartingWindow startingWindow, IRecentTasks recentTasks,
ISysuiUnlockAnimationController sysuiUnlockAnimationController,
- IBackAnimation backAnimation, IDesktopMode desktopMode) {
+ IBackAnimation backAnimation, IDesktopMode desktopMode,
+ IUnfoldAnimation unfoldAnimation) {
unlinkToDeath();
mSystemUiProxy = proxy;
mPip = pip;
@@ -184,6 +193,7 @@ public class SystemUiProxy implements ISystemUiProxy {
mRecentTasks = recentTasks;
mBackAnimation = backAnimation;
mDesktopMode = desktopMode;
+ mUnfoldAnimation = unfoldAnimation;
linkToDeath();
// re-attach the listeners once missing due to setProxy has not been initialized yet.
if (mPipAnimationListener != null && mPip != null) {
@@ -205,10 +215,13 @@ public class SystemUiProxy implements ISystemUiProxy {
if (mBackAnimation != null && mBackToLauncherCallback != null) {
setBackToLauncherCallback(mBackToLauncherCallback);
}
+ if (unfoldAnimation != null && mUnfoldAnimationListener != null) {
+ setUnfoldAnimationListener(mUnfoldAnimationListener);
+ }
}
public void clearProxy() {
- setProxy(null, null, null, null, null, null, null, null, null, null);
+ setProxy(null, null, null, null, null, null, null, null, null, null, null);
}
// TODO(141886704): Find a way to remove this
@@ -384,14 +397,12 @@ public class SystemUiProxy implements ISystemUiProxy {
}
@Override
- public void handleImageBundleAsScreenshot(Bundle screenImageBundle, Rect locationInScreen,
- Insets visibleInsets, Task.TaskKey task) {
+ public void takeScreenshot(ScreenshotRequest request) {
if (mSystemUiProxy != null) {
try {
- mSystemUiProxy.handleImageBundleAsScreenshot(screenImageBundle, locationInScreen,
- visibleInsets, task);
+ mSystemUiProxy.takeScreenshot(request);
} catch (RemoteException e) {
- Log.w(TAG, "Failed call handleImageBundleAsScreenshot");
+ Log.w(TAG, "Failed call takeScreenshot");
}
}
}
@@ -447,6 +458,33 @@ public class SystemUiProxy implements ISystemUiProxy {
}
}
+ /**
+ * Sets the height of the keep clear area that is going to be reported by
+ * the Launcher for the Hotseat.
+ */
+ public void setLauncherKeepClearAreaHeight(boolean visible, int height) {
+ Message.obtain(mAsyncHandler, MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT,
+ visible ? 1 : 0 , height).sendToTarget();
+ }
+
+ @WorkerThread
+ private void setLauncherKeepClearAreaHeight(int visibleInt, int height) {
+ boolean visible = visibleInt != 0;
+ boolean changed = visible != mLastLauncherKeepClearAreaHeightVisible
+ || height != mLastLauncherKeepClearAreaHeight;
+ IPip pip = mPip;
+ if (pip != null && changed) {
+ mLastLauncherKeepClearAreaHeightVisible = visible;
+ mLastLauncherKeepClearAreaHeight = height;
+ try {
+ pip.setLauncherKeepClearAreaHeight(visible, height);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call setLauncherKeepClearAreaHeight visible: " + visible
+ + " height: " + height, e);
+ }
+ }
+ }
+
/**
* Sets listener to get pip animation callbacks.
*/
@@ -508,6 +546,19 @@ public class SystemUiProxy implements ISystemUiProxy {
}
}
+ /**
+ * Sets the app icon size in pixel used by Launcher all apps.
+ */
+ public void setLauncherAppIconSize(int iconSizePx) {
+ if (mPip != null) {
+ try {
+ mPip.setLauncherAppIconSize(iconSizePx);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call setLauncherAppIconSize", e);
+ }
+ }
+ }
+
//
// Splitscreen
//
@@ -631,14 +682,20 @@ public class SystemUiProxy implements ISystemUiProxy {
}
}
- public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, Bundle options1,
- PendingIntent pendingIntent2, Bundle options2,
- @SplitConfigurationOptions.StagePosition int sidePosition, float splitRatio,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ /**
+ * Starts a pair of intents or shortcuts in split-screen using legacy transition. Passing a
+ * non-null shortcut info means to start the app as a shortcut.
+ */
+ public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1,
+ @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
+ PendingIntent pendingIntent2, @Nullable ShortcutInfo shortcutInfo2,
+ @Nullable Bundle options2, @SplitConfigurationOptions.StagePosition int sidePosition,
+ float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
- mSplitScreen.startIntentsWithLegacyTransition(pendingIntent1, options1,
- pendingIntent2, options2, sidePosition, splitRatio, adapter, instanceId);
+ mSplitScreen.startIntentsWithLegacyTransition(pendingIntent1, shortcutInfo1,
+ options1, pendingIntent2, shortcutInfo2, options2, sidePosition, splitRatio,
+ adapter, instanceId);
} catch (RemoteException e) {
Log.w(TAG, "Failed call startIntentsWithLegacyTransition");
}
@@ -933,6 +990,9 @@ public class SystemUiProxy implements ISystemUiProxy {
case MSG_SET_SHELF_HEIGHT:
setShelfHeightAsync(msg.arg1, msg.arg2);
return true;
+ case MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT:
+ setLauncherKeepClearAreaHeight(msg.arg1, msg.arg2);
+ return true;
}
return false;
@@ -952,4 +1012,34 @@ public class SystemUiProxy implements ISystemUiProxy {
}
}
}
+
+ /** Call shell to get number of visible freeform tasks */
+ public int getVisibleDesktopTaskCount() {
+ if (mDesktopMode != null) {
+ try {
+ return mDesktopMode.getVisibleTaskCount();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call getVisibleDesktopTaskCount", e);
+ }
+ }
+ return 0;
+ }
+
+ //
+ // Unfold transition
+ //
+
+ /** Sets the unfold animation lister to sysui. */
+ public void setUnfoldAnimationListener(IUnfoldTransitionListener callback) {
+ mUnfoldAnimationListener = callback;
+ if (mUnfoldAnimation == null) {
+ return;
+ }
+ try {
+ Log.d(TAG, "Registering unfold animation receiver");
+ mUnfoldAnimation.setListener(callback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed call setUnfoldAnimationListener", e);
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index c45b2f05f8..dde5d55de4 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -40,6 +40,7 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -187,12 +188,8 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
return;
}
} else if (nonAppTargets.length > 0) {
- TaskViewUtils.createSplitAuxiliarySurfacesAnimator(
- nonAppTargets /* nonApps */,
- true /*shown*/, dividerAnimator -> {
- dividerAnimator.start();
- dividerAnimator.end();
- });
+ TaskViewUtils.createSplitAuxiliarySurfacesAnimator(nonAppTargets /* nonApps */,
+ true /*shown*/, null /* animatorHandler */);
}
if (mController != null) {
if (mLastAppearedTaskTarget == null
@@ -238,6 +235,12 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
// to let the transition controller collect Home activity.
CachedTaskInfo cti = gestureState.getRunningTask();
boolean homeIsOnTop = cti != null && cti.isHomeTask();
+ if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+ if (cti != null && cti.isFreeformTask()) {
+ // No transient launch when desktop task is on top
+ homeIsOnTop = true;
+ }
+ }
if (!homeIsOnTop) {
options.setTransientLaunch();
}
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index d40f2ae017..6e47ff4716 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -36,6 +36,7 @@ import androidx.annotation.RequiresApi;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.SystemShortcut;
@@ -77,9 +78,12 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
RecentsOrientedState orientedState = taskView.getRecentsView().getPagedViewOrientedState();
boolean canLauncherRotate = orientedState.isRecentsActivityRotationAllowed();
boolean isInLandscape = orientedState.getTouchRotation() != ROTATION_0;
+ boolean isTablet = activity.getDeviceProfile().isTablet;
- // Add overview actions to the menu when in in-place rotate landscape mode.
- if (!canLauncherRotate && isInLandscape) {
+ boolean isGridOnlyOverview = isTablet && FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get();
+ // Add overview actions to the menu when in in-place rotate landscape mode, or in
+ // grid-only overview.
+ if ((!canLauncherRotate && isInLandscape) || isGridOnlyOverview) {
// Add screenshot action to task menu.
List screenshotShortcuts = TaskShortcutFactory.SCREENSHOT
.getShortcuts(activity, taskContainer);
@@ -87,8 +91,9 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
shortcuts.addAll(screenshotShortcuts);
}
- // Add modal action only if display orientation is the same as the device orientation.
- if (orientedState.getDisplayRotation() == ROTATION_0) {
+ // Add modal action only if display orientation is the same as the device orientation,
+ // or in grid-only overview.
+ if (orientedState.getDisplayRotation() == ROTATION_0 || isGridOnlyOverview) {
List modalShortcuts = TaskShortcutFactory.MODAL
.getShortcuts(activity, taskContainer);
if (modalShortcuts != null) {
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 663525d0c5..255b9107b9 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -18,7 +18,6 @@ package com.android.quickstep;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_SELECTIONS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP;
import android.app.Activity;
@@ -296,7 +295,8 @@ public interface TaskShortcutFactory {
return null;
}
- return Collections.singletonList(new FreeformSystemShortcut(R.drawable.ic_split_screen,
+ return Collections.singletonList(new FreeformSystemShortcut(
+ R.drawable.ic_caption_desktop_button_foreground,
R.string.recent_task_option_freeform, activity, taskContainer,
LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP));
}
@@ -392,10 +392,7 @@ public interface TaskShortcutFactory {
taskContainer.getThumbnailView().getTaskOverlay()
.getModalStateSystemShortcut(
taskContainer.getItemInfo(), taskContainer.getTaskView());
- if (ENABLE_OVERVIEW_SELECTIONS.get()) {
- return createSingletonShortcutList(modalStateSystemShortcut);
- }
- return null;
+ return createSingletonShortcutList(modalStateSystemShortcut);
}
};
}
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 67de4b1679..f0d0bdb0ed 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -39,6 +39,7 @@ import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
import static com.android.launcher3.anim.Interpolators.clampToProgress;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
+import static com.android.quickstep.views.DesktopTaskView.DESKTOP_MODE_SUPPORTED;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -78,6 +79,7 @@ import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams;
+import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.GroupedTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskThumbnailView;
@@ -182,10 +184,13 @@ public final class TaskViewUtils {
// Re-use existing handles
remoteTargetHandles = recentsViewHandles;
} else {
+ boolean forDesktop = DESKTOP_MODE_SUPPORTED && v instanceof DesktopTaskView;
RemoteTargetGluer gluer = new RemoteTargetGluer(v.getContext(),
- recentsView.getSizeStrategy(), targets);
- if (v.containsMultipleTasks()) {
- remoteTargetHandles = gluer.assignTargetsForSplitScreen(targets, v.getTaskIds());
+ recentsView.getSizeStrategy(), targets, forDesktop);
+ if (forDesktop) {
+ remoteTargetHandles = gluer.assignTargetsForDesktop(targets);
+ } else if (v.containsMultipleTasks()) {
+ remoteTargetHandles = gluer.assignTargetsForSplitScreen(targets);
} else {
remoteTargetHandles = gluer.assignTargets(targets);
}
@@ -674,28 +679,36 @@ public final class TaskViewUtils {
/**
* Creates an animation to show/hide the auxiliary surfaces (aka. divider bar), only calling
* {@param animatorHandler} if there are valid surfaces to animate.
+ * Passing null handler to apply the visibility immediately.
*
* @return the animator animating the surfaces
*/
public static ValueAnimator createSplitAuxiliarySurfacesAnimator(
- RemoteAnimationTarget[] nonApps, boolean shown,
- Consumer animatorHandler) {
+ @Nullable RemoteAnimationTarget[] nonApps, boolean shown,
+ @Nullable Consumer animatorHandler) {
if (nonApps == null || nonApps.length == 0) {
return null;
}
- SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- List auxiliarySurfaces = new ArrayList<>(nonApps.length);
- boolean hasSurfaceToAnimate = false;
- for (int i = 0; i < nonApps.length; ++i) {
- final RemoteAnimationTarget targ = nonApps[i];
- final SurfaceControl leash = targ.leash;
- if (targ.windowType == TYPE_DOCK_DIVIDER && leash != null && leash.isValid()) {
+ List auxiliarySurfaces = new ArrayList<>();
+ for (RemoteAnimationTarget target : nonApps) {
+ final SurfaceControl leash = target.leash;
+ if (target.windowType == TYPE_DOCK_DIVIDER && leash != null && leash.isValid()) {
auxiliarySurfaces.add(leash);
- hasSurfaceToAnimate = true;
}
}
- if (!hasSurfaceToAnimate) {
+ if (auxiliarySurfaces.isEmpty()) {
+ return null;
+ }
+
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ if (animatorHandler == null) {
+ // Apply the visibility directly without fade animation.
+ for (SurfaceControl leash : auxiliarySurfaces) {
+ t.setVisibility(leash, shown);
+ }
+ t.apply();
+ t.close();
return null;
}
diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java
index d4bf5c75aa..6f502d0ece 100644
--- a/quickstep/src/com/android/quickstep/TopTaskTracker.java
+++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java
@@ -18,6 +18,7 @@ package com.android.quickstep;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.content.Intent.ACTION_CHOOSER;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -254,6 +255,15 @@ public class TopTaskTracker extends ISplitScreenListener.Stub implements TaskSta
.getActivityType() == ACTIVITY_TYPE_HOME;
}
+ /**
+ * Returns {@code true} if this task windowing mode is set to {@link
+ * android.app.WindowConfiguration#WINDOWING_MODE_FREEFORM}
+ */
+ public boolean isFreeformTask() {
+ return mTopTask != null && mTopTask.configuration.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_FREEFORM;
+ }
+
/**
* Returns {@link Task} array which can be used as a placeholder until the true object
* is loaded by the model
@@ -292,7 +302,10 @@ public class TopTaskTracker extends ISplitScreenListener.Stub implements TaskSta
@Nullable
public String getPackageName() {
- return mTopTask == null ? null : mTopTask.baseActivity.getPackageName();
+ if (mTopTask == null || mTopTask.baseActivity == null) {
+ return null;
+ }
+ return mTopTask.baseActivity.getPackageName();
}
}
}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 5d17cc77f4..a0255ac1b6 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -28,6 +28,7 @@ import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_UP;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
@@ -68,7 +69,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
-import com.android.app.viewcapture.ViewCapture;
+import com.android.app.viewcapture.SettingsAwareViewCapture;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
@@ -85,6 +86,7 @@ import com.android.launcher3.testing.shared.ResourceUtils;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.tracing.LauncherTraceProto;
import com.android.launcher3.tracing.TouchInteractionServiceProto;
+import com.android.launcher3.uioverrides.flags.FlagsFactory;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.OnboardingPrefs;
@@ -113,6 +115,7 @@ import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController;
import com.android.systemui.shared.tracing.ProtoTraceable;
+import com.android.systemui.unfold.progress.IUnfoldAnimation;
import com.android.wm.shell.back.IBackAnimation;
import com.android.wm.shell.desktopmode.IDesktopMode;
import com.android.wm.shell.onehanded.IOneHanded;
@@ -173,10 +176,13 @@ public class TouchInteractionService extends Service
bundle.getBinder(KEY_EXTRA_SHELL_BACK_ANIMATION));
IDesktopMode desktopMode = IDesktopMode.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SHELL_DESKTOP_MODE));
+ IUnfoldAnimation unfoldTransition = IUnfoldAnimation.Stub.asInterface(
+ bundle.getBinder(KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER));
MAIN_EXECUTOR.execute(() -> {
SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy, pip,
splitscreen, onehanded, shellTransitions, startingWindow,
- recentTasks, launcherUnlockAnimationController, backAnimation, desktopMode);
+ recentTasks, launcherUnlockAnimationController, backAnimation, desktopMode,
+ unfoldTransition);
TouchInteractionService.this.initInputMonitor("TISBinder#onInitialize()");
preloadOverview(true /* fromInit */);
});
@@ -199,7 +205,7 @@ public class TouchInteractionService extends Service
public void onOverviewShown(boolean triggeredFromAltTab) {
if (triggeredFromAltTab) {
TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
- mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_SHOW_NEXT_FOCUS);
+ mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_KEYBOARD_INPUT);
} else {
mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_SHOW);
}
@@ -404,6 +410,7 @@ public class TouchInteractionService extends Service
mDeviceState = new RecentsAnimationDeviceState(this, true);
mTaskbarManager = new TaskbarManager(this);
mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
+ BootAwarePreloader.start(this);
// Call runOnUserUnlocked() before any other callbacks to ensure everything is initialized.
mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
@@ -531,8 +538,8 @@ public class TouchInteractionService extends Service
boolean isFreeformActive =
(systemUiStateFlags & SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0;
if (wasFreeformActive != isFreeformActive) {
- DesktopVisibilityController controller = mOverviewComponentObserver
- .getActivityInterface().getDesktopVisibilityController();
+ DesktopVisibilityController controller =
+ LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
if (controller != null) {
controller.setFreeformTasksVisible(isFreeformActive);
}
@@ -800,7 +807,7 @@ public class TouchInteractionService extends Service
// If Taskbar is present, we listen for long press to unstash it.
TaskbarActivityContext tac = mTaskbarManager.getCurrentActivityContext();
- if (tac != null) {
+ if (tac != null && canStartSystemGesture) {
reasonString.append(NEWLINE_PREFIX)
.append(reasonPrefix)
.append(SUBSTRING_PREFIX)
@@ -1125,6 +1132,10 @@ public class TouchInteractionService extends Service
return;
}
+ // TODO(b/258022658): Remove temporary logging.
+ Log.i(TAG, "preloadOverview: forSUWAllSet=" + forSUWAllSet
+ + ", isHomeAndOverviewSame=" + mOverviewComponentObserver.isHomeAndOverviewSame());
+
mTaskAnimationManager.preloadRecentsAnimation(overviewIntent);
}
@@ -1170,7 +1181,7 @@ public class TouchInteractionService extends Service
}
} else {
// Dump everything
- FeatureFlags.dump(pw);
+ FlagsFactory.dump(pw);
if (mDeviceState.isUserUnlocked()) {
PluginManagerWrapper.INSTANCE.get(getBaseContext()).dump(pw);
}
@@ -1206,7 +1217,7 @@ public class TouchInteractionService extends Service
mTaskbarManager.dumpLogs("", pw);
if (FeatureFlags.CONTINUOUS_VIEW_TREE_CAPTURE.get()) {
- ViewCapture.getInstance().dump(pw, fd, this);
+ SettingsAwareViewCapture.getInstance(this).dump(pw, fd, this);
}
}
}
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 062e50e30b..11b1ab8ec9 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -130,15 +130,9 @@ public class FallbackRecentsStateController implements StateHandler= mTaskbarNavThreshold;
if (!mHasPassedTaskbarNavThreshold && passedTaskbarNavThreshold) {
mHasPassedTaskbarNavThreshold = true;
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index e0262d02eb..79971de0e8 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -88,6 +88,10 @@ public class AllSetActivity extends Activity {
private static final float ANIMATION_PAUSE_ALPHA_THRESHOLD = 0.1f;
+ private final Rect mTempSettingsBounds = new Rect();
+ private final Rect mTempInclusionBounds = new Rect();
+ private final Rect mTempExclusionBounds = new Rect();
+
private TISBindHelper mTISBindHelper;
private TISBinder mBinder;
@@ -131,9 +135,9 @@ public class AllSetActivity extends Activity {
!TextUtils.isEmpty(suwDeviceName)
? suwDeviceName : getString(R.string.default_device_name)));
- TextView tv = findViewById(R.id.navigation_settings);
- tv.setTextColor(accentColor);
- tv.setOnClickListener(v -> {
+ TextView settings = findViewById(R.id.navigation_settings);
+ settings.setTextColor(accentColor);
+ settings.setOnClickListener(v -> {
try {
startActivityForResult(
Intent.parseUri(URI_SYSTEM_NAVIGATION_SETTING, 0), 0);
@@ -142,12 +146,41 @@ public class AllSetActivity extends Activity {
}
});
- TextView hintTextView = findViewById(R.id.hint);
+ TextView hint = findViewById(R.id.hint);
DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(this).getDeviceProfile(this);
if (!dp.isGestureMode) {
- hintTextView.setText(R.string.allset_button_hint);
+ hint.setText(R.string.allset_button_hint);
}
- hintTextView.setAccessibilityDelegate(new SkipButtonAccessibilityDelegate());
+ hint.setAccessibilityDelegate(new SkipButtonAccessibilityDelegate());
+
+ View textContent = findViewById(R.id.text_content_view);
+ textContent.addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ mTempSettingsBounds.set(
+ settings.getLeft(),
+ settings.getTop(),
+ settings.getRight(),
+ settings.getBottom());
+ mTempInclusionBounds.set(
+ 0,
+ // Do not allow overlapping with the subtitle text
+ subtitle.getBottom(),
+ textContent.getWidth(),
+ textContent.getHeight());
+ mTempExclusionBounds.set(
+ hint.getLeft(),
+ hint.getTop(),
+ hint.getRight(),
+ hint.getBottom());
+
+ Utilities.translateOverlappingView(
+ settings,
+ mTempSettingsBounds,
+ mTempInclusionBounds,
+ mTempExclusionBounds,
+ Utilities.TRANSLATE_UP);
+ });
+
mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
mVibrator = getSystemService(Vibrator.class);
@@ -240,6 +273,7 @@ public class AllSetActivity extends Activity {
maybeResumeOrPauseBackgroundAnimation();
if (mSwipeProgress.value >= 1) {
finishAndRemoveTask();
+ dispatchLauncherAnimStartEnd();
}
}
@@ -251,6 +285,18 @@ public class AllSetActivity extends Activity {
}
}
+ /**
+ * Should be called when we have successfully reached Launcher, so we dispatch to animation
+ * listeners to ensure the state matches the visual animation that just occurred.
+ */
+ private void dispatchLauncherAnimStartEnd() {
+ if (mLauncherStartAnim != null) {
+ mLauncherStartAnim.dispatchOnStart();
+ mLauncherStartAnim.dispatchOnEnd();
+ mLauncherStartAnim = null;
+ }
+ }
+
@Override
protected void onDestroy() {
super.onDestroy();
@@ -259,6 +305,7 @@ public class AllSetActivity extends Activity {
if (mBackgroundAnimatorListener != null) {
mAnimatedBackground.removeAnimatorListener(mBackgroundAnimatorListener);
}
+ dispatchLauncherAnimStartEnd();
}
private AnimatedFloat createSwipeUpProxy(GestureState state) {
diff --git a/quickstep/src/com/android/quickstep/interaction/AnimatedTaskView.java b/quickstep/src/com/android/quickstep/interaction/AnimatedTaskView.java
index 53ad138b6a..73937f5914 100644
--- a/quickstep/src/com/android/quickstep/interaction/AnimatedTaskView.java
+++ b/quickstep/src/com/android/quickstep/interaction/AnimatedTaskView.java
@@ -15,6 +15,8 @@
*/
package com.android.quickstep.interaction;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_NEW_GESTURE_NAV_TUTORIAL;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -22,15 +24,19 @@ import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.ColorInt;
import android.content.Context;
+import android.graphics.Matrix;
import android.graphics.Outline;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.View;
import android.view.ViewOutlineProvider;
+import android.view.animation.ScaleAnimation;
+import android.view.animation.TranslateAnimation;
+import android.widget.ViewAnimator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.cardview.widget.CardView;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.launcher3.R;
@@ -46,8 +52,8 @@ import java.util.ArrayList;
public class AnimatedTaskView extends ConstraintLayout {
private View mFullTaskView;
- private CardView mTopTaskView;
- private CardView mBottomTaskView;
+ private View mTopTaskView;
+ private View mBottomTaskView;
private ViewOutlineProvider mTaskViewOutlineProvider = null;
private final Rect mTaskViewAnimatedRect = new Rect();
@@ -86,6 +92,45 @@ public class AnimatedTaskView extends ConstraintLayout {
setToSingleRowLayout(false);
}
+ void animateToFillScreen(@Nullable Runnable onAnimationEndCallback) {
+
+ AnimatorSet set = new AnimatorSet();
+ ArrayList animations = new ArrayList<>();
+
+ // center view
+ animations.add(ObjectAnimator.ofFloat(this, TRANSLATION_X, 0));
+
+ // calculate full screen scaling, scale should be 1:1 for x and y
+ Matrix matrix = getAnimationMatrix();
+ float[] matrixValues = new float[9];
+ matrix.getValues(matrixValues);
+ float scaleX = matrixValues[Matrix.MSCALE_X];
+ float scaleToFullScreen = 1 / scaleX;
+
+ // scale view to full screen
+ ValueAnimator scale = ValueAnimator.ofFloat(1f, scaleToFullScreen);
+ scale.addUpdateListener(animation -> {
+ float value = (float) animation.getAnimatedValue();
+ mFullTaskView.setScaleX(value);
+ mFullTaskView.setScaleY(value);
+ });
+
+ animations.add(scale);
+ set.playSequentially(animations);
+
+ set.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (onAnimationEndCallback != null) {
+ onAnimationEndCallback.run();
+ }
+ }
+ });
+
+ set.start();
+ }
+
AnimatorSet createAnimationToMultiRowLayout() {
if (mTaskViewOutlineProvider == null) {
// This is an illegal state.
@@ -185,8 +230,11 @@ public class AnimatedTaskView extends ConstraintLayout {
void setFakeTaskViewFillColor(@ColorInt int colorResId) {
mFullTaskView.setBackgroundColor(colorResId);
- mTopTaskView.setCardBackgroundColor(colorResId);
- mBottomTaskView.setCardBackgroundColor(colorResId);
+
+ if (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()){
+ mTopTaskView.getBackground().setTint(colorResId);
+ mBottomTaskView.getBackground().setTint(colorResId);
+ }
}
@Override
diff --git a/quickstep/src/com/android/quickstep/interaction/AnimatedTaskbarView.java b/quickstep/src/com/android/quickstep/interaction/AnimatedTaskbarView.java
index e8cc45b97b..d70671115d 100644
--- a/quickstep/src/com/android/quickstep/interaction/AnimatedTaskbarView.java
+++ b/quickstep/src/com/android/quickstep/interaction/AnimatedTaskbarView.java
@@ -43,12 +43,12 @@ public class AnimatedTaskbarView extends ConstraintLayout {
private View mBackground;
private View mIconContainer;
+ private View mAllAppsButton;
private View mIcon1;
private View mIcon2;
private View mIcon3;
private View mIcon4;
private View mIcon5;
- private View mIcon6;
@Nullable private Animator mRunningAnimator;
@@ -78,12 +78,12 @@ public class AnimatedTaskbarView extends ConstraintLayout {
mBackground = findViewById(R.id.taskbar_background);
mIconContainer = findViewById(R.id.icon_container);
+ mAllAppsButton = findViewById(R.id.taskbar_all_apps);
mIcon1 = findViewById(R.id.taskbar_icon_1);
mIcon2 = findViewById(R.id.taskbar_icon_2);
mIcon3 = findViewById(R.id.taskbar_icon_3);
mIcon4 = findViewById(R.id.taskbar_icon_4);
mIcon5 = findViewById(R.id.taskbar_icon_5);
- mIcon6 = findViewById(R.id.taskbar_icon_6);
}
/**
@@ -92,22 +92,20 @@ public class AnimatedTaskbarView extends ConstraintLayout {
public void animateDisappearanceToHotseat(ViewGroup hotseat) {
ArrayList animators = new ArrayList<>();
int hotseatTop = hotseat.getTop();
+ int hotseatLeft = hotseat.getLeft();
- animators.add(ObjectAnimator.ofFloat(
- mBackground, View.TRANSLATION_Y, 0, mBackground.getHeight()));
animators.add(ObjectAnimator.ofFloat(mBackground, View.ALPHA, 1f, 0f));
+ animators.add(ObjectAnimator.ofFloat(mAllAppsButton, View.ALPHA, 1f, 0f));
animators.add(createIconDisappearanceToHotseatAnimator(
- mIcon1, hotseat.findViewById(R.id.hotseat_icon_1), hotseatTop));
+ mIcon1, hotseat.findViewById(R.id.hotseat_icon_1), hotseatTop, hotseatLeft));
animators.add(createIconDisappearanceToHotseatAnimator(
- mIcon2, hotseat.findViewById(R.id.hotseat_icon_2), hotseatTop));
+ mIcon2, hotseat.findViewById(R.id.hotseat_icon_2), hotseatTop, hotseatLeft));
animators.add(createIconDisappearanceToHotseatAnimator(
- mIcon3, hotseat.findViewById(R.id.hotseat_icon_3), hotseatTop));
+ mIcon3, hotseat.findViewById(R.id.hotseat_icon_3), hotseatTop, hotseatLeft));
animators.add(createIconDisappearanceToHotseatAnimator(
- mIcon4, hotseat.findViewById(R.id.hotseat_icon_4), hotseatTop));
+ mIcon4, hotseat.findViewById(R.id.hotseat_icon_4), hotseatTop, hotseatLeft));
animators.add(createIconDisappearanceToHotseatAnimator(
- mIcon5, hotseat.findViewById(R.id.hotseat_icon_5), hotseatTop));
- animators.add(createIconDisappearanceToHotseatAnimator(
- mIcon6, hotseat.findViewById(R.id.hotseat_icon_6), hotseatTop));
+ mIcon5, hotseat.findViewById(R.id.hotseat_icon_5), hotseatTop, hotseatLeft));
AnimatorSet animatorSet = new AnimatorSet();
@@ -135,22 +133,20 @@ public class AnimatedTaskbarView extends ConstraintLayout {
public void animateAppearanceFromHotseat(ViewGroup hotseat) {
ArrayList animators = new ArrayList<>();
int hotseatTop = hotseat.getTop();
+ int hotseatLeft = hotseat.getLeft();
- animators.add(ObjectAnimator.ofFloat(
- mBackground, View.TRANSLATION_Y, mBackground.getHeight(), 0));
animators.add(ObjectAnimator.ofFloat(mBackground, View.ALPHA, 0f, 1f));
+ animators.add(ObjectAnimator.ofFloat(mAllAppsButton, View.ALPHA, 0f, 1f));
animators.add(createIconAppearanceFromHotseatAnimator(
- mIcon1, hotseat.findViewById(R.id.hotseat_icon_1), hotseatTop));
+ mIcon1, hotseat.findViewById(R.id.hotseat_icon_1), hotseatTop, hotseatLeft));
animators.add(createIconAppearanceFromHotseatAnimator(
- mIcon2, hotseat.findViewById(R.id.hotseat_icon_2), hotseatTop));
+ mIcon2, hotseat.findViewById(R.id.hotseat_icon_2), hotseatTop, hotseatLeft));
animators.add(createIconAppearanceFromHotseatAnimator(
- mIcon3, hotseat.findViewById(R.id.hotseat_icon_3), hotseatTop));
+ mIcon3, hotseat.findViewById(R.id.hotseat_icon_3), hotseatTop, hotseatLeft));
animators.add(createIconAppearanceFromHotseatAnimator(
- mIcon4, hotseat.findViewById(R.id.hotseat_icon_4), hotseatTop));
+ mIcon4, hotseat.findViewById(R.id.hotseat_icon_4), hotseatTop, hotseatLeft));
animators.add(createIconAppearanceFromHotseatAnimator(
- mIcon5, hotseat.findViewById(R.id.hotseat_icon_5), hotseatTop));
- animators.add(createIconAppearanceFromHotseatAnimator(
- mIcon6, hotseat.findViewById(R.id.hotseat_icon_6), hotseatTop));
+ mIcon5, hotseat.findViewById(R.id.hotseat_icon_5), hotseatTop, hotseatLeft));
AnimatorSet animatorSet = new AnimatorSet();
@@ -166,98 +162,6 @@ public class AnimatedTaskbarView extends ConstraintLayout {
start(animatorSet);
}
- /**
- * Animates this fake taskbar's disappearance to the bottom of the screen.
- */
- public void animateDisappearanceToBottom() {
- ArrayList animators = new ArrayList<>();
-
- animators.add(ObjectAnimator.ofFloat(
- mBackground, View.TRANSLATION_Y, 0, mBackground.getHeight()));
- animators.add(ObjectAnimator.ofFloat(mBackground, View.ALPHA, 1f, 0f));
- animators.add(ObjectAnimator.ofFloat(mIconContainer, View.SCALE_X, 1f, 0f));
- animators.add(ObjectAnimator.ofFloat(mIconContainer, View.SCALE_Y, 1f, 0f));
-
- initializeIconContainerPivot();
-
- AnimatorSet animatorSet = new AnimatorSet();
-
- animatorSet.playTogether(animators);
- animatorSet.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- setVisibility(INVISIBLE);
- resetIconContainerPivot();
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- super.onAnimationCancel(animation);
- resetIconContainerPivot();
- }
-
- @Override
- public void onAnimationStart(Animator animation) {
- super.onAnimationStart(animation);
- setVisibility(VISIBLE);
- }
- });
-
- start(animatorSet);
- }
-
- /**
- * Animates this fake taskbar's appearance from the bottom of the screen.
- */
- public void animateAppearanceFromBottom() {
- ArrayList animators = new ArrayList<>();
-
- animators.add(ObjectAnimator.ofFloat(
- mBackground, View.TRANSLATION_Y, mBackground.getHeight(), 0));
- animators.add(ObjectAnimator.ofFloat(mBackground, View.ALPHA, 0f, 1f));
- animators.add(ObjectAnimator.ofFloat(mIconContainer, View.SCALE_X, 0f, 1f));
- animators.add(ObjectAnimator.ofFloat(mIconContainer, View.SCALE_Y, 0f, 1f));
-
- initializeIconContainerPivot();
-
- AnimatorSet animatorSet = new AnimatorSet();
-
- animatorSet.playTogether(animators);
- animatorSet.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- super.onAnimationStart(animation);
- setVisibility(VISIBLE);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- resetIconContainerPivot();
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- super.onAnimationCancel(animation);
- resetIconContainerPivot();
- }
- });
-
- start(animatorSet);
- }
-
- private void initializeIconContainerPivot() {
- mIconContainer.setPivotX(getWidth() / 2f);
- mIconContainer.setPivotY(getHeight() * 0.8f);
- }
-
- private void resetIconContainerPivot() {
- mIconContainer.resetPivot();
- mIconContainer.setScaleX(1f);
- mIconContainer.setScaleY(1f);
- }
-
private void start(Animator animator) {
if (mRunningAnimator != null) {
mRunningAnimator.cancel();
@@ -287,7 +191,7 @@ public class AnimatedTaskbarView extends ConstraintLayout {
}
private Animator createIconDisappearanceToHotseatAnimator(
- View taskbarIcon, View hotseatIcon, int hotseatTop) {
+ View taskbarIcon, View hotseatIcon, int hotseatTop, int hotseatLeft) {
ArrayList animators = new ArrayList<>();
animators.add(ObjectAnimator.ofFloat(
@@ -296,7 +200,10 @@ public class AnimatedTaskbarView extends ConstraintLayout {
0,
(hotseatTop + hotseatIcon.getTop()) - (getTop() + taskbarIcon.getTop())));
animators.add(ObjectAnimator.ofFloat(
- taskbarIcon, View.TRANSLATION_X, 0, hotseatIcon.getLeft() - taskbarIcon.getLeft()));
+ taskbarIcon,
+ View.TRANSLATION_X,
+ 0,
+ (hotseatLeft + hotseatIcon.getLeft()) - (getLeft() + taskbarIcon.getLeft())));
animators.add(ObjectAnimator.ofFloat(
taskbarIcon,
View.SCALE_X,
@@ -330,7 +237,7 @@ public class AnimatedTaskbarView extends ConstraintLayout {
}
private Animator createIconAppearanceFromHotseatAnimator(
- View taskbarIcon, View hotseatIcon, int hotseatTop) {
+ View taskbarIcon, View hotseatIcon, int hotseatTop, int hotseatLeft) {
ArrayList animators = new ArrayList<>();
animators.add(ObjectAnimator.ofFloat(
@@ -339,7 +246,10 @@ public class AnimatedTaskbarView extends ConstraintLayout {
(hotseatTop + hotseatIcon.getTop()) - (getTop() + taskbarIcon.getTop()),
0));
animators.add(ObjectAnimator.ofFloat(
- taskbarIcon, View.TRANSLATION_X, hotseatIcon.getLeft() - taskbarIcon.getLeft(), 0));
+ taskbarIcon,
+ View.TRANSLATION_X,
+ (hotseatLeft + hotseatIcon.getLeft()) - (getLeft() + taskbarIcon.getLeft()),
+ 0));
animators.add(ObjectAnimator.ofFloat(
taskbarIcon,
View.SCALE_X,
diff --git a/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialController.java
index 2f3a912fb4..40c600f512 100644
--- a/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialController.java
@@ -15,8 +15,6 @@
*/
package com.android.quickstep.interaction;
-import static com.android.quickstep.interaction.TutorialController.TutorialType.ASSISTANT_COMPLETE;
-
import android.graphics.PointF;
import com.android.launcher3.R;
@@ -47,7 +45,7 @@ final class AssistantGestureTutorialController extends TutorialController {
case ASSISTANT_COMPLETE:
if (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT
|| result == BackGestureResult.BACK_COMPLETED_FROM_RIGHT) {
- mTutorialFragment.closeTutorial();
+ mTutorialFragment.close();
}
break;
}
@@ -81,7 +79,7 @@ final class AssistantGestureTutorialController extends TutorialController {
break;
case ASSISTANT_COMPLETE:
if (result == NavBarGestureResult.HOME_GESTURE_COMPLETED) {
- mTutorialFragment.closeTutorial();
+ mTutorialFragment.close();
}
break;
}
diff --git a/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialFragment.java
index f440638125..90a1c36dc4 100644
--- a/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialFragment.java
@@ -26,7 +26,9 @@ import com.android.quickstep.interaction.TutorialController.TutorialType;
/** Shows the Home gesture interactive tutorial. */
public class AssistantGestureTutorialFragment extends TutorialFragment {
- public AssistantGestureTutorialFragment() {}
+ public AssistantGestureTutorialFragment(boolean fromTutorialMenu) {
+ super(fromTutorialMenu);
+ }
@Override
TutorialController createController(TutorialType type) {
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
index 35d9f22d3a..920b32d4c1 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
@@ -15,18 +15,24 @@
*/
package com.android.quickstep.interaction;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_NEW_GESTURE_NAV_TUTORIAL;
import static com.android.quickstep.interaction.TutorialController.TutorialType.BACK_NAVIGATION;
import static com.android.quickstep.interaction.TutorialController.TutorialType.BACK_NAVIGATION_COMPLETE;
import android.annotation.LayoutRes;
import android.graphics.PointF;
+import android.view.View;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult;
/** A {@link TutorialController} for the Back tutorial. */
final class BackGestureTutorialController extends TutorialController {
+ private static final float Y_TRANSLATION_SMOOTHENING_FACTOR = .2f;
+ private static final float EXITING_APP_MIN_SIZE_PERCENTAGE = .8f;
BackGestureTutorialController(BackGestureTutorialFragment fragment, TutorialType tutorialType) {
super(fragment, tutorialType);
@@ -34,7 +40,9 @@ final class BackGestureTutorialController extends TutorialController {
@Override
public int getIntroductionTitle() {
- return R.string.back_gesture_intro_title;
+ return ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()
+ ? R.string.back_gesture_tutorial_title
+ : R.string.back_gesture_intro_title;
}
@Override
@@ -59,18 +67,34 @@ final class BackGestureTutorialController extends TutorialController {
return getMockAppTaskCurrentPageLayoutResId();
}
+ @Override
+ protected int getGestureLottieAnimationId() {
+ return mTutorialFragment.isLargeScreen()
+ ? R.raw.back_gesture_tutorial_tablet_animation
+ : R.raw.back_gesture_tutorial_animation;
+ }
+
@LayoutRes
int getMockAppTaskCurrentPageLayoutResId() {
- return mTutorialFragment.isLargeScreen()
- ? R.layout.gesture_tutorial_tablet_mock_conversation
- : R.layout.gesture_tutorial_mock_conversation;
+ return ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()
+ ? R.layout.back_gesture_tutorial_background
+ : mTutorialFragment.isLargeScreen()
+ ? R.layout.gesture_tutorial_tablet_mock_conversation
+ : R.layout.gesture_tutorial_mock_conversation;
}
@LayoutRes
int getMockAppTaskPreviousPageLayoutResId() {
- return mTutorialFragment.isLargeScreen()
- ? R.layout.gesture_tutorial_tablet_mock_conversation_list
- : R.layout.gesture_tutorial_mock_conversation_list;
+ return ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()
+ ? R.layout.back_gesture_tutorial_background
+ : mTutorialFragment.isLargeScreen()
+ ? R.layout.gesture_tutorial_tablet_mock_conversation_list
+ : R.layout.gesture_tutorial_mock_conversation_list;
+ }
+
+ @Override
+ protected int getSwipeActionColorResId() {
+ return R.color.gesture_back_tutorial_background;
}
@Override
@@ -85,17 +109,65 @@ final class BackGestureTutorialController extends TutorialController {
case BACK_NAVIGATION_COMPLETE:
if (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT
|| result == BackGestureResult.BACK_COMPLETED_FROM_RIGHT) {
- mTutorialFragment.closeTutorial();
+ mTutorialFragment.close();
}
break;
}
}
+ @Override
+ public void onBackGestureProgress(float diffx, float diffy, boolean isLeftGesture) {
+ if (isGestureCompleted()) {
+ return;
+ }
+
+ float normalizedSwipeProgress = Math.abs(diffx / mScreenWidth);
+ float smoothedExitingAppScale = Utilities.mapBoundToRange(
+ normalizedSwipeProgress,
+ /* lowerBound = */ 0f,
+ /* upperBound = */ 1f,
+ /* toMin = */ 1f,
+ /* toMax = */ EXITING_APP_MIN_SIZE_PERCENTAGE,
+ Interpolators.DEACCEL);
+
+ // shrink the exiting app as we progress through the back gesture
+ mExitingAppView.setPivotX(isLeftGesture ? mScreenWidth : 0);
+ mExitingAppView.setPivotY(mScreenHeight / 2f);
+ mExitingAppView.setScaleX(smoothedExitingAppScale);
+ mExitingAppView.setScaleY(smoothedExitingAppScale);
+ mExitingAppView.setTranslationY(diffy * Y_TRANSLATION_SMOOTHENING_FACTOR);
+ mExitingAppView.setTranslationX(Utilities.mapBoundToRange(
+ normalizedSwipeProgress,
+ /* lowerBound = */ 0f,
+ /* upperBound = */ 1f,
+ /* toMin = */ 0,
+ /* toMax = */ mExitingAppMargin,
+ Interpolators.DEACCEL)
+ * (isLeftGesture ? -1 : 1));
+
+ // round the corners of the exiting app as we progress through the back gesture
+ mExitingAppRadius = (int) Utilities.mapBoundToRange(
+ normalizedSwipeProgress,
+ /* lowerBound = */ 0f,
+ /* upperBound = */ 1f,
+ /* toMin = */ mExitingAppStartingCornerRadius,
+ /* toMax = */ mExitingAppEndingCornerRadius,
+ Interpolators.EMPHASIZED_DECELERATE);
+ mExitingAppView.invalidateOutline();
+ }
+
private void handleBackAttempt(BackGestureResult result) {
+ if (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) {
+ resetViewsForBackGesture();
+ }
+
switch (result) {
case BACK_COMPLETED_FROM_LEFT:
case BACK_COMPLETED_FROM_RIGHT:
mTutorialFragment.releaseFeedbackAnimation();
+ if (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) {
+ mExitingAppView.setVisibility(View.GONE);
+ }
updateFakeAppTaskViewLayout(getMockAppTaskPreviousPageLayoutResId());
showSuccessFeedback();
break;
@@ -119,7 +191,7 @@ final class BackGestureTutorialController extends TutorialController {
}
if (mTutorialType == BACK_NAVIGATION_COMPLETE) {
if (result == NavBarGestureResult.HOME_GESTURE_COMPLETED) {
- mTutorialFragment.closeTutorial();
+ mTutorialFragment.close();
}
} else if (mTutorialType == BACK_NAVIGATION) {
switch (result) {
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
index 37253e2b40..a16b239ff5 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
@@ -34,7 +34,13 @@ import java.util.ArrayList;
/** Shows the Back gesture interactive tutorial. */
public class BackGestureTutorialFragment extends TutorialFragment {
- public BackGestureTutorialFragment() {}
+ public BackGestureTutorialFragment() {
+ this(false);
+ }
+
+ public BackGestureTutorialFragment(boolean fromTutorialMenu) {
+ super(fromTutorialMenu);
+ }
@Nullable
@Override
diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
index 8660d871cc..1b12be85df 100644
--- a/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
@@ -15,6 +15,8 @@
*/
package com.android.quickstep.interaction;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_NEW_GESTURE_NAV_TUTORIAL;
+
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
@@ -29,8 +31,8 @@ import android.view.ViewGroup.LayoutParams;
import androidx.annotation.Nullable;
-import com.android.launcher3.testing.shared.ResourceUtils;
import com.android.launcher3.Utilities;
+import com.android.launcher3.testing.shared.ResourceUtils;
import com.android.launcher3.util.DisplayController;
/**
@@ -207,7 +209,11 @@ public class EdgeBackGestureHandler implements OnTouchListener {
mThresholdCrossed = true;
}
}
+ }
+ if (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) {
+ mGestureCallback.onBackGestureProgress(ev.getX() - mDownPoint.x,
+ ev.getY() - mDownPoint.y, mEdgeBackPanel.getIsLeftPanel());
}
// forward touch
@@ -242,5 +248,8 @@ public class EdgeBackGestureHandler implements OnTouchListener {
interface BackGestureAttemptCallback {
/** Called whenever any touch is completed. */
void onBackGestureAttempted(BackGestureResult result);
+
+ /** Called when the back gesture is recognized and is in progress. */
+ default void onBackGestureProgress(float diffx, float diffy, boolean isLeftGesture) {}
}
}
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
index 4a701202db..1ac07425bd 100644
--- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
@@ -26,10 +26,12 @@ import android.view.View;
import android.view.Window;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.StatsLogManager;
import com.android.quickstep.TouchInteractionService.TISBinder;
import com.android.quickstep.interaction.TutorialController.TutorialType;
@@ -42,11 +44,12 @@ public class GestureSandboxActivity extends FragmentActivity {
private static final String KEY_TUTORIAL_STEPS = "tutorial_steps";
private static final String KEY_CURRENT_STEP = "current_step";
- private static final String KEY_GESTURE_COMPLETE = "gesture_complete";
+ static final String KEY_TUTORIAL_TYPE = "tutorial_type";
+ static final String KEY_GESTURE_COMPLETE = "gesture_complete";
+ static final String KEY_USE_TUTORIAL_MENU = "use_tutorial_menu";
- private TutorialType[] mTutorialSteps;
- private TutorialType mCurrentTutorialStep;
- private TutorialFragment mFragment;
+ @Nullable private TutorialType[] mTutorialSteps;
+ private GestureSandboxFragment mFragment;
private int mCurrentStep;
private int mNumSteps;
@@ -67,10 +70,26 @@ public class GestureSandboxActivity extends FragmentActivity {
mStatsLogManager = StatsLogManager.newInstance(getApplicationContext());
Bundle args = savedInstanceState == null ? getIntent().getExtras() : savedInstanceState;
- mTutorialSteps = getTutorialSteps(args);
- mCurrentTutorialStep = mTutorialSteps[mCurrentStep - 1];
- mFragment = TutorialFragment.newInstance(
- mCurrentTutorialStep, args.getBoolean(KEY_GESTURE_COMPLETE, false));
+
+ boolean gestureComplete = args != null && args.getBoolean(KEY_GESTURE_COMPLETE, false);
+ if (FeatureFlags.ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()
+ && args != null
+ && args.getBoolean(KEY_USE_TUTORIAL_MENU, false)) {
+ mTutorialSteps = null;
+ TutorialType tutorialTypeOverride = (TutorialType) args.get(KEY_TUTORIAL_TYPE);
+ mFragment = tutorialTypeOverride == null
+ ? new MenuFragment()
+ : makeTutorialFragment(
+ tutorialTypeOverride,
+ gestureComplete,
+ /* fromMenu= */ true);
+ } else {
+ mTutorialSteps = getTutorialSteps(args);
+ mFragment = makeTutorialFragment(
+ mTutorialSteps[mCurrentStep - 1],
+ gestureComplete,
+ /* fromMenu= */ false);
+ }
getSupportFragmentManager().beginTransaction()
.add(R.id.gesture_tutorial_fragment_container, mFragment)
.commit();
@@ -81,7 +100,9 @@ public class GestureSandboxActivity extends FragmentActivity {
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
- disableSystemGestures();
+ if (mFragment.shouldDisableSystemGestures()) {
+ disableSystemGestures();
+ }
mFragment.onAttachedToWindow();
}
@@ -103,7 +124,7 @@ public class GestureSandboxActivity extends FragmentActivity {
protected void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
savedInstanceState.putStringArray(KEY_TUTORIAL_STEPS, getTutorialStepNames());
savedInstanceState.putInt(KEY_CURRENT_STEP, mCurrentStep);
- savedInstanceState.putBoolean(KEY_GESTURE_COMPLETE, mFragment.isGestureComplete());
+ mFragment.onSaveInstanceState(savedInstanceState);
super.onSaveInstanceState(savedInstanceState);
}
@@ -134,21 +155,45 @@ public class GestureSandboxActivity extends FragmentActivity {
* If there is no following step, the tutorial is closed.
*/
public void continueTutorial() {
- if (isTutorialComplete()) {
- mFragment.closeTutorial();
+ if (isTutorialComplete() || mTutorialSteps == null) {
+ mFragment.close();
return;
}
- mCurrentTutorialStep = mTutorialSteps[mCurrentStep];
- mFragment = TutorialFragment.newInstance(
- mCurrentTutorialStep, /* gestureComplete= */ false);
+ launchTutorialStep(mTutorialSteps[mCurrentStep], false);
+ mCurrentStep++;
+ }
+
+ private TutorialFragment makeTutorialFragment(
+ @NonNull TutorialType tutorialType, boolean gestureComplete, boolean fromMenu) {
+ return TutorialFragment.newInstance(tutorialType, gestureComplete, fromMenu);
+ }
+
+ /**
+ * Launches the given gesture nav tutorial step.
+ *
+ * If the step is being launched from the gesture nav tutorial menu, then that step will launch
+ * the menu when complete.
+ */
+ public void launchTutorialStep(@NonNull TutorialType tutorialType, boolean fromMenu) {
+ mFragment = makeTutorialFragment(tutorialType, false, fromMenu);
getSupportFragmentManager().beginTransaction()
.replace(R.id.gesture_tutorial_fragment_container, mFragment)
.runOnCommit(() -> mFragment.onAttachedToWindow())
.commit();
- mCurrentStep++;
+ }
+
+ /** Launches the gesture nav tutorial menu page */
+ public void launchTutorialMenu() {
+ mFragment = new MenuFragment();
+ getSupportFragmentManager().beginTransaction()
+ .add(R.id.gesture_tutorial_fragment_container, mFragment)
+ .commit();
}
private String[] getTutorialStepNames() {
+ if (mTutorialSteps == null) {
+ return new String[0];
+ }
String[] tutorialStepNames = new String[mTutorialSteps.length];
int i = 0;
@@ -160,18 +205,19 @@ public class GestureSandboxActivity extends FragmentActivity {
}
private TutorialType[] getTutorialSteps(Bundle extras) {
- TutorialType[] defaultSteps = new TutorialType[] {TutorialType.BACK_NAVIGATION};
+ TutorialType[] defaultSteps = new TutorialType[] {
+ TutorialType.HOME_NAVIGATION,
+ TutorialType.BACK_NAVIGATION,
+ TutorialType.OVERVIEW_NAVIGATION};
mCurrentStep = 1;
- mNumSteps = 1;
+ mNumSteps = defaultSteps.length;
if (extras == null || !extras.containsKey(KEY_TUTORIAL_STEPS)) {
return defaultSteps;
}
- Object savedSteps = extras.get(KEY_TUTORIAL_STEPS);
- int currentStep = extras.getInt(KEY_CURRENT_STEP, -1);
String[] savedStepsNames;
-
+ Object savedSteps = extras.get(KEY_TUTORIAL_STEPS);
if (savedSteps instanceof String) {
savedStepsNames = TextUtils.isEmpty((String) savedSteps)
? null : ((String) savedSteps).split(",");
@@ -181,7 +227,7 @@ public class GestureSandboxActivity extends FragmentActivity {
return defaultSteps;
}
- if (savedStepsNames == null) {
+ if (savedStepsNames == null || savedStepsNames.length == 0) {
return defaultSteps;
}
@@ -190,7 +236,7 @@ public class GestureSandboxActivity extends FragmentActivity {
tutorialSteps[i] = TutorialType.valueOf(savedStepsNames[i]);
}
- mCurrentStep = Math.max(currentStep, 1);
+ mCurrentStep = Math.max(extras.getInt(KEY_CURRENT_STEP, -1), 1);
mNumSteps = tutorialSteps.length;
return tutorialSteps;
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxFragment.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxFragment.java
new file mode 100644
index 0000000000..d52f19a2b5
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxFragment.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.interaction;
+
+import android.app.Activity;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+
+/** Displays one page of the gesture nav tutorial. */
+public abstract class GestureSandboxFragment extends Fragment {
+
+ void onAttachedToWindow() {}
+
+ void onDetachedFromWindow() {}
+
+ boolean shouldDisableSystemGestures() {
+ return true;
+ }
+
+ void close() {
+ FragmentActivity activity = getActivity();
+ if (activity != null) {
+ activity.setResult(Activity.RESULT_OK);
+ activity.finish();
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
index f519d50613..91d337fa53 100644
--- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
@@ -15,6 +15,8 @@
*/
package com.android.quickstep.interaction;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_NEW_GESTURE_NAV_TUTORIAL;
+
import android.annotation.TargetApi;
import android.graphics.PointF;
import android.os.Build;
@@ -33,12 +35,16 @@ final class HomeGestureTutorialController extends SwipeUpGestureTutorialControll
@Override
public int getIntroductionTitle() {
- return R.string.home_gesture_intro_title;
+ return ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()
+ ? R.string.home_gesture_tutorial_title
+ : R.string.home_gesture_intro_title;
}
@Override
public int getIntroductionSubtitle() {
- return R.string.home_gesture_intro_subtitle;
+ return ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()
+ ? R.string.home_gesture_tutorial_subtitle
+ : R.string.home_gesture_intro_subtitle;
}
@Override
@@ -55,9 +61,23 @@ final class HomeGestureTutorialController extends SwipeUpGestureTutorialControll
@Override
protected int getMockAppTaskLayoutResId() {
+ return ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()
+ ? R.layout.swipe_up_gesture_tutorial_shape
+ : mTutorialFragment.isLargeScreen()
+ ? R.layout.gesture_tutorial_tablet_mock_webpage
+ : R.layout.gesture_tutorial_mock_webpage;
+ }
+
+ @Override
+ protected int getGestureLottieAnimationId() {
return mTutorialFragment.isLargeScreen()
- ? R.layout.gesture_tutorial_tablet_mock_webpage
- : R.layout.gesture_tutorial_mock_webpage;
+ ? R.raw.home_gesture_tutorial_tablet_animation
+ : R.raw.home_gesture_tutorial_animation;
+ }
+
+ @Override
+ protected int getSwipeActionColorResId() {
+ return R.color.gesture_home_tutorial_swipe_up_rect;
}
@Override
@@ -72,6 +92,7 @@ final class HomeGestureTutorialController extends SwipeUpGestureTutorialControll
case BACK_COMPLETED_FROM_RIGHT:
case BACK_CANCELLED_FROM_LEFT:
case BACK_CANCELLED_FROM_RIGHT:
+ case BACK_NOT_STARTED_TOO_FAR_FROM_EDGE:
showFeedback(R.string.home_gesture_feedback_swipe_too_far_from_edge);
break;
}
@@ -79,7 +100,7 @@ final class HomeGestureTutorialController extends SwipeUpGestureTutorialControll
case HOME_NAVIGATION_COMPLETE:
if (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT
|| result == BackGestureResult.BACK_COMPLETED_FROM_RIGHT) {
- mTutorialFragment.closeTutorial();
+ mTutorialFragment.close();
}
break;
}
@@ -118,7 +139,7 @@ final class HomeGestureTutorialController extends SwipeUpGestureTutorialControll
break;
case HOME_NAVIGATION_COMPLETE:
if (result == NavBarGestureResult.HOME_GESTURE_COMPLETED) {
- mTutorialFragment.closeTutorial();
+ mTutorialFragment.close();
}
break;
}
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java
index 95eafdae33..bced8c4aef 100644
--- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java
@@ -33,7 +33,13 @@ import java.util.ArrayList;
/** Shows the Home gesture interactive tutorial. */
public class HomeGestureTutorialFragment extends TutorialFragment {
- public HomeGestureTutorialFragment() {}
+ public HomeGestureTutorialFragment() {
+ this(false);
+ }
+
+ public HomeGestureTutorialFragment(boolean fromTutorialMenu) {
+ super(fromTutorialMenu);
+ }
@Nullable
@Override
diff --git a/quickstep/src/com/android/quickstep/interaction/MenuFragment.java b/quickstep/src/com/android/quickstep/interaction/MenuFragment.java
new file mode 100644
index 0000000000..ccff30d319
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/interaction/MenuFragment.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.interaction;
+
+import static com.android.quickstep.interaction.GestureSandboxActivity.KEY_GESTURE_COMPLETE;
+import static com.android.quickstep.interaction.GestureSandboxActivity.KEY_TUTORIAL_TYPE;
+import static com.android.quickstep.interaction.GestureSandboxActivity.KEY_USE_TUTORIAL_MENU;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+
+/** Displays the gesture nav tutorial menu. */
+public final class MenuFragment extends GestureSandboxFragment {
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View root = inflater.inflate(
+ R.layout.gesture_tutorial_step_menu, container, false);
+
+ root.findViewById(R.id.gesture_tutorial_menu_home_button).setOnClickListener(
+ v -> launchTutorialStep(TutorialController.TutorialType.HOME_NAVIGATION));
+ root.findViewById(R.id.gesture_tutorial_menu_back_button).setOnClickListener(
+ v -> launchTutorialStep(TutorialController.TutorialType.BACK_NAVIGATION));
+ root.findViewById(R.id.gesture_tutorial_menu_overview_button).setOnClickListener(
+ v -> launchTutorialStep(TutorialController.TutorialType.OVERVIEW_NAVIGATION));
+ root.findViewById(R.id.gesture_tutorial_menu_done_button).setOnClickListener(
+ v -> close());
+
+ return root;
+ }
+
+ @Override
+ boolean shouldDisableSystemGestures() {
+ return false;
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle savedInstanceState) {
+ savedInstanceState.putBoolean(KEY_USE_TUTORIAL_MENU, true);
+ savedInstanceState.remove(KEY_TUTORIAL_TYPE);
+ savedInstanceState.remove(KEY_GESTURE_COMPLETE);
+ super.onSaveInstanceState(savedInstanceState);
+ }
+
+ private void launchTutorialStep(@NonNull TutorialController.TutorialType tutorialType) {
+ ((GestureSandboxActivity) getActivity()).launchTutorialStep(tutorialType, true);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
index 05b246b906..1b7aee6b7b 100644
--- a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
@@ -16,12 +16,17 @@
package com.android.quickstep.interaction;
import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_NEW_GESTURE_NAV_TUTORIAL;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
+import android.annotation.Nullable;
import android.annotation.TargetApi;
import android.graphics.PointF;
import android.os.Build;
+import android.os.Handler;
+import android.view.View;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatedFloat;
@@ -40,10 +45,11 @@ final class OverviewGestureTutorialController extends SwipeUpGestureTutorialCont
TutorialType tutorialType) {
super(fragment, tutorialType);
}
-
@Override
public int getIntroductionTitle() {
- return R.string.overview_gesture_intro_title;
+ return ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()
+ ? R.string.overview_gesture_tutorial_title
+ : R.string.overview_gesture_intro_title;
}
@Override
@@ -65,9 +71,30 @@ final class OverviewGestureTutorialController extends SwipeUpGestureTutorialCont
@Override
protected int getMockAppTaskLayoutResId() {
+ return ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()
+ ? R.layout.gesture_tutorial_mock_task_view
+ : mTutorialFragment.isLargeScreen()
+ ? R.layout.gesture_tutorial_tablet_mock_conversation_list
+ : R.layout.gesture_tutorial_mock_conversation_list;
+ }
+
+ @Override
+ protected int getGestureLottieAnimationId() {
return mTutorialFragment.isLargeScreen()
- ? R.layout.gesture_tutorial_tablet_mock_conversation_list
- : R.layout.gesture_tutorial_mock_conversation_list;
+ ? R.raw.overview_gesture_tutorial_tablet_animation
+ : R.raw.overview_gesture_tutorial_animation;
+ }
+
+ @Override
+ protected int getSwipeActionColorResId() {
+ return ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()
+ ? R.color.gesture_overview_background
+ : R.color.gesture_overview_tutorial_swipe_rect;
+ }
+
+ @Override
+ protected int getMockPreviousAppTaskThumbnailColorResId() {
+ return R.color.gesture_overview_tutorial_swipe_rect;
}
@Override
@@ -89,7 +116,7 @@ final class OverviewGestureTutorialController extends SwipeUpGestureTutorialCont
case OVERVIEW_NAVIGATION_COMPLETE:
if (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT
|| result == BackGestureResult.BACK_COMPLETED_FROM_RIGHT) {
- mTutorialFragment.closeTutorial();
+ mTutorialFragment.close();
}
break;
}
@@ -116,9 +143,31 @@ final class OverviewGestureTutorialController extends SwipeUpGestureTutorialCont
break;
case OVERVIEW_GESTURE_COMPLETED:
mTutorialFragment.releaseFeedbackAnimation();
- animateTaskViewToOverview();
- onMotionPaused(true /*arbitrary value*/);
- showSuccessFeedback();
+ if (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) {
+ onMotionPaused(true /*arbitrary value*/);
+ animateTaskViewToOverview(() -> {
+ mFakeTaskView.setVisibility(View.INVISIBLE);
+ if(!mTutorialFragment.isLargeScreen()){
+ mFakePreviousTaskView.animateToFillScreen(() -> {
+ mFakeLauncherView.setBackgroundColor(
+ mContext.getColor(
+ R.color.gesture_overview_tutorial_swipe_rect
+ ));
+ showSuccessFeedback();
+ });
+ } else {
+ mFakeLauncherView.setBackgroundColor(
+ mContext.getColor(
+ R.color.gesture_overview_tutorial_swipe_rect
+ ));
+ showSuccessFeedback();
+ }
+ });
+ } else {
+ animateTaskViewToOverview(null);
+ onMotionPaused(true /*arbitrary value*/);
+ showSuccessFeedback();
+ }
break;
case HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION:
case HOME_OR_OVERVIEW_CANCELLED:
@@ -129,17 +178,30 @@ final class OverviewGestureTutorialController extends SwipeUpGestureTutorialCont
break;
case OVERVIEW_NAVIGATION_COMPLETE:
if (result == NavBarGestureResult.HOME_GESTURE_COMPLETED) {
- mTutorialFragment.closeTutorial();
+ mTutorialFragment.close();
}
break;
}
}
- public void animateTaskViewToOverview() {
+ /**
+ * runnable executed with slight delay to ease the swipe animation after landing on overview
+ * @param runnable
+ */
+ public void animateTaskViewToOverview(@Nullable Runnable runnable) {
PendingAnimation anim = new PendingAnimation(TASK_VIEW_END_ANIMATION_DURATION_MILLIS);
anim.setFloat(mTaskViewSwipeUpAnimation
.getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ if (runnable != null) {
+ new Handler().postDelayed(runnable, 300);
+ }
+ }
+ });
+
ArrayList animators = new ArrayList<>();
if (mTutorialFragment.isLargeScreen()) {
@@ -154,7 +216,6 @@ final class OverviewGestureTutorialController extends SwipeUpGestureTutorialCont
AnimatorSet animset = new AnimatorSet();
animset.playTogether(animators);
- hideFakeTaskbar(/* animateToHotseat= */ false);
animset.start();
mRunningWindowAnim = SwipeUpAnimationLogic.RunningWindowAnim.wrap(animset);
}
diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java
index 4e1521f161..c471a13869 100644
--- a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java
@@ -33,7 +33,13 @@ import java.util.ArrayList;
/** Shows the Overview gesture interactive tutorial. */
public class OverviewGestureTutorialFragment extends TutorialFragment {
- public OverviewGestureTutorialFragment() {}
+ public OverviewGestureTutorialFragment() {
+ this(false);
+ }
+
+ public OverviewGestureTutorialFragment(boolean fromTutorialMenu) {
+ super(fromTutorialMenu);
+ }
@Nullable
@Override
@@ -67,7 +73,7 @@ public class OverviewGestureTutorialFragment extends TutorialFragment {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
- controller.animateTaskViewToOverview();
+ controller.animateTaskViewToOverview(null);
}
});
@@ -76,7 +82,7 @@ public class OverviewGestureTutorialFragment extends TutorialFragment {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
- controller.resetFakeTaskView(false);
+ controller.resetFakeTaskViewFromOverview();
}
});
ArrayList animators = new ArrayList<>();
diff --git a/quickstep/src/com/android/quickstep/interaction/RootSandboxLayout.java b/quickstep/src/com/android/quickstep/interaction/RootSandboxLayout.java
index ac0c17d72f..b5084843b8 100644
--- a/quickstep/src/com/android/quickstep/interaction/RootSandboxLayout.java
+++ b/quickstep/src/com/android/quickstep/interaction/RootSandboxLayout.java
@@ -15,17 +15,35 @@
*/
package com.android.quickstep.interaction;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_NEW_GESTURE_NAV_TUTORIAL;
+
import android.content.Context;
import android.graphics.Insets;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
+import android.view.View;
import android.view.WindowInsets;
import android.widget.RelativeLayout;
+import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentManager;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+
/** Root layout that TutorialFragment uses to intercept motion events. */
public class RootSandboxLayout extends RelativeLayout {
+
+ private final Rect mTempStepIndicatorBounds = new Rect();
+ private final Rect mTempInclusionBounds = new Rect();
+ private final Rect mTempExclusionBounds = new Rect();
+
+ private View mFeedbackView;
+ private View mTutorialStepView;
+ private View mSkipButton;
+ private View mDoneButton;
+
public RootSandboxLayout(Context context) {
super(context);
}
@@ -52,4 +70,56 @@ public class RootSandboxLayout extends RelativeLayout {
return getHeight() + insets.top + insets.bottom;
}
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ if (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) {
+ return;
+ }
+ mFeedbackView = findViewById(R.id.gesture_tutorial_fragment_feedback_view);
+ mTutorialStepView =
+ mFeedbackView.findViewById(R.id.gesture_tutorial_fragment_feedback_tutorial_step);
+ mSkipButton = mFeedbackView.findViewById(R.id.gesture_tutorial_fragment_close_button);
+ mDoneButton = mFeedbackView.findViewById(R.id.gesture_tutorial_fragment_action_button);
+
+ mFeedbackView.addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ if (mSkipButton.getVisibility() != VISIBLE
+ && mDoneButton.getVisibility() != VISIBLE) {
+ return;
+ }
+ // Either the skip or the done button is ever shown at once, never both.
+ boolean showingSkipButton = mSkipButton.getVisibility() == VISIBLE;
+ boolean isRTL = Utilities.isRtl(getContext().getResources());
+ updateTutorialStepViewTranslation(
+ showingSkipButton ? mSkipButton : mDoneButton,
+ // Translate the step indicator away from whichever button is being
+ // shown. The skip button in on the left in LTR or on the right in RTL.
+ // The done button is on the right in LTR or left in RTL.
+ (showingSkipButton && !isRTL) || (!showingSkipButton && isRTL));
+ });
+ }
+
+ private void updateTutorialStepViewTranslation(
+ @NonNull View anchorView, boolean translateToRight) {
+ mTempStepIndicatorBounds.set(
+ mTutorialStepView.getLeft(),
+ mTutorialStepView.getTop(),
+ mTutorialStepView.getRight(),
+ mTutorialStepView.getBottom());
+ mTempInclusionBounds.set(0, 0, mFeedbackView.getWidth(), mFeedbackView.getHeight());
+ mTempExclusionBounds.set(
+ anchorView.getLeft(),
+ anchorView.getTop(),
+ anchorView.getRight(),
+ anchorView.getBottom());
+
+ Utilities.translateOverlappingView(
+ mTutorialStepView,
+ mTempStepIndicatorBounds,
+ mTempInclusionBounds,
+ mTempExclusionBounds,
+ translateToRight ? Utilities.TRANSLATE_RIGHT : Utilities.TRANSLATE_LEFT);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/interaction/SandboxModeTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/SandboxModeTutorialFragment.java
index 5183e2c961..7bd52f750d 100644
--- a/quickstep/src/com/android/quickstep/interaction/SandboxModeTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/SandboxModeTutorialFragment.java
@@ -26,7 +26,9 @@ import com.android.quickstep.interaction.TutorialController.TutorialType;
/** Shows the general navigation gesture sandbox environment. */
public class SandboxModeTutorialFragment extends TutorialFragment {
- public SandboxModeTutorialFragment() {}
+ public SandboxModeTutorialFragment(boolean fromTutorialMenu) {
+ super(fromTutorialMenu);
+ }
@Override
TutorialController createController(TutorialType type) {
diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
index 670ee9b333..b3243ff31f 100644
--- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
@@ -203,7 +203,15 @@ abstract class SwipeUpGestureTutorialController extends TutorialController {
mRunningWindowAnim = RunningWindowAnim.wrap(animset);
}
+ void resetFakeTaskViewFromOverview() {
+ resetFakeTaskView(false, false);
+ }
+
void resetFakeTaskView(boolean animateFromHome) {
+ resetFakeTaskView(animateFromHome, true);
+ }
+
+ void resetFakeTaskView(boolean animateFromHome, boolean animateTaskbar) {
mFakeTaskView.setVisibility(View.VISIBLE);
PendingAnimation anim = new PendingAnimation(300);
anim.setFloat(mTaskViewSwipeUpAnimation
@@ -211,7 +219,9 @@ abstract class SwipeUpGestureTutorialController extends TutorialController {
anim.setViewAlpha(mFakeTaskView, 1, ACCEL);
anim.addListener(mResetTaskView);
AnimatorSet animset = anim.buildAnim();
- showFakeTaskbar(animateFromHome);
+ if (animateTaskbar) {
+ showFakeTaskbar(animateFromHome);
+ }
animset.start();
mRunningWindowAnim = RunningWindowAnim.wrap(animset);
}
@@ -344,7 +354,7 @@ abstract class SwipeUpGestureTutorialController extends TutorialController {
mFakeIconView.setVisibility(View.VISIBLE);
mFakeIconView.update(rect, progress,
1f - SHAPE_PROGRESS_DURATION /* shapeProgressStart */,
- radius, 255,
+ radius,
false, /* isOpening */
mFakeIconView, mDp);
mFakeIconView.setAlpha(1);
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index fa7d848a7c..6f50e3e4a5 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -19,20 +19,26 @@ import static android.view.View.GONE;
import static android.view.View.NO_ID;
import static android.view.View.inflate;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_NEW_GESTURE_NAV_TUTORIAL;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.ColorRes;
+import android.annotation.RawRes;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.graphics.Outline;
+import android.graphics.Rect;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.RippleDrawable;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
import android.view.accessibility.AccessibilityEvent;
import android.widget.Button;
import android.widget.FrameLayout;
@@ -56,6 +62,9 @@ import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.views.ClipIconView;
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureAttemptCallback;
import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureAttemptCallback;
+import com.android.systemui.shared.system.QuickStepContract;
+
+import com.airbnb.lottie.LottieAnimationView;
import java.util.ArrayList;
@@ -76,6 +85,11 @@ abstract class TutorialController implements BackGestureAttemptCallback,
private static final int GESTURE_ANIMATION_DELAY_MS = 1500;
private static final int ADVANCE_TUTORIAL_TIMEOUT_MS = 2000;
private static final long GESTURE_ANIMATION_PAUSE_DURATION_MILLIS = 1000;
+ protected float mExitingAppEndingCornerRadius;
+ protected float mExitingAppStartingCornerRadius;
+ protected int mScreenHeight;
+ protected float mScreenWidth;
+ protected float mExitingAppMargin;
final TutorialFragment mTutorialFragment;
TutorialType mTutorialType;
@@ -85,6 +99,7 @@ abstract class TutorialController implements BackGestureAttemptCallback,
final Button mDoneButton;
final ViewGroup mFeedbackView;
final TextView mFeedbackTitleView;
+ final TextView mFeedbackSubtitleView;
final ImageView mEdgeGestureVideoView;
final RelativeLayout mFakeLauncherView;
final FrameLayout mFakeHotseatView;
@@ -97,9 +112,15 @@ abstract class TutorialController implements BackGestureAttemptCallback,
final RippleDrawable mRippleDrawable;
final TutorialStepIndicator mTutorialStepView;
final ImageView mFingerDotView;
+ private final Rect mExitingAppRect = new Rect();
+ protected View mExitingAppView;
+ protected int mExitingAppRadius;
private final AlertDialog mSkipTutorialDialog;
private boolean mGestureCompleted = false;
+ private LottieAnimationView mAnimatedGestureDemonstration;
+ private LottieAnimationView mCheckmarkAnimation;
+ private RelativeLayout mFullGestureDemonstration;
// These runnables should be used when posting callbacks to their views and cleared from their
// views before posting new callbacks.
@@ -120,6 +141,8 @@ abstract class TutorialController implements BackGestureAttemptCallback,
mFeedbackView = rootView.findViewById(R.id.gesture_tutorial_fragment_feedback_view);
mFeedbackTitleView = mFeedbackView.findViewById(
R.id.gesture_tutorial_fragment_feedback_title);
+ mFeedbackSubtitleView = mFeedbackView.findViewById(
+ R.id.gesture_tutorial_fragment_feedback_subtitle);
mEdgeGestureVideoView = rootView.findViewById(R.id.gesture_tutorial_edge_gesture_video);
mFakeLauncherView = rootView.findViewById(R.id.gesture_tutorial_fake_launcher_view);
mFakeHotseatView = rootView.findViewById(R.id.gesture_tutorial_fake_hotseat_view);
@@ -136,6 +159,31 @@ abstract class TutorialController implements BackGestureAttemptCallback,
mFingerDotView = rootView.findViewById(R.id.gesture_tutorial_finger_dot);
mSkipTutorialDialog = createSkipTutorialDialog();
+ if (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) {
+ mFullGestureDemonstration = rootView.findViewById(R.id.full_gesture_demonstration);
+ mCheckmarkAnimation = rootView.findViewById(R.id.checkmark_animation);
+ mAnimatedGestureDemonstration = rootView.findViewById(
+ R.id.gesture_demonstration_animations);
+ mExitingAppView = rootView.findViewById(R.id.exiting_app_back);
+ mScreenWidth = mTutorialFragment.getDeviceProfile().widthPx;
+ mScreenHeight = mTutorialFragment.getDeviceProfile().heightPx;
+ mExitingAppMargin = mContext.getResources().getDimensionPixelSize(
+ R.dimen.gesture_tutorial_back_gesture_exiting_app_margin);
+ mExitingAppStartingCornerRadius = QuickStepContract.getWindowCornerRadius(mContext);
+ mExitingAppEndingCornerRadius = mContext.getResources().getDimensionPixelSize(
+ R.dimen.gesture_tutorial_back_gesture_end_corner_radius);
+
+ mFeedbackTitleView.setText(getIntroductionTitle());
+ mFeedbackSubtitleView.setText(getIntroductionSubtitle());
+ mExitingAppView.setClipToOutline(true);
+ mExitingAppView.setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRoundRect(mExitingAppRect, mExitingAppRadius);
+ }
+ });
+ }
+
mTitleViewCallback = () -> mFeedbackTitleView.sendAccessibilityEvent(
AccessibilityEvent.TYPE_VIEW_FOCUSED);
mShowFeedbackRunnable = () -> {
@@ -189,12 +237,19 @@ abstract class TutorialController implements BackGestureAttemptCallback,
? (mTutorialFragment.isFoldable()
? R.layout.gesture_tutorial_foldable_mock_hotseat
: R.layout.gesture_tutorial_tablet_mock_hotseat)
- : R.layout.gesture_tutorial_mock_hotseat;
+ : (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()
+ ? R.layout.redesigned_gesture_tutorial_mock_hotseat
+ : R.layout.gesture_tutorial_mock_hotseat);
}
@LayoutRes
protected int getMockAppTaskLayoutResId() {
- return View.NO_ID;
+ return NO_ID;
+ }
+
+ @RawRes
+ protected int getGestureLottieAnimationId() {
+ return NO_ID;
}
@ColorRes
@@ -202,9 +257,16 @@ abstract class TutorialController implements BackGestureAttemptCallback,
return R.color.gesture_tutorial_fake_previous_task_view_color;
}
+ @ColorRes
+ protected int getSwipeActionColorResId() {
+ return NO_ID;
+ }
+
@DrawableRes
public int getMockAppIconResId() {
- return R.drawable.default_sandbox_app_icon;
+ return ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()
+ ? R.drawable.redesigned_default_sandbox_app_icon
+ : R.drawable.default_sandbox_app_icon;
}
@DrawableRes
@@ -301,9 +363,7 @@ abstract class TutorialController implements BackGestureAttemptCallback,
}
mFeedbackTitleView.setText(titleResId);
- TextView subtitle =
- mFeedbackView.findViewById(R.id.gesture_tutorial_fragment_feedback_subtitle);
- subtitle.setText(spokenSubtitleResId == NO_ID
+ mFeedbackSubtitleView.setText(spokenSubtitleResId == NO_ID
? mContext.getText(subtitleResId)
: Utilities.wrapForTts(
mContext.getText(subtitleResId), mContext.getString(spokenSubtitleResId)));
@@ -316,6 +376,10 @@ abstract class TutorialController implements BackGestureAttemptCallback,
mFakeTaskView.removeCallbacks(mFakeTaskViewCallback);
mFakeTaskViewCallback = null;
}
+
+ if (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) {
+ showSuccessPage();
+ }
}
mGestureCompleted = isGestureSuccessful;
@@ -336,6 +400,14 @@ abstract class TutorialController implements BackGestureAttemptCallback,
mFeedbackView.post(mFeedbackViewCallback);
}
+ private void showSuccessPage() {
+ mCheckmarkAnimation.setVisibility(View.VISIBLE);
+ mCheckmarkAnimation.playAnimation();
+ mFeedbackTitleView.setTextAppearance(R.style.TextAppearance_GestureTutorial_SuccessTitle);
+ mFeedbackSubtitleView.setTextAppearance(
+ R.style.TextAppearance_GestureTutorial_SuccessSubtitle);
+ }
+
public boolean isGestureCompleted() {
return mGestureCompleted;
}
@@ -371,6 +443,14 @@ abstract class TutorialController implements BackGestureAttemptCallback,
@NonNull Runnable onStartRunnable,
boolean useGestureAnimationDelay) {
+ if (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) {
+ mFeedbackView.setVisibility(View.VISIBLE);
+ mAnimatedGestureDemonstration.setVisibility(View.VISIBLE);
+ mFullGestureDemonstration.setVisibility(View.VISIBLE);
+ mAnimatedGestureDemonstration.playAnimation();
+ return;
+ }
+
if (gestureAnimation.isRunning()) {
gestureAnimation.cancel();
}
@@ -436,19 +516,56 @@ abstract class TutorialController implements BackGestureAttemptCallback,
@CallSuper
void transitToController() {
- hideFeedback();
- hideActionButton();
updateCloseButton();
updateSubtext();
updateDrawables();
updateLayout();
+ if (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) {
+ mCheckmarkAnimation.setAnimation(mTutorialFragment.isAtFinalStep()
+ ? R.raw.checkmark_animation_end
+ : R.raw.checkmark_animation_in_progress);
+ if (!isGestureCompleted()) {
+ mCheckmarkAnimation.setVisibility(GONE);
+ startGestureAnimation();
+ if (mTutorialType == TutorialType.BACK_NAVIGATION) {
+ resetViewsForBackGesture();
+ }
+
+ }
+ } else {
+ hideFeedback();
+ hideActionButton();
+ }
+
mGestureCompleted = false;
if (mFakeHotseatView != null) {
mFakeHotseatView.setVisibility(View.INVISIBLE);
}
}
+ protected void resetViewsForBackGesture() {
+ mFakeTaskView.setVisibility(View.VISIBLE);
+ mFakeTaskView.setBackgroundColor(
+ mContext.getColor(R.color.gesture_back_tutorial_background));
+ mExitingAppView.setVisibility(View.VISIBLE);
+
+ // reset the exiting app's dimensions
+ mExitingAppRect.set(0, 0, (int) mScreenWidth, (int) mScreenHeight);
+ mExitingAppRadius = 0;
+ mExitingAppView.resetPivot();
+ mExitingAppView.setScaleX(1f);
+ mExitingAppView.setScaleY(1f);
+ mExitingAppView.setTranslationX(0);
+ mExitingAppView.setTranslationY(0);
+ mExitingAppView.invalidateOutline();
+ }
+
+ private void startGestureAnimation() {
+ mAnimatedGestureDemonstration.setAnimation(getGestureLottieAnimationId());
+ mAnimatedGestureDemonstration.playAnimation();
+ }
+
void updateCloseButton() {
mSkipButton.setTextAppearance(Utilities.isDarkTheme(mContext)
? R.style.TextAppearance_GestureTutorial_Feedback_Subtext
@@ -478,8 +595,6 @@ abstract class TutorialController implements BackGestureAttemptCallback,
if (animateToHotseat) {
mFakeTaskbarViewCallback = () ->
mFakeTaskbarView.animateDisappearanceToHotseat(mFakeHotseatView);
- } else {
- mFakeTaskbarViewCallback = mFakeTaskbarView::animateDisappearanceToBottom;
}
mFakeTaskbarView.post(mFakeTaskbarViewCallback);
}
@@ -494,8 +609,6 @@ abstract class TutorialController implements BackGestureAttemptCallback,
if (animateFromHotseat) {
mFakeTaskbarViewCallback = () ->
mFakeTaskbarView.animateAppearanceFromHotseat(mFakeHotseatView);
- } else {
- mFakeTaskbarViewCallback = mFakeTaskbarView::animateAppearanceFromBottom;
}
mFakeTaskbarView.post(mFakeTaskbarViewCallback);
}
@@ -516,8 +629,10 @@ abstract class TutorialController implements BackGestureAttemptCallback,
}
private void updateSubtext() {
- mTutorialStepView.setTutorialProgress(
- mTutorialFragment.getCurrentStep(), mTutorialFragment.getNumSteps());
+ if (!ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) {
+ mTutorialStepView.setTutorialProgress(
+ mTutorialFragment.getCurrentStep(), mTutorialFragment.getNumSteps());
+ }
}
private void updateDrawables() {
@@ -536,6 +651,11 @@ abstract class TutorialController implements BackGestureAttemptCallback,
getMockPreviousAppTaskThumbnailColorResId()));
mFakeIconView.setBackground(AppCompatResources.getDrawable(
mContext, getMockAppIconResId()));
+
+ if (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) {
+ mFakeLauncherView.setBackgroundColor(
+ mContext.getColor(getSwipeActionColorResId()));
+ }
}
}
@@ -558,7 +678,8 @@ abstract class TutorialController implements BackGestureAttemptCallback,
? R.dimen.gesture_tutorial_tablet_feedback_margin_top
: R.dimen.gesture_tutorial_feedback_margin_top);
- mFakeTaskbarView.setVisibility(mTutorialFragment.isLargeScreen() ? View.VISIBLE : GONE);
+ mFakeTaskbarView.setVisibility((mTutorialFragment.isLargeScreen()
+ && !ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) ? View.VISIBLE : GONE);
RelativeLayout.LayoutParams hotseatLayoutParams =
(RelativeLayout.LayoutParams) mFakeHotseatView.getLayoutParams();
@@ -582,66 +703,65 @@ abstract class TutorialController implements BackGestureAttemptCallback,
}
private AlertDialog createSkipTutorialDialog() {
- if (mContext instanceof GestureSandboxActivity) {
- GestureSandboxActivity sandboxActivity = (GestureSandboxActivity) mContext;
- View contentView = View.inflate(
- sandboxActivity, R.layout.gesture_tutorial_dialog, null);
- AlertDialog tutorialDialog = new AlertDialog
- .Builder(sandboxActivity, R.style.Theme_AppCompat_Dialog_Alert)
- .setView(contentView)
- .create();
+ if (!(mContext instanceof GestureSandboxActivity)) {
+ return null;
+ }
+ GestureSandboxActivity sandboxActivity = (GestureSandboxActivity) mContext;
+ View contentView = View.inflate(
+ sandboxActivity, R.layout.gesture_tutorial_dialog, null);
+ AlertDialog tutorialDialog = new AlertDialog
+ .Builder(sandboxActivity, R.style.Theme_AppCompat_Dialog_Alert)
+ .setView(contentView)
+ .create();
- PackageManager packageManager = mContext.getPackageManager();
- CharSequence tipsAppName = DEFAULT_PIXEL_TIPS_APP_NAME;
+ PackageManager packageManager = mContext.getPackageManager();
+ CharSequence tipsAppName = DEFAULT_PIXEL_TIPS_APP_NAME;
- try {
- tipsAppName = packageManager.getApplicationLabel(
- packageManager.getApplicationInfo(
- PIXEL_TIPS_APP_PACKAGE_NAME, PackageManager.GET_META_DATA));
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(LOG_TAG,
- "Could not find app label for package name: "
- + PIXEL_TIPS_APP_PACKAGE_NAME
- + ". Defaulting to 'Pixel Tips.'",
- e);
- }
-
- TextView subtitleTextView = (TextView) contentView.findViewById(
- R.id.gesture_tutorial_dialog_subtitle);
- if (subtitleTextView != null) {
- subtitleTextView.setText(
- mContext.getString(R.string.skip_tutorial_dialog_subtitle, tipsAppName));
- } else {
- Log.w(LOG_TAG, "No subtitle view in the skip tutorial dialog to update.");
- }
-
- Button cancelButton = (Button) contentView.findViewById(
- R.id.gesture_tutorial_dialog_cancel_button);
- if (cancelButton != null) {
- cancelButton.setOnClickListener(
- v -> tutorialDialog.dismiss());
- } else {
- Log.w(LOG_TAG, "No cancel button in the skip tutorial dialog to update.");
- }
-
- Button confirmButton = contentView.findViewById(
- R.id.gesture_tutorial_dialog_confirm_button);
- if (confirmButton != null) {
- confirmButton.setOnClickListener(v -> {
- mTutorialFragment.closeTutorial(true);
- tutorialDialog.dismiss();
- });
- } else {
- Log.w(LOG_TAG, "No confirm button in the skip tutorial dialog to update.");
- }
-
- tutorialDialog.getWindow().setBackgroundDrawable(
- new ColorDrawable(sandboxActivity.getColor(android.R.color.transparent)));
-
- return tutorialDialog;
+ try {
+ tipsAppName = packageManager.getApplicationLabel(
+ packageManager.getApplicationInfo(
+ PIXEL_TIPS_APP_PACKAGE_NAME, PackageManager.GET_META_DATA));
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(LOG_TAG,
+ "Could not find app label for package name: "
+ + PIXEL_TIPS_APP_PACKAGE_NAME
+ + ". Defaulting to 'Pixel Tips.'",
+ e);
}
- return null;
+ TextView subtitleTextView = (TextView) contentView.findViewById(
+ R.id.gesture_tutorial_dialog_subtitle);
+ if (subtitleTextView != null) {
+ subtitleTextView.setText(
+ mContext.getString(R.string.skip_tutorial_dialog_subtitle, tipsAppName));
+ } else {
+ Log.w(LOG_TAG, "No subtitle view in the skip tutorial dialog to update.");
+ }
+
+ Button cancelButton = (Button) contentView.findViewById(
+ R.id.gesture_tutorial_dialog_cancel_button);
+ if (cancelButton != null) {
+ cancelButton.setOnClickListener(
+ v -> tutorialDialog.dismiss());
+ } else {
+ Log.w(LOG_TAG, "No cancel button in the skip tutorial dialog to update.");
+ }
+
+ Button confirmButton = contentView.findViewById(
+ R.id.gesture_tutorial_dialog_confirm_button);
+ if (confirmButton != null) {
+ confirmButton.setOnClickListener(v -> {
+ mTutorialFragment.closeTutorialStep(true);
+ tutorialDialog.dismiss();
+ });
+ } else {
+ Log.w(LOG_TAG, "No confirm button in the skip tutorial dialog to update.");
+ }
+
+ tutorialDialog.getWindow().setBackgroundDrawable(
+ new ColorDrawable(sandboxActivity.getColor(android.R.color.transparent)));
+
+ return tutorialDialog;
}
protected AnimatorSet createFingerDotAppearanceAnimatorSet() {
@@ -690,6 +810,12 @@ abstract class TutorialController implements BackGestureAttemptCallback,
return ValueAnimator.ofFloat(0f, 1f).setDuration(GESTURE_ANIMATION_PAUSE_DURATION_MILLIS);
}
+ void pauseAndHideLottieAnimation() {
+ mAnimatedGestureDemonstration.pauseAnimation();
+ mAnimatedGestureDemonstration.setVisibility(View.INVISIBLE);
+ mFullGestureDemonstration.setVisibility(View.INVISIBLE);
+ }
+
/** Denotes the type of the tutorial. */
enum TutorialType {
BACK_NAVIGATION,
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
index 1599c8c32d..3faa7e477a 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
@@ -17,10 +17,14 @@ package com.android.quickstep.interaction;
import static android.view.View.NO_ID;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_NEW_GESTURE_NAV_TUTORIAL;
+import static com.android.quickstep.interaction.GestureSandboxActivity.KEY_GESTURE_COMPLETE;
+import static com.android.quickstep.interaction.GestureSandboxActivity.KEY_TUTORIAL_TYPE;
+import static com.android.quickstep.interaction.GestureSandboxActivity.KEY_USE_TUTORIAL_MENU;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.Activity;
-import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Insets;
@@ -41,8 +45,6 @@ import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
@@ -52,16 +54,17 @@ import com.android.quickstep.interaction.TutorialController.TutorialType;
import java.util.Set;
-abstract class TutorialFragment extends Fragment implements OnTouchListener {
+/** Displays a gesture nav tutorial step. */
+abstract class TutorialFragment extends GestureSandboxFragment implements OnTouchListener {
private static final String LOG_TAG = "TutorialFragment";
- static final String KEY_TUTORIAL_TYPE = "tutorial_type";
- static final String KEY_GESTURE_COMPLETE = "gesture_complete";
private static final String TUTORIAL_SKIPPED_PREFERENCE_KEY = "pref_gestureTutorialSkipped";
private static final String COMPLETED_TUTORIAL_STEPS_PREFERENCE_KEY =
"pref_completedTutorialSteps";
+ private final boolean mFromTutorialMenu;
+
TutorialType mTutorialType;
boolean mGestureComplete = false;
@Nullable TutorialController mTutorialController = null;
@@ -83,10 +86,10 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener {
private boolean mIsFoldable;
public static TutorialFragment newInstance(
- TutorialType tutorialType, boolean gestureComplete) {
- TutorialFragment fragment = getFragmentForTutorialType(tutorialType);
+ TutorialType tutorialType, boolean gestureComplete, boolean fromTutorialMenu) {
+ TutorialFragment fragment = getFragmentForTutorialType(tutorialType, fromTutorialMenu);
if (fragment == null) {
- fragment = new BackGestureTutorialFragment();
+ fragment = new BackGestureTutorialFragment(fromTutorialMenu);
tutorialType = TutorialType.BACK_NAVIGATION;
}
@@ -97,23 +100,28 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener {
return fragment;
}
+ TutorialFragment(boolean fromTutorialMenu) {
+ mFromTutorialMenu = fromTutorialMenu;
+ }
+
@Nullable
- private static TutorialFragment getFragmentForTutorialType(TutorialType tutorialType) {
+ private static TutorialFragment getFragmentForTutorialType(
+ TutorialType tutorialType, boolean fromTutorialMenu) {
switch (tutorialType) {
case BACK_NAVIGATION:
case BACK_NAVIGATION_COMPLETE:
- return new BackGestureTutorialFragment();
+ return new BackGestureTutorialFragment(fromTutorialMenu);
case HOME_NAVIGATION:
case HOME_NAVIGATION_COMPLETE:
- return new HomeGestureTutorialFragment();
+ return new HomeGestureTutorialFragment(fromTutorialMenu);
case OVERVIEW_NAVIGATION:
case OVERVIEW_NAVIGATION_COMPLETE:
- return new OverviewGestureTutorialFragment();
+ return new OverviewGestureTutorialFragment(fromTutorialMenu);
case ASSISTANT:
case ASSISTANT_COMPLETE:
- return new AssistantGestureTutorialFragment();
+ return new AssistantGestureTutorialFragment(fromTutorialMenu);
case SANDBOX_MODE:
- return new SandboxModeTutorialFragment();
+ return new SandboxModeTutorialFragment(fromTutorialMenu);
default:
Log.e(LOG_TAG, "Failed to find an appropriate fragment for " + tutorialType.name());
}
@@ -184,7 +192,12 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener {
super.onCreateView(inflater, container, savedInstanceState);
mRootView = (RootSandboxLayout) inflater.inflate(
- R.layout.gesture_tutorial_fragment, container, false);
+ ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()
+ ? R.layout.redesigned_gesture_tutorial_fragment
+ : R.layout.gesture_tutorial_fragment,
+ container,
+ false);
+
mRootView.setOnApplyWindowInsetsListener((view, insets) -> {
Insets systemInsets = insets.getInsets(WindowInsets.Type.systemBars());
mEdgeBackGestureHandler.setInsets(systemInsets.left, systemInsets.right);
@@ -195,6 +208,7 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener {
mFingerDotView = mRootView.findViewById(R.id.gesture_tutorial_finger_dot);
mFakePreviousTaskView = mRootView.findViewById(
R.id.gesture_tutorial_fake_previous_task_view);
+
return mRootView;
}
@@ -305,7 +319,6 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener {
if (mEdgeAnimation != null && mEdgeAnimation.isRunning()) {
mEdgeAnimation.stop();
}
-
mEdgeGestureVideoView.setVisibility(View.GONE);
}
@@ -333,6 +346,11 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener {
if (mTutorialController != null && !isGestureComplete()) {
mTutorialController.hideFeedback();
}
+
+ if (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) {
+ mTutorialController.pauseAndHideLottieAnimation();
+ }
+
// Note: Using logical-or to ensure both functions get called.
return mEdgeBackGestureHandler.onTouch(view, motionEvent)
| mNavBarGestureHandler.onTouch(view, motionEvent);
@@ -344,6 +362,7 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener {
| mNavBarGestureHandler.onInterceptTouch(motionEvent);
}
+ @Override
void onAttachedToWindow() {
StatsLogManager statsLogManager = getStatsLogManager();
if (statsLogManager != null) {
@@ -352,6 +371,7 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener {
mEdgeBackGestureHandler.setViewGroupParent(getRootView());
}
+ @Override
void onDetachedFromWindow() {
mEdgeBackGestureHandler.setViewGroupParent(null);
}
@@ -374,6 +394,8 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener {
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putSerializable(KEY_TUTORIAL_TYPE, mTutorialType);
+ savedInstanceState.putBoolean(KEY_GESTURE_COMPLETE, isGestureComplete());
+ savedInstanceState.putBoolean(KEY_USE_TUTORIAL_MENU, mFromTutorialMenu);
super.onSaveInstanceState(savedInstanceState);
}
@@ -399,17 +421,18 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener {
GestureSandboxActivity gestureSandboxActivity = getGestureSandboxActivity();
if (gestureSandboxActivity == null) {
- closeTutorial();
+ close();
return;
}
gestureSandboxActivity.continueTutorial();
}
- void closeTutorial() {
- closeTutorial(false);
+ @Override
+ void close() {
+ closeTutorialStep(false);
}
- void closeTutorial(boolean tutorialSkipped) {
+ void closeTutorialStep(boolean tutorialSkipped) {
if (tutorialSkipped) {
SharedPreferences sharedPrefs = getSharedPreferences();
if (sharedPrefs != null) {
@@ -421,11 +444,12 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener {
StatsLogManager.LauncherEvent.LAUNCHER_GESTURE_TUTORIAL_SKIPPED);
}
}
- FragmentActivity activity = getActivity();
- if (activity != null) {
- activity.setResult(Activity.RESULT_OK);
- activity.finish();
+ GestureSandboxActivity gestureSandboxActivity = getGestureSandboxActivity();
+ if (mFromTutorialMenu && gestureSandboxActivity != null) {
+ gestureSandboxActivity.launchTutorialMenu();
+ return;
}
+ super.close();
}
void startSystemNavigationSetting() {
@@ -459,9 +483,10 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener {
@Nullable
private GestureSandboxActivity getGestureSandboxActivity() {
- Context context = getContext();
+ Activity activity = getActivity();
- return context instanceof GestureSandboxActivity ? (GestureSandboxActivity) context : null;
+ return activity instanceof GestureSandboxActivity
+ ? (GestureSandboxActivity) activity : null;
}
@Nullable
diff --git a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
index 5efc45e385..3d5c143413 100644
--- a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
+++ b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
@@ -16,6 +16,7 @@
package com.android.quickstep.logging;
+import static com.android.launcher3.LauncherPrefs.THEMED_ICONS;
import static com.android.launcher3.LauncherPrefs.getDevicePrefs;
import static com.android.launcher3.LauncherPrefs.getPrefs;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_SCREEN_SUGGESTIONS_DISABLED;
@@ -39,6 +40,7 @@ import android.util.Log;
import android.util.Xml;
import com.android.launcher3.AutoInstallsLayout;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager;
@@ -178,7 +180,7 @@ public class SettingsChangeLogger implements
logger::log);
SharedPreferences prefs = getPrefs(mContext);
- logger.log(prefs.getBoolean(KEY_THEMED_ICONS, false)
+ logger.log(LauncherPrefs.get(mContext).get(THEMED_ICONS)
? LAUNCHER_THEMED_ICON_ENABLED
: LAUNCHER_THEMED_ICON_DISABLED);
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 0a155cbd47..4690d94175 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -139,7 +139,7 @@ public class StatsLogCompatManager extends StatsLogManager {
if (IS_VERBOSE) {
Log.d(TAG, String.format("\nwriteSnapshot(%d):\n%s", instanceId.getId(), info));
}
- if (!Utilities.ATLEAST_R || Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ if (!Utilities.ATLEAST_R || Utilities.isRunningInTestHarness()) {
return;
}
SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_SNAPSHOT,
@@ -438,7 +438,7 @@ public class StatsLogCompatManager extends StatsLogManager {
}
// TODO: remove this when b/231648228 is fixed.
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ if (Utilities.isRunningInTestHarness()) {
return;
}
int cardinality = mCardinality.orElseGet(() -> getCardinality(atomInfo));
@@ -636,7 +636,7 @@ public class StatsLogCompatManager extends StatsLogManager {
}
private static int getCardinality(LauncherAtom.ItemInfo info) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ if (Utilities.isRunningInTestHarness()) {
return 0;
}
switch (info.getContainerInfo().getContainerCase()) {
@@ -758,7 +758,7 @@ public class StatsLogCompatManager extends StatsLogManager {
}
private static int getHierarchy(LauncherAtom.ItemInfo info) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ if (Utilities.isRunningInTestHarness()) {
return 0;
}
if (info.getContainerInfo().getContainerCase() == FOLDER) {
@@ -801,7 +801,7 @@ public class StatsLogCompatManager extends StatsLogManager {
}
private static int getSearchAttributes(LauncherAtom.ItemInfo info) {
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ if (Utilities.isRunningInTestHarness()) {
return 0;
}
ContainerInfo containerInfo = info.getContainerInfo();
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
index 60065fb16c..2964868aa0 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
@@ -20,7 +20,6 @@ import android.util.ArraySet;
import androidx.annotation.NonNull;
import java.io.PrintWriter;
-import java.util.List;
import java.util.Set;
/**
@@ -37,7 +36,7 @@ public class ActiveGestureErrorDetector {
ON_SETTLED_ON_END_TARGET, START_RECENTS_ANIMATION, FINISH_RECENTS_ANIMATION,
CANCEL_RECENTS_ANIMATION, SET_ON_PAGE_TRANSITION_END_CALLBACK, CANCEL_CURRENT_ANIMATION,
CLEANUP_SCREENSHOT, SCROLLER_ANIMATION_ABORTED, TASK_APPEARED, EXPECTING_TASK_APPEARED,
- FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER,
+ FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER, LAUNCHER_DESTROYED,
/**
* These GestureEvents are specifically associated to state flags that get set in
@@ -68,288 +67,288 @@ public class ActiveGestureErrorDetector {
protected static void analyseAndDump(
@NonNull String prefix,
@NonNull PrintWriter writer,
- List eventLogs) {
- writer.println(prefix + "ActiveGestureErrorDetector:");
- for (int i = 0; i < eventLogs.size(); i++) {
- ActiveGestureLog.EventLog eventLog = eventLogs.get(i);
- if (eventLog == null) {
+ @NonNull ActiveGestureLog.EventLog eventLog) {
+ writer.println(prefix + "Error messages for gesture ID: " + eventLog.logId);
+
+ boolean errorDetected = false;
+ // Use a Set since the order is inherently checked in the loop.
+ final Set encounteredEvents = new ArraySet<>();
+ // Set flags and check order of operations.
+ for (ActiveGestureLog.EventEntry eventEntry : eventLog.eventEntries) {
+ GestureEvent gestureEvent = eventEntry.getGestureEvent();
+ if (gestureEvent == null) {
continue;
}
- int gestureId = eventLog.logId;
- writer.println(prefix + "\tError messages for gesture ID: " + gestureId);
+ encounteredEvents.add(gestureEvent);
- boolean errorDetected = false;
- // Use a Set since the order is inherently checked in the loop.
- final Set encounteredEvents = new ArraySet<>();
- // Set flags and check order of operations.
- for (ActiveGestureLog.EventEntry eventEntry : eventLog.eventEntries) {
- GestureEvent gestureEvent = eventEntry.getGestureEvent();
- if (gestureEvent == null) {
- continue;
- }
- encounteredEvents.add(gestureEvent);
- switch (gestureEvent) {
- case MOTION_UP:
- errorDetected |= printErrorIfTrue(
- !encounteredEvents.contains(GestureEvent.MOTION_DOWN),
- prefix,
- /* errorMessage= */ "Motion up detected before/without"
- + " motion down.",
- writer);
- break;
- case ON_SETTLED_ON_END_TARGET:
- errorDetected |= printErrorIfTrue(
- !encounteredEvents.contains(GestureEvent.SET_END_TARGET),
- prefix,
- /* errorMessage= */ "onSettledOnEndTarget called "
- + "before/without setEndTarget.",
- writer);
- break;
- case FINISH_RECENTS_ANIMATION:
- errorDetected |= printErrorIfTrue(
- !encounteredEvents.contains(GestureEvent.START_RECENTS_ANIMATION),
- prefix,
- /* errorMessage= */ "finishRecentsAnimation called "
- + "before/without startRecentsAnimation.",
- writer);
- break;
- case CANCEL_RECENTS_ANIMATION:
- errorDetected |= printErrorIfTrue(
- !encounteredEvents.contains(GestureEvent.START_RECENTS_ANIMATION),
- prefix,
- /* errorMessage= */ "cancelRecentsAnimation called "
- + "before/without startRecentsAnimation.",
- writer);
- break;
- case CLEANUP_SCREENSHOT:
- errorDetected |= printErrorIfTrue(
- !encounteredEvents.contains(GestureEvent.STATE_SCREENSHOT_CAPTURED),
- prefix,
- /* errorMessage= */ "recents activity screenshot was "
- + "cleaned up before/without STATE_SCREENSHOT_CAPTURED "
- + "being set.",
- writer);
- break;
- case SCROLLER_ANIMATION_ABORTED:
- errorDetected |= printErrorIfTrue(
- encounteredEvents.contains(GestureEvent.SET_END_TARGET_HOME)
- && !encounteredEvents.contains(
- GestureEvent.ON_SETTLED_ON_END_TARGET),
- prefix,
- /* errorMessage= */ "recents view scroller animation "
- + "aborted after setting end target HOME, but before"
- + " settling on end target.",
- writer);
- break;
- case TASK_APPEARED:
- errorDetected |= printErrorIfTrue(
- !encounteredEvents.contains(GestureEvent.SET_END_TARGET_NEW_TASK),
- prefix,
- /* errorMessage= */ "onTasksAppeared called "
- + "before/without setting end target to new task",
- writer);
- errorDetected |= printErrorIfTrue(
- !encounteredEvents.contains(GestureEvent.EXPECTING_TASK_APPEARED),
- prefix,
- /* errorMessage= */ "onTasksAppeared was not expected to be called",
- writer);
- break;
- case EXPECTING_TASK_APPEARED:
- errorDetected |= printErrorIfTrue(
- !encounteredEvents.contains(GestureEvent.SET_END_TARGET_NEW_TASK),
- prefix,
- /* errorMessage= */ "expecting onTasksAppeared to be called "
- + "before/without setting end target to new task",
- writer);
- break;
- case STATE_GESTURE_COMPLETED:
- errorDetected |= printErrorIfTrue(
- !encounteredEvents.contains(GestureEvent.MOTION_UP),
- prefix,
- /* errorMessage= */ "STATE_GESTURE_COMPLETED set "
- + "before/without motion up.",
- writer);
- errorDetected |= printErrorIfTrue(
- !encounteredEvents.contains(GestureEvent.STATE_GESTURE_STARTED),
- prefix,
- /* errorMessage= */ "STATE_GESTURE_COMPLETED set "
- + "before/without STATE_GESTURE_STARTED.",
- writer);
- break;
- case STATE_GESTURE_CANCELLED:
- errorDetected |= printErrorIfTrue(
- !encounteredEvents.contains(GestureEvent.MOTION_UP),
- prefix,
- /* errorMessage= */ "STATE_GESTURE_CANCELLED set "
- + "before/without motion up.",
- writer);
- errorDetected |= printErrorIfTrue(
- !encounteredEvents.contains(GestureEvent.STATE_GESTURE_STARTED),
- prefix,
- /* errorMessage= */ "STATE_GESTURE_CANCELLED set "
- + "before/without STATE_GESTURE_STARTED.",
- writer);
- break;
- case STATE_SCREENSHOT_CAPTURED:
- errorDetected |= printErrorIfTrue(
- !encounteredEvents.contains(GestureEvent.STATE_CAPTURE_SCREENSHOT),
- prefix,
- /* errorMessage= */ "STATE_SCREENSHOT_CAPTURED set "
- + "before/without STATE_CAPTURE_SCREENSHOT.",
- writer);
- break;
- case STATE_RECENTS_SCROLLING_FINISHED:
- errorDetected |= printErrorIfTrue(
- !encounteredEvents.contains(
- GestureEvent.SET_ON_PAGE_TRANSITION_END_CALLBACK),
- prefix,
- /* errorMessage= */ "STATE_RECENTS_SCROLLING_FINISHED "
- + "set before/without calling "
- + "setOnPageTransitionEndCallback.",
- writer);
- break;
- case STATE_RECENTS_ANIMATION_CANCELED:
- errorDetected |= printErrorIfTrue(
- !encounteredEvents.contains(
- GestureEvent.START_RECENTS_ANIMATION),
- prefix,
- /* errorMessage= */ "STATE_RECENTS_ANIMATION_CANCELED "
- + "set before/without startRecentsAnimation.",
- writer);
- break;
- case MOTION_DOWN:
- case SET_END_TARGET:
- case SET_END_TARGET_HOME:
- case SET_END_TARGET_NEW_TASK:
- case START_RECENTS_ANIMATION:
- case SET_ON_PAGE_TRANSITION_END_CALLBACK:
- case CANCEL_CURRENT_ANIMATION:
- case FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER:
- case STATE_GESTURE_STARTED:
- case STATE_END_TARGET_ANIMATION_FINISHED:
- case STATE_CAPTURE_SCREENSHOT:
- case STATE_HANDLER_INVALIDATED:
- case STATE_LAUNCHER_DRAWN:
- default:
- // No-Op
- }
+ switch (gestureEvent) {
+ case MOTION_UP:
+ errorDetected |= printErrorIfTrue(
+ !encounteredEvents.contains(GestureEvent.MOTION_DOWN),
+ prefix,
+ /* errorMessage= */ "Motion up detected before/without"
+ + " motion down.",
+ writer);
+ break;
+ case ON_SETTLED_ON_END_TARGET:
+ errorDetected |= printErrorIfTrue(
+ !encounteredEvents.contains(GestureEvent.SET_END_TARGET),
+ prefix,
+ /* errorMessage= */ "onSettledOnEndTarget called "
+ + "before/without setEndTarget.",
+ writer);
+ break;
+ case FINISH_RECENTS_ANIMATION:
+ errorDetected |= printErrorIfTrue(
+ !encounteredEvents.contains(GestureEvent.START_RECENTS_ANIMATION),
+ prefix,
+ /* errorMessage= */ "finishRecentsAnimation called "
+ + "before/without startRecentsAnimation.",
+ writer);
+ break;
+ case CANCEL_RECENTS_ANIMATION:
+ errorDetected |= printErrorIfTrue(
+ !encounteredEvents.contains(GestureEvent.START_RECENTS_ANIMATION),
+ prefix,
+ /* errorMessage= */ "cancelRecentsAnimation called "
+ + "before/without startRecentsAnimation.",
+ writer);
+ break;
+ case CLEANUP_SCREENSHOT:
+ errorDetected |= printErrorIfTrue(
+ !encounteredEvents.contains(GestureEvent.STATE_SCREENSHOT_CAPTURED),
+ prefix,
+ /* errorMessage= */ "recents activity screenshot was "
+ + "cleaned up before/without STATE_SCREENSHOT_CAPTURED "
+ + "being set.",
+ writer);
+ break;
+ case SCROLLER_ANIMATION_ABORTED:
+ errorDetected |= printErrorIfTrue(
+ encounteredEvents.contains(GestureEvent.SET_END_TARGET_HOME)
+ && !encounteredEvents.contains(
+ GestureEvent.ON_SETTLED_ON_END_TARGET),
+ prefix,
+ /* errorMessage= */ "recents view scroller animation "
+ + "aborted after setting end target HOME, but before"
+ + " settling on end target.",
+ writer);
+ break;
+ case TASK_APPEARED:
+ errorDetected |= printErrorIfTrue(
+ !encounteredEvents.contains(GestureEvent.SET_END_TARGET_NEW_TASK),
+ prefix,
+ /* errorMessage= */ "onTasksAppeared called "
+ + "before/without setting end target to new task",
+ writer);
+ errorDetected |= printErrorIfTrue(
+ !encounteredEvents.contains(GestureEvent.EXPECTING_TASK_APPEARED),
+ prefix,
+ /* errorMessage= */ "onTasksAppeared was not expected to be called",
+ writer);
+ break;
+ case EXPECTING_TASK_APPEARED:
+ errorDetected |= printErrorIfTrue(
+ !encounteredEvents.contains(GestureEvent.SET_END_TARGET_NEW_TASK),
+ prefix,
+ /* errorMessage= */ "expecting onTasksAppeared to be called "
+ + "before/without setting end target to new task",
+ writer);
+ break;
+ case LAUNCHER_DESTROYED:
+ errorDetected |= printErrorIfTrue(
+ true,
+ prefix,
+ /* errorMessage= */ "Launcher destroyed mid-gesture",
+ writer);
+ break;
+ case STATE_GESTURE_COMPLETED:
+ errorDetected |= printErrorIfTrue(
+ !encounteredEvents.contains(GestureEvent.MOTION_UP),
+ prefix,
+ /* errorMessage= */ "STATE_GESTURE_COMPLETED set "
+ + "before/without motion up.",
+ writer);
+ errorDetected |= printErrorIfTrue(
+ !encounteredEvents.contains(GestureEvent.STATE_GESTURE_STARTED),
+ prefix,
+ /* errorMessage= */ "STATE_GESTURE_COMPLETED set "
+ + "before/without STATE_GESTURE_STARTED.",
+ writer);
+ break;
+ case STATE_GESTURE_CANCELLED:
+ errorDetected |= printErrorIfTrue(
+ !encounteredEvents.contains(GestureEvent.MOTION_UP),
+ prefix,
+ /* errorMessage= */ "STATE_GESTURE_CANCELLED set "
+ + "before/without motion up.",
+ writer);
+ errorDetected |= printErrorIfTrue(
+ !encounteredEvents.contains(GestureEvent.STATE_GESTURE_STARTED),
+ prefix,
+ /* errorMessage= */ "STATE_GESTURE_CANCELLED set "
+ + "before/without STATE_GESTURE_STARTED.",
+ writer);
+ break;
+ case STATE_SCREENSHOT_CAPTURED:
+ errorDetected |= printErrorIfTrue(
+ !encounteredEvents.contains(GestureEvent.STATE_CAPTURE_SCREENSHOT),
+ prefix,
+ /* errorMessage= */ "STATE_SCREENSHOT_CAPTURED set "
+ + "before/without STATE_CAPTURE_SCREENSHOT.",
+ writer);
+ break;
+ case STATE_RECENTS_SCROLLING_FINISHED:
+ errorDetected |= printErrorIfTrue(
+ !encounteredEvents.contains(
+ GestureEvent.SET_ON_PAGE_TRANSITION_END_CALLBACK),
+ prefix,
+ /* errorMessage= */ "STATE_RECENTS_SCROLLING_FINISHED "
+ + "set before/without calling "
+ + "setOnPageTransitionEndCallback.",
+ writer);
+ break;
+ case STATE_RECENTS_ANIMATION_CANCELED:
+ errorDetected |= printErrorIfTrue(
+ !encounteredEvents.contains(
+ GestureEvent.START_RECENTS_ANIMATION),
+ prefix,
+ /* errorMessage= */ "STATE_RECENTS_ANIMATION_CANCELED "
+ + "set before/without startRecentsAnimation.",
+ writer);
+ break;
+ case MOTION_DOWN:
+ case SET_END_TARGET:
+ case SET_END_TARGET_HOME:
+ case SET_END_TARGET_NEW_TASK:
+ case START_RECENTS_ANIMATION:
+ case SET_ON_PAGE_TRANSITION_END_CALLBACK:
+ case CANCEL_CURRENT_ANIMATION:
+ case FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER:
+ case STATE_GESTURE_STARTED:
+ case STATE_END_TARGET_ANIMATION_FINISHED:
+ case STATE_CAPTURE_SCREENSHOT:
+ case STATE_HANDLER_INVALIDATED:
+ case STATE_LAUNCHER_DRAWN:
+ default:
+ // No-Op
}
+ }
- // Check that all required events were found.
- errorDetected |= printErrorIfTrue(
- !encounteredEvents.contains(GestureEvent.MOTION_DOWN),
- prefix,
- /* errorMessage= */ "Motion down never detected.",
- writer);
- errorDetected |= printErrorIfTrue(
- !encounteredEvents.contains(GestureEvent.MOTION_UP),
- prefix,
- /* errorMessage= */ "Motion up never detected.",
- writer);
+ // Check that all required events were found.
+ errorDetected |= printErrorIfTrue(
+ !encounteredEvents.contains(GestureEvent.MOTION_DOWN),
+ prefix,
+ /* errorMessage= */ "Motion down never detected.",
+ writer);
+ errorDetected |= printErrorIfTrue(
+ !encounteredEvents.contains(GestureEvent.MOTION_UP),
+ prefix,
+ /* errorMessage= */ "Motion up never detected.",
+ writer);
- errorDetected |= printErrorIfTrue(
- /* condition= */ encounteredEvents.contains(GestureEvent.SET_END_TARGET)
- && !encounteredEvents.contains(GestureEvent.ON_SETTLED_ON_END_TARGET),
- prefix,
- /* errorMessage= */ "setEndTarget was called, but "
- + "onSettledOnEndTarget wasn't.",
- writer);
- errorDetected |= printErrorIfTrue(
- /* condition= */ encounteredEvents.contains(GestureEvent.SET_END_TARGET)
- && !encounteredEvents.contains(
- GestureEvent.STATE_END_TARGET_ANIMATION_FINISHED),
- prefix,
- /* errorMessage= */ "setEndTarget was called, but "
- + "STATE_END_TARGET_ANIMATION_FINISHED was never set.",
- writer);
- errorDetected |= printErrorIfTrue(
- /* condition= */ encounteredEvents.contains(GestureEvent.SET_END_TARGET)
- && !encounteredEvents.contains(
- GestureEvent.STATE_RECENTS_SCROLLING_FINISHED),
- prefix,
- /* errorMessage= */ "setEndTarget was called, but "
- + "STATE_RECENTS_SCROLLING_FINISHED was never set.",
- writer);
- errorDetected |= printErrorIfTrue(
- /* condition= */ encounteredEvents.contains(
- GestureEvent.STATE_END_TARGET_ANIMATION_FINISHED)
- && encounteredEvents.contains(
- GestureEvent.STATE_RECENTS_SCROLLING_FINISHED)
- && !encounteredEvents.contains(GestureEvent.ON_SETTLED_ON_END_TARGET),
- prefix,
- /* errorMessage= */ "STATE_END_TARGET_ANIMATION_FINISHED and "
- + "STATE_RECENTS_SCROLLING_FINISHED were set, but onSettledOnEndTarget "
- + "wasn't called.",
- writer);
+ errorDetected |= printErrorIfTrue(
+ /* condition= */ encounteredEvents.contains(GestureEvent.SET_END_TARGET)
+ && !encounteredEvents.contains(GestureEvent.ON_SETTLED_ON_END_TARGET),
+ prefix,
+ /* errorMessage= */ "setEndTarget was called, but "
+ + "onSettledOnEndTarget wasn't.",
+ writer);
+ errorDetected |= printErrorIfTrue(
+ /* condition= */ encounteredEvents.contains(GestureEvent.SET_END_TARGET)
+ && !encounteredEvents.contains(
+ GestureEvent.STATE_END_TARGET_ANIMATION_FINISHED),
+ prefix,
+ /* errorMessage= */ "setEndTarget was called, but "
+ + "STATE_END_TARGET_ANIMATION_FINISHED was never set.",
+ writer);
+ errorDetected |= printErrorIfTrue(
+ /* condition= */ encounteredEvents.contains(GestureEvent.SET_END_TARGET)
+ && !encounteredEvents.contains(
+ GestureEvent.STATE_RECENTS_SCROLLING_FINISHED),
+ prefix,
+ /* errorMessage= */ "setEndTarget was called, but "
+ + "STATE_RECENTS_SCROLLING_FINISHED was never set.",
+ writer);
+ errorDetected |= printErrorIfTrue(
+ /* condition= */ encounteredEvents.contains(
+ GestureEvent.STATE_END_TARGET_ANIMATION_FINISHED)
+ && encounteredEvents.contains(
+ GestureEvent.STATE_RECENTS_SCROLLING_FINISHED)
+ && !encounteredEvents.contains(GestureEvent.ON_SETTLED_ON_END_TARGET),
+ prefix,
+ /* errorMessage= */ "STATE_END_TARGET_ANIMATION_FINISHED and "
+ + "STATE_RECENTS_SCROLLING_FINISHED were set, but onSettledOnEndTarget "
+ + "wasn't called.",
+ writer);
- errorDetected |= printErrorIfTrue(
- /* condition= */ encounteredEvents.contains(
- GestureEvent.START_RECENTS_ANIMATION)
- && !encounteredEvents.contains(GestureEvent.FINISH_RECENTS_ANIMATION)
- && !encounteredEvents.contains(GestureEvent.CANCEL_RECENTS_ANIMATION),
- prefix,
- /* errorMessage= */ "startRecentsAnimation was called, but "
- + "finishRecentsAnimation and cancelRecentsAnimation weren't.",
- writer);
+ errorDetected |= printErrorIfTrue(
+ /* condition= */ encounteredEvents.contains(
+ GestureEvent.START_RECENTS_ANIMATION)
+ && !encounteredEvents.contains(GestureEvent.FINISH_RECENTS_ANIMATION)
+ && !encounteredEvents.contains(GestureEvent.CANCEL_RECENTS_ANIMATION),
+ prefix,
+ /* errorMessage= */ "startRecentsAnimation was called, but "
+ + "finishRecentsAnimation and cancelRecentsAnimation weren't.",
+ writer);
- errorDetected |= printErrorIfTrue(
- /* condition= */ encounteredEvents.contains(GestureEvent.STATE_GESTURE_STARTED)
- && !encounteredEvents.contains(GestureEvent.STATE_GESTURE_COMPLETED)
- && !encounteredEvents.contains(GestureEvent.STATE_GESTURE_CANCELLED),
- prefix,
- /* errorMessage= */ "STATE_GESTURE_STARTED was set, but "
- + "STATE_GESTURE_COMPLETED and STATE_GESTURE_CANCELLED weren't.",
- writer);
+ errorDetected |= printErrorIfTrue(
+ /* condition= */ encounteredEvents.contains(GestureEvent.STATE_GESTURE_STARTED)
+ && !encounteredEvents.contains(GestureEvent.STATE_GESTURE_COMPLETED)
+ && !encounteredEvents.contains(GestureEvent.STATE_GESTURE_CANCELLED),
+ prefix,
+ /* errorMessage= */ "STATE_GESTURE_STARTED was set, but "
+ + "STATE_GESTURE_COMPLETED and STATE_GESTURE_CANCELLED weren't.",
+ writer);
- errorDetected |= printErrorIfTrue(
- /* condition= */ encounteredEvents.contains(
- GestureEvent.STATE_CAPTURE_SCREENSHOT)
- && !encounteredEvents.contains(GestureEvent.STATE_SCREENSHOT_CAPTURED),
- prefix,
- /* errorMessage= */ "STATE_CAPTURE_SCREENSHOT was set, but "
- + "STATE_SCREENSHOT_CAPTURED wasn't.",
- writer);
+ errorDetected |= printErrorIfTrue(
+ /* condition= */ encounteredEvents.contains(
+ GestureEvent.STATE_CAPTURE_SCREENSHOT)
+ && !encounteredEvents.contains(GestureEvent.STATE_SCREENSHOT_CAPTURED),
+ prefix,
+ /* errorMessage= */ "STATE_CAPTURE_SCREENSHOT was set, but "
+ + "STATE_SCREENSHOT_CAPTURED wasn't.",
+ writer);
- errorDetected |= printErrorIfTrue(
- /* condition= */ encounteredEvents.contains(
- GestureEvent.SET_ON_PAGE_TRANSITION_END_CALLBACK)
- && !encounteredEvents.contains(
- GestureEvent.STATE_RECENTS_SCROLLING_FINISHED),
- prefix,
- /* errorMessage= */ "setOnPageTransitionEndCallback called, but "
- + "STATE_RECENTS_SCROLLING_FINISHED wasn't set.",
- writer);
+ errorDetected |= printErrorIfTrue(
+ /* condition= */ encounteredEvents.contains(
+ GestureEvent.SET_ON_PAGE_TRANSITION_END_CALLBACK)
+ && !encounteredEvents.contains(
+ GestureEvent.STATE_RECENTS_SCROLLING_FINISHED),
+ prefix,
+ /* errorMessage= */ "setOnPageTransitionEndCallback called, but "
+ + "STATE_RECENTS_SCROLLING_FINISHED wasn't set.",
+ writer);
- errorDetected |= printErrorIfTrue(
- /* condition= */ encounteredEvents.contains(
- GestureEvent.FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER)
- && !encounteredEvents.contains(GestureEvent.CANCEL_CURRENT_ANIMATION)
- && !encounteredEvents.contains(GestureEvent.STATE_HANDLER_INVALIDATED),
- prefix,
- /* errorMessage= */ "AbsSwipeUpHandler.cancelCurrentAnimation "
- + "wasn't called and STATE_HANDLER_INVALIDATED wasn't set.",
- writer);
+ errorDetected |= printErrorIfTrue(
+ /* condition= */ encounteredEvents.contains(
+ GestureEvent.FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER)
+ && !encounteredEvents.contains(GestureEvent.CANCEL_CURRENT_ANIMATION)
+ && !encounteredEvents.contains(GestureEvent.STATE_HANDLER_INVALIDATED),
+ prefix,
+ /* errorMessage= */ "AbsSwipeUpHandler.cancelCurrentAnimation "
+ + "wasn't called and STATE_HANDLER_INVALIDATED wasn't set.",
+ writer);
- errorDetected |= printErrorIfTrue(
- /* condition= */ encounteredEvents.contains(
- GestureEvent.STATE_RECENTS_ANIMATION_CANCELED)
- && !encounteredEvents.contains(GestureEvent.CLEANUP_SCREENSHOT),
- prefix,
- /* errorMessage= */ "STATE_RECENTS_ANIMATION_CANCELED was set but "
- + "the task screenshot wasn't cleaned up.",
- writer);
+ errorDetected |= printErrorIfTrue(
+ /* condition= */ encounteredEvents.contains(
+ GestureEvent.STATE_RECENTS_ANIMATION_CANCELED)
+ && !encounteredEvents.contains(GestureEvent.CLEANUP_SCREENSHOT),
+ prefix,
+ /* errorMessage= */ "STATE_RECENTS_ANIMATION_CANCELED was set but "
+ + "the task screenshot wasn't cleaned up.",
+ writer);
- errorDetected |= printErrorIfTrue(
- /* condition= */ encounteredEvents.contains(
- GestureEvent.EXPECTING_TASK_APPEARED)
- && !encounteredEvents.contains(GestureEvent.TASK_APPEARED),
- prefix,
- /* errorMessage= */ "onTaskAppeared was expected to be called but wasn't.",
- writer);
+ errorDetected |= printErrorIfTrue(
+ /* condition= */ encounteredEvents.contains(
+ GestureEvent.EXPECTING_TASK_APPEARED)
+ && !encounteredEvents.contains(GestureEvent.TASK_APPEARED),
+ prefix,
+ /* errorMessage= */ "onTaskAppeared was expected to be called but wasn't.",
+ writer);
- if (!errorDetected) {
- writer.println(prefix + "\t\tNo errors detected.");
- }
+ if (!errorDetected) {
+ writer.println(prefix + "\tNo errors detected.");
}
}
@@ -358,7 +357,8 @@ public class ActiveGestureErrorDetector {
if (!condition) {
return false;
}
- writer.println(prefix + "\t\t- " + errorMessage);
+ writer.println(prefix + "\t- " + errorMessage);
+
return true;
}
}
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
index 23fdd58877..e05d85c2de 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
@@ -155,19 +155,27 @@ public class ActiveGestureLog {
}
public void dump(String prefix, PrintWriter writer) {
+ if (FeatureFlags.ENABLE_GESTURE_ERROR_DETECTION.get()) {
+ writer.println(prefix + "ActiveGestureErrorDetector:");
+ for (int i = 0; i < logs.length; i++) {
+ EventLog eventLog = logs[(nextIndex + i) % logs.length];
+ if (eventLog == null) {
+ continue;
+ }
+ ActiveGestureErrorDetector.analyseAndDump(prefix + '\t', writer, eventLog);
+ }
+ }
+
writer.println(prefix + "ActiveGestureLog history:");
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSSZ ", Locale.US);
Date date = new Date();
- ArrayList eventLogs = new ArrayList<>();
-
for (int i = 0; i < logs.length; i++) {
EventLog eventLog = logs[(nextIndex + i) % logs.length];
if (eventLog == null) {
continue;
}
- eventLogs.add(eventLog);
- writer.println(prefix + "\tLogs for logId: " + eventLog.logId);
+ writer.println(prefix + "\tLogs for logId: " + eventLog.logId);
for (EventEntry eventEntry : eventLog.eventEntries) {
date.setTime(eventEntry.time);
@@ -199,10 +207,6 @@ public class ActiveGestureLog {
writer.println(msg);
}
}
-
- if (FeatureFlags.ENABLE_GESTURE_ERROR_DETECTION.get()) {
- ActiveGestureErrorDetector.analyseAndDump(prefix + '\t', writer, eventLogs);
- }
}
/**
diff --git a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
index 7c838332a6..baca76cfba 100644
--- a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
+++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
@@ -158,7 +158,8 @@ public class AnimatorControllerWithResistance {
Rect startRect = new Rect();
PagedOrientationHandler orientationHandler = params.recentsOrientedState
.getOrientationHandler();
- LauncherActivityInterface.INSTANCE.calculateTaskSize(params.context, params.dp, startRect);
+ LauncherActivityInterface.INSTANCE.calculateTaskSize(params.context, params.dp, startRect,
+ orientationHandler);
long distanceToCover = startRect.bottom;
PendingAnimation resistAnim = params.resistAnim != null
? params.resistAnim
diff --git a/quickstep/src/com/android/quickstep/util/BaseDepthController.java b/quickstep/src/com/android/quickstep/util/BaseDepthController.java
index 877e28ab4e..cecf58d105 100644
--- a/quickstep/src/com/android/quickstep/util/BaseDepthController.java
+++ b/quickstep/src/com/android/quickstep/util/BaseDepthController.java
@@ -108,7 +108,10 @@ public class BaseDepthController {
float depth = mDepth;
IBinder windowToken = mLauncher.getRootView().getWindowToken();
if (windowToken != null) {
- mWallpaperManager.setWallpaperZoomOut(windowToken, depth);
+ // The API's full zoom-out is three times larger than the zoom-out we apply to the
+ // icons. To keep the two consistent throughout the animation while keeping Launcher's
+ // concept of full depth unchanged, we divide the depth by 3 here.
+ mWallpaperManager.setWallpaperZoomOut(windowToken, depth / 3);
}
if (!BlurUtils.supportsBlursOnWindows()) {
diff --git a/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java b/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java
index 2a513ee7ee..ad11b7e953 100644
--- a/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java
@@ -56,7 +56,6 @@ public abstract class BaseUnfoldMoveFromCenterAnimator implements TransitionProg
mAnimationInProgress = true;
mMoveFromCenterAnimation.updateDisplayProperties();
onPrepareViewsForAnimation();
- onTransitionProgress(0f);
mRotationChangeProvider.addCallback(mRotationListener);
}
@@ -87,6 +86,7 @@ public abstract class BaseUnfoldMoveFromCenterAnimator implements TransitionProg
}
private void clearRegisteredViews() {
+ restoreClippings();
mMoveFromCenterAnimation.clearRegisteredViews();
mOriginalClipChildren.clear();
@@ -101,22 +101,34 @@ public abstract class BaseUnfoldMoveFromCenterAnimator implements TransitionProg
mMoveFromCenterAnimation.registerViewForAnimation(view);
}
- protected void disableClipping(ViewGroup view) {
+ /**
+ * Sets clipToPadding for the view which then could be restored to the original value
+ * using {@link BaseUnfoldMoveFromCenterAnimator#restoreClippings} method call
+ * @param view view to set the property
+ * @param clipToPadding value of the property
+ */
+ protected void setClipToPadding(ViewGroup view, boolean clipToPadding) {
mOriginalClipToPadding.put(view, view.getClipToPadding());
- mOriginalClipChildren.put(view, view.getClipChildren());
- view.setClipToPadding(false);
- view.setClipChildren(false);
+ view.setClipToPadding(clipToPadding);
}
- protected void restoreClipping(ViewGroup view) {
- final Boolean originalClipToPadding = mOriginalClipToPadding.get(view);
- if (originalClipToPadding != null) {
- view.setClipToPadding(originalClipToPadding);
- }
- final Boolean originalClipChildren = mOriginalClipChildren.get(view);
- if (originalClipChildren != null) {
- view.setClipChildren(originalClipChildren);
- }
+ /**
+ * Sets clipChildren for the view which then could be restored to the original value
+ * using {@link BaseUnfoldMoveFromCenterAnimator#restoreClippings} method call
+ * @param view view to set the property
+ * @param clipChildren value of the property
+ */
+ protected void setClipChildren(ViewGroup view, boolean clipChildren) {
+ mOriginalClipChildren.put(view, view.getClipChildren());
+ view.setClipChildren(clipChildren);
+ }
+
+ /**
+ * Restores original clip properties after their modifications
+ */
+ protected void restoreClippings() {
+ mOriginalClipToPadding.forEach(ViewGroup::setClipToPadding);
+ mOriginalClipChildren.forEach(ViewGroup::setClipChildren);
}
private class UnfoldMoveFromCenterRotationListener implements
diff --git a/quickstep/src/com/android/quickstep/util/BorderAnimator.java b/quickstep/src/com/android/quickstep/util/BorderAnimator.java
new file mode 100644
index 0000000000..1f1c15bdb8
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/BorderAnimator.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import android.animation.Animator;
+import android.annotation.ColorInt;
+import android.annotation.Nullable;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.view.animation.Interpolator;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Px;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimatedFloat;
+import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.anim.Interpolators;
+
+/**
+ * Utility class for drawing a rounded-rect border around a view.
+ *
+ * To use this class:
+ * 1. Create an instance in the target view.
+ * 2. Override the target view's {@link android.view.View#draw(Canvas)} method and call
+ * {@link BorderAnimator#drawBorder(Canvas)} after {@code super.draw(canvas)}.
+ * 3. Call {@link BorderAnimator#buildAnimator(boolean)} and start the animation or call
+ * {@link BorderAnimator#setBorderVisible(boolean)} where appropriate.
+ */
+public final class BorderAnimator {
+
+ public static final int DEFAULT_BORDER_COLOR = 0xffffffff;
+
+ private static final long DEFAULT_APPEARANCE_ANIMATION_DURATION_MS = 300;
+ private static final long DEFAULT_DISAPPEARANCE_ANIMATION_DURATION_MS = 133;
+ private static final Interpolator DEFAULT_INTERPOLATOR = Interpolators.EMPHASIZED_DECELERATE;
+
+ @NonNull private final AnimatedFloat mBorderAnimationProgress = new AnimatedFloat(
+ this::updateOutline);
+ @NonNull private final Rect mBorderBounds = new Rect();
+ @NonNull private final BorderBoundsBuilder mBorderBoundsBuilder;
+ @Px private final int mBorderWidthPx;
+ @Px private final int mBorderRadiusPx;
+ @NonNull private final Runnable mInvalidateViewCallback;
+ private final long mAppearanceDurationMs;
+ private final long mDisappearanceDurationMs;
+ @NonNull private final Interpolator mInterpolator;
+ @NonNull private final Paint mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+ private int mAlignmentAdjustment;
+
+ @Nullable private Animator mRunningBorderAnimation;
+
+ public BorderAnimator(
+ @NonNull BorderBoundsBuilder borderBoundsBuilder,
+ int borderWidthPx,
+ int borderRadiusPx,
+ @ColorInt int borderColor,
+ @NonNull Runnable invalidateViewCallback) {
+ this(borderBoundsBuilder,
+ borderWidthPx,
+ borderRadiusPx,
+ borderColor,
+ invalidateViewCallback,
+ DEFAULT_APPEARANCE_ANIMATION_DURATION_MS,
+ DEFAULT_DISAPPEARANCE_ANIMATION_DURATION_MS,
+ DEFAULT_INTERPOLATOR);
+ }
+
+ public BorderAnimator(
+ @NonNull BorderBoundsBuilder borderBoundsBuilder,
+ int borderWidthPx,
+ int borderRadiusPx,
+ @ColorInt int borderColor,
+ @NonNull Runnable invalidateViewCallback,
+ long appearanceDurationMs,
+ long disappearanceDurationMs,
+ @NonNull Interpolator interpolator) {
+ mBorderBoundsBuilder = borderBoundsBuilder;
+ mBorderWidthPx = borderWidthPx;
+ mBorderRadiusPx = borderRadiusPx;
+ mInvalidateViewCallback = invalidateViewCallback;
+ mAppearanceDurationMs = appearanceDurationMs;
+ mDisappearanceDurationMs = disappearanceDurationMs;
+ mInterpolator = interpolator;
+
+ mBorderPaint.setColor(borderColor);
+ mBorderPaint.setStyle(Paint.Style.STROKE);
+ mBorderPaint.setAlpha(0);
+ }
+
+ private void updateOutline() {
+ float interpolatedProgress = mInterpolator.getInterpolation(
+ mBorderAnimationProgress.value);
+ mAlignmentAdjustment = (int) Utilities.mapBoundToRange(
+ mBorderAnimationProgress.value,
+ /* lowerBound= */ 0f,
+ /* upperBound= */ 1f,
+ /* toMin= */ 0f,
+ /* toMax= */ (float) (mBorderWidthPx / 2f),
+ mInterpolator);
+
+ mBorderPaint.setAlpha(Math.round(255 * interpolatedProgress));
+ mBorderPaint.setStrokeWidth(Math.round(mBorderWidthPx * interpolatedProgress));
+ mInvalidateViewCallback.run();
+ }
+
+ /**
+ * Draws the border on the given canvas.
+ *
+ * Call this method in the target view's {@link android.view.View#draw(Canvas)} method after
+ * calling super.
+ */
+ public void drawBorder(Canvas canvas) {
+ canvas.drawRoundRect(
+ /* left= */ mBorderBounds.left + mAlignmentAdjustment,
+ /* top= */ mBorderBounds.top + mAlignmentAdjustment,
+ /* right= */ mBorderBounds.right - mAlignmentAdjustment,
+ /* bottom= */ mBorderBounds.bottom - mAlignmentAdjustment,
+ /* rx= */ mBorderRadiusPx - mAlignmentAdjustment,
+ /* ry= */ mBorderRadiusPx - mAlignmentAdjustment,
+ /* paint= */ mBorderPaint);
+ }
+
+ /**
+ * Builds the border appearance/disappearance animation.
+ */
+ @NonNull
+ public Animator buildAnimator(boolean isAppearing) {
+ mBorderBoundsBuilder.updateBorderBounds(mBorderBounds);
+ mRunningBorderAnimation = mBorderAnimationProgress.animateToValue(isAppearing ? 1f : 0f);
+ mRunningBorderAnimation.setDuration(
+ isAppearing ? mAppearanceDurationMs : mDisappearanceDurationMs);
+
+ mRunningBorderAnimation.addListener(
+ AnimatorListeners.forEndCallback(() -> mRunningBorderAnimation = null));
+
+ return mRunningBorderAnimation;
+ }
+
+ /**
+ * Immediately shows/hides the border without an animation.
+ *
+ * To animate the appearance/disappearance, see {@link BorderAnimator#buildAnimator(boolean)}
+ */
+ public void setBorderVisible(boolean visible) {
+ if (mRunningBorderAnimation != null) {
+ mRunningBorderAnimation.end();
+ }
+ mBorderAnimationProgress.updateValue(visible ? 1f : 0f);
+ }
+
+ /**
+ * Callback to update the border bounds when building this animation.
+ */
+ public interface BorderBoundsBuilder {
+
+ /**
+ * Sets the given rect to the most up-to-date bounds.
+ */
+ void updateBorderBounds(Rect rect);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/DesktopTask.java b/quickstep/src/com/android/quickstep/util/DesktopTask.java
index 433d23fa97..b3f5d82637 100644
--- a/quickstep/src/com/android/quickstep/util/DesktopTask.java
+++ b/quickstep/src/com/android/quickstep/util/DesktopTask.java
@@ -16,6 +16,8 @@
package com.android.quickstep.util;
+import androidx.annotation.NonNull;
+
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
@@ -27,9 +29,10 @@ import java.util.ArrayList;
*/
public class DesktopTask extends GroupTask {
- public ArrayList tasks;
+ @NonNull
+ public final ArrayList tasks;
- public DesktopTask(ArrayList tasks) {
+ public DesktopTask(@NonNull ArrayList tasks) {
super(tasks.get(0), null, null, TaskView.Type.DESKTOP);
this.tasks = tasks;
}
diff --git a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
index 9fe24dec66..3a1c99b078 100644
--- a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
+++ b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
@@ -18,6 +18,8 @@ package com.android.quickstep.util;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW;
+import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
@@ -47,7 +49,7 @@ import androidx.annotation.WorkerThread;
import androidx.core.content.FileProvider;
import com.android.internal.app.ChooserActivity;
-import com.android.internal.util.ScreenshotHelper;
+import com.android.internal.util.ScreenshotRequest;
import com.android.launcher3.BuildConfig;
import com.android.quickstep.SystemUiProxy;
import com.android.systemui.shared.recents.model.Task;
@@ -74,11 +76,17 @@ public class ImageActionUtils {
* Saves screenshot to location determine by SystemUiProxy
*/
public static void saveScreenshot(SystemUiProxy systemUiProxy, Bitmap screenshot,
- Rect screenshotBounds,
- Insets visibleInsets, Task.TaskKey task) {
- systemUiProxy.handleImageBundleAsScreenshot(
- ScreenshotHelper.HardwareBitmapBundler.hardwareBitmapToBundle(screenshot),
- screenshotBounds, visibleInsets, task);
+ Rect screenshotBounds, Insets visibleInsets, Task.TaskKey task) {
+ ScreenshotRequest request =
+ new ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OVERVIEW)
+ .setTopComponent(task.sourceComponent)
+ .setTaskId(task.id)
+ .setUserId(task.userId)
+ .setBitmap(screenshot)
+ .setBoundsOnScreen(screenshotBounds)
+ .setInsets(visibleInsets)
+ .build();
+ systemUiProxy.takeScreenshot(request);
}
/**
diff --git a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
index 170c622035..8fdafc6059 100644
--- a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
+++ b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
@@ -20,6 +20,7 @@ import static com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_UNFOLD_ANIMATI
import static com.android.launcher3.LauncherAnimUtils.WORKSPACE_SCALE_PROPERTY_FACTORY;
import android.annotation.Nullable;
+import android.os.Trace;
import android.util.FloatProperty;
import android.util.MathUtils;
import android.view.WindowManager;
@@ -43,7 +44,7 @@ public class LauncherUnfoldAnimationController {
// Percentage of the width of the quick search bar that will be reduced
// from the both sides of the bar when progress is 0
- private static final float MAX_WIDTH_INSET_FRACTION = 0.15f;
+ private static final float MAX_WIDTH_INSET_FRACTION = 0.04f;
private static final FloatProperty> WORKSPACE_SCALE_PROPERTY =
WORKSPACE_SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_UNFOLD_ANIMATION);
private static final FloatProperty HOTSEAT_SCALE_PROPERTY =
@@ -55,6 +56,9 @@ public class LauncherUnfoldAnimationController {
private final UnfoldMoveFromCenterHotseatAnimator mUnfoldMoveFromCenterHotseatAnimator;
private final UnfoldMoveFromCenterWorkspaceAnimator mUnfoldMoveFromCenterWorkspaceAnimator;
+ private static final String TRACE_WAIT_TO_HANDLE_UNFOLD_TRANSITION =
+ "waitingOneFrameBeforeHandlingUnfoldAnimation";
+
@Nullable
private HorizontalInsettableView mQsbInsettable;
@@ -92,8 +96,18 @@ public class LauncherUnfoldAnimationController {
mQsbInsettable = (HorizontalInsettableView) hotseat.getQsb();
}
+ handleTransitionOnNextFrame();
+ }
+
+ private void handleTransitionOnNextFrame() {
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_APP,
+ TRACE_WAIT_TO_HANDLE_UNFOLD_TRANSITION, /* cookie= */ 0);
OneShotPreDrawListener.add(mLauncher.getWorkspace(),
- () -> mProgressProvider.setReadyToHandleTransition(true));
+ () -> {
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_APP,
+ TRACE_WAIT_TO_HANDLE_UNFOLD_TRANSITION, /* cookie= */ 0);
+ mProgressProvider.setReadyToHandleTransition(true);
+ });
}
/**
@@ -142,6 +156,8 @@ public class LauncherUnfoldAnimationController {
private class LauncherScaleAnimationListener implements TransitionProgressListener {
+ private static final float SCALE_LAUNCHER_FROM = 0.92f;
+
@Override
public void onTransitionStarted() {
mLauncher.getWorkspace().setPivotToScaleWithSelf(mLauncher.getHotseat());
@@ -154,7 +170,7 @@ public class LauncherUnfoldAnimationController {
@Override
public void onTransitionProgress(float progress) {
- setScale(MathUtils.constrainedMap(0.85f, 1, 0, 1, progress));
+ setScale(MathUtils.constrainedMap(SCALE_LAUNCHER_FROM, 1, 0, 1, progress));
}
private void setScale(float value) {
diff --git a/quickstep/src/com/android/quickstep/util/LauncherViewsMoveFromCenterTranslationApplier.java b/quickstep/src/com/android/quickstep/util/LauncherViewsMoveFromCenterTranslationApplier.java
index effdfdd97b..f6b2441054 100644
--- a/quickstep/src/com/android/quickstep/util/LauncherViewsMoveFromCenterTranslationApplier.java
+++ b/quickstep/src/com/android/quickstep/util/LauncherViewsMoveFromCenterTranslationApplier.java
@@ -15,12 +15,13 @@
*/
package com.android.quickstep.util;
+import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_MOVE_FROM_CENTER_ANIM;
+
import android.annotation.NonNull;
import android.view.View;
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.widget.NavigableAppWidgetHostView;
+import com.android.launcher3.Reorderable;
+import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator.TranslationApplier;
/**
@@ -31,12 +32,9 @@ public class LauncherViewsMoveFromCenterTranslationApplier implements Translatio
@Override
public void apply(@NonNull View view, float x, float y) {
- if (view instanceof NavigableAppWidgetHostView) {
- ((NavigableAppWidgetHostView) view).setTranslationForMoveFromCenterAnimation(x, y);
- } else if (view instanceof BubbleTextView) {
- ((BubbleTextView) view).setTranslationForMoveFromCenterAnimation(x, y);
- } else if (view instanceof FolderIcon) {
- ((FolderIcon) view).setTranslationForMoveFromCenterAnimation(x, y);
+ if (view instanceof Reorderable) {
+ MultiTranslateDelegate mtd = ((Reorderable) view).getTranslateDelegate();
+ mtd.setTranslation(INDEX_MOVE_FROM_CENTER_ANIM, x, y);
} else {
view.setTranslationX(x);
view.setTranslationY(y);
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index f7136a5b68..79656c27a4 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -43,7 +43,8 @@ public class LayoutUtils {
PagedOrientationHandler orientationHandler) {
// Track the bottom of the window.
Rect taskSize = new Rect();
- LauncherActivityInterface.INSTANCE.calculateTaskSize(context, dp, taskSize);
+ LauncherActivityInterface.INSTANCE.calculateTaskSize(context, dp, taskSize,
+ orientationHandler);
return orientationHandler.getDistanceToBottomOfRect(dp, taskSize);
}
diff --git a/quickstep/src/com/android/quickstep/util/LogUtils.kt b/quickstep/src/com/android/quickstep/util/LogUtils.kt
index bad8506eec..23a41f6068 100644
--- a/quickstep/src/com/android/quickstep/util/LogUtils.kt
+++ b/quickstep/src/com/android/quickstep/util/LogUtils.kt
@@ -20,15 +20,14 @@ import com.android.internal.logging.InstanceIdSequence
import com.android.launcher3.logging.InstanceId
object LogUtils {
- /**
- * @return a [Pair] of two InstanceIds but with different types, one that can be used by framework
- * (if needing to pass through an intent or such) and one used in Launcher
- */
- @JvmStatic
- fun getShellShareableInstanceId():
- Pair {
- val internalInstanceId = InstanceIdSequence(InstanceId.INSTANCE_ID_MAX).newInstanceId()
- val launcherInstanceId = InstanceId(internalInstanceId.id)
- return Pair(internalInstanceId, launcherInstanceId)
- }
+ /**
+ * @return a [Pair] of two InstanceIds but with different types, one that can be used by
+ * framework (if needing to pass through an intent or such) and one used in Launcher
+ */
+ @JvmStatic
+ fun getShellShareableInstanceId(): Pair {
+ val internalInstanceId = InstanceIdSequence(InstanceId.INSTANCE_ID_MAX).newInstanceId()
+ val launcherInstanceId = InstanceId(internalInstanceId.id)
+ return Pair(internalInstanceId, launcherInstanceId)
+ }
}
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index 69ed2f8028..4bc41bc4ca 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -129,7 +129,7 @@ public class MotionPauseDetector {
* @param pointerIndex Index for the pointer being tracked in the motion event
*/
public void addPosition(MotionEvent ev, int pointerIndex) {
- long timeoutMs = Utilities.IS_RUNNING_IN_TEST_HARNESS
+ long timeoutMs = Utilities.isRunningInTestHarness()
? TEST_HARNESS_TRIGGER_TIMEOUT
: mMakePauseHarderToTrigger
? HARDER_TRIGGER_TIMEOUT
@@ -195,7 +195,7 @@ public class MotionPauseDetector {
if (mIsPaused != isPaused) {
mIsPaused = isPaused;
String logString = "onMotionPauseChanged, paused=" + mIsPaused + " reason=" + reason;
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ if (Utilities.isRunningInTestHarness()) {
Log.d(TAG, logString);
}
ActiveGestureLog.INSTANCE.addLog(logString);
diff --git a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
index e928b27751..cf07e6e687 100644
--- a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
+++ b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
@@ -64,7 +64,7 @@ public class QuickstepOnboardingPrefs extends OnboardingPrefs
});
}
- if (!Utilities.IS_RUNNING_IN_TEST_HARNESS
+ if (!Utilities.isRunningInTestHarness()
&& !hasReachedMaxCount(HOTSEAT_DISCOVERY_TIP_COUNT)) {
stateManager.addStateListener(new StateListener() {
boolean mFromAllApps = false;
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index db8c7f23b9..c4ba39a76e 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -22,7 +22,7 @@ import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
-import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
+import static com.android.launcher3.LauncherPrefs.ALLOW_ROTATION;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SettingsCache.ROTATION_SETTING_URI;
import static com.android.quickstep.BaseActivityInterface.getTaskDimension;
@@ -116,7 +116,6 @@ public class RecentsOrientedState implements
| FLAG_SWIPE_UP_NOT_RUNNING;
private final Context mContext;
- private final SharedPreferences mSharedPrefs;
private final OrientationEventListener mOrientationListener;
private final SettingsCache mSettingsCache;
private final SettingsCache.OnChangeListener mRotationChangeListener =
@@ -139,7 +138,6 @@ public class RecentsOrientedState implements
public RecentsOrientedState(Context context, BaseActivityInterface sizeStrategy,
IntConsumer rotationChangeListener) {
mContext = context;
- mSharedPrefs = LauncherPrefs.getPrefs(context);
mOrientationListener = new OrientationEventListener(context) {
@Override
public void onOrientationChanged(int degrees) {
@@ -278,7 +276,7 @@ public class RecentsOrientedState implements
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
- if (ALLOW_ROTATION_PREFERENCE_KEY.equals(s)) {
+ if (LauncherPrefs.ALLOW_ROTATION.getSharedPrefKey().equals(s)) {
updateHomeRotationSetting();
}
}
@@ -289,7 +287,7 @@ public class RecentsOrientedState implements
}
private void updateHomeRotationSetting() {
- boolean homeRotationEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY, false);
+ boolean homeRotationEnabled = LauncherPrefs.get(mContext).get(ALLOW_ROTATION);
setFlag(FLAG_HOME_ROTATION_ALLOWED_IN_PREFS, homeRotationEnabled);
SystemUiProxy.INSTANCE.get(mContext).setHomeRotationEnabled(homeRotationEnabled);
}
@@ -303,13 +301,13 @@ public class RecentsOrientedState implements
}
private void initMultipleOrientationListeners() {
- mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
+ LauncherPrefs.get(mContext).addListener(this, ALLOW_ROTATION);
mSettingsCache.register(ROTATION_SETTING_URI, mRotationChangeListener);
updateAutoRotateSetting();
}
private void destroyMultipleOrientationListeners() {
- mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
+ LauncherPrefs.get(mContext).removeListener(this, ALLOW_ROTATION);
mSettingsCache.unregister(ROTATION_SETTING_URI, mRotationChangeListener);
}
@@ -398,7 +396,7 @@ public class RecentsOrientedState implements
* Returns the scale and pivot so that the provided taskRect can fit the provided full size
*/
public float getFullScreenScaleAndPivot(Rect taskView, DeviceProfile dp, PointF outPivot) {
- getTaskDimension(dp, outPivot);
+ getTaskDimension(mContext, dp, outPivot);
float scale = Math.min(outPivot.x / taskView.width(), outPivot.y / taskView.height());
if (scale == 1) {
outPivot.set(taskView.centerX(), taskView.centerY());
diff --git a/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java
index 68739ba458..251b7567b1 100644
--- a/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java
+++ b/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java
@@ -128,37 +128,27 @@ public class RectFSpringAnim extends ReleaseCheck {
@Tracking
public final int mTracking;
+ protected final float mStiffnessX;
+ protected final float mStiffnessY;
+ protected final float mDampingX;
+ protected final float mDampingY;
+ protected final float mRectStiffness;
- public RectFSpringAnim(RectF startRect, RectF targetRect, Context context,
- @Nullable DeviceProfile deviceProfile) {
- mStartRect = startRect;
- mTargetRect = targetRect;
+ public RectFSpringAnim(SpringConfig config) {
+ mStartRect = config.startRect;
+ mTargetRect = config.targetRect;
mCurrentCenterX = mStartRect.centerX();
- ResourceProvider rp = DynamicResource.provider(context);
- mMinVisChange = rp.getDimension(R.dimen.swipe_up_fling_min_visible_change);
- mMaxVelocityPxPerS = (int) rp.getDimension(R.dimen.swipe_up_max_velocity);
+ mMinVisChange = config.minVisChange;
+ mMaxVelocityPxPerS = config.maxVelocityPxPerS;
setCanRelease(true);
- if (deviceProfile == null) {
- mTracking = startRect.bottom < targetRect.bottom
- ? TRACKING_BOTTOM
- : TRACKING_TOP;
- } else {
- int heightPx = deviceProfile.heightPx;
- Rect padding = deviceProfile.workspacePadding;
-
- final float topThreshold = heightPx / 3f;
- final float bottomThreshold = deviceProfile.heightPx - padding.bottom;
-
- if (targetRect.bottom > bottomThreshold) {
- mTracking = TRACKING_BOTTOM;
- } else if (targetRect.top < topThreshold) {
- mTracking = TRACKING_TOP;
- } else {
- mTracking = TRACKING_CENTER;
- }
- }
+ mTracking = config.tracking;
+ mStiffnessX = config.stiffnessX;
+ mStiffnessY = config.stiffnessY;
+ mDampingX = config.dampingX;
+ mDampingY = config.dampingY;
+ mRectStiffness = config.rectStiffness;
mCurrentY = getTrackedYFromRect(mStartRect);
}
@@ -240,14 +230,15 @@ public class RectFSpringAnim extends ReleaseCheck {
float maxXValue = Math.max(startX, endX);
mRectXAnim = new FlingSpringAnim(this, context, RECT_CENTER_X, startX, endX,
- dampedXVelocityPxPerS, mMinVisChange, minXValue, maxXValue, onXEndListener);
+ dampedXVelocityPxPerS, mMinVisChange, minXValue, maxXValue, mDampingX, mStiffnessX,
+ onXEndListener);
float startY = mCurrentY;
float endY = getTrackedYFromRect(mTargetRect);
float minYValue = Math.min(startY, endY);
float maxYValue = Math.max(startY, endY);
mRectYAnim = new FlingSpringAnim(this, context, RECT_Y, startY, endY, dampedYVelocityPxPerS,
- mMinVisChange, minYValue, maxYValue, onYEndListener);
+ mMinVisChange, minYValue, maxYValue, mDampingY, mStiffnessY, onYEndListener);
float minVisibleChange = Math.abs(1f / mStartRect.height());
ResourceProvider rp = DynamicResource.provider(context);
@@ -368,4 +359,98 @@ public class RectFSpringAnim extends ReleaseCheck {
default void onCancel() { }
}
+
+ private abstract static class SpringConfig {
+ protected RectF startRect;
+ protected RectF targetRect;
+ protected @Tracking int tracking;
+ protected float stiffnessX;
+ protected float stiffnessY;
+ protected float dampingX;
+ protected float dampingY;
+ protected float rectStiffness;
+ protected float minVisChange;
+ protected int maxVelocityPxPerS;
+
+ private SpringConfig(Context context, RectF start, RectF target) {
+ startRect = start;
+ targetRect = target;
+
+ ResourceProvider rp = DynamicResource.provider(context);
+ minVisChange = rp.getDimension(R.dimen.swipe_up_fling_min_visible_change);
+ maxVelocityPxPerS = (int) rp.getDimension(R.dimen.swipe_up_max_velocity);
+ }
+ }
+
+ /**
+ * Standard spring configuration parameters.
+ */
+ public static class DefaultSpringConfig extends SpringConfig {
+
+ public DefaultSpringConfig(Context context, DeviceProfile deviceProfile,
+ RectF startRect, RectF targetRect) {
+ super(context, startRect, targetRect);
+
+ ResourceProvider rp = DynamicResource.provider(context);
+ tracking = getDefaultTracking(deviceProfile);
+ stiffnessX = rp.getFloat(R.dimen.swipe_up_rect_xy_stiffness);
+ stiffnessY = rp.getFloat(R.dimen.swipe_up_rect_xy_stiffness);
+ dampingX = rp.getFloat(R.dimen.swipe_up_rect_xy_damping_ratio);
+ dampingY = rp.getFloat(R.dimen.swipe_up_rect_xy_damping_ratio);
+
+ this.startRect = startRect;
+ this.targetRect = targetRect;
+
+ // Increase the stiffness for devices where we want the window size to transform
+ // quicker.
+ boolean shouldUseHigherStiffness = deviceProfile != null
+ && (deviceProfile.isLandscape || deviceProfile.isTablet);
+ rectStiffness = shouldUseHigherStiffness
+ ? rp.getFloat(R.dimen.swipe_up_rect_scale_higher_stiffness)
+ : rp.getFloat(R.dimen.swipe_up_rect_scale_stiffness);
+ }
+
+ private @Tracking int getDefaultTracking(@Nullable DeviceProfile deviceProfile) {
+ @Tracking int tracking;
+ if (deviceProfile == null) {
+ tracking = startRect.bottom < targetRect.bottom
+ ? TRACKING_BOTTOM
+ : TRACKING_TOP;
+ } else {
+ int heightPx = deviceProfile.heightPx;
+ Rect padding = deviceProfile.workspacePadding;
+
+ final float topThreshold = heightPx / 3f;
+ final float bottomThreshold = deviceProfile.heightPx - padding.bottom;
+
+ if (targetRect.bottom > bottomThreshold) {
+ tracking = TRACKING_BOTTOM;
+ } else if (targetRect.top < topThreshold) {
+ tracking = TRACKING_TOP;
+ } else {
+ tracking = TRACKING_CENTER;
+ }
+ }
+ return tracking;
+ }
+ }
+
+ /**
+ * Spring configuration parameters for Taskbar/Hotseat items on devices that have a taskbar.
+ */
+ public static class TaskbarHotseatSpringConfig extends SpringConfig {
+
+ public TaskbarHotseatSpringConfig(Context context, RectF start, RectF target) {
+ super(context, start, target);
+
+ ResourceProvider rp = DynamicResource.provider(context);
+ tracking = TRACKING_CENTER;
+ stiffnessX = rp.getFloat(R.dimen.taskbar_swipe_up_rect_x_stiffness);
+ stiffnessY = rp.getFloat(R.dimen.taskbar_swipe_up_rect_y_stiffness);
+ dampingX = rp.getFloat(R.dimen.taskbar_swipe_up_rect_x_damping);
+ dampingY = rp.getFloat(R.dimen.taskbar_swipe_up_rect_y_damping);
+ rectStiffness = rp.getFloat(R.dimen.taskbar_swipe_up_rect_scale_stiffness);
+ }
+ }
+
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
new file mode 100644
index 0000000000..b76fe5cb03
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.quickstep.util
+
+import android.animation.ObjectAnimator
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.view.View
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.anim.PendingAnimation
+import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource
+import com.android.quickstep.views.IconView
+import com.android.quickstep.views.TaskThumbnailView
+import com.android.quickstep.views.TaskView
+import com.android.quickstep.views.TaskView.TaskIdAttributeContainer
+import java.util.function.Supplier
+
+/**
+ * Utils class to help run animations for initiating split screen from launcher.
+ * Will be expanded with future refactors. Works in conjunction with the state stored in
+ * [SplitSelectStateController]
+ */
+class SplitAnimationController(val splitSelectStateController: SplitSelectStateController) {
+ companion object {
+ // Break this out into maybe enums? Abstractions into its own classes? Tbd.
+ data class SplitAnimInitProps(
+ val originalView: View,
+ val originalBitmap: Bitmap?,
+ val iconDrawable: Drawable,
+ val fadeWithThumbnail: Boolean,
+ val isStagedTask: Boolean,
+ val iconView: View?
+ )
+ }
+
+ /**
+ * Returns different elements to animate for the initial split selection animation
+ * depending on the state of the surface from which the split was initiated
+ */
+ fun getFirstAnimInitViews(taskViewSupplier: Supplier,
+ splitSelectSourceSupplier: Supplier)
+ : SplitAnimInitProps {
+ val splitSelectSource = splitSelectSourceSupplier.get()
+ if (!splitSelectStateController.isAnimateCurrentTaskDismissal) {
+ // Initiating from home
+ return SplitAnimInitProps(splitSelectSource!!.view, originalBitmap = null,
+ splitSelectSource.drawable, fadeWithThumbnail = false, isStagedTask = true,
+ iconView = null)
+ } else if (splitSelectStateController.isDismissingFromSplitPair) {
+ // Initiating split from overview, but on a split pair
+ val taskView = taskViewSupplier.get()
+ for (container : TaskIdAttributeContainer in taskView.taskIdAttributeContainers) {
+ if (container.task.getKey().getId() == splitSelectStateController.initialTaskId) {
+ val drawable = getDrawable(container.iconView, splitSelectSource)
+ return SplitAnimInitProps(container.thumbnailView,
+ container.thumbnailView.thumbnail, drawable!!,
+ fadeWithThumbnail = true, isStagedTask = true,
+ iconView = container.iconView
+ )
+ }
+ }
+ throw IllegalStateException("Attempting to init split from existing split pair " +
+ "without a valid taskIdAttributeContainer")
+ } else {
+ // Initiating split from overview on fullscreen task TaskView
+ val taskView = taskViewSupplier.get()
+ val drawable = getDrawable(taskView.iconView, splitSelectSource)
+ return SplitAnimInitProps(taskView.thumbnail, taskView.thumbnail.thumbnail,
+ drawable!!, fadeWithThumbnail = true, isStagedTask = true,
+ taskView.iconView
+ )
+ }
+ }
+
+ /**
+ * Returns the drawable that's provided in iconView, however if that
+ * is null it falls back to the drawable that's in splitSelectSource.
+ * TaskView's icon drawable can be null if the TaskView is scrolled far enough off screen
+ * @return [Drawable]
+ */
+ fun getDrawable(iconView: IconView, splitSelectSource: SplitSelectSource?) : Drawable? {
+ if (iconView.drawable == null && splitSelectSource != null) {
+ return splitSelectSource.drawable
+ }
+ return iconView.drawable
+ }
+
+ /**
+ * When selecting first app from split pair, second app's thumbnail remains. This animates
+ * the second thumbnail by expanding it to take up the full taskViewWidth/Height and overlaying
+ * it with [TaskThumbnailView]'s splashView. Adds animations to the provided builder.
+ * Note: The app that **was not** selected as the first split app should be the container that's
+ * passed through.
+ *
+ * @param builder Adds animation to this
+ * @param taskIdAttributeContainer container of the app that **was not** selected
+ * @param isPrimaryTaskSplitting if true, task that was split would be top/left in the pair
+ * (opposite of that representing [taskIdAttributeContainer])
+ */
+ fun addInitialSplitFromPair(taskIdAttributeContainer: TaskIdAttributeContainer,
+ builder: PendingAnimation, deviceProfile: DeviceProfile,
+ taskViewWidth: Int, taskViewHeight: Int,
+ isPrimaryTaskSplitting: Boolean) {
+ val thumbnail = taskIdAttributeContainer.thumbnailView
+ val iconView: View = taskIdAttributeContainer.iconView
+ builder.add(ObjectAnimator.ofFloat(thumbnail, TaskThumbnailView.SPLASH_ALPHA, 1f))
+ thumbnail.setShowSplashForSplitSelection(true)
+ if (deviceProfile.isLandscape) {
+ // Center view first so scaling happens uniformly, alternatively we can move pivotX to 0
+ val centerThumbnailTranslationX: Float = (taskViewWidth - thumbnail.width) / 2f
+ val centerIconTranslationX: Float = (taskViewWidth - iconView.width) / 2f
+ val finalScaleX: Float = taskViewWidth.toFloat() / thumbnail.width
+ builder.add(ObjectAnimator.ofFloat(thumbnail,
+ TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, centerThumbnailTranslationX))
+ // icons are anchored from Gravity.END, so need to use negative translation
+ builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X,
+ -centerIconTranslationX))
+ builder.add(ObjectAnimator.ofFloat(thumbnail, View.SCALE_X, finalScaleX))
+
+ // Reset other dimensions
+ // TODO(b/271468547), can't set Y translate to 0, need to account for top space
+ thumbnail.scaleY = 1f
+ val translateYResetVal: Float = if (!isPrimaryTaskSplitting) 0f else
+ deviceProfile.overviewTaskThumbnailTopMarginPx.toFloat()
+ builder.add(ObjectAnimator.ofFloat(thumbnail,
+ TaskThumbnailView.SPLIT_SELECT_TRANSLATE_Y,
+ translateYResetVal))
+ } else {
+ val thumbnailSize = taskViewHeight - deviceProfile.overviewTaskThumbnailTopMarginPx
+ // Center view first so scaling happens uniformly, alternatively we can move pivotY to 0
+ // primary thumbnail has layout margin above it, so secondary thumbnail needs to take
+ // that into account. We should migrate to only using translations otherwise this
+ // asymmetry causes problems..
+
+ // Icon defaults to center | horizontal, we add additional translation for split
+ val centerIconTranslationX = 0f
+ var centerThumbnailTranslationY: Float
+
+ // TODO(b/271468547), primary thumbnail has layout margin above it, so secondary
+ // thumbnail needs to take that into account. We should migrate to only using
+ // translations otherwise this asymmetry causes problems..
+ if (isPrimaryTaskSplitting) {
+ centerThumbnailTranslationY = (thumbnailSize - thumbnail.height) / 2f
+ centerThumbnailTranslationY += deviceProfile.overviewTaskThumbnailTopMarginPx
+ .toFloat()
+ } else {
+ centerThumbnailTranslationY = (thumbnailSize - thumbnail.height) / 2f
+ }
+ val finalScaleY: Float = thumbnailSize.toFloat() / thumbnail.height
+ builder.add(ObjectAnimator.ofFloat(thumbnail,
+ TaskThumbnailView.SPLIT_SELECT_TRANSLATE_Y, centerThumbnailTranslationY))
+
+ // icons are anchored from Gravity.END, so need to use negative translation
+ builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X,
+ centerIconTranslationX))
+ builder.add(ObjectAnimator.ofFloat(thumbnail, View.SCALE_Y, finalScaleY))
+
+ // Reset other dimensions
+ thumbnail.scaleX = 1f
+ builder.add(ObjectAnimator.ofFloat(thumbnail,
+ TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, 0f))
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 681f068e52..5a883bf4eb 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -57,8 +57,10 @@ import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
+import com.android.quickstep.RecentsModel;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskAnimationManager;
import com.android.quickstep.TaskViewUtils;
@@ -79,19 +81,33 @@ public class SplitSelectStateController {
private final Context mContext;
private final Handler mHandler;
+ private final RecentsModel mRecentTasksModel;
+ private final SplitAnimationController mSplitAnimationController;
private StatsLogManager mStatsLogManager;
private final SystemUiProxy mSystemUiProxy;
private final StateManager mStateManager;
- private final DepthController mDepthController;
- private @StagePosition int mStagePosition;
+ @Nullable
+ private DepthController mDepthController;
+ private @StagePosition int mInitialStagePosition;
private ItemInfo mItemInfo;
+ /** {@link #mInitialTaskIntent} and {@link #mInitialUser} (the user of the Intent) are set
+ * together when split is initiated from an Intent. */
private Intent mInitialTaskIntent;
+ private UserHandle mInitialUser;
private int mInitialTaskId = INVALID_TASK_ID;
+ /** {@link #mSecondTaskIntent} and {@link #mSecondUser} (the user of the Intent) are set
+ * together when split is confirmed with an Intent. */
private Intent mSecondTaskIntent;
+ private UserHandle mSecondUser;
private int mSecondTaskId = INVALID_TASK_ID;
private boolean mRecentsAnimationRunning;
- @Nullable
- private UserHandle mUser;
+ /** If {@code true}, animates the existing task view split placeholder view */
+ private boolean mAnimateCurrentTaskDismissal;
+ /**
+ * Acts as a subset of {@link #mAnimateCurrentTaskDismissal}, we can't be dismissing from a
+ * split pair task view without wanting to animate current task dismissal overall
+ */
+ private boolean mDismissingFromSplitPair;
/** If not null, this is the TaskView we want to launch from */
@Nullable
private GroupedTaskView mLaunchingTaskView;
@@ -101,35 +117,31 @@ public class SplitSelectStateController {
private FloatingTaskView mFirstFloatingTaskView;
public SplitSelectStateController(Context context, Handler handler, StateManager stateManager,
- DepthController depthController, StatsLogManager statsLogManager) {
+ DepthController depthController, StatsLogManager statsLogManager,
+ SystemUiProxy systemUiProxy, RecentsModel recentsModel) {
mContext = context;
mHandler = handler;
mStatsLogManager = statsLogManager;
- mSystemUiProxy = SystemUiProxy.INSTANCE.get(mContext);
+ mSystemUiProxy = systemUiProxy;
mStateManager = stateManager;
mDepthController = depthController;
+ mRecentTasksModel = recentsModel;
+ mSplitAnimationController = new SplitAnimationController(this);
}
/**
- * To be called after first task selected in Overview.
+ * @param alreadyRunningTask if set to {@link android.app.ActivityTaskManager#INVALID_TASK_ID}
+ * then @param intent will be used to launch the initial task
+ * @param intent will be ignored if @param alreadyRunningTask is set
*/
- public void setInitialTaskSelect(Task task, @StagePosition int stagePosition,
- StatsLogManager.EventEnum splitEvent, ItemInfo itemInfo) {
- mInitialTaskId = task.key.id;
- setInitialData(stagePosition, splitEvent, itemInfo);
- }
-
- /**
- * To be called after first task selected from home or all apps.
- */
- public void setInitialTaskSelect(Intent intent, @StagePosition int stagePosition,
+ public void setInitialTaskSelect(@Nullable Intent intent, @StagePosition int stagePosition,
@NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent,
- @Nullable Task alreadyRunningTask) {
- if (alreadyRunningTask != null) {
- mInitialTaskId = alreadyRunningTask.key.id;
+ int alreadyRunningTask) {
+ if (alreadyRunningTask != INVALID_TASK_ID) {
+ mInitialTaskId = alreadyRunningTask;
} else {
mInitialTaskIntent = intent;
- mUser = itemInfo.user;
+ mInitialUser = itemInfo.user;
}
setInitialData(stagePosition, splitEvent, itemInfo);
@@ -149,10 +161,55 @@ public class SplitSelectStateController {
private void setInitialData(@StagePosition int stagePosition,
StatsLogManager.EventEnum splitEvent, ItemInfo itemInfo) {
mItemInfo = itemInfo;
- mStagePosition = stagePosition;
+ mInitialStagePosition = stagePosition;
mSplitEvent = splitEvent;
}
+ /**
+ * Pulls the list of active Tasks from RecentsModel, and finds the most recently active Task
+ * matching a given ComponentName. Then uses that Task (which could be null) with the given
+ * callback.
+ *
+ * Used in various task-switching or splitscreen operations when we need to check if there is a
+ * currently running Task of a certain type and use the most recent one.
+ */
+ public void findLastActiveTaskAndRunCallback(ComponentKey componentKey,
+ Consumer callback) {
+ mRecentTasksModel.getTasks(taskGroups -> {
+ Task lastActiveTask = null;
+ // Loop through tasks in reverse, since they are ordered with most-recent tasks last.
+ for (int i = taskGroups.size() - 1; i >= 0; i--) {
+ GroupTask groupTask = taskGroups.get(i);
+ Task task1 = groupTask.task1;
+ if (isInstanceOfComponent(task1, componentKey)) {
+ lastActiveTask = task1;
+ break;
+ }
+ Task task2 = groupTask.task2;
+ if (isInstanceOfComponent(task2, componentKey)) {
+ lastActiveTask = task2;
+ break;
+ }
+ }
+
+ callback.accept(lastActiveTask);
+ });
+ }
+
+ /**
+ * Checks if a given Task is the most recently-active Task of type componentName. Used for
+ * selecting already-running Tasks for splitscreen.
+ */
+ public boolean isInstanceOfComponent(@Nullable Task task, ComponentKey componentKey) {
+ // Exclude the task that is already staged
+ if (task == null || task.key.id == mInitialTaskId) {
+ return false;
+ }
+
+ return task.key.baseIntent.getComponent().equals(componentKey.componentName)
+ && task.key.userId == componentKey.user.getIdentifier();
+ }
+
/**
* To be called when the actual tasks ({@link #mInitialTaskId}, {@link #mSecondTaskId}) are
* to be launched. Call after launcher side animations are complete.
@@ -161,7 +218,7 @@ public class SplitSelectStateController {
Pair instanceIds =
LogUtils.getShellShareableInstanceId();
launchTasks(mInitialTaskId, mInitialTaskIntent, mSecondTaskId, mSecondTaskIntent,
- mStagePosition, callback, false /* freezeTaskList */, DEFAULT_SPLIT_RATIO,
+ mInitialStagePosition, callback, false /* freezeTaskList */, DEFAULT_SPLIT_RATIO,
instanceIds.first);
mStatsLogManager.logger()
@@ -178,8 +235,14 @@ public class SplitSelectStateController {
mSecondTaskId = task.key.id;
}
- public void setSecondTask(Intent intent) {
+ /**
+ * To be called as soon as user selects the second app (even if animations aren't complete)
+ * @param intent The second intent that will be launched.
+ * @param user The user of that intent.
+ */
+ public void setSecondTask(Intent intent, UserHandle user) {
mSecondTaskIntent = intent;
+ mSecondUser = user;
}
/**
@@ -236,16 +299,17 @@ public class SplitSelectStateController {
null /* options2 */, stagePosition, splitRatio, remoteTransition,
shellInstanceId);
} else if (intent2 == null) {
- launchIntentOrShortcut(intent1, options1, taskId2, stagePosition, splitRatio,
- remoteTransition, shellInstanceId);
+ launchIntentOrShortcut(intent1, mInitialUser, options1, taskId2, stagePosition,
+ splitRatio, remoteTransition, shellInstanceId);
} else if (intent1 == null) {
- launchIntentOrShortcut(intent2, options1, taskId1,
+ launchIntentOrShortcut(intent2, mSecondUser, options1, taskId1,
getOppositeStagePosition(stagePosition), splitRatio, remoteTransition,
shellInstanceId);
} else {
- mSystemUiProxy.startIntents(getPendingIntent(intent1), options1.toBundle(),
- getPendingIntent(intent2), null /* options2 */, stagePosition,
- splitRatio, remoteTransition, shellInstanceId);
+ mSystemUiProxy.startIntents(getPendingIntent(intent1, mInitialUser),
+ options1.toBundle(), getPendingIntent(intent2, mSecondUser),
+ null /* options2 */, stagePosition, splitRatio, remoteTransition,
+ shellInstanceId);
}
} else {
final RemoteSplitLaunchAnimationRunner animationRunner =
@@ -259,63 +323,63 @@ public class SplitSelectStateController {
taskId2, null /* options2 */, stagePosition, splitRatio, adapter,
shellInstanceId);
} else if (intent2 == null) {
- launchIntentOrShortcutLegacy(intent1, options1, taskId2, stagePosition, splitRatio,
- adapter, shellInstanceId);
+ launchIntentOrShortcutLegacy(intent1, mInitialUser, options1, taskId2,
+ stagePosition, splitRatio, adapter, shellInstanceId);
} else if (intent1 == null) {
- launchIntentOrShortcutLegacy(intent2, options1, taskId1,
+ launchIntentOrShortcutLegacy(intent2, mSecondUser, options1, taskId1,
getOppositeStagePosition(stagePosition), splitRatio, adapter,
shellInstanceId);
} else {
- mSystemUiProxy.startIntentsWithLegacyTransition(getPendingIntent(intent1),
- options1.toBundle(), getPendingIntent(intent2), null /* options2 */,
- stagePosition, splitRatio, adapter, shellInstanceId);
+ mSystemUiProxy.startIntentsWithLegacyTransition(
+ getPendingIntent(intent1, mInitialUser),
+ getShortcutInfo(intent1, mInitialUser), options1.toBundle(),
+ getPendingIntent(intent2, mSecondUser),
+ getShortcutInfo(intent2, mSecondUser), null /* options2 */, stagePosition,
+ splitRatio, adapter, shellInstanceId);
}
}
}
- private void launchIntentOrShortcut(Intent intent, ActivityOptions options1, int taskId,
- @StagePosition int stagePosition, float splitRatio, RemoteTransition remoteTransition,
- @Nullable InstanceId shellInstanceId) {
- PendingIntent pendingIntent = getPendingIntent(intent);
- final ShortcutInfo shortcutInfo = getShortcutInfo(intent,
- pendingIntent.getCreatorUserHandle());
+ private void launchIntentOrShortcut(Intent intent, UserHandle user, ActivityOptions options1,
+ int taskId, @StagePosition int stagePosition, float splitRatio,
+ RemoteTransition remoteTransition, @Nullable InstanceId shellInstanceId) {
+ final ShortcutInfo shortcutInfo = getShortcutInfo(intent, user);
if (shortcutInfo != null) {
mSystemUiProxy.startShortcutAndTask(shortcutInfo,
options1.toBundle(), taskId, null /* options2 */, stagePosition,
splitRatio, remoteTransition, shellInstanceId);
} else {
- mSystemUiProxy.startIntentAndTask(pendingIntent, options1.toBundle(), taskId,
- null /* options2 */, stagePosition, splitRatio, remoteTransition,
- shellInstanceId);
+ mSystemUiProxy.startIntentAndTask(getPendingIntent(intent, user),
+ options1.toBundle(), taskId, null /* options2 */, stagePosition, splitRatio,
+ remoteTransition, shellInstanceId);
}
}
- private void launchIntentOrShortcutLegacy(Intent intent, ActivityOptions options1, int taskId,
- @StagePosition int stagePosition, float splitRatio, RemoteAnimationAdapter adapter,
+ private void launchIntentOrShortcutLegacy(Intent intent, UserHandle user,
+ ActivityOptions options1, int taskId, @StagePosition int stagePosition,
+ float splitRatio, RemoteAnimationAdapter adapter,
@Nullable InstanceId shellInstanceId) {
- PendingIntent pendingIntent = getPendingIntent(intent);
- final ShortcutInfo shortcutInfo = getShortcutInfo(intent,
- pendingIntent.getCreatorUserHandle());
+ final ShortcutInfo shortcutInfo = getShortcutInfo(intent, user);
if (shortcutInfo != null) {
mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(shortcutInfo,
options1.toBundle(), taskId, null /* options2 */, stagePosition,
splitRatio, adapter, shellInstanceId);
} else {
- mSystemUiProxy.startIntentAndTaskWithLegacyTransition(pendingIntent,
- options1.toBundle(), taskId, null /* options2 */, stagePosition, splitRatio,
- adapter, shellInstanceId);
+ mSystemUiProxy.startIntentAndTaskWithLegacyTransition(
+ getPendingIntent(intent, user), options1.toBundle(), taskId,
+ null /* options2 */, stagePosition, splitRatio, adapter, shellInstanceId);
}
}
- private PendingIntent getPendingIntent(Intent intent) {
- return intent == null ? null : (mUser != null
+ private PendingIntent getPendingIntent(Intent intent, UserHandle user) {
+ return intent == null ? null : (user != null
? PendingIntent.getActivityAsUser(mContext, 0, intent,
- FLAG_MUTABLE, null /* options */, mUser)
+ FLAG_MUTABLE, null /* options */, user)
: PendingIntent.getActivity(mContext, 0, intent, FLAG_MUTABLE));
}
public @StagePosition int getActiveSplitStagePosition() {
- return mStagePosition;
+ return mInitialStagePosition;
}
public StatsLogManager.EventEnum getSplitEvent() {
@@ -327,7 +391,7 @@ public class SplitSelectStateController {
}
@Nullable
- private ShortcutInfo getShortcutInfo(Intent intent, UserHandle userHandle) {
+ private ShortcutInfo getShortcutInfo(Intent intent, UserHandle user) {
if (intent == null || intent.getPackage() == null) {
return null;
}
@@ -339,7 +403,7 @@ public class SplitSelectStateController {
try {
final Context context = mContext.createPackageContextAsUser(
- intent.getPackage(), 0 /* flags */, userHandle);
+ intent.getPackage(), 0 /* flags */, user);
return new ShortcutInfo.Builder(context, shortcutId).build();
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Failed to create a ShortcutInfo for " + intent.getPackage());
@@ -348,6 +412,26 @@ public class SplitSelectStateController {
return null;
}
+ public boolean isAnimateCurrentTaskDismissal() {
+ return mAnimateCurrentTaskDismissal;
+ }
+
+ public void setAnimateCurrentTaskDismissal(boolean animateCurrentTaskDismissal) {
+ mAnimateCurrentTaskDismissal = animateCurrentTaskDismissal;
+ }
+
+ public boolean isDismissingFromSplitPair() {
+ return mDismissingFromSplitPair;
+ }
+
+ public void setDismissingFromSplitPair(boolean dismissingFromSplitPair) {
+ mDismissingFromSplitPair = dismissingFromSplitPair;
+ }
+
+ public SplitAnimationController getSplitAnimationController() {
+ return mSplitAnimationController;
+ }
+
/**
* Requires Shell Transitions
*/
@@ -449,11 +533,15 @@ public class SplitSelectStateController {
mInitialTaskIntent = null;
mSecondTaskId = INVALID_TASK_ID;
mSecondTaskIntent = null;
- mStagePosition = SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
+ mInitialUser = null;
+ mSecondUser = null;
+ mInitialStagePosition = SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
mRecentsAnimationRunning = false;
mLaunchingTaskView = null;
mItemInfo = null;
mSplitEvent = null;
+ mAnimateCurrentTaskDismissal = false;
+ mDismissingFromSplitPair = false;
}
/**
@@ -480,6 +568,10 @@ public class SplitSelectStateController {
return mInitialTaskId;
}
+ public int getSecondTaskId() {
+ return mSecondTaskId;
+ }
+
private boolean isSecondTaskIntentSet() {
return (mSecondTaskId != INVALID_TASK_ID || mSecondTaskIntent != null);
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
index e5c74dc2e3..dd10c2da5d 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
@@ -24,6 +24,7 @@ import android.animation.AnimatorListenerAdapter;
import android.content.Intent;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.os.UserHandle;
import android.view.View;
import com.android.launcher3.DeviceProfile;
@@ -66,21 +67,24 @@ public class SplitToWorkspaceController {
}
Object tag = view.getTag();
Intent intent;
+ UserHandle user;
BitmapInfo bitmapInfo;
if (tag instanceof WorkspaceItemInfo) {
final WorkspaceItemInfo workspaceItemInfo = (WorkspaceItemInfo) tag;
intent = workspaceItemInfo.intent;
+ user = workspaceItemInfo.user;
bitmapInfo = workspaceItemInfo.bitmap;
} else if (tag instanceof com.android.launcher3.model.data.AppInfo) {
final com.android.launcher3.model.data.AppInfo appInfo =
(com.android.launcher3.model.data.AppInfo) tag;
intent = appInfo.intent;
+ user = appInfo.user;
bitmapInfo = appInfo.bitmap;
} else {
return false;
}
- mController.setSecondTask(intent);
+ mController.setSecondTask(intent, user);
boolean isTablet = mLauncher.getDeviceProfile().isTablet;
SplitAnimationTimings timings = AnimUtils.getDeviceSplitToConfirmTimings(isTablet);
diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index ad54a709d7..cd5edab9d6 100644
--- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -124,7 +124,7 @@ public class StaggeredWorkspaceAnim {
for (int i = hotseatIcons.getChildCount() - 1; i >= 0; i--) {
View child = hotseatIcons.getChildAt(i);
CellLayoutLayoutParams lp = ((CellLayoutLayoutParams) child.getLayoutParams());
- addStaggeredAnimationForView(child, lp.cellY + 1, totalRows, duration);
+ addStaggeredAnimationForView(child, lp.getCellY() + 1, totalRows, duration);
}
} else {
final int hotseatRow, qsbRow;
@@ -194,7 +194,7 @@ public class StaggeredWorkspaceAnim {
for (int i = itemsContainer.getChildCount() - 1; i >= 0; i--) {
View child = itemsContainer.getChildAt(i);
CellLayoutLayoutParams lp = ((CellLayoutLayoutParams) child.getLayoutParams());
- addStaggeredAnimationForView(child, lp.cellY + lp.cellVSpan, totalRows, duration);
+ addStaggeredAnimationForView(child, lp.getCellY() + lp.cellVSpan, totalRows, duration);
}
mAnimators.addListener(new AnimatorListenerAdapter() {
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index 74e4acc421..1112f4dd9e 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -22,27 +22,26 @@ import android.animation.Animator;
import android.animation.RectEvaluator;
import android.content.ComponentName;
import android.content.Context;
-import android.graphics.Color;
+import android.content.pm.ActivityInfo;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.os.SystemProperties;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceControl;
-import android.view.SurfaceSession;
import android.view.View;
import android.window.PictureInPictureSurfaceTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.util.Themes;
+import com.android.launcher3.icons.IconProvider;
import com.android.quickstep.TaskAnimationManager;
import com.android.systemui.shared.pip.PipSurfaceTransactionHelper;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
+import com.android.wm.shell.pip.PipContentOverlay;
/**
* Subclass of {@link RectFSpringAnim} that animates an Activity to PiP (picture-in-picture) window
@@ -54,7 +53,7 @@ public class SwipePipToHomeAnimator extends RectFSpringAnim {
private static final float END_PROGRESS = 1.0f;
private final int mTaskId;
- private final ComponentName mComponentName;
+ private final ActivityInfo mActivityInfo;
private final SurfaceControl mLeash;
private final Rect mSourceRectHint = new Rect();
private final Rect mAppBounds = new Rect();
@@ -65,7 +64,10 @@ public class SwipePipToHomeAnimator extends RectFSpringAnim {
private final Rect mDestinationBounds = new Rect();
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
- /** for calculating transform in {@link #onAnimationUpdate(AppCloseConfig, RectF, float)} */
+ /**
+ * For calculating transform in
+ * {@link #onAnimationUpdate(SurfaceControl.Transaction, RectF, float)}
+ */
private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect());
private final Rect mSourceHintRectInsets;
private final Rect mSourceInsets = new Rect();
@@ -81,16 +83,18 @@ public class SwipePipToHomeAnimator extends RectFSpringAnim {
private boolean mHasAnimationEnded;
/**
- * An overlay used to mask changes in content when entering PiP for apps that aren't seamless.
+ * Wrapper of {@link SurfaceControl} that is used when entering PiP without valid
+ * source rect hint.
*/
@Nullable
- private SurfaceControl mContentOverlay;
+ private PipContentOverlay mPipContentOverlay;
/**
* @param context {@link Context} provides Launcher resources
* @param taskId Task id associated with this animator, see also {@link #getTaskId()}
- * @param componentName Component associated with this animator,
+ * @param activityInfo {@link ActivityInfo} associated with this animator,
* see also {@link #getComponentName()}
+ * @param appIconSizePx The size in pixel for the app icon in content overlay
* @param leash {@link SurfaceControl} this animator operates on
* @param sourceRectHint See the definition in {@link android.app.PictureInPictureParams}
* @param appBounds Bounds of the application, sourceRectHint is based on this bounds
@@ -107,7 +111,8 @@ public class SwipePipToHomeAnimator extends RectFSpringAnim {
*/
private SwipePipToHomeAnimator(@NonNull Context context,
int taskId,
- @NonNull ComponentName componentName,
+ @NonNull ActivityInfo activityInfo,
+ int appIconSizePx,
@NonNull SurfaceControl leash,
@Nullable Rect sourceRectHint,
@NonNull Rect appBounds,
@@ -119,9 +124,10 @@ public class SwipePipToHomeAnimator extends RectFSpringAnim {
int cornerRadius,
int shadowRadius,
@NonNull View view) {
- super(startBounds, new RectF(destinationBoundsTransformed), context, null);
+ super(new DefaultSpringConfig(context, null, startBounds,
+ new RectF(destinationBoundsTransformed)));
mTaskId = taskId;
- mComponentName = componentName;
+ mActivityInfo = activityInfo;
mLeash = leash;
mAppBounds.set(appBounds);
mHomeToWindowPositionMap.set(homeToWindowPositionMap);
@@ -144,32 +150,19 @@ public class SwipePipToHomeAnimator extends RectFSpringAnim {
mSourceRectHint.setEmpty();
mSourceHintRectInsets = null;
- // Create a new overlay layer
- SurfaceSession session = new SurfaceSession();
- mContentOverlay = new SurfaceControl.Builder(session)
- .setCallsite("SwipePipToHomeAnimator")
- .setName("PipContentOverlay")
- .setColorLayer()
- .build();
- SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- t.show(mContentOverlay);
- t.setLayer(mContentOverlay, Integer.MAX_VALUE);
- int color = Themes.getColorBackground(view.getContext());
- float[] bgColor = new float[] {Color.red(color) / 255f, Color.green(color) / 255f,
- Color.blue(color) / 255f};
- t.setColor(mContentOverlay, bgColor);
- t.setAlpha(mContentOverlay, 0f);
- t.reparent(mContentOverlay, mLeash);
- t.apply();
-
- addOnUpdateListener((currentRect, progress) -> {
- float alpha = progress < 0.5f
- ? 0
- : Utilities.mapToRange(Math.min(progress, 1f), 0.5f, 1f,
- 0f, 1f, Interpolators.FAST_OUT_SLOW_IN);
- t.setAlpha(mContentOverlay, alpha);
- t.apply();
- });
+ // Create a new overlay layer. We do not call detach on this instance, it's propagated
+ // to other classes like PipTaskOrganizer / RecentsAnimationController to complete
+ // the cleanup.
+ if (SystemProperties.getBoolean(
+ "persist.wm.debug.enable_pip_app_icon_overlay", true)) {
+ mPipContentOverlay = new PipContentOverlay.PipAppIconOverlay(view.getContext(),
+ mAppBounds, new IconProvider(context).getIcon(mActivityInfo),
+ appIconSizePx);
+ } else {
+ mPipContentOverlay = new PipContentOverlay.PipColorOverlay(view.getContext());
+ }
+ final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+ mPipContentOverlay.attach(tx, mLeash);
} else {
mSourceRectHint.set(sourceRectHint);
mSourceHintRectInsets = new Rect(sourceRectHint.left - appBounds.left,
@@ -218,6 +211,9 @@ public class SwipePipToHomeAnimator extends RectFSpringAnim {
private PictureInPictureSurfaceTransaction onAnimationUpdate(SurfaceControl.Transaction tx,
RectF currentRect, float progress) {
currentRect.round(mCurrentBounds);
+ if (mPipContentOverlay != null) {
+ mPipContentOverlay.onAnimationUpdate(tx, mCurrentBounds, progress);
+ }
final PictureInPictureSurfaceTransaction op;
if (mSourceHintRectInsets == null) {
// no source rect hint been set, directly scale the window down
@@ -262,7 +258,7 @@ public class SwipePipToHomeAnimator extends RectFSpringAnim {
}
public ComponentName getComponentName() {
- return mComponentName;
+ return mActivityInfo.getComponentName();
}
public Rect getDestinationBounds() {
@@ -271,7 +267,7 @@ public class SwipePipToHomeAnimator extends RectFSpringAnim {
@Nullable
public SurfaceControl getContentOverlay() {
- return mContentOverlay;
+ return mPipContentOverlay == null ? null : mPipContentOverlay.getLeash();
}
/** @return {@link PictureInPictureSurfaceTransaction} for the final leash transaction. */
@@ -324,7 +320,8 @@ public class SwipePipToHomeAnimator extends RectFSpringAnim {
public static class Builder {
private Context mContext;
private int mTaskId;
- private ComponentName mComponentName;
+ private ActivityInfo mActivityInfo;
+ private int mAppIconSizePx;
private SurfaceControl mLeash;
private Rect mSourceRectHint;
private Rect mDisplayCutoutInsets;
@@ -348,8 +345,13 @@ public class SwipePipToHomeAnimator extends RectFSpringAnim {
return this;
}
- public Builder setComponentName(ComponentName componentName) {
- mComponentName = componentName;
+ public Builder setActivityInfo(ActivityInfo activityInfo) {
+ mActivityInfo = activityInfo;
+ return this;
+ }
+
+ public Builder setAppIconSizePx(int appIconSizePx) {
+ mAppIconSizePx = appIconSizePx;
return this;
}
@@ -433,8 +435,8 @@ public class SwipePipToHomeAnimator extends RectFSpringAnim {
mAppBounds.inset(mDisplayCutoutInsets);
}
}
- return new SwipePipToHomeAnimator(mContext, mTaskId, mComponentName, mLeash,
- mSourceRectHint, mAppBounds,
+ return new SwipePipToHomeAnimator(mContext, mTaskId, mActivityInfo, mAppIconSizePx,
+ mLeash, mSourceRectHint, mAppBounds,
mHomeToWindowPositionMap, mStartBounds, mDestinationBounds,
mFromRotation, mDestinationBoundsTransformed,
mCornerRadius, mShadowRadius, mAttachedView);
diff --git a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
index 5dc461363b..a34888f684 100644
--- a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
+++ b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
@@ -23,6 +23,7 @@ import android.view.Surface;
import android.view.WindowManager;
import android.view.WindowMetrics;
+import com.android.internal.policy.SystemBarUtils;
import com.android.launcher3.util.WindowBounds;
import com.android.launcher3.util.window.CachedDisplayInfo;
import com.android.launcher3.util.window.WindowManagerProxy;
@@ -44,6 +45,13 @@ public class SystemWindowManagerProxy extends WindowManagerProxy {
.getRotation();
}
+ @Override
+ protected int getStatusBarHeight(Context context, boolean isPortrait, int statusBarInset) {
+ // See b/264656380, calculate the status bar height manually as the inset in the system
+ // server might not be updated by this point yet causing extra DeviceProfile updates
+ return SystemBarUtils.getStatusBarHeight(context);
+ }
+
@Override
public ArrayMap estimateInternalDisplayBounds(
Context displayInfoContext) {
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 04af19f729..f8893bd9dd 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -104,6 +104,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
private SplitBounds mSplitBounds;
private Boolean mDrawsBelowRecents = null;
private boolean mIsGridTask;
+ private boolean mIsDesktopTask;
private int mTaskRectTranslationX;
private int mTaskRectTranslationY;
@@ -145,11 +146,19 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
if (mDp == null) {
return 1;
}
+
+ if (mIsDesktopTask) {
+ mTaskRect.set(mThumbnailPosition);
+ mPivot.set(mTaskRect.centerX(), mTaskRect.centerY());
+ return 1;
+ }
+
if (mIsGridTask) {
mSizeStrategy.calculateGridTaskSize(mContext, mDp, mTaskRect,
mOrientationState.getOrientationHandler());
} else {
- mSizeStrategy.calculateTaskSize(mContext, mDp, mTaskRect);
+ mSizeStrategy.calculateTaskSize(mContext, mDp, mTaskRect,
+ mOrientationState.getOrientationHandler());
}
Rect fullTaskSize;
@@ -227,6 +236,13 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
mIsGridTask = isGridTask;
}
+ /**
+ * Sets whether this task is part of desktop tasks in overview.
+ */
+ public void setIsDesktopTask(boolean desktop) {
+ mIsDesktopTask = desktop;
+ }
+
/**
* Apply translations on TaskRect's starting location.
*/
@@ -256,9 +272,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
*/
public RectF getCurrentCropRect() {
// Crop rect is the inverse of thumbnail matrix
- RectF insets = mCurrentFullscreenParams.mCurrentDrawnInsets;
- mTempRectF.set(-insets.left, -insets.top,
- mTaskRect.width() + insets.right, mTaskRect.height() + insets.bottom);
+ mTempRectF.set(0, 0, mTaskRect.width(), mTaskRect.height());
mInversePositionMatrix.mapRect(mTempRectF);
return mTempRectF;
}
@@ -322,7 +336,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
boolean isRtlEnabled = !mIsRecentsRtl;
mPositionHelper.updateThumbnailMatrix(
mThumbnailPosition, mThumbnailData, mTaskRect.width(), mTaskRect.height(),
- mDp.widthPx, mDp.heightPx, mDp.taskbarSize, mDp.isTablet,
+ mDp.widthPx, mDp.heightPx, mDp.taskbarHeight, mDp.isTablet,
mOrientationState.getRecentsActivityRotation(), isRtlEnabled);
mPositionHelper.getMatrix().invert(mInversePositionMatrix);
if (DEBUG) {
@@ -335,14 +349,10 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
/* taskViewScale= */1f, mTaskRect.width(), mDp, mPositionHelper);
// Apply thumbnail matrix
- RectF insets = mCurrentFullscreenParams.mCurrentDrawnInsets;
- float scale = mCurrentFullscreenParams.mScale;
float taskWidth = mTaskRect.width();
float taskHeight = mTaskRect.height();
mMatrix.set(mPositionHelper.getMatrix());
- mMatrix.postTranslate(insets.left, insets.top);
- mMatrix.postScale(scale, scale);
// Apply TaskView matrix: taskRect, translate
mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);
@@ -362,8 +372,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
applyWindowToHomeRotation(mMatrix);
// Crop rect is the inverse of thumbnail matrix
- mTempRectF.set(-insets.left, -insets.top,
- taskWidth + insets.right, taskHeight + insets.bottom);
+ mTempRectF.set(0, 0, taskWidth, taskHeight);
mInversePositionMatrix.mapRect(mTempRectF);
mTempRectF.roundOut(mTmpCropRect);
@@ -373,7 +382,6 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
return;
}
Log.d(TAG, "progress: " + fullScreenProgress
- + " scale: " + scale
+ " recentsViewScale: " + recentsViewScale.value
+ " crop: " + mTmpCropRect
+ " radius: " + getCurrentCornerRadius()
diff --git a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java
index 01a997a451..70a12d6435 100644
--- a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java
@@ -41,7 +41,8 @@ public class UnfoldMoveFromCenterHotseatAnimator extends BaseUnfoldMoveFromCente
Hotseat hotseat = mLauncher.getHotseat();
ViewGroup hotseatIcons = hotseat.getShortcutsAndWidgets();
- disableClipping(hotseat);
+ setClipChildren(hotseat, false);
+ setClipToPadding(hotseat, false);
for (int i = 0; i < hotseatIcons.getChildCount(); i++) {
View child = hotseatIcons.getChildAt(i);
@@ -51,7 +52,7 @@ public class UnfoldMoveFromCenterHotseatAnimator extends BaseUnfoldMoveFromCente
@Override
public void onTransitionFinished() {
- restoreClipping(mLauncher.getHotseat());
+ restoreClippings();
super.onTransitionFinished();
}
}
diff --git a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
index 95a4b8f2e2..7da103ee58 100644
--- a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
@@ -47,7 +47,8 @@ public class UnfoldMoveFromCenterWorkspaceAnimator extends BaseUnfoldMoveFromCen
final CellLayout cellLayout = (CellLayout) page;
ShortcutAndWidgetContainer itemsContainer = cellLayout
.getShortcutsAndWidgets();
- disableClipping(cellLayout);
+ setClipChildren(cellLayout, false);
+ setClipToPadding(cellLayout, false);
for (int i = 0; i < itemsContainer.getChildCount(); i++) {
View child = itemsContainer.getChildAt(i);
@@ -55,13 +56,13 @@ public class UnfoldMoveFromCenterWorkspaceAnimator extends BaseUnfoldMoveFromCen
}
});
- disableClipping(workspace);
+ setClipChildren(workspace, false);
+ setClipToPadding(workspace, true);
}
@Override
public void onTransitionFinished() {
- restoreClipping(mLauncher.getWorkspace());
- mLauncher.getWorkspace().forEachVisiblePage(page -> restoreClipping((CellLayout) page));
+ restoreClippings();
super.onTransitionFinished();
}
}
diff --git a/quickstep/src/com/android/quickstep/views/AllAppsEduView.java b/quickstep/src/com/android/quickstep/views/AllAppsEduView.java
index d79b318023..716d389a2c 100644
--- a/quickstep/src/com/android/quickstep/views/AllAppsEduView.java
+++ b/quickstep/src/com/android/quickstep/views/AllAppsEduView.java
@@ -111,11 +111,6 @@ public class AllAppsEduView extends AbstractFloatingView {
return (type & TYPE_ALL_APPS_EDU) != 0;
}
- @Override
- public boolean onBackPressed() {
- return true;
- }
-
@Override
public boolean canInterceptEventsInSystemGestureRegion() {
return true;
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
index d098ffc2fb..6813857ba9 100644
--- a/quickstep/src/com/android/quickstep/views/ClearAllButton.java
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
@@ -67,7 +67,6 @@ public class ClearAllButton extends Button {
private float mGridTranslationPrimary;
private float mGridScrollOffset;
private float mScrollOffsetPrimary;
- private float mSplitSelectScrollOffsetPrimary;
private int mSidePadding;
@@ -176,10 +175,6 @@ public class ClearAllButton extends Button {
mScrollOffsetPrimary = scrollOffsetPrimary;
}
- public void setSplitSelectScrollOffsetPrimary(float splitSelectScrollOffsetPrimary) {
- mSplitSelectScrollOffsetPrimary = splitSelectScrollOffsetPrimary;
- }
-
public float getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
float scrollAdjustment = 0;
if (fullscreenEnabled) {
@@ -189,7 +184,6 @@ public class ClearAllButton extends Button {
scrollAdjustment += mGridTranslationPrimary + mGridScrollOffset;
}
scrollAdjustment += mScrollOffsetPrimary;
- scrollAdjustment += mSplitSelectScrollOffsetPrimary;
return scrollAdjustment;
}
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index c878278da5..ccc2df61c9 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -18,11 +18,14 @@ package com.android.quickstep.views;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.os.SystemProperties;
@@ -30,13 +33,16 @@ import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.RunnableList;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.SystemUiProxy;
@@ -59,20 +65,24 @@ import java.util.function.Consumer;
// TODO(b/249371338): TaskView needs to be refactored to have better support for N tasks.
public class DesktopTaskView extends TaskView {
+ /** Flag to indicate whether desktop windowing proto 1 is enabled */
+ private static final boolean DESKTOP_IS_PROTO1_ENABLED = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_mode", false);
+
/** Flag to indicate whether desktop windowing proto 2 is enabled */
public static final boolean DESKTOP_IS_PROTO2_ENABLED = SystemProperties.getBoolean(
"persist.wm.debug.desktop_mode_2", false);
/** Flags to indicate whether desktop mode is available on the device */
public static final boolean DESKTOP_MODE_SUPPORTED =
- SystemProperties.getBoolean("persist.wm.debug.desktop_mode", false)
- || DESKTOP_IS_PROTO2_ENABLED;
+ DESKTOP_IS_PROTO1_ENABLED || DESKTOP_IS_PROTO2_ENABLED;
private static final String TAG = DesktopTaskView.class.getSimpleName();
private static final boolean DEBUG = true;
- private List mTasks;
+ @NonNull
+ private List mTasks = new ArrayList<>();
private final ArrayList mSnapshotViews = new ArrayList<>();
@@ -81,6 +91,8 @@ public class DesktopTaskView extends TaskView {
private final ArrayList> mPendingThumbnailRequests = new ArrayList<>();
+ private View mBackgroundView;
+
public DesktopTaskView(Context context) {
this(context, null);
}
@@ -96,15 +108,34 @@ public class DesktopTaskView extends TaskView {
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+
+ mBackgroundView = findViewById(R.id.background);
+
+ int topMarginPx =
+ mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
+ FrameLayout.LayoutParams params = (LayoutParams) mBackgroundView.getLayoutParams();
+ params.topMargin = topMarginPx;
+ mBackgroundView.setLayoutParams(params);
+
float[] outerRadii = new float[8];
Arrays.fill(outerRadii, getTaskCornerRadius());
RoundRectShape shape = new RoundRectShape(outerRadii, null, null);
ShapeDrawable background = new ShapeDrawable(shape);
- background.setTint(getResources().getColor(android.R.color.system_neutral2_300));
+ background.setTint(getResources().getColor(android.R.color.system_neutral2_300,
+ getContext().getTheme()));
// TODO(b/244348395): this should be wallpaper
- setBackground(background);
+ mBackgroundView.setBackground(background);
- mSnapshotViews.add(mSnapshotView);
+ Drawable icon = getResources().getDrawable(R.drawable.ic_desktop, getContext().getTheme());
+ Drawable iconBackground = getResources().getDrawable(R.drawable.bg_circle,
+ getContext().getTheme());
+ mIconView.setDrawable(new LayerDrawable(new Drawable[]{iconBackground, icon}));
+ }
+
+ @Override
+ protected void updateBorderBounds(Rect bounds) {
+ bounds.set(mBackgroundView.getLeft(), mBackgroundView.getTop(), mBackgroundView.getRight(),
+ mBackgroundView.getBottom());
}
@Override
@@ -124,12 +155,9 @@ public class DesktopTaskView extends TaskView {
}
Log.d(TAG, sb.toString());
}
- if (tasks.isEmpty()) {
- return;
- }
cancelPendingLoadTasks();
- mTasks = tasks;
+ mTasks = new ArrayList<>(tasks);
mSnapshotViewMap.clear();
// Ensure there are equal number of snapshot views and tasks.
@@ -202,7 +230,8 @@ public class DesktopTaskView extends TaskView {
if (task != null) {
return mSnapshotViewMap.get(task.key.id);
}
- return null;
+ // Return the place holder snapshot views. Callers expect this to be non-null
+ return mSnapshotView;
}
@Override
@@ -247,20 +276,9 @@ public class DesktopTaskView extends TaskView {
}
@Override
- public void setOrientationState(RecentsOrientedState orientationState) {
- // TODO(b/249371338): this copies logic from TaskView
- PagedOrientationHandler orientationHandler = orientationState.getOrientationHandler();
- boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+ protected void setThumbnailOrientation(RecentsOrientedState orientationState) {
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
-
- LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams();
-
int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
- int taskIconHeight = deviceProfile.overviewTaskIconSizePx;
- int taskMargin = deviceProfile.overviewTaskMarginPx;
-
- orientationHandler.setTaskIconParams(iconParams, taskMargin, taskIconHeight,
- thumbnailTopMargin, isRtl);
LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams();
snapshotParams.topMargin = thumbnailTopMargin;
@@ -292,7 +310,7 @@ public class DesktopTaskView extends TaskView {
@Override
public RunnableList launchTasks() {
SystemUiProxy.INSTANCE.get(getContext()).showDesktopApps();
- getRecentsView().startHome();
+ Launcher.getLauncher(mActivity).getStateManager().goToState(NORMAL, false /* animated */);
return null;
}
@@ -308,6 +326,11 @@ public class DesktopTaskView extends TaskView {
callback.accept(true);
}
+ @Override
+ public boolean isDesktopTask() {
+ return true;
+ }
+
@Override
void refreshThumbnails(@Nullable HashMap thumbnailDatas) {
// Sets new thumbnails based on the incoming data and refreshes the rest.
@@ -354,6 +377,7 @@ public class DesktopTaskView extends TaskView {
}
setOverlayEnabled(false);
onTaskListVisibilityChanged(false);
+ setVisibility(VISIBLE);
}
@Override
@@ -364,6 +388,9 @@ public class DesktopTaskView extends TaskView {
setMeasuredDimension(containerWidth, containerHeight);
+ int thumbnailTopMarginPx = mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
+ containerHeight -= thumbnailTopMarginPx;
+
int thumbnails = mSnapshotViewMap.size();
if (thumbnails == 0) {
return;
@@ -406,6 +433,8 @@ public class DesktopTaskView extends TaskView {
}
int taskX = (int) (positionInParent.x * scaleWidth);
int taskY = (int) (positionInParent.y * scaleHeight);
+ // move task down by margin size
+ taskY += thumbnailTopMarginPx;
thumbnailView.setX(taskX);
thumbnailView.setY(taskY);
@@ -427,6 +456,12 @@ public class DesktopTaskView extends TaskView {
// TODO(b/249371338): this copies parent implementation and makes it work for N thumbs
progress = Utilities.boundToRange(progress, 0, 1);
mFullscreenProgress = progress;
+ if (mFullscreenProgress > 0) {
+ // Don't show background while we are transitioning to/from fullscreen
+ mBackgroundView.setVisibility(INVISIBLE);
+ } else {
+ mBackgroundView.setVisibility(VISIBLE);
+ }
for (int i = 0; i < mSnapshotViewMap.size(); i++) {
TaskThumbnailView thumbnailView = mSnapshotViewMap.valueAt(i);
thumbnailView.getTaskOverlay().setFullscreenProgress(progress);
@@ -461,7 +496,7 @@ public class DesktopTaskView extends TaskView {
}
@Override
- void setThumbnailVisibility(int visibility) {
+ void setThumbnailVisibility(int visibility, int taskId) {
for (int i = 0; i < mSnapshotViewMap.size(); i++) {
mSnapshotViewMap.valueAt(i).setVisibility(visibility);
}
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 96504afcd4..7cd6756362 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -97,11 +97,6 @@ public final class DigitalWellBeingToast {
private View mBanner;
private ViewOutlineProvider mOldBannerOutlineProvider;
private float mBannerOffsetPercentage;
- /**
- * Clips rect provided by {@link #mOldBannerOutlineProvider} when in the model state to
- * hide this banner as the taskView scales up and down
- */
- private float mModalOffset = 0f;
@Nullable
private SplitBounds mSplitBounds;
private int mSplitBannerConfig = SPLIT_BANNER_FULLSCREEN;
@@ -331,22 +326,24 @@ public final class DigitalWellBeingToast {
}
private void setBannerOutline() {
- mOldBannerOutlineProvider = mBanner.getOutlineProvider();
+ // TODO(b\273367585) to investigate why mBanner.getOutlineProvider() can be null
+ mOldBannerOutlineProvider = mBanner.getOutlineProvider() != null
+ ? mBanner.getOutlineProvider()
+ : ViewOutlineProvider.BACKGROUND;
+
mBanner.setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
mOldBannerOutlineProvider.getOutline(view, outline);
- float verticalTranslation = -view.getTranslationY() + mModalOffset
- + mSplitOffsetTranslationY;
+ float verticalTranslation = -view.getTranslationY() + mSplitOffsetTranslationY;
outline.offset(0, Math.round(verticalTranslation));
}
});
mBanner.setClipToOutline(true);
}
- void updateBannerOffset(float offsetPercentage, float verticalOffset) {
+ void updateBannerOffset(float offsetPercentage) {
if (mBanner != null && mBannerOffsetPercentage != offsetPercentage) {
- mModalOffset = verticalOffset;
mBannerOffsetPercentage = offsetPercentage;
updateTranslationY();
mBanner.invalidateOutline();
@@ -359,10 +356,7 @@ public final class DigitalWellBeingToast {
}
mBanner.setTranslationY(
- (mBannerOffsetPercentage * mBanner.getHeight()) +
- mModalOffset +
- mSplitOffsetTranslationY
- );
+ (mBannerOffsetPercentage * mBanner.getHeight()) + mSplitOffsetTranslationY);
}
private void updateTranslationX() {
diff --git a/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
index adea1a40cf..4ea77532bc 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
@@ -27,8 +27,10 @@ import android.view.View;
import android.view.ViewOutlineProvider;
import android.widget.RemoteViews.RemoteViewOutlineProvider;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.launcher3.R;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.launcher3.widget.RoundedCornerEnforcement;
@@ -65,14 +67,20 @@ final class FloatingWidgetBackgroundView extends View {
setClipToOutline(true);
}
- void init(LauncherAppWidgetHostView hostView, View backgroundView, float finalRadius,
- int fallbackBackgroundColor) {
+ void init(LauncherAppWidgetHostView hostView, @NonNull View backgroundView,
+ float finalRadius, int fallbackBackgroundColor) {
mFinalRadius = finalRadius;
mSourceView = backgroundView;
mInitialOutlineRadius = getOutlineRadius(hostView, backgroundView);
mIsUsingFallback = false;
if (isSupportedDrawable(backgroundView.getForeground())) {
- mOriginalForeground = backgroundView.getForeground();
+ if (backgroundView.getTag(R.id.saved_floating_widget_foreground) == null) {
+ mOriginalForeground = backgroundView.getForeground();
+ backgroundView.setTag(R.id.saved_floating_widget_foreground, mOriginalForeground);
+ } else {
+ mOriginalForeground = (Drawable) backgroundView.getTag(
+ R.id.saved_floating_widget_foreground);
+ }
mForegroundProperties.init(
mOriginalForeground.getConstantState().newDrawable().mutate());
setForeground(mForegroundProperties.mDrawable);
@@ -82,7 +90,13 @@ final class FloatingWidgetBackgroundView extends View {
mSourceView.setForeground(clipPlaceholder);
}
if (isSupportedDrawable(backgroundView.getBackground())) {
- mOriginalBackground = backgroundView.getBackground();
+ if (backgroundView.getTag(R.id.saved_floating_widget_background) == null) {
+ mOriginalBackground = backgroundView.getBackground();
+ backgroundView.setTag(R.id.saved_floating_widget_background, mOriginalBackground);
+ } else {
+ mOriginalBackground = (Drawable) backgroundView.getTag(
+ R.id.saved_floating_widget_background);
+ }
mBackgroundProperties.init(
mOriginalBackground.getConstantState().newDrawable().mutate());
setBackground(mBackgroundProperties.mDrawable);
@@ -115,6 +129,10 @@ final class FloatingWidgetBackgroundView extends View {
}
void recycle() {
+ if (mSourceView != null) {
+ mSourceView.setTag(R.id.saved_floating_widget_foreground, null);
+ mSourceView.setTag(R.id.saved_floating_widget_background, null);
+ }
mSourceView = null;
mOriginalForeground = null;
mOriginalBackground = null;
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 3f7d677970..5bfd0350b5 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -1,10 +1,13 @@
package com.android.quickstep.views;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+
import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import android.content.Context;
import android.graphics.PointF;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
@@ -24,6 +27,7 @@ import com.android.quickstep.TaskIconCache;
import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.util.CancellableTask;
import com.android.quickstep.util.RecentsOrientedState;
+import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -71,6 +75,23 @@ public class GroupedTaskView extends TaskView {
mDigitalWellBeingToast2 = new DigitalWellBeingToast(mActivity, this);
}
+ @Override
+ protected void updateBorderBounds(Rect bounds) {
+ if (mSplitBoundsConfig == null) {
+ super.updateBorderBounds(bounds);
+ return;
+ }
+ bounds.set(
+ Math.min(mSnapshotView.getLeft() + Math.round(mSnapshotView.getTranslationX()),
+ mSnapshotView2.getLeft() + Math.round(mSnapshotView2.getTranslationX())),
+ Math.min(mSnapshotView.getTop() + Math.round(mSnapshotView.getTranslationY()),
+ mSnapshotView2.getTop() + Math.round(mSnapshotView2.getTranslationY())),
+ Math.max(mSnapshotView.getRight() + Math.round(mSnapshotView.getTranslationX()),
+ mSnapshotView2.getRight() + Math.round(mSnapshotView2.getTranslationX())),
+ Math.max(mSnapshotView.getBottom() + Math.round(mSnapshotView.getTranslationY()),
+ mSnapshotView2.getBottom() + Math.round(mSnapshotView2.getTranslationY())));
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -249,6 +270,19 @@ public class GroupedTaskView extends TaskView {
@Override
protected int getLastSelectedChildTaskIndex() {
+ SplitSelectStateController splitSelectController =
+ getRecentsView().getSplitSelectController();
+ if (splitSelectController.isDismissingFromSplitPair()) {
+ // return the container index of the task that wasn't initially selected to split with
+ // because that is the only remaining app that can be selected. The coordinate checks
+ // below aren't reliable since both of those views may be gone/transformed
+ int initSplitTaskId = getThisTaskCurrentlyInSplitSelection();
+ if (initSplitTaskId != INVALID_TASK_ID) {
+ return initSplitTaskId == mTask.key.id ? 1 : 0;
+ }
+ }
+
+ // Check which of the two apps was selected
if (isCoordInView(mIconView2, mLastTouchDownPosition)
|| isCoordInView(mSnapshotView2, mLastTouchDownPosition)) {
return 1;
@@ -278,9 +312,30 @@ public class GroupedTaskView extends TaskView {
if (mSplitBoundsConfig == null || mSnapshotView == null || mSnapshotView2 == null) {
return;
}
- getPagedOrientationHandler().measureGroupedTaskViewThumbnailBounds(mSnapshotView,
- mSnapshotView2, widthSize, heightSize, mSplitBoundsConfig,
- mActivity.getDeviceProfile(), getLayoutDirection() == LAYOUT_DIRECTION_RTL);
+ int initSplitTaskId = getThisTaskCurrentlyInSplitSelection();
+ if (initSplitTaskId == INVALID_TASK_ID) {
+ getPagedOrientationHandler().measureGroupedTaskViewThumbnailBounds(mSnapshotView,
+ mSnapshotView2, widthSize, heightSize, mSplitBoundsConfig,
+ mActivity.getDeviceProfile(), getLayoutDirection() == LAYOUT_DIRECTION_RTL);
+ // Should we be having a separate translation step apart from the measuring above?
+ // The following only applies to large screen for now, but for future reference
+ // we'd want to abstract this out in PagedViewHandlers to get the primary/secondary
+ // translation directions
+ mSnapshotView.applySplitSelectTranslateX(mSnapshotView.getTranslationX());
+ mSnapshotView.applySplitSelectTranslateY(mSnapshotView.getTranslationY());
+ mSnapshotView2.applySplitSelectTranslateX(mSnapshotView2.getTranslationX());
+ mSnapshotView2.applySplitSelectTranslateY(mSnapshotView2.getTranslationY());
+ } else {
+ // Currently being split with this taskView, let the non-split selected thumbnail
+ // take up full thumbnail area
+ TaskIdAttributeContainer container =
+ mTaskIdAttributeContainer[initSplitTaskId == mTask.key.id ? 1 : 0];
+ container.getThumbnailView().measure(widthMeasureSpec,
+ View.MeasureSpec.makeMeasureSpec(
+ heightSize -
+ mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx,
+ MeasureSpec.EXACTLY));
+ }
updateIconPlacement();
}
@@ -336,9 +391,7 @@ public class GroupedTaskView extends TaskView {
// Value set by super call
float scale = mIconView.getAlpha();
mIconView2.setAlpha(scale);
- mDigitalWellBeingToast2.updateBannerOffset(1f - scale,
- mCurrentFullscreenParams.mCurrentDrawnInsets.top
- + mCurrentFullscreenParams.mCurrentDrawnInsets.bottom);
+ mDigitalWellBeingToast2.updateBannerOffset(1f - scale);
}
@Override
@@ -355,21 +408,33 @@ public class GroupedTaskView extends TaskView {
mSnapshotView2.setSplashAlpha(mTaskThumbnailSplashAlpha);
}
+ @Override
+ protected void refreshTaskThumbnailSplash() {
+ super.refreshTaskThumbnailSplash();
+ mSnapshotView2.refreshSplashView();
+ }
+
+ @Override
+ protected void resetViewTransforms() {
+ super.resetViewTransforms();
+ mSnapshotView2.resetViewTransforms();
+ }
+
/**
- * Sets visibility for thumbnails and associated elements (DWB banners).
- * IconView is unaffected.
+ * Sets visibility for thumbnails and associated elements (DWB banners).
+ * IconView is unaffected.
*
- * When setting INVISIBLE, sets the visibility for the last selected child task.
- * When setting VISIBLE (as a reset), sets the visibility for both tasks.
+ * When setting INVISIBLE, sets the visibility for the last selected child task.
+ * When setting VISIBLE (as a reset), sets the visibility for both tasks.
*/
@Override
- void setThumbnailVisibility(int visibility) {
+ void setThumbnailVisibility(int visibility, int taskId) {
if (visibility == VISIBLE) {
mSnapshotView.setVisibility(visibility);
mDigitalWellBeingToast.setBannerVisibility(visibility);
mSnapshotView2.setVisibility(visibility);
mDigitalWellBeingToast2.setBannerVisibility(visibility);
- } else if (getLastSelectedChildTaskIndex() == 0) {
+ } else if (taskId == getTaskIds()[0]) {
mSnapshotView.setVisibility(visibility);
mDigitalWellBeingToast.setBannerVisibility(visibility);
} else {
diff --git a/quickstep/src/com/android/quickstep/views/LaunchableConstraintLayout.kt b/quickstep/src/com/android/quickstep/views/LaunchableConstraintLayout.kt
new file mode 100644
index 0000000000..537eca132a
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/LaunchableConstraintLayout.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.views
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.constraintlayout.widget.ConstraintLayout
+import com.android.systemui.animation.LaunchableView
+import com.android.systemui.animation.LaunchableViewDelegate
+
+/** A [ConstraintLayout] that also implements [LaunchableView]. */
+open class LaunchableConstraintLayout : ConstraintLayout, LaunchableView {
+ private val delegate =
+ LaunchableViewDelegate(
+ this,
+ superSetVisibility = { super.setVisibility(it) },
+ )
+
+ constructor(context: Context) : super(context)
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+ constructor(
+ context: Context,
+ attrs: AttributeSet?,
+ defStyleAttr: Int,
+ ) : super(context, attrs, defStyleAttr)
+
+ constructor(
+ context: Context,
+ attrs: AttributeSet?,
+ defStyleAttr: Int,
+ defStyleRes: Int,
+ ) : super(context, attrs, defStyleAttr, defStyleRes)
+
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {
+ delegate.setShouldBlockVisibilityChanges(block)
+ }
+
+ override fun setVisibility(visibility: Int) {
+ // Note that super.setVisibility() is passed to the delegate upon creation and called by it.
+ // This method is just a passthrough if no animation is in progress, whereas otherwise it
+ // caches the passed value and restores it at the end of the animation.
+ delegate.setVisibility(visibility)
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 6c27587058..c165accc52 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -15,6 +15,8 @@
*/
package com.android.quickstep.views;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+
import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
@@ -34,14 +36,19 @@ import androidx.annotation.Nullable;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.LauncherState;
import com.android.launcher3.logging.StatsLogManager;
-import com.android.launcher3.popup.QuickstepSystemShortcut;
import com.android.launcher3.statehandlers.DepthController;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.PendingSplitSelectInfo;
import com.android.launcher3.util.SplitConfigurationOptions;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
+import com.android.quickstep.GestureState;
import com.android.quickstep.LauncherActivityInterface;
+import com.android.quickstep.RotationTouchHelper;
+import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.SplitSelectStateController;
+import com.android.systemui.shared.recents.model.Task;
/**
* {@link RecentsView} used in Launcher activity
@@ -89,6 +96,7 @@ public class LauncherRecentsView extends RecentsView extends FrameLayo
HIDDEN_NO_TASKS,
HIDDEN_NO_RECENTS,
HIDDEN_SPLIT_SCREEN,
- HIDDEN_SPLIT_SELECT_ACTIVE
+ HIDDEN_SPLIT_SELECT_ACTIVE,
+ HIDDEN_ACTIONS_IN_MENU,
+ HIDDEN_DESKTOP
})
@Retention(RetentionPolicy.SOURCE)
public @interface ActionsHiddenFlags { }
@@ -65,6 +67,8 @@ public class OverviewActionsView extends FrameLayo
public static final int HIDDEN_NO_RECENTS = 1 << 2;
public static final int HIDDEN_SPLIT_SCREEN = 1 << 3;
public static final int HIDDEN_SPLIT_SELECT_ACTIVE = 1 << 4;
+ public static final int HIDDEN_ACTIONS_IN_MENU = 1 << 5;
+ public static final int HIDDEN_DESKTOP = 1 << 6;
@IntDef(flag = true, value = {
DISABLED_SCROLLING,
@@ -257,26 +261,8 @@ public class OverviewActionsView extends FrameLayo
* Offsets OverviewActionsView horizontal position based on 3 button nav container in taskbar.
*/
private void updatePadding() {
- if (mDp == null) {
- return;
- }
- boolean largeScreenLandscape = mDp.isTablet && !mDp.isTwoPanels && mDp.isLandscape;
- // If in 3-button mode, shift action buttons to accommodate 3-button layout.
- // (Special exception for landscape tablets, where there is enough room and we don't need to
- // shift the action buttons.)
- if (mDp.areNavButtonsInline && !largeScreenLandscape
- // If taskbar is in overview, overview action has dedicated space above nav buttons
- && !FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get()) {
- // Add extra horizontal spacing
- int additionalPadding = mDp.hotseatBarEndOffset;
- if (isLayoutRtl()) {
- setPadding(mInsets.left + additionalPadding, 0, mInsets.right, 0);
- } else {
- setPadding(mInsets.left, 0, mInsets.right + additionalPadding, 0);
- }
- } else {
- setPadding(mInsets.left, 0, mInsets.right, 0);
- }
+ // If taskbar is in overview, overview action has dedicated space above nav buttons
+ setPadding(mInsets.left, 0, mInsets.right, 0);
}
/** Updates vertical margins for different navigation mode or configuration changes. */
@@ -296,9 +282,8 @@ public class OverviewActionsView extends FrameLayo
return 0;
}
- if (!mDp.isGestureMode && mDp.isTaskbarPresent
- && !FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get()) {
- return mDp.getOverviewActionsClaimedSpaceBelow();
+ if (mDp.isTablet && FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get()) {
+ return mDp.stashedTaskbarHeight;
}
// Align to bottom of task Rect.
@@ -316,7 +301,7 @@ public class OverviewActionsView extends FrameLayo
requestLayout();
- mSplitButton.setCompoundDrawablesWithIntrinsicBounds(
+ mSplitButton.setCompoundDrawablesRelativeWithIntrinsicBounds(
(dp.isLandscape ? R.drawable.ic_split_horizontal : R.drawable.ic_split_vertical),
0, 0, 0);
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 5e645ea917..904537fddf 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -16,6 +16,7 @@
package com.android.quickstep.views;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.view.Surface.ROTATION_0;
import static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.makeMeasureSpec;
@@ -41,8 +42,8 @@ import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
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.clampToProgress;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW;
import static com.android.launcher3.config.FeatureFlags.ENABLE_LAUNCH_FROM_STAGED_APP;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_ACTIONS_SPLIT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_CLEAR_ALL;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP;
@@ -51,14 +52,14 @@ import static com.android.launcher3.touch.PagedOrientationHandler.CANVAS_TRANSLA
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.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
import static com.android.quickstep.views.ClearAllButton.DISMISS_ALPHA;
import static com.android.quickstep.views.DesktopTaskView.DESKTOP_MODE_SUPPORTED;
import static com.android.quickstep.views.OverviewActionsView.FLAG_IS_NOT_TABLET;
import static com.android.quickstep.views.OverviewActionsView.FLAG_SINGLE_TASK;
+import static com.android.quickstep.views.OverviewActionsView.HIDDEN_ACTIONS_IN_MENU;
+import static com.android.quickstep.views.OverviewActionsView.HIDDEN_DESKTOP;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
@@ -76,7 +77,6 @@ import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.WindowConfiguration;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.LocusId;
@@ -144,10 +144,11 @@ import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.cache.HandlerRunnable;
import com.android.launcher3.logging.StatsLogManager;
-import com.android.launcher3.popup.QuickstepSystemShortcut;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.touch.OverScroll;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.DynamicResource;
@@ -156,6 +157,7 @@ import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.ResourceBasedOverride.Overrides;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.TranslateEdgeEffect;
@@ -184,6 +186,7 @@ import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.RecentsOrientedState;
+import com.android.quickstep.util.SplitAnimationController.Companion.SplitAnimInitProps;
import com.android.quickstep.util.SplitAnimationTimings;
import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.util.SurfaceTransaction;
@@ -192,6 +195,7 @@ import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TaskVisualsChangeListener;
import com.android.quickstep.util.TransformParams;
import com.android.quickstep.util.VibrationConstants;
+import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
import com.android.systemui.plugins.ResourceProvider;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -450,6 +454,8 @@ public abstract class RecentsView callback) {
- mModel.getTasks(taskGroups -> {
- Task lastActiveTask = null;
- // Loop through tasks in reverse, since they are ordered with most-recent tasks last.
- for (int i = taskGroups.size() - 1; i >= 0; i--) {
- GroupTask groupTask = taskGroups.get(i);
- Task task1 = groupTask.task1;
- if (isInstanceOfComponent(task1, componentName)) {
- lastActiveTask = task1;
- break;
- }
- Task task2 = groupTask.task2;
- if (isInstanceOfComponent(task2, componentName)) {
- lastActiveTask = task2;
- break;
- }
- }
-
- callback.accept(lastActiveTask);
- });
- }
-
- /**
- * Checks if a given Task is the most recently-active Task of type componentName. Used for
- * selecting already-running Tasks for splitscreen.
- */
- public boolean isInstanceOfComponent(@Nullable Task task, ComponentName componentName) {
- if (task == null) {
- return false;
- }
- // Exclude the task that is already staged
- if (mSplitHiddenTaskView != null && mSplitHiddenTaskView.getTask().equals(task)) {
- return false;
- }
-
- return task.key.baseIntent.getComponent().equals(componentName);
- }
-
public void setOverviewStateEnabled(boolean enabled) {
mOverviewStateEnabled = enabled;
updateTaskStackListenerState();
@@ -1351,8 +1323,10 @@ public abstract class RecentsView= 0; i--) {
GroupTask groupTask = taskGroups.get(i);
- boolean isRemovalNeeded = stagedTaskToBeRemovedFromGrid != null
- && groupTask.containsTask(stagedTaskToBeRemovedFromGrid.key.id);
+ boolean isRemovalNeeded = stagedTaskIdToBeRemovedFromGrid != INVALID_TASK_ID
+ && groupTask.containsTask(stagedTaskIdToBeRemovedFromGrid);
+
+ if (groupTask instanceof DesktopTask) {
+ desktopTask = (DesktopTask) groupTask;
+ // Desktop task will be added separately in the end
+ continue;
+ }
TaskView taskView;
if (isRemovalNeeded && groupTask.hasMultipleTasks()) {
@@ -1630,7 +1626,7 @@ public abstract class RecentsView 0) {
newFocusedTaskView = getTaskViewAt(0);
+ // Check if the first task is the desktop.
+ // If first task is desktop, try to find another task to set as the focused task
+ if (newFocusedTaskView != null && newFocusedTaskView.isDesktopTask()
+ && getTaskViewCount() > 1) {
+ newFocusedTaskView = getTaskViewAt(1);
+ }
}
- mFocusedTaskViewId = newFocusedTaskView != null ?
- newFocusedTaskView.getTaskViewId() : -1;
+ mFocusedTaskViewId = newFocusedTaskView != null && !ENABLE_GRID_ONLY_OVERVIEW.get()
+ ? newFocusedTaskView.getTaskViewId() : INVALID_TASK_ID;
updateTaskSize();
updateChildTaskOrientations();
TaskView newRunningTaskView = null;
- if (runningTaskId != -1) {
+ if (runningTaskId != INVALID_TASK_ID) {
// Update mRunningTaskViewId to be the new TaskView that was assigned by binding
// the full list of tasks to taskViews
newRunningTaskView = getTaskViewByTaskId(runningTaskId);
if (newRunningTaskView != null) {
mRunningTaskViewId = newRunningTaskView.getTaskViewId();
} else {
- mRunningTaskViewId = -1;
+ mRunningTaskViewId = INVALID_TASK_ID;
}
}
@@ -1692,7 +1704,7 @@ public abstract class RecentsView 0) {
- targetPage = indexOfChild(requireTaskViewAt(0));
+ TaskView taskView = requireTaskViewAt(0);
+ // If first task id desktop, try to find another task to set the target page
+ if (taskView.isDesktopTask() && getTaskViewCount() > 1) {
+ taskView = requireTaskViewAt(1);
+ }
+ targetPage = indexOfChild(taskView);
}
}
if (targetPage != -1 && mCurrentPage != targetPage) {
int finalTargetPage = targetPage;
runOnPageScrollsInitialized(() -> {
// TODO(b/246283207): Remove logging once root cause of flake detected.
- if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ if (Utilities.isRunningInTestHarness()) {
Log.d("b/246283207", "RecentsView#applyLoadPlan() -> "
+ "previousCurrentPage: " + previousCurrentPage
+ ", targetPage: " + finalTargetPage
@@ -1721,12 +1738,12 @@ public abstract class RecentsView setCurrentPage(getRunningTaskIndex()));
setRunningTaskViewShowScreenshot(false);
setRunningTaskHidden(runningTaskTileHidden);
@@ -2562,7 +2616,7 @@ public abstract class RecentsView focusedTaskIndex) {
// For tasks after the focused task, shift by focused task's width and spacing.
@@ -2772,7 +2844,7 @@ public abstract class RecentsView= 0; j--) {
- if (j == focusedTaskIndex) {
+ if (j == focusedTaskIndex || j == desktopTaskIndex) {
continue;
}
widthOffset += requireTaskViewAt(j).getLayoutParams().width + mPageSpacing;
@@ -2791,7 +2863,7 @@ public abstract class RecentsView= 0; j--) {
- if (j == focusedTaskIndex) {
+ if (j == focusedTaskIndex || j == desktopTaskIndex) {
continue;
}
widthOffset += requireTaskViewAt(j).getLayoutParams().width + mPageSpacing;
@@ -2814,7 +2886,7 @@ public abstract class RecentsView mSplitHiddenTaskView, () -> mSplitSelectSource);
+ if (mSplitSelectStateController.isAnimateCurrentTaskDismissal()) {
// Create the split select animation from Overview
- mSplitHiddenTaskView.setThumbnailVisibility(INVISIBLE);
- anim.setViewAlpha(mSplitHiddenTaskView.getIconView(), 0, clampToProgress(LINEAR,
+ mSplitHiddenTaskView.setThumbnailVisibility(INVISIBLE,
+ mSplitSelectStateController.getInitialTaskId());
+ anim.setViewAlpha(splitAnimInitProps.getIconView(), 0, clampToProgress(LINEAR,
timings.getIconFadeStartOffset(),
timings.getIconFadeEndOffset()));
- mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
- mSplitHiddenTaskView.getThumbnail(),
- mSplitHiddenTaskView.getThumbnail().getThumbnail(),
- mSplitHiddenTaskView.getIconView().getDrawable(), startingTaskRect);
- mFirstFloatingTaskView.setAlpha(1);
- mFirstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
- true /* fadeWithThumbnail */, true /* isStagedTask */);
- } else {
- // Create the split select animation from Home
- mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
- mSplitSelectSource.view, null /* thumbnail */,
- mSplitSelectSource.drawable, startingTaskRect);
- mFirstFloatingTaskView.setAlpha(1);
- mFirstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
- false /* fadeWithThumbnail */, true /* isStagedTask */);
}
- // TODO (b/257513449): Launch animation not fully complete. OK to remove flag once it is.
+ mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
+ splitAnimInitProps.getOriginalView(),
+ splitAnimInitProps.getOriginalBitmap(),
+ splitAnimInitProps.getIconDrawable(), startingTaskRect);
+ mFirstFloatingTaskView.setAlpha(1);
+ mFirstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
+ splitAnimInitProps.getFadeWithThumbnail(), splitAnimInitProps.isStagedTask());
+
+ // Allow user to click staged app to launch into fullscreen
if (ENABLE_LAUNCH_FROM_STAGED_APP.get()) {
mFirstFloatingTaskView.setOnClickListener(this::animateToFullscreen);
}
@@ -3085,6 +3168,8 @@ public abstract class RecentsView launchStagedTask());
+ pendingAnimation.addEndListener(animationSuccess ->
+ mSplitSelectStateController.launchSplitTasks(launchSuccess ->
+ resetFromSplitSelectionState()));
pendingAnimation.buildAnim().start();
}
@@ -3145,6 +3232,7 @@ public abstract class RecentsView 0 && mTopRowIdSet.size() >= (taskCount - 1) / 2f;
- // Pick the next focused task from the preferred row.
- for (int i = 0; i < taskCount; i++) {
- TaskView taskView = requireTaskViewAt(i);
- if (taskView == dismissedTaskView) {
- continue;
+ if (isFocusedTaskDismissed) {
+ if (isSplitSelectionActive()) {
+ isStagingFocusedTask = true;
+ } else {
+ nextFocusedTaskFromTop =
+ mTopRowIdSet.size() > 0 && mTopRowIdSet.size() >= (taskCount - 1) / 2f;
+ // Pick the next focused task from the preferred row.
+ for (int i = 0; i < taskCount; i++) {
+ TaskView taskView = requireTaskViewAt(i);
+ if (taskView == dismissedTaskView) {
+ continue;
+ }
+ boolean isTopRow = mTopRowIdSet.contains(taskView.getTaskViewId());
+ if ((nextFocusedTaskFromTop && isTopRow
+ || (!nextFocusedTaskFromTop && !isTopRow))) {
+ nextFocusedTaskView = taskView;
+ break;
+ }
}
- boolean isTopRow = mTopRowIdSet.contains(taskView.getTaskViewId());
- if ((nextFocusedTaskFromTop && isTopRow
- || (!nextFocusedTaskFromTop && !isTopRow))) {
- nextFocusedTaskView = taskView;
- break;
+ if (nextFocusedTaskView != null) {
+ nextFocusedTaskWidth =
+ nextFocusedTaskView.getLayoutParams().width + mPageSpacing;
}
}
- if (nextFocusedTaskView != null) {
- nextFocusedTaskWidth =
- nextFocusedTaskView.getLayoutParams().width + mPageSpacing;
- }
}
} else {
getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC);
@@ -3189,16 +3281,12 @@ public abstract class RecentsView bottomGridRowSize;
boolean bottomRowLonger = bottomGridRowSize > topGridRowSize;
boolean dismissedTaskFromTop = mTopRowIdSet.contains(dismissedTaskViewId);
boolean dismissedTaskFromBottom = !dismissedTaskFromTop && !isFocusedTaskDismissed;
+ if (dismissedTaskFromTop || (isFocusedTaskDismissed && nextFocusedTaskFromTop)) {
+ topGridRowSize--;
+ }
+ if (dismissedTaskFromBottom || (isFocusedTaskDismissed && !nextFocusedTaskFromTop)) {
+ bottomGridRowSize--;
+ }
+ int longRowWidth = Math.max(topGridRowSize, bottomGridRowSize)
+ * (mLastComputedGridTaskSize.width() + mPageSpacing);
+ if (!ENABLE_GRID_ONLY_OVERVIEW.get() && !isStagingFocusedTask) {
+ longRowWidth += mLastComputedTaskSize.width() + mPageSpacing;
+ }
+
float gapWidth = 0;
if ((topRowLonger && dismissedTaskFromTop)
|| (bottomRowLonger && dismissedTaskFromBottom)) {
gapWidth = dismissedTaskWidth;
- } else if ((topRowLonger && nextFocusedTaskFromTop)
- || (bottomRowLonger && !nextFocusedTaskFromTop)) {
+ } else if (nextFocusedTaskView != null
+ && ((topRowLonger && nextFocusedTaskFromTop)
+ || (bottomRowLonger && !nextFocusedTaskFromTop))) {
gapWidth = nextFocusedTaskWidth;
}
if (gapWidth > 0) {
- if (taskCount > 2) {
- // Compensate the removed gap.
- longGridRowWidthDiff += mIsRtl ? -gapWidth : gapWidth;
- if (isClearAllHidden) {
- // If ClearAllButton isn't fully shown, snap to the last task.
- snapToLastTask = true;
+ if (mClearAllShortTotalWidthTranslation == 0) {
+ // Compensate the removed gap if we don't already have shortTotalCompensation,
+ // and adjust accordingly to the new shortTotalCompensation after dismiss.
+ int newClearAllShortTotalWidthTranslation = 0;
+ if (longRowWidth < mLastComputedGridSize.width()) {
+ DeviceProfile deviceProfile = mActivity.getDeviceProfile();
+ newClearAllShortTotalWidthTranslation =
+ (mIsRtl
+ ? mLastComputedTaskSize.right
+ : deviceProfile.widthPx - mLastComputedTaskSize.left)
+ - longRowWidth - deviceProfile.overviewGridSideMargin;
}
- } else {
- // If only focused task will be left, snap to focused task instead.
- longGridRowWidthDiff += getSnapToFocusedTaskScrollDiff(isClearAllHidden);
+ float gapCompensation = gapWidth - newClearAllShortTotalWidthTranslation;
+ longGridRowWidthDiff += mIsRtl ? -gapCompensation : gapCompensation;
+ }
+ if (isClearAllHidden) {
+ // If ClearAllButton isn't fully shown, snap to the last task.
+ snapToLastTask = true;
}
}
- if (mClearAllButton.getAlpha() != 0f && isLandscapeSplit) {
- // ClearAllButton will not be available in split select, snap to last task instead.
- snapToLastTask = true;
+ if (isLandscapeSplit && !isStagingFocusedTask) {
+ // LastTask's scroll is the minimum scroll in split select, if current scroll is
+ // beyond that, we'll need to snap to last task instead.
+ TaskView lastTask = getLastGridTaskView();
+ if (lastTask != null) {
+ int primaryScroll = mOrientationHandler.getPrimaryScroll(this);
+ int lastTaskScroll = getScrollForPage(indexOfChild(lastTask));
+ if ((mIsRtl && primaryScroll < lastTaskScroll)
+ || (!mIsRtl && primaryScroll > lastTaskScroll)) {
+ snapToLastTask = true;
+ }
+ }
}
if (snapToLastTask) {
longGridRowWidthDiff += getSnapToLastTaskScrollDiff();
- if (isSplitPlaceholderLastInGrid) {
- // Shift all the tasks to make space for split placeholder.
- longGridRowWidthDiff += mIsRtl ? mSplitPlaceholderSize : -mSplitPlaceholderSize;
- }
} else if (isLandscapeSplit && currentPageSnapsToEndOfGrid) {
// Use last task as reference point for scroll diff and snapping calculation as it's
// the only invariant point in landscape split screen.
@@ -3403,8 +3518,6 @@ public abstract class RecentsView 0;
+ if (isModalGridWithoutFocusedTask) {
+ modalMidpoint = indexOfChild(mSelectedTask);
+ }
float midpointOffsetSize = 0;
float leftOffsetSize = midpoint - 1 >= 0
@@ -4132,7 +4240,6 @@ public abstract class RecentsView remoteTargetHandle.getTaskViewSimulator()
- .taskPrimaryTranslation.value = totalTranslation);
+ .taskPrimaryTranslation.value = totalTranslationX);
redrawLiveTile();
}
+
+ if (showAsGrid && ENABLE_GRID_ONLY_OVERVIEW.get() && child instanceof TaskView) {
+ float totalTranslationY = getVerticalOffsetSize(i, modalOffset);
+ FloatProperty translationPropertyY =
+ ((TaskView) child).getSecondaryTaskOffsetTranslationProperty();
+ translationPropertyY.set(child, totalTranslationY);
+ }
}
updateCurveProperties();
}
@@ -4274,6 +4392,38 @@ public abstract class RecentsView{
+ thumbnail.refreshSplashView();
+ mSplitHiddenTaskView.updateSnapshotRadius();
+ });
+ } else if (isInitiatingSplitFromTaskView) {
+ // Splitting from Overview for fullscreen task
createTaskDismissAnimation(builder, mSplitHiddenTaskView, true, false, duration,
true /* dismissingForSplitSelection*/);
} else {
@@ -4435,11 +4575,13 @@ public abstract class RecentsView resetFromSplitSelectionState());
- } else {
- // Split staging was started from a new intent (from app menu in Home/AllApps)
- mActivity.startActivity(mSplitSelectSource.intent);
- }
- }
-
protected void onTaskLaunchAnimationEnd(boolean success) {
if (success) {
resetTaskVisuals();
@@ -4917,9 +5065,15 @@ public abstract class RecentsView lastTaskScroll)) {
pageScroll = lastTaskScroll;
@@ -5162,8 +5330,7 @@ public abstract class RecentsView= 0
&& (mIsRtl
diff --git a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
index b0b111d376..0d9e41243b 100644
--- a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
+++ b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
@@ -16,8 +16,6 @@
package com.android.quickstep.views;
-import static com.android.launcher3.util.NavigationMode.THREE_BUTTONS;
-
import android.content.Context;
import android.util.AttributeSet;
import android.util.FloatProperty;
@@ -27,11 +25,8 @@ import android.widget.FrameLayout;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatTextView;
-import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.launcher3.util.DisplayController;
/**
* A rounded rectangular component containing a single TextView.
@@ -103,36 +98,10 @@ public class SplitInstructionsView extends FrameLayout {
this,
mLauncher.getDeviceProfile(),
getMeasuredHeight(),
- getMeasuredWidth(),
- getThreeButtonNavShift()
+ getMeasuredWidth()
);
}
- // In some cases, when user is using 3-button nav, there isn't enough room for both the
- // 3-button nav and a centered SplitInstructionsView. This function will return an int that will
- // be used to shift the SplitInstructionsView over a bit so that everything looks well-spaced.
- // In many cases, this will return 0, since we don't need to shift it away from the center.
- int getThreeButtonNavShift() {
- DeviceProfile dp = mLauncher.getDeviceProfile();
- if ((DisplayController.getNavigationMode(getContext()) == THREE_BUTTONS)
- && ((dp.isTwoPanels) || (dp.isTablet && !dp.isLandscape))
- // If taskbar is in overview, overview action has dedicated space above nav buttons
- && !FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get()) {
- int navButtonWidth = getResources().getDimensionPixelSize(
- R.dimen.taskbar_nav_buttons_size);
- int extraMargin = getResources().getDimensionPixelSize(
- R.dimen.taskbar_split_instructions_margin);
- // Explanation: The 3-button nav for non-phones sits on one side of the screen, taking
- // up 3 buttons + a side margin worth of space. Our splitInstructionsView starts in the
- // center of the screen and we want to center it in the remaining space, therefore we
- // want to shift it over by half the 3-button layout's width.
- // If the user is using an RtL layout, we shift it the opposite way.
- return -((3 * navButtonWidth + extraMargin) / 2) * (isLayoutRtl() ? -1 : 1);
- } else {
- return 0;
- }
- }
-
public AppCompatTextView getTextView() {
return mTextView;
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
index bdc0585f83..428bd95159 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
@@ -46,33 +46,36 @@ class TaskMenuViewWithArrow : ArrowPopup {
fun showForTask(
taskContainer: TaskIdAttributeContainer,
- alignSecondRow: Boolean = false
+ alignedOptionIndex: Int = 0
): Boolean {
- val activity = BaseDraggingActivity
- .fromContext(taskContainer.taskView.context)
- val taskMenuViewWithArrow = activity.layoutInflater
- .inflate(
- R.layout.task_menu_with_arrow,
- activity.dragLayer,
- false
+ val activity =
+ BaseDraggingActivity.fromContext(
+ taskContainer.taskView.context
+ )
+ val taskMenuViewWithArrow =
+ activity.layoutInflater.inflate(
+ R.layout.task_menu_with_arrow,
+ activity.dragLayer,
+ false
) as TaskMenuViewWithArrow<*>
- return taskMenuViewWithArrow.populateAndShowForTask(taskContainer, alignSecondRow)
+ return taskMenuViewWithArrow.populateAndShowForTask(taskContainer, alignedOptionIndex)
}
}
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
- constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
- context,
- attrs,
- defStyleAttr
- )
+ constructor(
+ context: Context,
+ attrs: AttributeSet,
+ defStyleAttr: Int
+ ) : super(context, attrs, defStyleAttr)
init {
clipToOutline = true
shouldScaleArrow = true
+ mIsArrowRotated = true
// This synchronizes the arrow and menu to open at the same time
OPEN_CHILD_FADE_START_DELAY = OPEN_FADE_START_DELAY
OPEN_CHILD_FADE_DURATION = OPEN_FADE_DURATION
@@ -80,9 +83,9 @@ class TaskMenuViewWithArrow : ArrowPopup {
CLOSE_FADE_DURATION = CLOSE_CHILD_FADE_DURATION
}
- private var alignSecondRow: Boolean = false
- private val extraSpaceForSecondRowAlignment: Int
- get() = if (alignSecondRow) optionMeasuredHeight else 0
+ private var alignedOptionIndex: Int = 0
+ private val extraSpaceForRowAlignment: Int
+ get() = optionMeasuredHeight * alignedOptionIndex
private val menuWidth = context.resources.getDimensionPixelSize(R.dimen.task_menu_width_grid)
private lateinit var taskView: TaskView
@@ -91,10 +94,10 @@ class TaskMenuViewWithArrow : ArrowPopup {
private var optionMeasuredHeight = 0
private val arrowHorizontalPadding: Int
- get() = if (taskView.isFocusedTask)
- resources.getDimensionPixelSize(R.dimen.task_menu_horizontal_padding)
- else
- 0
+ get() =
+ if (taskView.isFocusedTask)
+ resources.getDimensionPixelSize(R.dimen.task_menu_horizontal_padding)
+ else 0
private var iconView: IconView? = null
private var scrim: View? = null
@@ -123,7 +126,7 @@ class TaskMenuViewWithArrow : ArrowPopup {
private fun populateAndShowForTask(
taskContainer: TaskIdAttributeContainer,
- alignSecondRow: Boolean
+ alignedOptionIndex: Int
): Boolean {
if (isAttachedToWindow) {
return false
@@ -131,7 +134,7 @@ class TaskMenuViewWithArrow : ArrowPopup {
taskView = taskContainer.taskView
this.taskContainer = taskContainer
- this.alignSecondRow = alignSecondRow
+ this.alignedOptionIndex = alignedOptionIndex
if (!populateMenu()) return false
addScrim()
show()
@@ -139,19 +142,20 @@ class TaskMenuViewWithArrow : ArrowPopup {
}
private fun addScrim() {
- scrim = View(context).apply {
- layoutParams = FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.MATCH_PARENT,
- FrameLayout.LayoutParams.MATCH_PARENT
- )
- setBackgroundColor(Themes.getAttrColor(context, R.attr.overviewScrimColor))
- alpha = 0f
- }
+ scrim =
+ View(context).apply {
+ layoutParams =
+ FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ FrameLayout.LayoutParams.MATCH_PARENT
+ )
+ setBackgroundColor(Themes.getAttrColor(context, R.attr.overviewScrimColor))
+ alpha = 0f
+ }
popupContainer.addView(scrim)
}
- /** @return true if successfully able to populate task view menu, false otherwise
- */
+ /** @return true if successfully able to populate task view menu, false otherwise */
private fun populateMenu(): Boolean {
// Icon may not be loaded
if (taskContainer.task.icon == null) return false
@@ -162,9 +166,9 @@ class TaskMenuViewWithArrow : ArrowPopup {
private fun addMenuOptions() {
// Add the options
- TaskOverlayFactory
- .getEnabledShortcuts(taskView, taskContainer)
- .forEach { this.addMenuOption(it) }
+ TaskOverlayFactory.getEnabledShortcuts(taskView, taskContainer).forEach {
+ this.addMenuOption(it)
+ }
// Add the spaces between items
val divider = ShapeDrawable(RectShape())
@@ -185,9 +189,9 @@ class TaskMenuViewWithArrow : ArrowPopup {
}
private fun addMenuOption(menuOption: SystemShortcut<*>) {
- val menuOptionView = mActivityContext.layoutInflater.inflate(
- R.layout.task_view_menu_option, this, false
- ) as LinearLayout
+ val menuOptionView =
+ mActivityContext.layoutInflater.inflate(R.layout.task_view_menu_option, this, false)
+ as LinearLayout
menuOption.setIconAndLabelFor(
menuOptionView.findViewById(R.id.icon),
menuOptionView.findViewById(R.id.text)
@@ -230,31 +234,31 @@ class TaskMenuViewWithArrow : ArrowPopup {
}
/**
- * Copy the iconView from taskView to dragLayer so it can stay on top of the scrim.
- * It needs to be called after [getTargetObjectLocation] because [mTempRect] needs to be
- * populated.
+ * Copy the iconView from taskView to dragLayer so it can stay on top of the scrim. It needs to
+ * be called after [getTargetObjectLocation] because [mTempRect] needs to be populated.
*/
private fun copyIconToDragLayer(insets: Rect) {
- iconView = IconView(context).apply {
- layoutParams = FrameLayout.LayoutParams(
- taskContainer.iconView.width,
- taskContainer.iconView.height
- )
- x = mTempRect.left.toFloat() - insets.left
- y = mTempRect.top.toFloat() - insets.top
- drawable = taskContainer.iconView.drawable
- setDrawableSize(
- taskContainer.iconView.drawableWidth,
- taskContainer.iconView.drawableHeight
- )
- }
+ iconView =
+ IconView(context).apply {
+ layoutParams =
+ FrameLayout.LayoutParams(
+ taskContainer.iconView.width,
+ taskContainer.iconView.height
+ )
+ x = mTempRect.left.toFloat() - insets.left
+ y = mTempRect.top.toFloat() - insets.top
+ drawable = taskContainer.iconView.drawable
+ setDrawableSize(
+ taskContainer.iconView.drawableWidth,
+ taskContainer.iconView.drawableHeight
+ )
+ }
popupContainer.addView(iconView)
}
/**
- * Orients this container to the left or right of the given icon, aligning with the first option
- * or second.
+ * Orients this container to the left or right of the given icon, aligning with the desired row.
*
* These are the preferred orientations, in order (RTL prefers right-aligned over left):
* - Right and first option aligned
@@ -281,19 +285,20 @@ class TaskMenuViewWithArrow : ArrowPopup {
// which means the arrow is left aligned with the menu
val rightAlignedMenuStartX = mTempRect.left - widthWithArrow
val leftAlignedMenuStartX = mTempRect.right + extraHorizontalSpace
- mIsLeftAligned = if (mIsRtl) {
- rightAlignedMenuStartX + insets.left < 0
- } else {
- leftAlignedMenuStartX + (widthWithArrow - extraHorizontalSpace) + insets.left <
+ mIsLeftAligned =
+ if (mIsRtl) {
+ rightAlignedMenuStartX + insets.left < 0
+ } else {
+ leftAlignedMenuStartX + (widthWithArrow - extraHorizontalSpace) + insets.left <
dragLayer.width - insets.right
- }
+ }
var menuStartX = if (mIsLeftAligned) leftAlignedMenuStartX else rightAlignedMenuStartX
// Offset y so that the arrow and row are center-aligned with the original icon.
val iconHeight = mTempRect.height()
val yOffset = (optionMeasuredHeight - iconHeight) / 2
- var menuStartY = mTempRect.top - yOffset - extraSpaceForSecondRowAlignment
+ var menuStartY = mTempRect.top - yOffset - extraSpaceForRowAlignment
// Insets are added later, so subtract them now.
menuStartX -= insets.left
@@ -311,8 +316,7 @@ class TaskMenuViewWithArrow : ArrowPopup {
override fun addArrow() {
popupContainer.addView(mArrow)
mArrow.x = getArrowX()
- mArrow.y = y + (optionMeasuredHeight / 2) - (mArrowHeight / 2) +
- extraSpaceForSecondRowAlignment
+ mArrow.y = y + (optionMeasuredHeight / 2) - (mArrowHeight / 2) + extraSpaceForRowAlignment
updateArrowColor()
@@ -322,22 +326,19 @@ class TaskMenuViewWithArrow : ArrowPopup