Merge "Set accessibility pane title for right pane on updating content." into main
diff --git a/aconfig/Android.bp b/aconfig/Android.bp
index dc30a35..5413601 100644
--- a/aconfig/Android.bp
+++ b/aconfig/Android.bp
@@ -20,6 +20,7 @@
aconfig_declarations {
name: "com_android_launcher3_flags",
package: "com.android.launcher3",
+ container: "system",
srcs: ["**/*.aconfig"],
}
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 82ae4cb..6d899d9 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -1,4 +1,5 @@
package: "com.android.launcher3"
+container: "system"
flag {
name: "enable_expanding_pause_work_button"
diff --git a/aconfig/launcher_search.aconfig b/aconfig/launcher_search.aconfig
index 4e16e7f..2f2690e 100644
--- a/aconfig/launcher_search.aconfig
+++ b/aconfig/launcher_search.aconfig
@@ -1,4 +1,5 @@
package: "com.android.launcher3"
+container: "system"
flag {
name: "enable_private_space"
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 68bad5c..853ac74 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -32,8 +32,11 @@
<dimen name="overview_minimum_next_prev_size">50dp</dimen>
<!-- Overview Task Views -->
- <!-- The primary task thumbnail uses up to this much of the total screen height/width -->
+ <!-- The thumbnail uses up to this much of the total screen height/width in Overview -->
<item name="overview_max_scale" format="float" type="dimen">0.7</item>
+ <!-- The thumbnail should not go smaller than this much of the total screen height/width in
+ tablet app to Overview carousel -->
+ <item name="overview_carousel_min_scale" format="float" type="dimen">0.46</item>
<!-- A touch target for icons, sometimes slightly larger than the icons themselves -->
<dimen name="task_thumbnail_icon_size">48dp</dimen>
<!-- The icon size for the focused task, placed in center of touch target -->
diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index 436fe3b..68e7824 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -23,6 +23,7 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ClipData;
import android.content.ClipDescription;
@@ -50,7 +51,6 @@
/** An Activity that can host Launcher's widget picker. */
public class WidgetPickerActivity extends BaseActivity {
private static final String TAG = "WidgetPickerActivity";
- private static final boolean DEBUG = false;
/**
* Name of the extra that indicates that a widget being dragged.
@@ -65,13 +65,13 @@
private static final String EXTRA_DESIRED_WIDGET_WIDTH = "desired_widget_width";
private static final String EXTRA_DESIRED_WIDGET_HEIGHT = "desired_widget_height";
-
private SimpleDragLayer<WidgetPickerActivity> mDragLayer;
private WidgetsModel mModel;
private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(i -> {});
private int mDesiredWidgetWidth;
private int mDesiredWidgetHeight;
+ private int mWidgetCategoryFilter;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -104,6 +104,10 @@
mDesiredWidgetHeight =
getIntent().getIntExtra(EXTRA_DESIRED_WIDGET_HEIGHT, 0);
+ // Defaults to '0' to indicate that there isn't a category filter.
+ mWidgetCategoryFilter =
+ getIntent().getIntExtra(AppWidgetManager.EXTRA_CATEGORY_FILTER, 0);
+
refreshAndBindWidgets();
}
@@ -199,6 +203,14 @@
return rejectWidget(widget, "shortcut");
}
+ if (mWidgetCategoryFilter > 0 && (info.widgetCategory & mWidgetCategoryFilter) == 0) {
+ return rejectWidget(
+ widget,
+ "doesn't match category filter [filter=%d, widget=%d]",
+ mWidgetCategoryFilter,
+ info.widgetCategory);
+ }
+
if (mDesiredWidgetWidth == 0 && mDesiredWidgetHeight == 0) {
// Accept the widget if the desired dimensions are unspecified.
return acceptWidget(widget);
@@ -210,22 +222,18 @@
if (info.maxResizeWidth > 0 && info.maxResizeWidth < mDesiredWidgetWidth) {
return rejectWidget(
widget,
- String.format(
- Locale.ENGLISH,
- "maxResizeWidth[%d] < mDesiredWidgetWidth[%d]",
- info.maxResizeWidth,
- mDesiredWidgetWidth));
+ "maxResizeWidth[%d] < mDesiredWidgetWidth[%d]",
+ info.maxResizeWidth,
+ mDesiredWidgetWidth);
}
final int minWidth = info.minResizeWidth > 0 ? info.minResizeWidth : info.minWidth;
if (minWidth > mDesiredWidgetWidth) {
return rejectWidget(
widget,
- String.format(
- Locale.ENGLISH,
- "minWidth[%d] > mDesiredWidgetWidth[%d]",
- minWidth,
- mDesiredWidgetWidth));
+ "minWidth[%d] > mDesiredWidgetWidth[%d]",
+ minWidth,
+ mDesiredWidgetWidth);
}
}
@@ -235,22 +243,18 @@
if (info.maxResizeHeight > 0 && info.maxResizeHeight < mDesiredWidgetHeight) {
return rejectWidget(
widget,
- String.format(
- Locale.ENGLISH,
- "maxResizeHeight[%d] < mDesiredWidgetHeight[%d]",
- info.maxResizeHeight,
- mDesiredWidgetHeight));
+ "maxResizeHeight[%d] < mDesiredWidgetHeight[%d]",
+ info.maxResizeHeight,
+ mDesiredWidgetHeight);
}
final int minHeight = info.minResizeHeight > 0 ? info.minResizeHeight : info.minHeight;
if (minHeight > mDesiredWidgetHeight) {
return rejectWidget(
widget,
- String.format(
- Locale.ENGLISH,
- "minHeight[%d] > mDesiredWidgetHeight[%d]",
- minHeight,
- mDesiredWidgetHeight));
+ "minHeight[%d] > mDesiredWidgetHeight[%d]",
+ minHeight,
+ mDesiredWidgetHeight);
}
}
@@ -264,8 +268,11 @@
}
private static WidgetAcceptabilityVerdict rejectWidget(
- WidgetItem widget, String rejectionReason) {
- return new WidgetAcceptabilityVerdict(false, widget.label, rejectionReason);
+ WidgetItem widget, String rejectionReason, Object... args) {
+ return new WidgetAcceptabilityVerdict(
+ false,
+ widget.label,
+ String.format(Locale.ENGLISH, rejectionReason, args));
}
private static WidgetAcceptabilityVerdict acceptWidget(WidgetItem widget) {
@@ -276,7 +283,7 @@
boolean isAcceptable, String widgetLabel, String reason) {
void maybeLogVerdict() {
// Only log a verdict if a reason is specified.
- if (DEBUG && !reason.isEmpty()) {
+ if (Log.isLoggable(TAG, Log.DEBUG) && !reason.isEmpty()) {
Log.i(TAG, String.format(
Locale.ENGLISH,
"%s: %s because %s",
diff --git a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
index 037f7a8..694475a 100644
--- a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
@@ -77,19 +77,15 @@
public AppsDividerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- boolean isMainColorDark = Themes.getAttrBoolean(context, R.attr.isMainColorDark);
mDividerSize = new int[]{
getResources().getDimensionPixelSize(R.dimen.all_apps_divider_width),
getResources().getDimensionPixelSize(R.dimen.all_apps_divider_height)
};
- mStrokeColor = ContextCompat.getColor(context, isMainColorDark
- ? R.color.all_apps_prediction_row_separator_dark
- : R.color.all_apps_prediction_row_separator);
+ mStrokeColor = ContextCompat.getColor(context, R.color.material_color_outline_variant);
- mAllAppsLabelTextColor = ContextCompat.getColor(context, isMainColorDark
- ? R.color.all_apps_label_text_dark
- : R.color.all_apps_label_text);
+ mAllAppsLabelTextColor = ContextCompat.getColor(context,
+ R.color.material_color_on_surface_variant);
mShowAllAppsLabel = !ALL_APPS_VISITED_COUNT.hasReachedMax(context);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 9f6994a..8d83716 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -222,8 +222,7 @@
public void onStateTransitionCompletedAfterSwipeToHome(LauncherState finalState) {
// TODO(b/279514548) Cleans up bad state that can occur when user interacts with
// taskbar on top of transparent activity.
- if (!FeatureFlags.enableHomeTransitionListener()
- && (finalState == LauncherState.NORMAL)
+ if ((finalState == LauncherState.NORMAL)
&& mLauncher.hasBeenResumed()) {
updateStateForFlag(FLAG_VISIBLE, true);
applyState();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index db7d0eb..7a69c55 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -21,6 +21,7 @@
import static com.android.app.animation.Interpolators.FINAL_FRAME;
import static com.android.app.animation.Interpolators.INSTANT;
import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.internal.jank.InteractionJankMonitor.Configuration;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_HIDE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_SHOW;
@@ -572,7 +573,8 @@
mAnimator.cancel();
}
mAnimator = new AnimatorSet();
- addJankMonitorListener(mAnimator, /* appearing= */ !mIsStashed);
+ addJankMonitorListener(
+ mAnimator, /* expanding= */ !mIsStashed, /* animationType= */ animationType);
boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivity);
final float stashTranslation = mActivity.isPhoneMode() || isTransientTaskbar
? 0
@@ -798,14 +800,20 @@
as.play(a);
}
- private void addJankMonitorListener(AnimatorSet animator, boolean expanding) {
+ private void addJankMonitorListener(
+ AnimatorSet animator, boolean expanding, @StashAnimation int animationType) {
View v = mControllers.taskbarActivityContext.getDragLayer();
int action = expanding ? InteractionJankMonitor.CUJ_TASKBAR_EXPAND :
InteractionJankMonitor.CUJ_TASKBAR_COLLAPSE;
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(@NonNull Animator animation) {
- InteractionJankMonitor.getInstance().begin(v, action);
+ final Configuration.Builder builder =
+ Configuration.Builder.withView(action, v);
+ if (animationType == TRANSITION_HOME_TO_APP) {
+ builder.setTag("HOME_TO_APP");
+ }
+ InteractionJankMonitor.getInstance().begin(builder);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index ca598c8..fc3eeba 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -38,6 +38,7 @@
import android.view.MotionEvent;
import android.view.ViewConfiguration;
+import com.android.internal.jank.Cuj;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
@@ -53,6 +54,7 @@
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.OverviewToHomeAnim;
import com.android.quickstep.views.RecentsView;
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import java.util.function.Consumer;
@@ -148,6 +150,8 @@
mMotionPauseDetector.clear();
if (handlingOverviewAnim()) {
+ InteractionJankMonitorWrapper.begin(mRecentsView, Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS,
+ "Home");
mMotionPauseDetector.setOnMotionPauseListener(this::onMotionPauseDetected);
}
@@ -182,6 +186,7 @@
if (mStartedOverview) {
goToOverviewOrHomeOnDragEnd(velocity);
} else {
+ InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS);
super.onDragEnd(velocity);
}
@@ -237,6 +242,7 @@
private void maybeSwipeInteractionToOverviewComplete() {
if (mReachedOverview && !mDetector.isDraggingState()) {
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS);
onSwipeInteractionCompleted(OVERVIEW);
}
}
@@ -280,6 +286,7 @@
if (goToHomeInsteadOfOverview) {
new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(NORMAL), null)
.animateWithVelocity(velocity);
+ InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS);
}
if (mReachedOverview) {
float distanceDp = dpiFromPx(Math.max(
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 6d3b60a..b7a907f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -193,6 +193,8 @@
mMotionPauseDetector.clear();
if (start) {
InteractionJankMonitorWrapper.begin(mRecentsView, Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
+ InteractionJankMonitorWrapper.begin(mRecentsView, Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS,
+ "Home");
mStartState = mLauncher.getStateManager().getState();
@@ -350,6 +352,7 @@
.dispatchOnStart();
return;
}
+ InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS);
final LauncherState targetState;
if (horizontalFling && verticalFling) {
@@ -471,6 +474,8 @@
if (targetState == QUICK_SWITCH_FROM_HOME) {
InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
+ } else if (targetState == OVERVIEW) {
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS);
}
mLauncher.getStateManager().goToState(targetState, false, forEndCallback(this::clearState));
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 6698600..4752225 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -30,6 +30,7 @@
import static com.android.launcher3.BaseActivity.EVENT_STARTED;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
+import static com.android.launcher3.Flags.enableGridOnlyOverview;
import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
import static com.android.launcher3.PagedView.INVALID_PAGE;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
@@ -2561,9 +2562,11 @@
}
float scrollOffset = Math.abs(mRecentsView.getScrollOffset(mRecentsView.getCurrentPage()));
+ Rect carouselTaskSize = enableGridOnlyOverview()
+ ? mRecentsView.getLastComputedCarouselTaskSize()
+ : mRecentsView.getLastComputedTaskSize();
int maxScrollOffset = mRecentsView.getPagedOrientationHandler().getPrimaryValue(
- mRecentsView.getLastComputedTaskSize().width(),
- mRecentsView.getLastComputedTaskSize().height());
+ carouselTaskSize.width(), carouselTaskSize.height());
maxScrollOffset += mRecentsView.getPageSpacing();
float maxScaleProgress =
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index b89d20c..879312d 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -261,6 +261,23 @@
}
}
+ /**
+ * Calculates the taskView size for carousel during app to overview animation on tablets.
+ */
+ public final void calculateCarouselTaskSize(Context context, DeviceProfile dp, Rect outRect,
+ PagedOrientationHandler orientedState) {
+ if (dp.isTablet && dp.isGestureMode) {
+ Resources res = context.getResources();
+ float minScale = res.getFloat(R.dimen.overview_carousel_min_scale);
+ Rect gridRect = new Rect();
+ calculateGridSize(dp, context, gridRect);
+ calculateTaskSizeInternal(context, dp, gridRect, minScale, Gravity.CENTER | Gravity.TOP,
+ outRect);
+ } else {
+ calculateTaskSize(context, dp, outRect, orientedState);
+ }
+ }
+
private void calculateFocusTaskSize(Context context, DeviceProfile dp, Rect outRect) {
Resources res = context.getResources();
float maxScale = res.getFloat(R.dimen.overview_max_scale);
@@ -286,13 +303,13 @@
}
private void calculateTaskSizeInternal(Context context, DeviceProfile dp,
- Rect potentialTaskRect, float maxScale, int gravity, Rect outRect) {
+ Rect potentialTaskRect, float targetScale, int gravity, Rect outRect) {
PointF taskDimension = getTaskDimension(context, dp);
float scale = Math.min(
potentialTaskRect.width() / taskDimension.x,
potentialTaskRect.height() / taskDimension.y);
- scale = Math.min(scale, maxScale);
+ scale = Math.min(scale, targetScale);
int outWidth = Math.round(scale * taskDimension.x);
int outHeight = Math.round(scale * taskDimension.y);
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index d617828..cb0aa8f 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -22,7 +22,6 @@
import static com.android.launcher3.util.DisplayController.CHANGE_ALL;
import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.NavigationMode.NO_BUTTON;
import static com.android.launcher3.util.NavigationMode.THREE_BUTTONS;
import static com.android.launcher3.util.SettingsCache.ONE_HANDED_ENABLED;
@@ -54,15 +53,11 @@
import android.os.RemoteException;
import android.os.SystemProperties;
import android.provider.Settings;
-import android.util.Log;
-import android.view.ISystemGestureExclusionListener;
-import android.view.IWindowManager;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
-import android.view.WindowManagerGlobal;
-import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.config.FeatureFlags;
@@ -73,6 +68,8 @@
import com.android.launcher3.util.SettingsCache;
import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.GestureExclusionManager;
+import com.android.quickstep.util.GestureExclusionManager.ExclusionListener;
import com.android.quickstep.util.NavBarPosition;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
@@ -86,7 +83,7 @@
/**
* Manages the state of the system during a swipe up gesture.
*/
-public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
+public class RecentsAnimationDeviceState implements DisplayInfoChangeListener, ExclusionListener {
private static final String TAG = "RecentsAnimationDeviceState";
@@ -99,25 +96,9 @@
private final Context mContext;
private final DisplayController mDisplayController;
- private final IWindowManager mIWindowManager;
+ private final GestureExclusionManager mExclusionManager;
- @VisibleForTesting
- final ISystemGestureExclusionListener mGestureExclusionListener =
- new ISystemGestureExclusionListener.Stub() {
- @BinderThread
- @Override
- public void onSystemGestureExclusionChanged(int displayId,
- Region systemGestureExclusionRegion, Region unrestrictedOrNull) {
- if (displayId != DEFAULT_DISPLAY) {
- return;
- }
- // Assignments are atomic, it should be safe on binder thread. Also we don't
- // think systemGestureExclusionRegion can be null but just in case, don't
- // let mExclusionRegion be null.
- mExclusionRegion = systemGestureExclusionRegion != null
- ? systemGestureExclusionRegion : new Region();
- }
- };
+
private final RotationTouchHelper mRotationTouchHelper;
private final TaskStackChangeListener mPipListener;
// Cache for better performance since it doesn't change at runtime.
@@ -140,20 +121,20 @@
private boolean mPipIsActive;
private int mGestureBlockingTaskId = -1;
- private @NonNull Region mExclusionRegion = new Region();
+ private @NonNull Region mExclusionRegion = GestureExclusionManager.EMPTY_REGION;
private boolean mExclusionListenerRegistered;
public RecentsAnimationDeviceState(Context context) {
- this(context, false, WindowManagerGlobal.getWindowManagerService());
+ this(context, false, GestureExclusionManager.INSTANCE);
}
public RecentsAnimationDeviceState(Context context, boolean isInstanceForTouches) {
- this(context, isInstanceForTouches, WindowManagerGlobal.getWindowManagerService());
+ this(context, isInstanceForTouches, GestureExclusionManager.INSTANCE);
}
@VisibleForTesting
- RecentsAnimationDeviceState(Context context, IWindowManager windowManager) {
- this(context, false, windowManager);
+ RecentsAnimationDeviceState(Context context, GestureExclusionManager exclusionManager) {
+ this(context, false, exclusionManager);
}
/**
@@ -162,10 +143,10 @@
*/
RecentsAnimationDeviceState(
Context context, boolean isInstanceForTouches,
- IWindowManager windowManager) {
+ GestureExclusionManager exclusionManager) {
mContext = context;
mDisplayController = DisplayController.INSTANCE.get(context);
- mIWindowManager = windowManager;
+ mExclusionManager = exclusionManager;
mIsOneHandedModeSupported = SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false);
mRotationTouchHelper = RotationTouchHelper.INSTANCE.get(context);
if (isInstanceForTouches) {
@@ -276,43 +257,33 @@
}
}
- /**
- * Registers {@link mGestureExclusionListener} for getting exclusion rect changes. Note that we
- * make binder call on {@link UI_HELPER_EXECUTOR} to avoid jank.
- */
- public void registerExclusionListener() {
- UI_HELPER_EXECUTOR.execute(() -> {
- if (mExclusionListenerRegistered) {
- return;
- }
- try {
- mIWindowManager.registerSystemGestureExclusionListener(
- mGestureExclusionListener, DEFAULT_DISPLAY);
- mExclusionListenerRegistered = true;
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to register window manager callbacks", e);
- }
- });
+ @Override
+ public void onGestureExclusionChanged(@Nullable Region exclusionRegion,
+ @Nullable Region unrestrictedOrNull) {
+ mExclusionRegion = exclusionRegion != null
+ ? exclusionRegion : GestureExclusionManager.EMPTY_REGION;
}
/**
- * Unregisters {@link mGestureExclusionListener} if previously registered. We make binder call
- * on same {@link UI_HELPER_EXECUTOR} as in {@link #registerExclusionListener()} so that
- * read/write {@link mExclusionListenerRegistered} field is thread safe.
+ * Registers itself for getting exclusion rect changes.
+ */
+ public void registerExclusionListener() {
+ if (mExclusionListenerRegistered) {
+ return;
+ }
+ mExclusionManager.addListener(this);
+ mExclusionListenerRegistered = true;
+ }
+
+ /**
+ * Unregisters itself as gesture exclusion listener if previously registered.
*/
public void unregisterExclusionListener() {
- UI_HELPER_EXECUTOR.execute(() -> {
- if (!mExclusionListenerRegistered) {
- return;
- }
- try {
- mIWindowManager.unregisterSystemGestureExclusionListener(
- mGestureExclusionListener, DEFAULT_DISPLAY);
- mExclusionListenerRegistered = false;
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to unregister window manager callbacks", e);
- }
- });
+ if (!mExclusionListenerRegistered) {
+ return;
+ }
+ mExclusionManager.removeListener(this);
+ mExclusionListenerRegistered = false;
}
public void onOneHandedModeChanged(int newGesturalHeight) {
@@ -515,10 +486,8 @@
* This is only used for quickswitch, and not swipe up.
*/
public boolean isInExclusionRegion(MotionEvent event) {
- // mExclusionRegion can change on binder thread, use a local instance here.
- Region exclusionRegion = mExclusionRegion;
return mMode == NO_BUTTON
- && exclusionRegion.contains((int) event.getX(), (int) event.getY());
+ && mExclusionRegion.contains((int) event.getX(), (int) event.getY());
}
/**
diff --git a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.java
index 24b48db..8648b56 100644
--- a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.java
+++ b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.java
@@ -602,7 +602,7 @@
: iconMenuParams.width / 2f);
iconAppChipView.setPivotY(
isRtl ? (iconMenuParams.height / 2f) : iconMenuParams.width / 2f);
- iconAppChipView.setTranslationY(0);
+ iconAppChipView.setSplitTranslationY(0);
iconAppChipView.setRotation(getDegreesRotated());
}
@@ -641,12 +641,14 @@
primaryIconView.setTranslationX(0);
secondaryIconView.setTranslationX(0);
if (enableOverviewIconMenu()) {
+ IconAppChipView primaryAppChipView = (IconAppChipView) primaryIconView;
+ IconAppChipView secondaryAppChipView = (IconAppChipView) secondaryIconView;
if (primaryIconView.getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
- secondaryIconView.setTranslationY(-primarySnapshotHeight);
- primaryIconView.setTranslationY(0);
+ secondaryAppChipView.setSplitTranslationY(-primarySnapshotHeight);
+ primaryAppChipView.setSplitTranslationY(0);
} else {
int secondarySnapshotHeight = groupedTaskViewHeight - primarySnapshotHeight;
- primaryIconView.setTranslationY(secondarySnapshotHeight);
+ primaryAppChipView.setSplitTranslationY(secondarySnapshotHeight);
}
} else if (splitConfig.initiatedFromSeascape) {
// if the split was initiated from seascape,
diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
index 2f996e1..60e6a25 100644
--- a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
+++ b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
@@ -639,7 +639,7 @@
iconAppChipView.setPivotX(0);
iconAppChipView.setPivotY(0);
- iconAppChipView.setTranslationY(0);
+ iconAppChipView.setSplitTranslationY(0);
iconAppChipView.setRotation(getDegreesRotated());
}
@@ -655,6 +655,8 @@
: new FrameLayout.LayoutParams(primaryIconParams);
if (enableOverviewIconMenu()) {
+ IconAppChipView primaryAppChipView = (IconAppChipView) primaryIconView;
+ IconAppChipView secondaryAppChipView = (IconAppChipView) secondaryIconView;
primaryIconParams.gravity = TOP | START;
secondaryIconParams.gravity = TOP | START;
secondaryIconParams.topMargin = primaryIconParams.topMargin;
@@ -662,16 +664,16 @@
if (deviceProfile.isLeftRightSplit) {
if (isRtl) {
int secondarySnapshotWidth = groupedTaskViewWidth - primarySnapshotWidth;
- primaryIconView.setTranslationX(-secondarySnapshotWidth);
+ primaryAppChipView.setSplitTranslationX(-secondarySnapshotWidth);
} else {
- secondaryIconView.setTranslationX(primarySnapshotWidth);
+ secondaryAppChipView.setSplitTranslationX(primarySnapshotWidth);
}
} else {
- primaryIconView.setTranslationX(0);
- secondaryIconView.setTranslationX(0);
+ primaryAppChipView.setSplitTranslationX(0);
+ secondaryAppChipView.setSplitTranslationX(0);
int dividerThickness = Math.min(splitConfig.visualDividerBounds.width(),
splitConfig.visualDividerBounds.height());
- secondaryIconView.setTranslationY(
+ secondaryAppChipView.setSplitTranslationY(
primarySnapshotHeight + (deviceProfile.isTablet ? 0 : dividerThickness));
}
} else if (deviceProfile.isLeftRightSplit) {
diff --git a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.java
index 9090d14..a964639 100644
--- a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.java
+++ b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.java
@@ -244,7 +244,7 @@
iconAppChipView.setPivotX(isRtl ? iconMenuParams.width / 2f : iconCenter);
iconAppChipView.setPivotY(
isRtl ? iconMenuParams.width / 2f : iconCenter - iconMenuMargin);
- iconAppChipView.setTranslationY(0);
+ iconAppChipView.setSplitTranslationY(0);
iconAppChipView.setRotation(getDegreesRotated());
}
@@ -281,12 +281,15 @@
primaryIconView.setTranslationX(0);
secondaryIconView.setTranslationX(0);
if (enableOverviewIconMenu()) {
+ IconAppChipView primaryAppChipView = (IconAppChipView) primaryIconView;
+ IconAppChipView secondaryAppChipView = (IconAppChipView) secondaryIconView;
if (isRtl) {
- primaryIconView.setTranslationY(groupedTaskViewHeight - primarySnapshotHeight);
- secondaryIconView.setTranslationY(0);
+ primaryAppChipView.setSplitTranslationY(
+ groupedTaskViewHeight - primarySnapshotHeight);
+ secondaryAppChipView.setSplitTranslationY(0);
} else {
- secondaryIconView.setTranslationY(-primarySnapshotHeight);
- primaryIconView.setTranslationY(0);
+ secondaryAppChipView.setSplitTranslationY(-primarySnapshotHeight);
+ primaryAppChipView.setSplitTranslationY(0);
}
} else if (splitConfig.initiatedFromSeascape) {
// if the split was initiated from seascape,
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
index 278ca56..1e05a69 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
@@ -67,15 +67,15 @@
/**
* Adds a log to be printed at log-dump-time.
*/
- public void addLog(String event) {
+ public void addLog(@NonNull String event) {
addLog(event, null);
}
- public void addLog(String event, int extras) {
+ public void addLog(@NonNull String event, int extras) {
addLog(event, extras, null);
}
- public void addLog(String event, boolean extras) {
+ public void addLog(@NonNull String event, boolean extras) {
addLog(event, extras, null);
}
@@ -85,30 +85,30 @@
* @param gestureEvent GestureEvent representing the event being logged.
*/
public void addLog(
- String event, @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
+ @NonNull String event, @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
addLog(new CompoundString(event), gestureEvent);
}
public void addLog(
- String event,
+ @NonNull String event,
int extras,
@Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
addLog(new CompoundString(event).append(": ").append(extras), gestureEvent);
}
public void addLog(
- String event,
+ @NonNull String event,
boolean extras,
@Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
addLog(new CompoundString(event).append(": ").append(extras), gestureEvent);
}
- public void addLog(CompoundString compoundString) {
+ public void addLog(@NonNull CompoundString compoundString) {
addLog(compoundString, null);
}
public void addLog(
- CompoundString compoundString,
+ @NonNull CompoundString compoundString,
@Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
EventLog lastEventLog = logs[(nextIndex + logs.length - 1) % logs.length];
if (lastEventLog == null || mCurrentLogId != lastEventLog.logId) {
@@ -259,21 +259,20 @@
public CompoundString(String substring) {
mIsNoOp = substring == null;
- if (mIsNoOp) {
- mSubstrings = null;
- mArgs = null;
- return;
+ mSubstrings = mIsNoOp ? null : new ArrayList<>();
+ mArgs = mIsNoOp ? null : new ArrayList<>();
+
+ if (!mIsNoOp) {
+ mSubstrings.add(substring);
}
- mSubstrings = new ArrayList<>();
- mSubstrings.add(substring);
- mArgs = new ArrayList<>();
}
public CompoundString append(CompoundString substring) {
- if (mIsNoOp) {
+ if (mIsNoOp || substring.mIsNoOp) {
return this;
}
mSubstrings.addAll(substring.mSubstrings);
+ mArgs.addAll(substring.mArgs);
return this;
}
@@ -288,30 +287,53 @@
}
public CompoundString append(int num) {
+ if (mIsNoOp) {
+ return this;
+ }
+ mArgs.add(num);
+
+ return append("%d");
+ }
+
+ public CompoundString append(long num) {
+ if (mIsNoOp) {
+ return this;
+ }
mArgs.add(num);
return append("%d");
}
public CompoundString append(float num) {
+ if (mIsNoOp) {
+ return this;
+ }
mArgs.add(num);
return append("%.2f");
}
public CompoundString append(double num) {
+ if (mIsNoOp) {
+ return this;
+ }
mArgs.add(num);
return append("%.2f");
}
public CompoundString append(boolean bool) {
+ if (mIsNoOp) {
+ return this;
+ }
mArgs.add(bool);
return append("%b");
}
- public Object[] getArgs() {
+ private Object[] getArgs() {
+ Preconditions.assertTrue(!mIsNoOp);
+
return mArgs.toArray();
}
@@ -320,7 +342,7 @@
return String.format(toUnformattedString(), getArgs());
}
- public String toUnformattedString() {
+ private String toUnformattedString() {
Preconditions.assertTrue(!mIsNoOp);
StringBuilder sb = new StringBuilder();
@@ -333,7 +355,7 @@
@Override
public int hashCode() {
- return Objects.hash(mIsNoOp, mSubstrings);
+ return Objects.hash(mIsNoOp, mSubstrings, mArgs);
}
@Override
@@ -342,7 +364,9 @@
return false;
}
CompoundString other = (CompoundString) obj;
- return (mIsNoOp == other.mIsNoOp) && Objects.equals(mSubstrings, other.mSubstrings);
+ return (mIsNoOp == other.mIsNoOp)
+ && Objects.equals(mSubstrings, other.mSubstrings)
+ && Objects.equals(mArgs, other.mArgs);
}
}
}
diff --git a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
index bc8b571..16f2065 100644
--- a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
+++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
@@ -17,6 +17,7 @@
import static com.android.app.animation.Interpolators.DECELERATE;
import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.launcher3.Flags.enableGridOnlyOverview;
import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
@@ -58,6 +59,7 @@
FROM_APP(0.75f, 0.5f, 1f, false),
FROM_APP_TO_ALL_APPS(1f, 0.6f, 0.8f, false),
FROM_APP_TABLET(1f, 0.7f, 1f, true),
+ FROM_APP_TABLET_GRID_ONLY(1f, 1f, 1f, true),
FROM_APP_TO_ALL_APPS_TABLET(1f, 0.5f, 0.5f, false),
FROM_OVERVIEW(1f, 0.75f, 0.5f, false);
@@ -239,10 +241,10 @@
float stopResist =
params.resistanceParams.stopScalingAtTop ? 1f - startRect.top / endRectF.top : 1f;
final TimeInterpolator scaleInterpolator = t -> {
- if (t < startResist) {
+ if (t <= startResist) {
return t;
}
- if (t > stopResist) {
+ if (t >= stopResist) {
return maxResist;
}
float resistProgress = Utilities.getProgress(t, startResist, stopResist);
@@ -304,7 +306,9 @@
resistanceParams =
recentsOrientedState.getActivityInterface().allowAllAppsFromOverview()
? RecentsResistanceParams.FROM_APP_TO_ALL_APPS_TABLET
- : RecentsResistanceParams.FROM_APP_TABLET;
+ : enableGridOnlyOverview()
+ ? RecentsResistanceParams.FROM_APP_TABLET_GRID_ONLY
+ : RecentsResistanceParams.FROM_APP_TABLET;
} else {
resistanceParams =
recentsOrientedState.getActivityInterface().allowAllAppsFromOverview()
diff --git a/quickstep/src/com/android/quickstep/util/GestureExclusionManager.kt b/quickstep/src/com/android/quickstep/util/GestureExclusionManager.kt
new file mode 100644
index 0000000..24b0e3a
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/GestureExclusionManager.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util
+
+import android.graphics.Region
+import android.os.RemoteException
+import android.util.Log
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.ISystemGestureExclusionListener
+import android.view.IWindowManager
+import android.view.WindowManagerGlobal
+import androidx.annotation.BinderThread
+import androidx.annotation.VisibleForTesting
+import com.android.launcher3.util.Executors
+
+/** Wrapper over system gesture exclusion listener to optimize for multiple RPCs */
+class GestureExclusionManager(private val windowManager: IWindowManager) {
+
+ private val listeners = mutableListOf<ExclusionListener>()
+
+ private var lastExclusionRegion: Region? = null
+ private var lastUnrestrictedOrNull: Region? = null
+
+ @VisibleForTesting
+ val exclusionListener =
+ object : ISystemGestureExclusionListener.Stub() {
+ @BinderThread
+ override fun onSystemGestureExclusionChanged(
+ displayId: Int,
+ exclusionRegion: Region?,
+ unrestrictedOrNull: Region?
+ ) {
+ if (displayId != DEFAULT_DISPLAY) {
+ return
+ }
+ Executors.MAIN_EXECUTOR.execute {
+ lastExclusionRegion = exclusionRegion
+ lastUnrestrictedOrNull = unrestrictedOrNull
+ listeners.forEach {
+ it.onGestureExclusionChanged(exclusionRegion, unrestrictedOrNull)
+ }
+ }
+ }
+ }
+
+ /** Adds a listener for receiving gesture exclusion regions */
+ fun addListener(listener: ExclusionListener) {
+ val wasEmpty = listeners.isEmpty()
+ listeners.add(listener)
+ if (wasEmpty) {
+ Executors.UI_HELPER_EXECUTOR.execute {
+ try {
+ windowManager.registerSystemGestureExclusionListener(
+ exclusionListener,
+ DEFAULT_DISPLAY
+ )
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Failed to register gesture exclusion listener", e)
+ }
+ }
+ } else {
+ // If we had already registered before, dispatch the last known value,
+ // otherwise registering the listener will initiate a dispatch
+ listener.onGestureExclusionChanged(lastExclusionRegion, lastUnrestrictedOrNull)
+ }
+ }
+
+ /** Removes a previously added exclusion listener */
+ fun removeListener(listener: ExclusionListener) {
+ if (listeners.remove(listener) && listeners.isEmpty()) {
+ Executors.UI_HELPER_EXECUTOR.execute {
+ try {
+ windowManager.unregisterSystemGestureExclusionListener(
+ exclusionListener,
+ DEFAULT_DISPLAY
+ )
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Failed to unregister gesture exclusion listener", e)
+ }
+ }
+ }
+ }
+
+ interface ExclusionListener {
+ fun onGestureExclusionChanged(exclusionRegion: Region?, unrestrictedOrNull: Region?)
+ }
+
+ companion object {
+
+ private const val TAG = "GestureExclusionManager"
+
+ @JvmField
+ val INSTANCE = GestureExclusionManager(WindowManagerGlobal.getWindowManagerService()!!)
+
+ @JvmField val EMPTY_REGION = Region()
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index a8a96ce..b8bc828 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -96,8 +96,14 @@
mSpeedSomewhatFast = res.getDimension(R.dimen.motion_pause_detector_speed_somewhat_fast);
mSpeedFast = res.getDimension(R.dimen.motion_pause_detector_speed_fast);
mForcePauseTimeout = new Alarm();
- mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */,
- "Force pause timeout after " + alarm.getLastSetTimeout() + "ms" /* reason */));
+ mForcePauseTimeout.setOnAlarmListener(alarm -> {
+ ActiveGestureLog.CompoundString log =
+ new ActiveGestureLog.CompoundString("Force pause timeout after ")
+ .append(alarm.getLastSetTimeout())
+ .append("ms");
+ addLogs(log);
+ updatePaused(true /* isPaused */, log);
+ });
mMakePauseHarderToTrigger = makePauseHarderToTrigger;
mVelocityProvider = new SystemVelocityProvider(axis);
}
@@ -113,8 +119,14 @@
* @param disallowPause If true, we will not detect any pauses until this is set to false again.
*/
public void setDisallowPause(boolean disallowPause) {
+ ActiveGestureLog.CompoundString log =
+ new ActiveGestureLog.CompoundString("Set disallowPause=")
+ .append(disallowPause);
+ if (mDisallowPause != disallowPause) {
+ addLogs(log);
+ }
mDisallowPause = disallowPause;
- updatePaused(mIsPaused, "Set disallowPause=" + disallowPause);
+ updatePaused(mIsPaused, log);
}
/**
@@ -148,27 +160,30 @@
float speed = Math.abs(velocity);
float previousSpeed = Math.abs(prevVelocity);
boolean isPaused;
- String isPausedReason = "";
+ ActiveGestureLog.CompoundString isPausedReason;
if (mIsPaused) {
// Continue to be paused until moving at a fast speed.
isPaused = speed < mSpeedFast || previousSpeed < mSpeedFast;
- isPausedReason = "Was paused, but started moving at a fast speed";
+ isPausedReason = new ActiveGestureLog.CompoundString(
+ "Was paused, but started moving at a fast speed");
} else {
if (velocity < 0 != prevVelocity < 0) {
// We're just changing directions, not necessarily stopping.
isPaused = false;
- isPausedReason = "Velocity changed directions";
+ isPausedReason = new ActiveGestureLog.CompoundString("Velocity changed directions");
} else {
isPaused = speed < mSpeedVerySlow && previousSpeed < mSpeedVerySlow;
- isPausedReason = "Pause requires back to back slow speeds";
+ isPausedReason = new ActiveGestureLog.CompoundString(
+ "Pause requires back to back slow speeds");
if (!isPaused && !mHasEverBeenPaused) {
// We want to be more aggressive about detecting the first pause to ensure it
// feels as responsive as possible; getting two very slow speeds back to back
// takes too long, so also check for a rapid deceleration.
boolean isRapidDeceleration = speed < previousSpeed * RAPID_DECELERATION_FACTOR;
isPaused = isRapidDeceleration && speed < mSpeedSomewhatFast;
- isPausedReason = "Didn't have back to back slow speeds, checking for rapid"
- + " deceleration on first pause only";
+ isPausedReason = new ActiveGestureLog.CompoundString(
+ "Didn't have back to back slow speeds, checking for rapid ")
+ .append(" deceleration on first pause only");
}
if (mMakePauseHarderToTrigger) {
if (speed < mSpeedSlow) {
@@ -176,12 +191,14 @@
mSlowStartTime = time;
}
isPaused = time - mSlowStartTime >= HARDER_TRIGGER_TIMEOUT;
- isPausedReason = "Maintained slow speed for sufficient duration when making"
- + " pause harder to trigger";
+ isPausedReason = new ActiveGestureLog.CompoundString(
+ "Maintained slow speed for sufficient duration when making")
+ .append(" pause harder to trigger");
} else {
mSlowStartTime = 0;
isPaused = false;
- isPausedReason = "Intentionally making pause harder to trigger";
+ isPausedReason = new ActiveGestureLog.CompoundString(
+ "Intentionally making pause harder to trigger");
}
}
}
@@ -189,18 +206,21 @@
updatePaused(isPaused, isPausedReason);
}
- private void updatePaused(boolean isPaused, String reason) {
+ private void updatePaused(boolean isPaused, ActiveGestureLog.CompoundString reason) {
if (mDisallowPause) {
- reason = "Disallow pause; otherwise, would have been " + isPaused + " due to " + reason;
+ reason = new ActiveGestureLog.CompoundString(
+ "Disallow pause; otherwise, would have been ")
+ .append(isPaused)
+ .append(" due to reason:")
+ .append(reason);
isPaused = false;
}
if (mIsPaused != isPaused) {
mIsPaused = isPaused;
- String logString = "onMotionPauseChanged, paused=" + mIsPaused + " reason=" + reason;
- if (Utilities.isRunningInTestHarness()) {
- Log.d(TAG, logString);
- }
- ActiveGestureLog.INSTANCE.addLog(logString);
+ addLogs(new ActiveGestureLog.CompoundString("onMotionPauseChanged triggered; paused=")
+ .append(mIsPaused)
+ .append(", reason=")
+ .append(reason));
boolean isFirstDetectedPause = !mHasEverBeenPaused && mIsPaused;
if (mIsPaused) {
AccessibilityManagerCompat.sendTestProtocolEventToTest(mContext,
@@ -219,6 +239,16 @@
}
}
+ private void addLogs(ActiveGestureLog.CompoundString compoundString) {
+ ActiveGestureLog.CompoundString logString =
+ new ActiveGestureLog.CompoundString("MotionPauseDetector: ")
+ .append(compoundString);
+ if (Utilities.isRunningInTestHarness()) {
+ Log.d(TAG, logString.toString());
+ }
+ ActiveGestureLog.INSTANCE.addLog(logString);
+ }
+
public void clear() {
mVelocityProvider.clear();
mPreviousVelocity = null;
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 0bb6b23..1152de2 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -38,10 +38,12 @@
import android.graphics.RectF;
import android.util.Log;
import android.view.RemoteAnimationTarget;
+import android.view.animation.Interpolator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
@@ -76,6 +78,8 @@
private final Rect mTaskRect = new Rect();
private final Rect mFullTaskSize = new Rect();
+ private final Rect mCarouselTaskSize = new Rect();
+ private PointF mPivotOverride = null;
private final PointF mPivot = new PointF();
private DeviceProfile mDp;
@StagePosition
@@ -95,6 +99,11 @@
public final AnimatedFloat taskPrimaryTranslation = new AnimatedFloat();
public final AnimatedFloat taskSecondaryTranslation = new AnimatedFloat();
+ // Carousel properties
+ public final AnimatedFloat carouselScale = new AnimatedFloat();
+ public final AnimatedFloat carouselPrimaryTranslation = new AnimatedFloat();
+ public final AnimatedFloat carouselSecondaryTranslation = new AnimatedFloat();
+
// RecentsView properties
public final AnimatedFloat recentsViewScale = new AnimatedFloat();
public final AnimatedFloat fullScreenProgress = new AnimatedFloat();
@@ -109,9 +118,9 @@
private Boolean mDrawsBelowRecents = null;
private boolean mIsGridTask;
private boolean mIsDesktopTask;
+ private boolean mScaleToCarouselTaskSize = false;
private int mTaskRectTranslationX;
private int mTaskRectTranslationY;
- private int mPivotOffsetX;
public TaskViewSimulator(Context context, BaseActivityInterface sizeStrategy) {
mContext = context;
@@ -124,6 +133,7 @@
mOrientationStateId = mOrientationState.getStateId();
Resources resources = context.getResources();
mIsRecentsRtl = mOrientationState.getOrientationHandler().getRecentsRtlSetting(resources);
+ carouselScale.value = 1f;
}
/**
@@ -149,6 +159,11 @@
mOrientationState.getOrientationHandler());
}
+ if (enableGridOnlyOverview()) {
+ mSizeStrategy.calculateCarouselTaskSize(mContext, mDp, mCarouselTaskSize,
+ mOrientationState.getOrientationHandler());
+ }
+
if (mSplitBounds != null) {
// The task rect changes according to the staged split task sizes, but recents
// fullscreen scale and pivot remains the same since the task fits into the existing
@@ -193,9 +208,18 @@
}
// Copy mFullTaskSize instead of updating it directly so it could be reused next time
// without recalculating
- Rect scaleRect = new Rect(mFullTaskSize);
- scaleRect.offset(mTaskRectTranslationX + mPivotOffsetX, mTaskRectTranslationY);
- return mOrientationState.getFullScreenScaleAndPivot(scaleRect, mDp, mPivot);
+ Rect scaleRect = new Rect();
+ if (mScaleToCarouselTaskSize) {
+ scaleRect.set(mCarouselTaskSize);
+ } else {
+ scaleRect.set(mFullTaskSize);
+ }
+ scaleRect.offset(mTaskRectTranslationX, mTaskRectTranslationY);
+ float scale = mOrientationState.getFullScreenScaleAndPivot(scaleRect, mDp, mPivot);
+ if (mPivotOverride != null) {
+ mPivot.set(mPivotOverride);
+ }
+ return scale;
}
/**
@@ -278,14 +302,64 @@
/**
* Adds animation for all the components corresponding to transition from an app to overview.
*/
- public void addAppToOverviewAnim(PendingAnimation pa, TimeInterpolator interpolator) {
+ public void addAppToOverviewAnim(PendingAnimation pa, Interpolator interpolator) {
pa.addFloat(fullScreenProgress, AnimatedFloat.VALUE, 1, 0, interpolator);
- if (enableGridOnlyOverview() && mDp.isTablet) {
- int translationXToMiddle = mDp.widthPx / 2 - mFullTaskSize.centerX();
- taskPrimaryTranslation.value = translationXToMiddle;
- mPivotOffsetX = translationXToMiddle;
+ float fullScreenScale;
+ if (enableGridOnlyOverview() && mDp.isTablet && mDp.isGestureMode) {
+ // Move pivot to top right edge of the screen, to avoid task scaling down in opposite
+ // direction of app window movement, otherwise the animation will wiggle left and right.
+ // Also translate the app window to top right edge of the screen to simplify
+ // calculations.
+ taskPrimaryTranslation.value = mIsRecentsRtl
+ ? mDp.widthPx - mFullTaskSize.right
+ : -mFullTaskSize.left;
+ taskSecondaryTranslation.value = -mFullTaskSize.top;
+ mPivotOverride = new PointF(mIsRecentsRtl ? mDp.widthPx : 0, 0);
+
+ // Scale down to the carousel and use the carousel Rect to calculate fullScreenScale.
+ mScaleToCarouselTaskSize = true;
+ carouselScale.value = mCarouselTaskSize.width() / (float) mFullTaskSize.width();
+ fullScreenScale = getFullScreenScale();
+
+ float carouselPrimaryTranslationTarget = mIsRecentsRtl
+ ? mCarouselTaskSize.right - mDp.widthPx
+ : mCarouselTaskSize.left;
+ float carouselSecondaryTranslationTarget = mCarouselTaskSize.top;
+
+ // Expected carousel position's center is in the middle, and invariant of
+ // recentsViewScale.
+ float exceptedCarouselCenterX = mCarouselTaskSize.centerX();
+ // Animating carousel translations linearly will result in a curved path, therefore
+ // we'll need to calculate the expected translation at each recentsView scale. Luckily
+ // primary and secondary follow the same translation, and primary is used here due to
+ // it being simpler.
+ Interpolator carouselTranslationInterpolator = t -> {
+ // recentsViewScale is calculated rather than using recentsViewScale.value, so that
+ // this interpolator works independently even if recentsViewScale don't animate.
+ float recentsViewScale =
+ Utilities.mapToRange(t, 0, 1, fullScreenScale, 1, Interpolators.LINEAR);
+ // Without the translation, the app window will animate from fullscreen into top
+ // right corner.
+ float expectedTaskCenterX = mIsRecentsRtl
+ ? mDp.widthPx - mCarouselTaskSize.width() * recentsViewScale / 2f
+ : mCarouselTaskSize.width() * recentsViewScale / 2f;
+ // Calculate the expected translation, then work back the animatedFraction that
+ // results in this value.
+ float carouselPrimaryTranslation =
+ (exceptedCarouselCenterX - expectedTaskCenterX) / recentsViewScale;
+ return carouselPrimaryTranslation / carouselPrimaryTranslationTarget;
+ };
+
+ // Use addAnimatedFloat so this animation can later be canceled and animate to a
+ // different value in RecentsView.onPrepareGestureEndAnimation.
+ pa.addAnimatedFloat(carouselPrimaryTranslation, 0, carouselPrimaryTranslationTarget,
+ carouselTranslationInterpolator);
+ pa.addAnimatedFloat(carouselSecondaryTranslation, 0, carouselSecondaryTranslationTarget,
+ carouselTranslationInterpolator);
+ } else {
+ fullScreenScale = getFullScreenScale();
}
- pa.addFloat(recentsViewScale, AnimatedFloat.VALUE, getFullScreenScale(), 1, interpolator);
+ pa.addFloat(recentsViewScale, AnimatedFloat.VALUE, fullScreenScale, 1, interpolator);
}
/**
@@ -382,7 +456,7 @@
float fullScreenProgress = Utilities.boundToRange(this.fullScreenProgress.value, 0, 1);
mCurrentFullscreenParams.setProgress(fullScreenProgress, recentsViewScale.value,
- /* taskViewScale= */1f);
+ carouselScale.value);
// Apply thumbnail matrix
float taskWidth = mTaskRect.width();
@@ -396,6 +470,13 @@
taskPrimaryTranslation.value);
mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
taskSecondaryTranslation.value);
+
+ mMatrix.postScale(carouselScale.value, carouselScale.value, mPivot.x, mPivot.y);
+ mOrientationState.getOrientationHandler().setPrimary(mMatrix, MATRIX_POST_TRANSLATE,
+ carouselPrimaryTranslation.value);
+ mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
+ carouselSecondaryTranslation.value);
+
mOrientationState.getOrientationHandler().setPrimary(
mMatrix, MATRIX_POST_TRANSLATE, recentsViewScroll.value);
@@ -420,15 +501,18 @@
return;
}
Log.d(TAG, "progress: " + fullScreenProgress
+ + " carouselScale: " + carouselScale.value
+ " recentsViewScale: " + recentsViewScale.value
+ " crop: " + mTmpCropRect
+ " radius: " + getCurrentCornerRadius()
+ " taskW: " + taskWidth + " H: " + taskHeight
+ " taskRect: " + mTaskRect
+ " taskPrimaryT: " + taskPrimaryTranslation.value
+ + " taskSecondaryT: " + taskSecondaryTranslation.value
+ + " carouselPrimaryT: " + carouselPrimaryTranslation.value
+ + " carouselSecondaryT: " + carouselSecondaryTranslation.value
+ " recentsPrimaryT: " + recentsViewPrimaryTranslation.value
+ " recentsSecondaryT: " + recentsViewSecondaryTranslation.value
- + " taskSecondaryT: " + taskSecondaryTranslation.value
+ " recentsScroll: " + recentsViewScroll.value
+ " pivot: " + mPivot
);
diff --git a/quickstep/src/com/android/quickstep/views/IconAppChipView.java b/quickstep/src/com/android/quickstep/views/IconAppChipView.java
index 958c28a..cb3566e 100644
--- a/quickstep/src/com/android/quickstep/views/IconAppChipView.java
+++ b/quickstep/src/com/android/quickstep/views/IconAppChipView.java
@@ -18,6 +18,8 @@
import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -42,6 +44,7 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.RecentsOrientedState;
@@ -87,6 +90,67 @@
private int mMaxWidth = Integer.MAX_VALUE;
+ private static final int INDEX_SPLIT_TRANSLATION = 0;
+ private static final int INDEX_MENU_TRANSLATION = 1;
+ private static final int INDEX_COUNT_TRANSLATION = 2;
+
+ private final MultiPropertyFactory<View> mViewTranslationX;
+ private final MultiPropertyFactory<View> mViewTranslationY;
+
+ /**
+ * Sets the view split x-axis translation
+ * @param translationX x-axis translation
+ */
+ public void setSplitTranslationX(float translationX) {
+ mViewTranslationX.get(INDEX_SPLIT_TRANSLATION).setValue(translationX);
+ }
+
+ /**
+ * Sets the view split y-axis translation
+ * @param translationY y-axis translation
+ */
+ public void setSplitTranslationY(float translationY) {
+ mViewTranslationY.get(INDEX_SPLIT_TRANSLATION).setValue(translationY);
+ }
+
+ /**
+ * Gets the menu x-axis translation for split task
+ */
+ public MultiPropertyFactory<View>.MultiProperty getMenuTranslationX() {
+ return mViewTranslationX.get(INDEX_MENU_TRANSLATION);
+ }
+
+ /**
+ * Translate the View on the X-axis without overriding the raw translation.
+ * This function is used for the menu split animation. It allows external animations to
+ * translate this view without affecting the value of the original translation. Thus,
+ * it is possible to restore the initial translation value.
+ *
+ * @param translationX Animated translation to be aggregated to the raw translation.
+ */
+ public void setMenuTranslationX(float translationX) {
+ mViewTranslationX.get(INDEX_MENU_TRANSLATION).setValue(translationX);
+ }
+
+ /**
+ * Gets the menu y-axis translation for split task
+ */
+ public MultiPropertyFactory<View>.MultiProperty getMenuTranslationY() {
+ return mViewTranslationY.get(INDEX_MENU_TRANSLATION);
+ }
+
+ /**
+ * Translate the View on the Y-axis without overriding the raw translation.
+ * This function is used for the menu split animation. It allows external animations to
+ * translate this view without affecting the value of the original translation. Thus,
+ * it is possible to restore the initial translation value.
+ *
+ * @param translationY Animated translation to be aggregated to the raw translation.
+ */
+ public void setMenuTranslationY(float translationY) {
+ mViewTranslationY.get(INDEX_MENU_TRANSLATION).setValue(translationY);
+ }
+
public IconAppChipView(Context context) {
this(context, null);
}
@@ -136,6 +200,13 @@
R.dimen.task_thumbnail_icon_menu_arrow_size);
mIconViewDrawableExpandedSize = res.getDimensionPixelSize(
R.dimen.task_thumbnail_icon_menu_app_icon_expanded_size);
+
+ mViewTranslationX = new MultiPropertyFactory<>(this, VIEW_TRANSLATE_X,
+ INDEX_COUNT_TRANSLATION,
+ Float::sum);
+ mViewTranslationY = new MultiPropertyFactory<>(this, VIEW_TRANSLATE_Y,
+ INDEX_COUNT_TRANSLATION,
+ Float::sum);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 9884d8d..f6afaf0 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -459,6 +459,7 @@
@Nullable
protected RemoteTargetHandle[] mRemoteTargetHandles;
+ protected final Rect mLastComputedCarouselTaskSize = new Rect();
protected final Rect mLastComputedTaskSize = new Rect();
protected final Rect mLastComputedGridSize = new Rect();
protected final Rect mLastComputedGridTaskSize = new Rect();
@@ -2108,6 +2109,10 @@
mSizeStrategy.calculateDesktopTaskSize(mActivity, mActivity.getDeviceProfile(),
mLastComputedDesktopTaskSize);
}
+ if (enableGridOnlyOverview()) {
+ mSizeStrategy.calculateCarouselTaskSize(mActivity, dp, mLastComputedCarouselTaskSize,
+ getPagedOrientationHandler());
+ }
mTaskGridVerticalDiff = mLastComputedGridTaskSize.top - mLastComputedTaskSize.top;
mTopBottomRowHeightDiff =
@@ -2137,9 +2142,12 @@
}
float accumulatedTranslationX = 0;
- float translateXToMiddle = enableGridOnlyOverview() && mActivity.getDeviceProfile().isTablet
- ? mActivity.getDeviceProfile().widthPx / 2 - mLastComputedGridTaskSize.centerX()
- : 0;
+ float translateXToMiddle = 0;
+ if (enableGridOnlyOverview() && mActivity.getDeviceProfile().isTablet) {
+ translateXToMiddle = mIsRtl
+ ? mLastComputedCarouselTaskSize.right - mLastComputedTaskSize.right
+ : mLastComputedCarouselTaskSize.left - mLastComputedTaskSize.left;
+ }
for (int i = 0; i < taskCount; i++) {
TaskView taskView = requireTaskViewAt(i);
taskView.updateTaskSize();
@@ -2206,6 +2214,10 @@
return mLastComputedDesktopTaskSize;
}
+ public Rect getLastComputedCarouselTaskSize() {
+ return mLastComputedCarouselTaskSize;
+ }
+
/** Gets the task size for modal state. */
public void getModalTaskSize(Rect outRect) {
mSizeStrategy.calculateModalTaskSize(mActivity, mActivity.getDeviceProfile(), outRect,
@@ -2675,6 +2687,9 @@
tvs.taskSecondaryTranslation.value = runningTaskSecondaryGridTranslation;
} else {
animatorSet.play(ObjectAnimator.ofFloat(this, RECENTS_GRID_PROGRESS, 1));
+ animatorSet.play(tvs.carouselScale.animateToValue(1));
+ animatorSet.play(tvs.carouselPrimaryTranslation.animateToValue(0));
+ animatorSet.play(tvs.carouselSecondaryTranslation.animateToValue(0));
animatorSet.play(tvs.taskPrimaryTranslation.animateToValue(
runningTaskPrimaryGridTranslation));
animatorSet.play(tvs.taskSecondaryTranslation.animateToValue(
@@ -4411,12 +4426,13 @@
mTempPointF.set(mLastComputedTaskSize.centerX(), mLastComputedTaskSize.bottom);
}
} else {
- mTempRect.set(mLastComputedTaskSize);
// Only update pivot when it is tablet and not in grid yet, so the pivot is correct
// for non-current tasks when swiping up to overview
if (enableGridOnlyOverview() && mActivity.getDeviceProfile().isTablet
&& !mOverviewGridEnabled) {
- mTempRect.offset(mActivity.getDeviceProfile().widthPx / 2 - mTempRect.centerX(), 0);
+ mTempRect.set(mLastComputedCarouselTaskSize);
+ } else {
+ mTempRect.set(mLastComputedTaskSize);
}
getPagedViewOrientedState().getFullScreenScaleAndPivot(mTempRect,
mActivity.getDeviceProfile(), mTempPointF);
@@ -5117,7 +5133,12 @@
* Returns the scale up required on the view, so that it coves the screen completely
*/
public float getMaxScaleForFullScreen() {
- getTaskSize(mTempRect);
+ if (enableGridOnlyOverview() && mActivity.getDeviceProfile().isTablet
+ && !mOverviewGridEnabled) {
+ mTempRect.set(mLastComputedCarouselTaskSize);
+ } else {
+ mTempRect.set(mLastComputedTaskSize);
+ }
return getPagedViewOrientedState().getFullScreenScaleAndPivot(
mTempRect, mActivity.getDeviceProfile(), mTempPointF);
}
@@ -5545,7 +5566,7 @@
for (int i = 0; i < taskCount; i++) {
TaskView taskView = requireTaskViewAt(i);
float scrollDiff = taskView.getScrollAdjustment(showAsGrid);
- int pageScroll = newPageScrolls[i] + (int) scrollDiff;
+ int pageScroll = newPageScrolls[i] + Math.round(scrollDiff);
if ((mIsRtl && pageScroll < lastTaskScroll)
|| (!mIsRtl && pageScroll > lastTaskScroll)) {
pageScroll = lastTaskScroll;
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 6404383..39f1e46 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -18,6 +18,7 @@
import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.launcher3.Flags.enableOverviewIconMenu;
+import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA;
@@ -78,8 +79,6 @@
private LinearLayout mOptionLayout;
private float mMenuTranslationYBeforeOpen;
private float mMenuTranslationXBeforeOpen;
- private float mIconViewTranslationYBeforeOpen;
- private float mIconViewTranslationXBeforeOpen;
public TaskMenuView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
@@ -277,8 +276,6 @@
private void animateOpen() {
mMenuTranslationYBeforeOpen = getTranslationY();
mMenuTranslationXBeforeOpen = getTranslationX();
- mIconViewTranslationYBeforeOpen = getIconView().getTranslationY();
- mIconViewTranslationXBeforeOpen = getIconView().getTranslationX();
animateOpenOrClosed(false);
mIsOpen = true;
}
@@ -323,10 +320,10 @@
: mMenuTranslationYBeforeOpen + additionalTranslationY);
translationYAnim.setInterpolator(EMPHASIZED);
+ IconAppChipView iconAppChip = (IconAppChipView) mTaskContainer.getIconView().asView();
ObjectAnimator menuTranslationYAnim = ObjectAnimator.ofFloat(
- getIconView(), TRANSLATION_Y,
- closing ? mIconViewTranslationYBeforeOpen
- : mIconViewTranslationYBeforeOpen + additionalTranslationY);
+ iconAppChip.getMenuTranslationY(),
+ MULTI_PROPERTY_VALUE, closing ? 0 : additionalTranslationY);
menuTranslationYAnim.setInterpolator(EMPHASIZED);
mOpenCloseAnimator.playTogether(translationYAnim, menuTranslationYAnim);
@@ -344,10 +341,10 @@
: mMenuTranslationXBeforeOpen - additionalTranslationX);
translationXAnim.setInterpolator(EMPHASIZED);
+ IconAppChipView iconAppChip = (IconAppChipView) mTaskContainer.getIconView().asView();
ObjectAnimator menuTranslationXAnim = ObjectAnimator.ofFloat(
- getIconView(), TRANSLATION_X,
- closing ? mIconViewTranslationXBeforeOpen
- : mIconViewTranslationXBeforeOpen - additionalTranslationX);
+ iconAppChip.getMenuTranslationX(),
+ MULTI_PROPERTY_VALUE, closing ? 0 : -additionalTranslationX);
menuTranslationXAnim.setInterpolator(EMPHASIZED);
mOpenCloseAnimator.playTogether(translationXAnim, menuTranslationXAnim);
@@ -387,9 +384,11 @@
private void resetOverviewIconMenu() {
if (enableOverviewIconMenu()) {
- ((IconAppChipView) mTaskContainer.getIconView()).reset();
+ IconAppChipView iconAppChipView = (IconAppChipView) mTaskContainer.getIconView();
+ iconAppChipView.reset();
+ iconAppChipView.setMenuTranslationX(0);
+ iconAppChipView.setMenuTranslationY(0);
setTranslationY(mMenuTranslationYBeforeOpen);
- getIconView().setTranslationY(mIconViewTranslationYBeforeOpen);
}
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 5057c38..55da160 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -23,6 +23,7 @@
import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.Flags.enableCursorHoverStates;
+import static com.android.launcher3.Flags.enableGridOnlyOverview;
import static com.android.launcher3.Flags.enableOverviewIconMenu;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
@@ -1750,7 +1751,13 @@
expectedHeight = boxHeight + thumbnailPadding;
// Scale to to fit task Rect.
- nonGridScale = taskWidth / (float) boxWidth;
+ if (enableGridOnlyOverview()) {
+ final Rect lastComputedCarouselTaskSize =
+ getRecentsView().getLastComputedCarouselTaskSize();
+ nonGridScale = lastComputedCarouselTaskSize.width() / (float) taskWidth;
+ } else {
+ nonGridScale = taskWidth / (float) boxWidth;
+ }
// Align to top of task Rect.
boxTranslationY = (expectedHeight - thumbnailPadding - taskHeight) / 2.0f;
diff --git a/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt b/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
index 53bc2a2..2916952 100644
--- a/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
@@ -2,17 +2,15 @@
import android.content.Context
import android.testing.AndroidTestingRunner
-import android.view.Display
-import android.view.IWindowManager
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.SmallTest
import com.android.launcher3.util.DisplayController.CHANGE_DENSITY
import com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE
import com.android.launcher3.util.DisplayController.CHANGE_ROTATION
import com.android.launcher3.util.DisplayController.Info
-import com.android.launcher3.util.Executors
import com.android.launcher3.util.NavigationMode
import com.android.launcher3.util.window.WindowManagerProxy
+import com.android.quickstep.util.GestureExclusionManager
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -28,7 +26,7 @@
@RunWith(AndroidTestingRunner::class)
class RecentsAnimationDeviceStateTest {
- @Mock private lateinit var windowManager: IWindowManager
+ @Mock private lateinit var exclusionManager: GestureExclusionManager
@Mock private lateinit var windowManagerProxy: WindowManagerProxy
@Mock private lateinit var info: Info
@@ -38,60 +36,45 @@
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- underTest = RecentsAnimationDeviceState(context, windowManager)
+ underTest = RecentsAnimationDeviceState(context, exclusionManager)
}
@Test
fun registerExclusionListener_success() {
underTest.registerExclusionListener()
- awaitTasksCompleted()
- verify(windowManager)
- .registerSystemGestureExclusionListener(
- underTest.mGestureExclusionListener,
- Display.DEFAULT_DISPLAY
- )
+ verify(exclusionManager).addListener(underTest)
}
@Test
fun registerExclusionListener_again_fail() {
underTest.registerExclusionListener()
- awaitTasksCompleted()
- reset(windowManager)
+ reset(exclusionManager)
underTest.registerExclusionListener()
- awaitTasksCompleted()
- verifyZeroInteractions(windowManager)
+ verifyZeroInteractions(exclusionManager)
}
@Test
fun unregisterExclusionListener_success() {
underTest.registerExclusionListener()
- awaitTasksCompleted()
- reset(windowManager)
+ reset(exclusionManager)
underTest.unregisterExclusionListener()
- awaitTasksCompleted()
- verify(windowManager)
- .unregisterSystemGestureExclusionListener(
- underTest.mGestureExclusionListener,
- Display.DEFAULT_DISPLAY
- )
+ verify(exclusionManager).removeListener(underTest)
}
@Test
fun unregisterExclusionListener_again_fail() {
underTest.registerExclusionListener()
underTest.unregisterExclusionListener()
- awaitTasksCompleted()
- reset(windowManager)
+ reset(exclusionManager)
underTest.unregisterExclusionListener()
- awaitTasksCompleted()
- verifyZeroInteractions(windowManager)
+ verifyZeroInteractions(exclusionManager)
}
@Test
@@ -100,45 +83,28 @@
underTest.onDisplayInfoChanged(context, info, CHANGE_ROTATION or CHANGE_NAVIGATION_MODE)
- awaitTasksCompleted()
- verify(windowManager)
- .registerSystemGestureExclusionListener(
- underTest.mGestureExclusionListener,
- Display.DEFAULT_DISPLAY
- )
+ verify(exclusionManager).addListener(underTest)
}
@Test
fun onDisplayInfoChanged_twoButton_unregisterExclusionListener() {
underTest.registerExclusionListener()
- awaitTasksCompleted()
whenever(info.getNavigationMode()).thenReturn(NavigationMode.TWO_BUTTONS)
- reset(windowManager)
+ reset(exclusionManager)
underTest.onDisplayInfoChanged(context, info, CHANGE_ROTATION or CHANGE_NAVIGATION_MODE)
- awaitTasksCompleted()
- verify(windowManager)
- .unregisterSystemGestureExclusionListener(
- underTest.mGestureExclusionListener,
- Display.DEFAULT_DISPLAY
- )
+ verify(exclusionManager).removeListener(underTest)
}
@Test
fun onDisplayInfoChanged_changeDensity_noOp() {
underTest.registerExclusionListener()
- awaitTasksCompleted()
whenever(info.getNavigationMode()).thenReturn(NavigationMode.NO_BUTTON)
- reset(windowManager)
+ reset(exclusionManager)
underTest.onDisplayInfoChanged(context, info, CHANGE_DENSITY)
- awaitTasksCompleted()
- verifyZeroInteractions(windowManager)
- }
-
- private fun awaitTasksCompleted() {
- Executors.UI_HELPER_EXECUTOR.submit<Any> { null }.get()
+ verifyZeroInteractions(exclusionManager)
}
}
diff --git a/quickstep/tests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt b/quickstep/tests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt
new file mode 100644
index 0000000..c190cfe
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util
+
+import android.graphics.Rect
+import android.graphics.Region
+import android.testing.AndroidTestingRunner
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.IWindowManager
+import androidx.test.filters.SmallTest
+import com.android.launcher3.util.Executors
+import com.android.quickstep.util.GestureExclusionManager.ExclusionListener
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyZeroInteractions
+
+/** Unit test for [GestureExclusionManager]. */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class GestureExclusionManagerTest {
+
+ @Mock private lateinit var windowManager: IWindowManager
+
+ @Mock private lateinit var listener1: ExclusionListener
+ @Mock private lateinit var listener2: ExclusionListener
+
+ private val r1 = Region().apply { union(Rect(0, 0, 100, 200)) }
+ private val r2 = Region().apply { union(Rect(200, 200, 500, 800)) }
+
+ private lateinit var underTest: GestureExclusionManager
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest = GestureExclusionManager(windowManager)
+ }
+
+ @Test
+ fun addListener_registers() {
+ underTest.addListener(listener1)
+
+ awaitTasksCompleted()
+ verify(windowManager)
+ .registerSystemGestureExclusionListener(underTest.exclusionListener, DEFAULT_DISPLAY)
+ }
+
+ @Test
+ fun addListener_again_skips_register() {
+ underTest.addListener(listener1)
+ awaitTasksCompleted()
+ reset(windowManager)
+
+ underTest.addListener(listener2)
+
+ awaitTasksCompleted()
+ verifyZeroInteractions(windowManager)
+ }
+
+ @Test
+ fun removeListener_unregisters() {
+ underTest.addListener(listener1)
+ awaitTasksCompleted()
+ reset(windowManager)
+
+ underTest.removeListener(listener1)
+
+ awaitTasksCompleted()
+ verify(windowManager)
+ .unregisterSystemGestureExclusionListener(underTest.exclusionListener, DEFAULT_DISPLAY)
+ }
+
+ @Test
+ fun removeListener_again_skips_unregister() {
+ underTest.addListener(listener1)
+ underTest.addListener(listener2)
+ awaitTasksCompleted()
+ reset(windowManager)
+
+ underTest.removeListener(listener1)
+
+ awaitTasksCompleted()
+ verifyZeroInteractions(windowManager)
+ }
+
+ @Test
+ fun onSystemGestureExclusionChanged_dispatches_to_listeners() {
+ underTest.addListener(listener1)
+ underTest.addListener(listener2)
+ awaitTasksCompleted()
+
+ underTest.exclusionListener.onSystemGestureExclusionChanged(DEFAULT_DISPLAY, r1, r2)
+ awaitTasksCompleted()
+ verify(listener1).onGestureExclusionChanged(r1, r2)
+ verify(listener2).onGestureExclusionChanged(r1, r2)
+ }
+
+ @Test
+ fun addLister_dispatches_second_time() {
+ underTest.exclusionListener.onSystemGestureExclusionChanged(DEFAULT_DISPLAY, r1, r2)
+ awaitTasksCompleted()
+ underTest.addListener(listener1)
+ awaitTasksCompleted()
+ verifyZeroInteractions(listener1)
+
+ underTest.addListener(listener2)
+ awaitTasksCompleted()
+
+ verifyZeroInteractions(listener1)
+ verify(listener2).onGestureExclusionChanged(r1, r2)
+ }
+
+ private fun awaitTasksCompleted() {
+ Executors.UI_HELPER_EXECUTOR.submit<Any> { null }.get()
+ Executors.MAIN_EXECUTOR.submit<Any> { null }.get()
+ }
+}
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 00082ae..c187000 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -485,4 +485,7 @@
<dimen name="ps_button_width">36dp</dimen>
<dimen name="ps_lock_button_width">89dp</dimen>
<dimen name="ps_app_divider_padding">16dp</dimen>
+
+ <!-- WindowManagerProxy -->
+ <dimen name="max_width_and_height_of_small_display_cutout">136px</dimen>
</resources>
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 984a9ae..ac0d7ce 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1636,9 +1636,15 @@
mDragController.addDragListener(
new AccessibleDragListenerAdapter(this, WorkspaceAccessibilityHelper::new) {
@Override
- protected void enableAccessibleDrag(boolean enable) {
- super.enableAccessibleDrag(enable);
+ protected void enableAccessibleDrag(boolean enable,
+ @Nullable DragObject dragObject) {
+ super.enableAccessibleDrag(enable, dragObject);
setEnableForLayout(mLauncher.getHotseat(), enable);
+ if (enable && dragObject != null
+ && dragObject.dragInfo instanceof LauncherAppWidgetInfo) {
+ mLauncher.getHotseat().setImportantForAccessibility(
+ IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+ }
}
});
}
diff --git a/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java
index 0d7df2b..79b8187 100644
--- a/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java
+++ b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java
@@ -20,6 +20,8 @@
import android.view.ViewGroup;
import android.view.ViewGroup.OnHierarchyChangeListener;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.CellLayout;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.Launcher;
@@ -50,13 +52,13 @@
@Override
public void onDragStart(DragObject dragObject, DragOptions options) {
mViewGroup.setOnHierarchyChangeListener(this);
- enableAccessibleDrag(true);
+ enableAccessibleDrag(true, dragObject);
}
@Override
public void onDragEnd() {
mViewGroup.setOnHierarchyChangeListener(null);
- enableAccessibleDrag(false);
+ enableAccessibleDrag(false, null);
Launcher.getLauncher(mViewGroup.getContext()).getDragController().removeDragListener(this);
}
@@ -75,7 +77,7 @@
}
}
- protected void enableAccessibleDrag(boolean enable) {
+ protected void enableAccessibleDrag(boolean enable, @Nullable DragObject dragObject) {
for (int i = 0; i < mViewGroup.getChildCount(); i++) {
setEnableForLayout((CellLayout) mViewGroup.getChildAt(i), enable);
}
diff --git a/src/com/android/launcher3/anim/AnimatedFloat.java b/src/com/android/launcher3/anim/AnimatedFloat.java
index 2f3fa63..b414ab6 100644
--- a/src/com/android/launcher3/anim/AnimatedFloat.java
+++ b/src/com/android/launcher3/anim/AnimatedFloat.java
@@ -109,6 +109,13 @@
public void cancelAnimation() {
if (mValueAnimator != null) {
mValueAnimator.cancel();
+ // Clears the property values, so further ObjectAnimator#setCurrentFraction from e.g.
+ // AnimatorPlaybackController calls would do nothing. The null check is necessary to
+ // avoid mValueAnimator being set to null in onAnimationEnd.
+ if (mValueAnimator != null) {
+ mValueAnimator.setValues();
+ mValueAnimator = null;
+ }
}
}
diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java
index 586beb2..e58890f 100644
--- a/src/com/android/launcher3/anim/PendingAnimation.java
+++ b/src/com/android/launcher3/anim/PendingAnimation.java
@@ -83,6 +83,20 @@
add(anim);
}
+ /**
+ * Add an {@link AnimatedFloat} to the animation.
+ * <p>
+ * Different from {@link #addFloat}, this method use animator provided by
+ * {@link AnimatedFloat#animateToValue}, which tracks the animator inside the AnimatedFloat,
+ * allowing the animation to be canceled and animate again from AnimatedFloat side.
+ */
+ public void addAnimatedFloat(AnimatedFloat target, float from, float to,
+ TimeInterpolator interpolator) {
+ Animator anim = target.animateToValue(from, to);
+ anim.setInterpolator(interpolator);
+ add(anim);
+ }
+
/** If trace is enabled, add counter to trace animation progress. */
public void logAnimationProgressToTrace(String counterName) {
if (Trace.isEnabled()) {
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 2f3f029..f013126 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -320,8 +320,9 @@
mDragController.addDragListener(new AccessibleDragListenerAdapter(
mContent, FolderAccessibilityHelper::new) {
@Override
- protected void enableAccessibleDrag(boolean enable) {
- super.enableAccessibleDrag(enable);
+ protected void enableAccessibleDrag(boolean enable,
+ @Nullable DragObject dragObject) {
+ super.enableAccessibleDrag(enable, dragObject);
mFooter.setImportantForAccessibility(enable
? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
: IMPORTANT_FOR_ACCESSIBILITY_AUTO);
diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
index 31ae7c2..88b98aa 100644
--- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
+++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
@@ -433,7 +433,9 @@
!c.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_RESTORE_STARTED) &&
!isSafeMode &&
(si == null) &&
- (lapi == null)
+ (lapi == null) &&
+ !(Utilities.enableSupportForArchiving() &&
+ pmHelper.isAppArchived(component.packageName))
) {
// Restore never started
c.markDeleted(
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 50d8886..92288e1 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -107,6 +107,23 @@
}
/**
+ * Returns whether the target app is in archived state
+ */
+ @SuppressWarnings("NewApi")
+ public boolean isAppArchived(@NonNull final String packageName) {
+ final ApplicationInfo info;
+ try {
+ info = mPm.getPackageInfo(packageName,
+ PackageManager.PackageInfoFlags.of(
+ PackageManager.MATCH_ARCHIVED_PACKAGES)).applicationInfo;
+ return info.isArchived;
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Failed to get applicationInfo for package: " + packageName, e);
+ return false;
+ }
+ }
+
+ /**
* Returns the application info for the provided package or null
*/
@Nullable
diff --git a/src/com/android/launcher3/util/window/WindowManagerProxy.java b/src/com/android/launcher3/util/window/WindowManagerProxy.java
index 32f1736..6a0090c 100644
--- a/src/com/android/launcher3/util/window/WindowManagerProxy.java
+++ b/src/com/android/launcher3/util/window/WindowManagerProxy.java
@@ -17,6 +17,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.launcher3.Utilities.dpToPx;
import static com.android.launcher3.Utilities.dpiFromPx;
import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE;
import static com.android.launcher3.testing.shared.ResourceUtils.NAVBAR_HEIGHT;
@@ -49,6 +50,9 @@
import android.view.WindowManager;
import android.view.WindowMetrics;
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.testing.shared.ResourceUtils;
@@ -130,11 +134,11 @@
Resources systemRes = context.getResources();
Configuration config = systemRes.getConfiguration();
- boolean isTablet = config.smallestScreenWidthDp > MIN_TABLET_WIDTH;
+ boolean isLargeScreen = config.smallestScreenWidthDp > MIN_TABLET_WIDTH;
boolean isGesture = isGestureNav(context);
boolean isPortrait = config.screenHeightDp > config.screenWidthDp;
- int bottomNav = isTablet
+ int bottomNav = isLargeScreen
? 0
: (isPortrait
? getDimenByName(systemRes, NAVBAR_HEIGHT)
@@ -165,6 +169,9 @@
insetsBuilder.setInsets(WindowInsets.Type.tappableElement(), newTappableInsets);
}
+ applyDisplayCutoutBottomInsetOverrideOnLargeScreen(
+ context, isLargeScreen, dpToPx(config.screenWidthDp), oldInsets, insetsBuilder);
+
WindowInsets result = insetsBuilder.build();
Insets systemWindowInsets = result.getInsetsIgnoringVisibility(
WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
@@ -173,6 +180,71 @@
return result;
}
+ /**
+ * For large screen, when display cutout is at bottom left/right corner of screen, override
+ * display cutout's bottom inset to 0, because launcher allows drawing content over that area.
+ */
+ private static void applyDisplayCutoutBottomInsetOverrideOnLargeScreen(
+ @NonNull Context context,
+ boolean isLargeScreen,
+ int screenWidthPx,
+ @NonNull WindowInsets windowInsets,
+ @NonNull WindowInsets.Builder insetsBuilder) {
+ if (!isLargeScreen || !Utilities.ATLEAST_S) {
+ return;
+ }
+
+ final DisplayCutout displayCutout = windowInsets.getDisplayCutout();
+ if (displayCutout == null) {
+ return;
+ }
+
+ if (!areBottomDisplayCutoutsSmallAndAtCorners(
+ displayCutout.getBoundingRectBottom(), screenWidthPx, context.getResources())) {
+ return;
+ }
+
+ Insets oldDisplayCutoutInset = windowInsets.getInsets(WindowInsets.Type.displayCutout());
+ Insets newDisplayCutoutInset = Insets.of(
+ oldDisplayCutoutInset.left,
+ oldDisplayCutoutInset.top,
+ oldDisplayCutoutInset.right,
+ 0);
+ insetsBuilder.setInsetsIgnoringVisibility(
+ WindowInsets.Type.displayCutout(), newDisplayCutoutInset);
+ }
+
+ /**
+ * @see doc at {@link #areBottomDisplayCutoutsSmallAndAtCorners(Rect, int, int)}
+ */
+ private static boolean areBottomDisplayCutoutsSmallAndAtCorners(
+ @NonNull Rect cutoutRectBottom, int screenWidthPx, @NonNull Resources res) {
+ return areBottomDisplayCutoutsSmallAndAtCorners(cutoutRectBottom, screenWidthPx,
+ res.getDimensionPixelSize(R.dimen.max_width_and_height_of_small_display_cutout));
+ }
+
+ /**
+ * Return true if bottom display cutouts are at bottom left/right corners, AND has width or
+ * height <= maxWidthAndHeightOfSmallCutoutPx. Note that display cutout rect and screenWidthPx
+ * passed to this method should be in the SAME screen rotation.
+ *
+ * @param cutoutRectBottom bottom display cutout rect, this is based on current screen rotation
+ * @param screenWidthPx screen width in px based on current screen rotation
+ * @param maxWidthAndHeightOfSmallCutoutPx maximum width and height pixels of cutout.
+ */
+ @VisibleForTesting
+ static boolean areBottomDisplayCutoutsSmallAndAtCorners(
+ @NonNull Rect cutoutRectBottom, int screenWidthPx,
+ int maxWidthAndHeightOfSmallCutoutPx) {
+ // Empty cutoutRectBottom means there is no display cutout at the bottom. We should ignore
+ // it by returning false.
+ if (cutoutRectBottom.isEmpty()) {
+ return false;
+ }
+ return (cutoutRectBottom.right <= maxWidthAndHeightOfSmallCutoutPx)
+ || cutoutRectBottom.left >= (screenWidthPx - maxWidthAndHeightOfSmallCutoutPx);
+ }
+
protected int getStatusBarHeight(Context context, boolean isPortrait, int statusBarInset) {
Resources systemRes = context.getResources();
int statusBarHeight = getDimenByName(systemRes,
@@ -249,6 +321,12 @@
DisplayCutout rotatedCutout = rotateCutout(
displayInfo.cutout, displayInfo.size.x, displayInfo.size.y, rotation, i);
Rect insets = getSafeInsets(rotatedCutout);
+ if (areBottomDisplayCutoutsSmallAndAtCorners(
+ rotatedCutout.getBoundingRectBottom(),
+ bounds.width(),
+ context.getResources())) {
+ insets.bottom = 0;
+ }
insets.top = Math.max(insets.top, statusBarHeight);
insets.bottom = Math.max(insets.bottom, navBarHeight);
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 28bae59..4e704fd 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -20,15 +20,16 @@
import static com.android.launcher3.Flags.enableUnfoldedTwoPanePicker;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.LauncherPrefs.WIDGETS_EDUCATION_DIALOG_SEEN;
+import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.SEARCH;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_SEARCHED;
import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
import android.animation.Animator;
import android.content.Context;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
import android.os.Build;
+import android.os.Parcelable;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
@@ -99,7 +100,6 @@
// resolution or landscape on phone. This ratio defines the max percentage of content area that
// the table can display.
private static final float RECOMMENDATION_TABLE_HEIGHT_RATIO = 0.75f;
-
private final UserCache mUserCache;
private final UserManagerState mUserManagerState = new UserManagerState();
private final UserHandle mCurrentUser = Process.myUserHandle();
@@ -522,11 +522,13 @@
}
@Override
- public void enterSearchMode() {
+ public void enterSearchMode(boolean shouldLog) {
if (mIsInSearchMode) return;
setViewVisibilityBasedOnSearch(/*isInSearchMode= */ true);
attachScrollbarToRecyclerView(mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView);
- mActivityContext.getStatsLogManager().logger().log(LAUNCHER_WIDGETSTRAY_SEARCHED);
+ if (shouldLog) {
+ mActivityContext.getStatsLogManager().logger().log(LAUNCHER_WIDGETSTRAY_SEARCHED);
+ }
}
@Override
@@ -789,16 +791,28 @@
+ marginLayoutParams.topMargin;
}
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
+ private int getCurrentAdapterHolderType() {
if (mIsInSearchMode) {
- mSearchBar.reset();
+ return SEARCH;
+ } else if (mViewPager != null) {
+ return mViewPager.getCurrentPage();
+ } else {
+ return AdapterHolder.PRIMARY;
+ }
+ }
+
+ private void restorePreviousAdapterHolderType(int previousAdapterHolderType) {
+ if (previousAdapterHolderType == AdapterHolder.WORK && mViewPager != null) {
+ mViewPager.setCurrentPage(previousAdapterHolderType);
+ } else if (previousAdapterHolderType == AdapterHolder.SEARCH) {
+ enterSearchMode(false);
}
}
@Override
public void onDeviceProfileChanged(DeviceProfile dp) {
+ super.onDeviceProfileChanged(dp);
+
if (mDeviceProfile.isLandscape != dp.isLandscape && dp.isTablet && !dp.isTwoPanels) {
handleClose(false);
show(BaseActivity.fromContext(getContext()), false);
@@ -810,8 +824,12 @@
// When folding/unfolding the foldables, we need to switch between the regular widget picker
// and the two pane picker, so we rebuild the picker with the correct layout.
if (mDeviceProfile.isTwoPanels != dp.isTwoPanels && enableUnfoldedTwoPanePicker()) {
+ SparseArray<Parcelable> widgetsState = new SparseArray<>();
+ saveHierarchyState(widgetsState);
handleClose(false);
- show(BaseActivity.fromContext(getContext()), false);
+ WidgetsFullSheet sheet = show(BaseActivity.fromContext(getContext()), false);
+ sheet.restoreHierarchyState(widgetsState);
+ sheet.restorePreviousAdapterHolderType(getCurrentAdapterHolderType());
}
mDeviceProfile = dp;
diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
index 3e4d289..744c45b 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
@@ -18,7 +18,6 @@
import static com.android.launcher3.Flags.enableUnfoldedTwoPanePicker;
import android.content.Context;
-import android.content.res.Configuration;
import android.graphics.Outline;
import android.graphics.Rect;
import android.os.Process;
@@ -127,10 +126,6 @@
mFastScroller.setVisibility(GONE);
}
- /** Overrides onConfigurationChanged method from WidgetsFullSheet. Needed for b/319150904 */
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {}
-
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
diff --git a/src/com/android/launcher3/widget/picker/search/SearchModeListener.java b/src/com/android/launcher3/widget/picker/search/SearchModeListener.java
index cee7d67..b2620d0 100644
--- a/src/com/android/launcher3/widget/picker/search/SearchModeListener.java
+++ b/src/com/android/launcher3/widget/picker/search/SearchModeListener.java
@@ -26,7 +26,7 @@
/**
* Notifies the subscriber when user enters widget picker search mode.
*/
- void enterSearchMode();
+ void enterSearchMode(boolean shouldLog);
/**
* Notifies the subscriber when user exits widget picker search mode.
diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java
index a15508a..2d96cbd 100644
--- a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java
+++ b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java
@@ -70,7 +70,7 @@
mCancelButton.setVisibility(GONE);
} else {
mSearchAlgorithm.cancel(/* interruptActiveRequests= */ false);
- mSearchModeListener.enterSearchMode();
+ mSearchModeListener.enterSearchMode(true);
mSearchAlgorithm.doSearch(mQuery, this);
mCancelButton.setVisibility(VISIBLE);
}
diff --git a/tests/src/com/android/launcher3/util/window/WindowManagerProxyTest.kt b/tests/src/com/android/launcher3/util/window/WindowManagerProxyTest.kt
new file mode 100644
index 0000000..4819388
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/window/WindowManagerProxyTest.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util.window
+
+import android.graphics.Rect
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.util.window.WindowManagerProxy.areBottomDisplayCutoutsSmallAndAtCorners
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Unit test for [WindowManagerProxy] */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WindowManagerProxyTest {
+
+ private val windowWidthPx = 2000
+
+ private val bottomLeftCutout = Rect(0, 2364, 136, 2500)
+ private val bottomRightCutout = Rect(1864, 2364, 2000, 2500)
+
+ private val bottomLeftCutoutWithOffset = Rect(10, 2364, 136, 2500)
+ private val bottomRightCutoutWithOffset = Rect(1864, 2364, 1990, 2500)
+
+ private val maxWidthAndHeightOfSmallCutoutPx = 136
+
+ @Test
+ fun cutout_at_bottom_right_corner() {
+ assertTrue(
+ areBottomDisplayCutoutsSmallAndAtCorners(
+ bottomRightCutout,
+ windowWidthPx,
+ maxWidthAndHeightOfSmallCutoutPx
+ )
+ )
+ }
+
+ @Test
+ fun cutout_at_bottom_left_corner_with_offset() {
+ assertTrue(
+ areBottomDisplayCutoutsSmallAndAtCorners(
+ bottomLeftCutoutWithOffset,
+ windowWidthPx,
+ maxWidthAndHeightOfSmallCutoutPx
+ )
+ )
+ }
+
+ @Test
+ fun cutout_at_bottom_right_corner_with_offset() {
+ assertTrue(
+ areBottomDisplayCutoutsSmallAndAtCorners(
+ bottomRightCutoutWithOffset,
+ windowWidthPx,
+ maxWidthAndHeightOfSmallCutoutPx
+ )
+ )
+ }
+
+ @Test
+ fun cutout_at_bottom_left_corner() {
+ assertTrue(
+ areBottomDisplayCutoutsSmallAndAtCorners(
+ bottomLeftCutout,
+ windowWidthPx,
+ maxWidthAndHeightOfSmallCutoutPx
+ )
+ )
+ }
+
+ @Test
+ fun cutout_at_bottom_edge_at_bottom_corners() {
+ assertTrue(
+ areBottomDisplayCutoutsSmallAndAtCorners(
+ bottomLeftCutout,
+ windowWidthPx,
+ maxWidthAndHeightOfSmallCutoutPx
+ )
+ )
+ }
+
+ @Test
+ fun cutout_too_big_not_at_bottom_corners() {
+ // Rect in size of 200px
+ val bigBottomLeftCutout = Rect(0, 2300, 200, 2500)
+
+ assertFalse(
+ areBottomDisplayCutoutsSmallAndAtCorners(
+ bigBottomLeftCutout,
+ windowWidthPx,
+ maxWidthAndHeightOfSmallCutoutPx
+ )
+ )
+ }
+
+ @Test
+ fun cutout_too_small_at_bottom_corners() {
+ // Rect in size of 100px
+ val smallBottomLeft = Rect(0, 2400, 100, 2500)
+
+ assertTrue(
+ areBottomDisplayCutoutsSmallAndAtCorners(
+ smallBottomLeft,
+ windowWidthPx,
+ maxWidthAndHeightOfSmallCutoutPx
+ )
+ )
+ }
+
+ @Test
+ fun cutout_empty_not_at_bottom_corners() {
+ val emptyRect = Rect(0, 0, 0, 0)
+
+ assertFalse(
+ areBottomDisplayCutoutsSmallAndAtCorners(
+ emptyRect,
+ windowWidthPx,
+ maxWidthAndHeightOfSmallCutoutPx
+ )
+ )
+ }
+}
diff --git a/tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java b/tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java
index 583d37f..8457ac6 100644
--- a/tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java
+++ b/tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java
@@ -78,7 +78,7 @@
public void afterTextChanged_shouldInformSearchModeListenerToEnterSearch() {
mEditText.setText("abc");
- verify(mSearchModeListener).enterSearchMode();
+ verify(mSearchModeListener).enterSearchMode(true);
verifyNoMoreInteractions(mSearchModeListener);
}