Merge "Fix issue with IconAppChipView animated states" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index defb0e6..c13c68c 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -140,3 +140,17 @@
description: "Tie unfold animation with state animation"
bug: "297057373"
}
+
+flag {
+ name: "enable_categorized_widget_suggestions"
+ namespace: "launcher"
+ description: "Enables widget suggestions in widget picker to be displayed in categories"
+ bug: "318410881"
+}
+
+flag {
+ name: "use_activity_overlay"
+ namespace: "launcher"
+ description: "Use an activity for home screen overlay"
+ bug: "273828110"
+}
diff --git a/aconfig/launcher_search.aconfig b/aconfig/launcher_search.aconfig
index b846604..4e16e7f 100644
--- a/aconfig/launcher_search.aconfig
+++ b/aconfig/launcher_search.aconfig
@@ -34,3 +34,10 @@
description: "This flag disables accessibility drag for Private Space Apps."
bug: "289223923"
}
+
+flag {
+ name: "private_space_restrict_item_drag"
+ namespace: "launcher_search"
+ description: "This flag disables drag and drop for Private Space Items."
+ bug: "289223923"
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index b9b4461..436fe3b 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -28,6 +28,7 @@
import android.content.ClipDescription;
import android.content.Intent;
import android.os.Bundle;
+import android.util.Log;
import android.view.View;
import android.view.WindowInsetsController;
import android.view.WindowManager;
@@ -35,6 +36,7 @@
import androidx.annotation.NonNull;
import com.android.launcher3.dragndrop.SimpleDragLayer;
+import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.widget.BaseWidgetSheet;
@@ -43,9 +45,13 @@
import com.android.launcher3.widget.picker.WidgetsFullSheet;
import java.util.ArrayList;
+import java.util.Locale;
/** 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.
*
@@ -54,10 +60,19 @@
*/
private static final String EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag";
+ // Intent extras that specify the desired widget width and height. If these are not specified in
+ // the intent, then widgets will not be filtered for size.
+ 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;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -82,6 +97,13 @@
widgetSheet.disableNavBarScrim(true);
widgetSheet.addOnCloseListener(this::finish);
+ // A value of 0 for either size means that no filtering will occur in that dimension. If
+ // both values are 0, then no size filtering will occur.
+ mDesiredWidgetWidth =
+ getIntent().getIntExtra(EXTRA_DESIRED_WIDGET_WIDTH, 0);
+ mDesiredWidgetHeight =
+ getIntent().getIntExtra(EXTRA_DESIRED_WIDGET_HEIGHT, 0);
+
refreshAndBindWidgets();
}
@@ -160,9 +182,108 @@
final ArrayList<WidgetsListBaseEntry> widgets =
mModel.getFilteredWidgetsListForPicker(
app.getContext(),
- /*widgetItemFilter=*/ item -> item.widgetInfo != null
+ /*widgetItemFilter=*/ widget -> {
+ final WidgetAcceptabilityVerdict verdict =
+ isWidgetAcceptable(widget);
+ verdict.maybeLogVerdict();
+ return verdict.isAcceptable;
+ }
);
MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setAllWidgets(widgets));
});
}
+
+ private WidgetAcceptabilityVerdict isWidgetAcceptable(WidgetItem widget) {
+ final AppWidgetProviderInfo info = widget.widgetInfo;
+ if (info == null) {
+ return rejectWidget(widget, "shortcut");
+ }
+
+ if (mDesiredWidgetWidth == 0 && mDesiredWidgetHeight == 0) {
+ // Accept the widget if the desired dimensions are unspecified.
+ return acceptWidget(widget);
+ }
+
+ final boolean isHorizontallyResizable =
+ (info.resizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0;
+ if (mDesiredWidgetWidth > 0 && isHorizontallyResizable) {
+ if (info.maxResizeWidth > 0 && info.maxResizeWidth < mDesiredWidgetWidth) {
+ return rejectWidget(
+ widget,
+ String.format(
+ Locale.ENGLISH,
+ "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));
+ }
+ }
+
+ final boolean isVerticallyResizable =
+ (info.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0;
+ if (mDesiredWidgetHeight > 0 && isVerticallyResizable) {
+ if (info.maxResizeHeight > 0 && info.maxResizeHeight < mDesiredWidgetHeight) {
+ return rejectWidget(
+ widget,
+ String.format(
+ Locale.ENGLISH,
+ "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));
+ }
+ }
+
+ if (!isHorizontallyResizable
+ && !isVerticallyResizable
+ && (info.minWidth < mDesiredWidgetWidth || info.minHeight < mDesiredWidgetHeight)) {
+ return rejectWidget(widget, "too small and not resizeable");
+ }
+
+ return acceptWidget(widget);
+ }
+
+ private static WidgetAcceptabilityVerdict rejectWidget(
+ WidgetItem widget, String rejectionReason) {
+ return new WidgetAcceptabilityVerdict(false, widget.label, rejectionReason);
+ }
+
+ private static WidgetAcceptabilityVerdict acceptWidget(WidgetItem widget) {
+ return new WidgetAcceptabilityVerdict(true, widget.label, "");
+ }
+
+ private record WidgetAcceptabilityVerdict(
+ boolean isAcceptable, String widgetLabel, String reason) {
+ void maybeLogVerdict() {
+ // Only log a verdict if a reason is specified.
+ if (DEBUG && !reason.isEmpty()) {
+ Log.i(TAG, String.format(
+ Locale.ENGLISH,
+ "%s: %s because %s",
+ widgetLabel,
+ isAcceptable ? "accepted" : "rejected",
+ reason));
+ }
+ }
+ }
}
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
index b7e1092..2d4894c 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
@@ -49,7 +49,7 @@
"persist.wm.debug.desktop_stashing", false);
private final Launcher mLauncher;
- private boolean mFreeformTasksVisible;
+ private int mVisibleFreeformTasksCount;
private boolean mInOverviewState;
private boolean mBackgroundStateEnabled;
private boolean mGestureInProgress;
@@ -68,13 +68,13 @@
public void registerSystemUiListener() {
mDesktopTaskListener = new IDesktopTaskListener.Stub() {
@Override
- public void onVisibilityChanged(int displayId, boolean visible) {
+ public void onTasksVisibilityChanged(int displayId, int visibleTasksCount) {
MAIN_EXECUTOR.execute(() -> {
if (displayId == mLauncher.getDisplayId()) {
if (DEBUG) {
- Log.d(TAG, "desktop visibility changed value=" + visible);
+ Log.d(TAG, "desktop visible tasks count changed=" + visibleTasksCount);
}
- setFreeformTasksVisible(visible);
+ setVisibleFreeformTasksCount(visibleTasksCount);
}
});
}
@@ -112,39 +112,53 @@
* Whether freeform windows are visible in desktop mode.
*/
public boolean areFreeformTasksVisible() {
+ boolean freeformTasksVisible = mVisibleFreeformTasksCount > 0;
if (DEBUG) {
- Log.d(TAG, "areFreeformTasksVisible: freeformVisible=" + mFreeformTasksVisible
+ Log.d(TAG, "areFreeformTasksVisible: freeformVisible=" + freeformTasksVisible
+ " overview=" + mInOverviewState);
}
- return mFreeformTasksVisible && !mInOverviewState;
+ return freeformTasksVisible && !mInOverviewState;
}
/**
- * Sets whether freeform windows are visible and updates launcher visibility based on that.
+ * Number of visible freeform windows in desktop mode.
*/
- public void setFreeformTasksVisible(boolean freeformTasksVisible) {
+ public int getVisibleFreeformTasksCount() {
+ return mVisibleFreeformTasksCount;
+ }
+
+ /**
+ * Sets the number of freeform windows that are visible and updates launcher visibility based on
+ * it.
+ */
+ public void setVisibleFreeformTasksCount(int visibleTasksCount) {
if (DEBUG) {
- Log.d(TAG, "setFreeformTasksVisible: visible=" + freeformTasksVisible
- + " currentValue=" + mFreeformTasksVisible);
+ Log.d(TAG, "setVisibleFreeformTasksCount: visibleTasksCount=" + visibleTasksCount
+ + " currentValue=" + mVisibleFreeformTasksCount);
}
if (!isDesktopModeSupported()) {
return;
}
- if (freeformTasksVisible != mFreeformTasksVisible) {
- mFreeformTasksVisible = freeformTasksVisible;
- if (mFreeformTasksVisible) {
- setLauncherViewsVisibility(View.INVISIBLE);
- if (!mInOverviewState) {
- // When freeform is visible & we're not in overview, we want launcher to appear
- // paused, this ensures that taskbar displays.
- markLauncherPaused();
+ if (visibleTasksCount != mVisibleFreeformTasksCount) {
+ final boolean wasVisible = mVisibleFreeformTasksCount > 0;
+ final boolean isVisible = visibleTasksCount > 0;
+ mVisibleFreeformTasksCount = visibleTasksCount;
+
+ if (wasVisible != isVisible) {
+ if (mVisibleFreeformTasksCount > 0) {
+ setLauncherViewsVisibility(View.INVISIBLE);
+ if (!mInOverviewState) {
+ // When freeform is visible & we're not in overview, we want launcher to
+ // appear paused, this ensures that taskbar displays.
+ markLauncherPaused();
+ }
+ } else {
+ setLauncherViewsVisibility(View.VISIBLE);
+ // If freeform isn't visible ensure that launcher appears resumed to behave
+ // normally.
+ markLauncherResumed();
}
- } else {
- setLauncherViewsVisibility(View.VISIBLE);
- // If freeform isn't visible ensure that launcher appears resumed to behave
- // normally.
- markLauncherResumed();
}
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index 5caf004..f15d12b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -22,6 +22,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.launcher3.R;
import com.android.launcher3.statehandlers.DesktopVisibilityController;
@@ -47,7 +48,8 @@
public final class KeyboardQuickSwitchController implements
TaskbarControllers.LoggableTaskbarController {
- static final int MAX_TASKS = 6;
+ @VisibleForTesting
+ public static final int MAX_TASKS = 6;
@NonNull private final ControllerCallbacks mControllerCallbacks = new ControllerCallbacks();
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 1a1c64d..9b8ab33 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -30,6 +30,7 @@
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_SPACE;
import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_KEYGUARD;
import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_SMALL_SCREEN;
import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
@@ -79,6 +80,7 @@
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
+import android.widget.Space;
import androidx.annotation.Nullable;
@@ -207,6 +209,7 @@
this::onComputeInsetsForSeparateWindow;
private final RecentsHitboxExtender mHitboxExtender = new RecentsHitboxExtender();
private ImageView mRecentsButton;
+ private Space mSpace;
public NavbarButtonsViewController(TaskbarActivityContext context,
@Nullable Context navigationBarPanelContext, NearestTouchFrame navButtonsView) {
@@ -432,6 +435,11 @@
mPropertyHolders.add(new StatePropertyHolder(mA11yButton,
flags -> (flags & FLAG_A11Y_VISIBLE) != 0
&& (flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0));
+
+ mSpace = new Space(mNavButtonsView.getContext());
+ mSpace.setOnClickListener(view -> navButtonController.onButtonClick(BUTTON_SPACE, view));
+ mSpace.setOnLongClickListener(view ->
+ navButtonController.onButtonLongClick(BUTTON_SPACE, view));
}
private void parseSystemUiFlags(int sysUiStateFlags) {
@@ -760,7 +768,7 @@
NavButtonLayoutFactory.Companion.getUiLayoutter(
dp, mNavButtonsView, mImeSwitcherButton,
mControllers.rotationButtonController.getRotationButton(),
- mA11yButton, res, isInKidsMode, isInSetup, isThreeButtonNav,
+ mA11yButton, mSpace, res, isInKidsMode, isInSetup, isThreeButtonNav,
mContext.isPhoneMode(), mWindowManagerProxy.getRotation(mContext));
navButtonLayoutter.layoutButtons(mContext, isA11yButtonPersistent());
updateButtonsBackground();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
index eea543e..7eed955 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
@@ -171,6 +171,7 @@
// for the original 2 edu steps) as a proxy to needing to show the separate pinning edu
if (
!enableTaskbarPinning() ||
+ !DisplayController.isTransientTaskbar(activityContext) ||
!isTooltipEnabled ||
tooltipStep > TOOLTIP_STEP_PINNING ||
tooltipStep < TOOLTIP_STEP_FEATURES
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index 3f72e5d..19293b5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -102,6 +102,7 @@
static final int BUTTON_A11Y = BUTTON_IME_SWITCH << 1;
static final int BUTTON_QUICK_SETTINGS = BUTTON_A11Y << 1;
static final int BUTTON_NOTIFICATIONS = BUTTON_QUICK_SETTINGS << 1;
+ static final int BUTTON_SPACE = BUTTON_NOTIFICATIONS << 1;
private static final int SCREEN_UNPIN_COMBO = BUTTON_BACK | BUTTON_RECENTS;
private int mLongPressedButtons = 0;
@@ -123,6 +124,9 @@
}
public void onButtonClick(@TaskbarButton int buttonType, View view) {
+ if (buttonType == BUTTON_SPACE) {
+ return;
+ }
// Provide the same haptic feedback that the system offers for virtual keys.
view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
switch (buttonType) {
@@ -156,6 +160,9 @@
}
public boolean onButtonLongClick(@TaskbarButton int buttonType, View view) {
+ if (buttonType == BUTTON_SPACE) {
+ return false;
+ }
// Provide the same haptic feedback that the system offers for virtual keys.
view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
switch (buttonType) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index ecedf8a..bb2ac73 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -93,6 +93,7 @@
public void onTaskbarIconLaunched(ItemInfo item) {
// When launching from Taskbar, e.g. from Overview, set FLAG_IN_APP immediately instead of
// waiting for onPause, to reduce potential visual noise during the app open transition.
+ if (mControllers.taskbarStashController == null) return;
mControllers.taskbarStashController.updateStateForFlag(FLAG_IN_APP, true);
mControllers.taskbarStashController.applyState();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 3fb7247..5819bb3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -46,6 +46,8 @@
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Path;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -66,6 +68,7 @@
import com.android.launcher3.icons.BubbleIconFactory;
import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.taskbar.TaskbarControllers;
+import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.Executors.SimpleThreadFactory;
import com.android.quickstep.SystemUiProxy;
import com.android.wm.shell.bubbles.IBubblesListener;
@@ -408,8 +411,7 @@
info.getFlags() | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
mSelectedBubble.getView().updateDotVisibility(true /* animate */);
}
- mSystemUiProxy.showBubble(getSelectedBubbleKey(),
- getBubbleBarOffsetX(), getBubbleBarOffsetY());
+ mSystemUiProxy.showBubble(getSelectedBubbleKey(), getExpandedBubbleBarDisplayBounds());
} else {
Log.w(TAG, "Trying to show the selected bubble but it's null");
}
@@ -577,12 +579,27 @@
return mIconFactory.createBadgedIconBitmap(drawable).icon;
}
- private int getBubbleBarOffsetY() {
+ /**
+ * Get bounds of the bubble bar as if it would be expanded.
+ * Calculates the bounds instead of retrieving current view location as the view may be
+ * animating.
+ */
+ private Rect getExpandedBubbleBarDisplayBounds() {
+ Point displaySize = DisplayController.INSTANCE.get(mContext).getInfo().currentSize;
+ Rect currentBarBounds = mBarView.getBubbleBarBounds();
+ Rect location = new Rect();
+ // currentBarBounds is only useful for distance from left or right edge.
+ // It contains the current bounds, calculate the expanded bounds.
+ if (mBarView.isOnLeft()) {
+ location.left = currentBarBounds.left;
+ location.right = (int) (currentBarBounds.left + mBarView.expandedWidth());
+ } else {
+ location.left = (int) (currentBarBounds.right - mBarView.expandedWidth());
+ location.right = currentBarBounds.right;
+ }
final int translation = (int) abs(mBubbleStashController.getBubbleBarTranslationY());
- return translation + mBarView.getHeight();
- }
-
- private int getBubbleBarOffsetX() {
- return mBarView.getWidth() + mBarView.getHorizontalMargin();
+ location.top = displaySize.y - mBarView.getHeight() - translation;
+ location.bottom = displaySize.y - translation;
+ return location;
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index fbc7da1..8f693a6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -205,7 +205,10 @@
mRelativePivotX = onLeft ? 0f : 1f;
}
- private boolean isOnLeft() {
+ /**
+ * @return <code>true</code> when bar is pinned to the left edge of the screen
+ */
+ public boolean isOnLeft() {
return getLayoutDirection() == LAYOUT_DIRECTION_RTL;
}
@@ -509,7 +512,12 @@
return mIsBarExpanded;
}
- private float expandedWidth() {
+ /**
+ * Get width of the bubble bar as if it would be expanded.
+ *
+ * @return width of the bubble bar in its expanded state, regardless of current width
+ */
+ public float expandedWidth() {
final int childCount = getChildCount();
final int horizontalPadding = getPaddingStart() + getPaddingEnd();
return childCount * (mIconSize + mIconSpacing) + horizontalPadding;
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
index 23e3310..fe91362 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
@@ -23,6 +23,7 @@
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
+import android.widget.Space
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter
@@ -46,7 +47,8 @@
protected val startContextualContainer: ViewGroup,
protected val imeSwitcher: ImageView?,
protected val rotationButton: RotationButton?,
- protected val a11yButton: ImageView?
+ protected val a11yButton: ImageView?,
+ protected val space: Space?
) : NavButtonLayoutter {
protected val homeButton: ImageView? = navButtonContainer.findViewById(R.id.home)
protected val recentsButton: ImageView? = navButtonContainer.findViewById(R.id.recent_apps)
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
index f31af09..4368b95 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
@@ -25,6 +25,7 @@
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
+import android.widget.Space
import com.android.launcher3.R
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.*
@@ -37,7 +38,8 @@
startContextualContainer: ViewGroup,
imeSwitcher: ImageView?,
rotationButton: RotationButton?,
- a11yButton: ImageView?
+ a11yButton: ImageView?,
+ space: Space?
) :
AbstractNavButtonLayoutter(
resources,
@@ -46,7 +48,8 @@
startContextualContainer,
imeSwitcher,
rotationButton,
- a11yButton
+ a11yButton,
+ space
) {
override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
@@ -114,6 +117,7 @@
}
if (a11yButton != null) {
endContextualContainer.addView(a11yButton)
+ a11yButton.layoutParams = getParamsToCenterView()
}
if (rotationButton != null) {
endContextualContainer.addView(rotationButton.currentView)
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
index 672bc0d..2b60dc0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
@@ -23,6 +23,7 @@
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
+import android.widget.Space
import com.android.launcher3.DeviceProfile
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.*
@@ -60,6 +61,7 @@
imeSwitcher: ImageView?,
rotationButton: RotationButton?,
a11yButton: ImageView?,
+ space: Space?,
resources: Resources,
isKidsMode: Boolean,
isInSetup: Boolean,
@@ -86,7 +88,8 @@
startContextualContainer,
imeSwitcher,
rotationButton,
- a11yButton
+ a11yButton,
+ space
)
} else if (surfaceRotation == ROTATION_90) {
navButtonsView.setIsVertical(true)
@@ -97,7 +100,8 @@
startContextualContainer,
imeSwitcher,
rotationButton,
- a11yButton
+ a11yButton,
+ space
)
} else {
navButtonsView.setIsVertical(true)
@@ -108,7 +112,8 @@
startContextualContainer,
imeSwitcher,
rotationButton,
- a11yButton
+ a11yButton,
+ space
)
}
}
@@ -120,7 +125,8 @@
startContextualContainer,
imeSwitcher,
rotationButton,
- a11yButton
+ a11yButton,
+ space
)
}
deviceProfile.isTaskbarPresent -> {
@@ -133,7 +139,8 @@
startContextualContainer,
imeSwitcher,
rotationButton,
- a11yButton
+ a11yButton,
+ space
)
}
isKidsMode -> {
@@ -144,7 +151,8 @@
startContextualContainer,
imeSwitcher,
rotationButton,
- a11yButton
+ a11yButton,
+ space
)
}
else ->
@@ -155,7 +163,8 @@
startContextualContainer,
imeSwitcher,
rotationButton,
- a11yButton
+ a11yButton,
+ space
)
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/NearestTouchFrame.java b/quickstep/src/com/android/launcher3/taskbar/navbutton/NearestTouchFrame.java
index a477303..bbf08bf 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/NearestTouchFrame.java
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/NearestTouchFrame.java
@@ -27,6 +27,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
@@ -42,7 +43,10 @@
private final List<View> mClickableChildren = new ArrayList<>();
private final List<View> mAttachedChildren = new ArrayList<>();
private final boolean mIsActive;
+ private final int[] mTmpInt = new int[2];
+ // Offset (as the base) to translate window cords to view cords.
+ private final int[] mWindowOffset = new int[2];
private boolean mIsVertical;
private View mTouchingChild;
private final Map<View, Rect> mTouchableRegions = new HashMap<>();
@@ -52,8 +56,11 @@
*/
private final Comparator<View> mChildRegionComparator =
(view1, view2) -> {
- int startingCoordView1 = mIsVertical ? view1.getTop() : view1.getLeft();
- int startingCoordView2 = mIsVertical ? view2.getTop() : view2.getLeft();
+ int leftTopIndex = mIsVertical ? 1 : 0;
+ view1.getLocationInWindow(mTmpInt);
+ int startingCoordView1 = mTmpInt[leftTopIndex] - mWindowOffset[leftTopIndex];
+ view2.getLocationInWindow(mTmpInt);
+ int startingCoordView2 = mTmpInt[leftTopIndex] - mWindowOffset[leftTopIndex];
return startingCoordView1 - startingCoordView2;
};
@@ -74,6 +81,7 @@
mAttachedChildren.clear();
mTouchableRegions.clear();
addClickableChildren(this);
+ getLocationInWindow(mWindowOffset);
cacheClosestChildLocations();
}
@@ -147,8 +155,9 @@
}
private Rect getChildsBounds(View child) {
- int left = child.getLeft();
- int top = child.getTop();
+ child.getLocationInWindow(mTmpInt);
+ int left = mTmpInt[0] - mWindowOffset[0];
+ int top = mTmpInt[1] - mWindowOffset[1];
int right = left + child.getWidth();
int bottom = top + child.getHeight();
return new Rect(left, top, right, bottom);
@@ -194,6 +203,7 @@
public void dumpLogs(String prefix, PrintWriter pw) {
pw.println(prefix + "NearestTouchFrame:");
+ pw.println(String.format("%s\tmWindowOffset=%s", prefix, Arrays.toString(mWindowOffset)));
pw.println(String.format("%s\tmIsVertical=%s", prefix, mIsVertical));
pw.println(String.format("%s\tmTouchingChild=%s", prefix, mTouchingChild));
pw.println(String.format("%s\tmTouchableRegions=%s", prefix,
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
index 3817f91..bf820c0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
@@ -20,6 +20,7 @@
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
+import android.widget.Space
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.systemui.shared.rotation.RotationButton
@@ -31,7 +32,8 @@
startContextualContainer: ViewGroup,
imeSwitcher: ImageView?,
rotationButton: RotationButton?,
- a11yButton: ImageView?
+ a11yButton: ImageView?,
+ space: Space?
) :
AbstractNavButtonLayoutter(
resources,
@@ -40,7 +42,8 @@
startContextualContainer,
imeSwitcher,
rotationButton,
- a11yButton
+ a11yButton,
+ space
) {
override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
index b1b50d6..6a935f1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
@@ -19,9 +19,11 @@
import android.content.res.Resources
import android.view.Gravity
import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
+import android.widget.Space
import com.android.launcher3.R
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.systemui.shared.rotation.RotationButton
@@ -34,6 +36,7 @@
imeSwitcher: ImageView?,
rotationButton: RotationButton?,
a11yButton: ImageView?,
+ space: Space?
) :
AbstractNavButtonLayoutter(
resources,
@@ -42,7 +45,8 @@
startContextualContainer,
imeSwitcher,
rotationButton,
- a11yButton
+ a11yButton,
+ space
) {
override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
@@ -60,7 +64,7 @@
val navButtonContainerHeight = contentWidth - contextualButtonHeight * 2
val navContainerParams = FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, navButtonContainerHeight.toInt())
+ MATCH_PARENT, navButtonContainerHeight.toInt())
navContainerParams.apply {
topMargin =
(contextualButtonHeight + contentPadding + roundedCornerContentMargin).toInt()
@@ -125,6 +129,8 @@
val contentPadding = resources.getDimensionPixelSize(R.dimen.taskbar_phone_content_padding)
repositionContextualContainer(startContextualContainer, buttonSize,
roundedCornerContentMargin + contentPadding, 0, Gravity.TOP)
+ repositionContextualContainer(endContextualContainer, buttonSize,
+ 0, roundedCornerContentMargin + contentPadding, Gravity.BOTTOM)
if (imeSwitcher != null) {
startContextualContainer.addView(imeSwitcher)
@@ -132,18 +138,19 @@
}
if (a11yButton != null) {
startContextualContainer.addView(a11yButton)
+ a11yButton.layoutParams = getParamsToCenterView()
}
if (rotationButton != null) {
startContextualContainer.addView(rotationButton.currentView)
rotationButton.currentView.layoutParams = getParamsToCenterView()
}
+ endContextualContainer.addView(space, MATCH_PARENT, MATCH_PARENT)
}
override fun repositionContextualContainer(contextualContainer: ViewGroup, buttonSize: Int,
barAxisMarginTop: Int, barAxisMarginBottom: Int,
gravity: Int) {
- val contextualContainerParams = FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, buttonSize)
+ val contextualContainerParams = FrameLayout.LayoutParams(MATCH_PARENT, buttonSize)
contextualContainerParams.apply {
marginStart = 0
marginEnd = 0
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
index 05183b8..0672270 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
@@ -19,9 +19,11 @@
import android.content.res.Resources
import android.view.Gravity
import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
+import android.widget.Space
import com.android.launcher3.R
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.systemui.shared.rotation.RotationButton
@@ -34,6 +36,7 @@
imeSwitcher: ImageView?,
rotationButton: RotationButton?,
a11yButton: ImageView?,
+ space: Space?
) :
AbstractNavButtonLayoutter(
resources,
@@ -42,7 +45,8 @@
startContextualContainer,
imeSwitcher,
rotationButton,
- a11yButton
+ a11yButton,
+ space
) {
override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
@@ -110,15 +114,19 @@
endContextualContainer.removeAllViews()
startContextualContainer.removeAllViews()
+ repositionContextualContainer(startContextualContainer, contextualButtonWidth.toInt(),
+ roundedCornerContentMargin + contentPadding, 0, Gravity.START)
repositionContextualContainer(endContextualContainer, contextualButtonWidth.toInt(), 0,
roundedCornerContentMargin + contentPadding, Gravity.END)
+ startContextualContainer.addView(space, MATCH_PARENT, MATCH_PARENT)
if (imeSwitcher != null) {
endContextualContainer.addView(imeSwitcher)
imeSwitcher.layoutParams = getParamsToCenterView()
}
if (a11yButton != null) {
endContextualContainer.addView(a11yButton)
+ a11yButton.layoutParams = getParamsToCenterView()
}
if (rotationButton != null) {
endContextualContainer.addView(rotationButton.currentView)
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
index 0f52552..869cc43 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
@@ -19,8 +19,10 @@
import android.content.res.Resources
import android.view.Gravity
import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.widget.ImageView
import android.widget.LinearLayout
+import android.widget.Space
import com.android.launcher3.R
import com.android.systemui.shared.rotation.RotationButton
@@ -31,7 +33,8 @@
startContextualContainer: ViewGroup,
imeSwitcher: ImageView?,
rotationButton: RotationButton?,
- a11yButton: ImageView?
+ a11yButton: ImageView?,
+ space: Space?
) :
PhoneLandscapeNavLayoutter(
resources,
@@ -40,7 +43,8 @@
startContextualContainer,
imeSwitcher,
rotationButton,
- a11yButton
+ a11yButton,
+ space
) {
override fun addThreeButtons() {
@@ -57,15 +61,19 @@
val roundedCornerContentMargin = resources.getDimensionPixelSize(
R.dimen.taskbar_phone_rounded_corner_content_margin)
val contentPadding = resources.getDimensionPixelSize(R.dimen.taskbar_phone_content_padding)
+ repositionContextualContainer(startContextualContainer, buttonSize,
+ roundedCornerContentMargin + contentPadding, 0, Gravity.TOP)
repositionContextualContainer(endContextualContainer, buttonSize, 0,
roundedCornerContentMargin + contentPadding, Gravity.BOTTOM)
+ startContextualContainer.addView(space, MATCH_PARENT, MATCH_PARENT)
if (imeSwitcher != null) {
endContextualContainer.addView(imeSwitcher)
imeSwitcher.layoutParams = getParamsToCenterView()
}
if (a11yButton != null) {
endContextualContainer.addView(a11yButton)
+ a11yButton.layoutParams = getParamsToCenterView()
}
if (rotationButton != null) {
endContextualContainer.addView(rotationButton.currentView)
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
index 5111bba..181e0ed 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
@@ -23,6 +23,7 @@
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
+import android.widget.Space
import com.android.launcher3.R
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.systemui.shared.rotation.RotationButton
@@ -34,7 +35,8 @@
startContextualContainer: ViewGroup,
imeSwitcher: ImageView?,
rotationButton: RotationButton?,
- a11yButton: ImageView?
+ a11yButton: ImageView?,
+ space: Space?
) :
AbstractNavButtonLayoutter(
resources,
@@ -43,7 +45,8 @@
startContextualContainer,
imeSwitcher,
rotationButton,
- a11yButton
+ a11yButton,
+ space
) {
override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
@@ -72,6 +75,7 @@
}
if (a11yButton != null) {
endContextualContainer.addView(a11yButton)
+ a11yButton.layoutParams = getParamsToCenterView()
}
if (rotationButton != null) {
endContextualContainer.addView(rotationButton.currentView)
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
index 45dbebb..5c57a01 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
@@ -23,6 +23,7 @@
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
+import android.widget.Space
import com.android.launcher3.R
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.systemui.shared.rotation.RotationButton
@@ -37,7 +38,8 @@
startContextualContainer: ViewGroup,
imeSwitcher: ImageView?,
rotationButton: RotationButton?,
- a11yButton: ImageView?
+ a11yButton: ImageView?,
+ space: Space?
) :
AbstractNavButtonLayoutter(
resources,
@@ -46,7 +48,8 @@
startContextualContainer,
imeSwitcher,
rotationButton,
- a11yButton
+ a11yButton,
+ space
) {
override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
@@ -107,6 +110,7 @@
}
if (a11yButton != null) {
endContextualContainer.addView(a11yButton)
+ a11yButton.layoutParams = getParamsToCenterView()
}
if (rotationButton != null) {
endContextualContainer.addView(rotationButton.currentView)
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 0991fce..32d10b0 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -43,6 +43,7 @@
import static com.android.launcher3.popup.SystemShortcut.DONT_SUGGEST_APP;
import static com.android.launcher3.popup.SystemShortcut.INSTALL;
import static com.android.launcher3.popup.SystemShortcut.PRIVATE_PROFILE_INSTALL;
+import static com.android.launcher3.popup.SystemShortcut.UNINSTALL_APP;
import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
import static com.android.launcher3.taskbar.LauncherTaskbarUIController.ALL_APPS_PAGE_PROGRESS_INDEX;
import static com.android.launcher3.taskbar.LauncherTaskbarUIController.MINUS_ONE_PAGE_PROGRESS_INDEX;
@@ -437,6 +438,9 @@
if (Flags.enableShortcutDontSuggestApp()) {
shortcuts.add(DONT_SUGGEST_APP);
}
+ if (Flags.enablePrivateSpace()) {
+ shortcuts.add(UNINSTALL_APP);
+ }
return shortcuts.stream();
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 27224f2..c961302 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -45,7 +45,7 @@
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.AnimUtils;
import com.android.quickstep.util.SplitAnimationTimings;
import com.android.quickstep.views.ClearAllButton;
@@ -130,7 +130,7 @@
}
// Create transition animations to split select
- PagedOrientationHandler orientationHandler =
+ RecentsPagedOrientationHandler orientationHandler =
((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler();
Pair<FloatProperty, FloatProperty> taskViewsFloat =
orientationHandler.getSplitSelectTaskOffset(
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java
index c1a85fa..369ff14 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java
@@ -21,14 +21,14 @@
import static android.view.View.VISIBLE;
import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_TIMEOUT_MS;
import static com.android.launcher3.LauncherPrefs.PRIVATE_SPACE_APPS;
+import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_DELAY;
+import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_END_SCALE_PERCENT;
+import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_ITERATIONS;
+import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_SCALE_EXPONENT;
+import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_START_SCALE_PERCENT;
+import static com.android.launcher3.config.FeatureFlags.LPNH_SLOP_PERCENTAGE;
+import static com.android.launcher3.config.FeatureFlags.LPNH_TIMEOUT_MS;
import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT_HIGHLIGHT_KEY;
import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.PLUGIN_CHANGED;
import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.pluginEnabledKey;
@@ -50,6 +50,7 @@
import android.text.Editable;
import android.text.TextWatcher;
import android.util.ArrayMap;
+import android.util.Log;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
@@ -88,11 +89,14 @@
private static final String ACTION_PLUGIN_SETTINGS =
"com.android.systemui.action.PLUGIN_SETTINGS";
+ private static final String TAG = "DeveloperOptionsUI";
private static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN";
private final PreferenceFragmentCompat mFragment;
private final PreferenceScreen mPreferenceScreen;
+ private final FlagTogglerPrefUi mFlagTogglerPrefUi;
+
private PreferenceCategory mPluginsCategory;
public DeveloperOptionsUI(PreferenceFragmentCompat fragment, PreferenceCategory flags) {
@@ -107,8 +111,9 @@
parent.addView(topBar, parent.indexOfChild(listView));
initSearch(topBar.findViewById(R.id.filter_box));
- new FlagTogglerPrefUi(mFragment.requireActivity(), topBar.findViewById(R.id.flag_apply_btn))
- .applyTo(flags);
+ mFlagTogglerPrefUi = new FlagTogglerPrefUi(mFragment.requireActivity(),
+ topBar.findViewById(R.id.flag_apply_btn));
+ mFlagTogglerPrefUi.applyTo(flags);
loadPluginPrefs();
maybeAddSandboxCategory();
@@ -350,23 +355,27 @@
private void addCustomLpnhCategory() {
PreferenceCategory category = newCategory("Long Press Nav Handle Config");
if (FeatureFlags.CUSTOM_LPNH_THRESHOLDS.get()) {
- category.addPreference(createSeekBarPreference("Slop multiplier (applied to edge slop, "
+ category.addPreference(createSeekBarPreference(
+ "Slop multiplier (applied to edge slop, "
+ "which is generally already 50% higher than touch slop)",
- 25, 200, 100, LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE));
- category.addPreference(createSeekBarPreference("Trigger milliseconds",
- 100, 500, 1, LONG_PRESS_NAV_HANDLE_TIMEOUT_MS));
+ 25, 200, 100, LPNH_SLOP_PERCENTAGE));
+ category.addPreference(createSeekBarPreference("LPNH timeout",
+ 100, 500, 1, LPNH_TIMEOUT_MS));
}
if (FeatureFlags.ENABLE_SEARCH_HAPTIC_HINT.get()) {
- category.addPreference(createSeekBarPreference("Haptic hint start scale",
- 0, 100, 100, LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT));
+ category.addPreference(
+ createSeekBarPreference("Haptic hint start scale",
+ 0, 100, 100, LPNH_HAPTIC_HINT_START_SCALE_PERCENT));
category.addPreference(createSeekBarPreference("Haptic hint end scale",
- 0, 100, 100, LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT));
- category.addPreference(createSeekBarPreference("Haptic hint scale exponent",
- 1, 5, 1, LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT));
- category.addPreference(createSeekBarPreference("Haptic hint iterations (12 ms each)",
- 0, 200, 1, LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS));
+ 0, 100, 100, LPNH_HAPTIC_HINT_END_SCALE_PERCENT));
+ category.addPreference(
+ createSeekBarPreference("Haptic hint scale exponent",
+ 1, 5, 1, LPNH_HAPTIC_HINT_SCALE_EXPONENT));
+ category.addPreference(
+ createSeekBarPreference("Haptic hint iterations (12 ms each)",
+ 0, 200, 1, LPNH_HAPTIC_HINT_ITERATIONS));
category.addPreference(createSeekBarPreference("Haptic hint delay (ms)",
- 0, 400, 1, LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY));
+ 0, 400, 1, LPNH_HAPTIC_HINT_DELAY));
}
}
@@ -376,6 +385,29 @@
"Number of Apps to put in private region", 0, 100, 1, PRIVATE_SPACE_APPS));
}
+ private SeekBarPreference createSeekBarPreference(String title, int min,
+ int max, int scale, FeatureFlags.IntFlag flag) {
+ if (!(flag instanceof IntDebugFlag)) {
+ Log.e(TAG, "Cannot create seekbar preference with IntFlag. Use a launcher preference "
+ + "flag or pref-backed IntDebugFlag instead");
+ return null;
+ }
+ IntDebugFlag debugflag = (IntDebugFlag) flag;
+ if (debugflag.launcherPrefFlag == null) {
+ Log.e(TAG, "Cannot create seekbar preference with IntDebugFlag. Use a launcher "
+ + "preference flag or pref-backed IntDebugFlag instead");
+ return null;
+ }
+ SeekBarPreference seekBarPref = createSeekBarPreference(title, min, max, scale,
+ debugflag.launcherPrefFlag);
+ int value = flag.get();
+ seekBarPref.setValue(value);
+ // For some reason the initial value is not triggering the summary update, so call manually.
+ seekBarPref.setSummary(String.valueOf(scale == 1 ? value
+ : value / (float) scale));
+ return seekBarPref;
+ }
+
/**
* Create a preference with text and a seek bar. Should be added to a PreferenceCategory.
*
@@ -401,6 +433,7 @@
LauncherPrefs.get(getContext()).put(launcherPref, newValue);
preference.setSummary(String.valueOf(scale == 1 ? newValue
: (int) newValue / (float) scale));
+ mFlagTogglerPrefUi.updateMenu();
return true;
});
int value = LauncherPrefs.get(getContext()).get(launcherPref);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java
index ec0566a..4326c67 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java
@@ -34,6 +34,7 @@
import androidx.preference.PreferenceViewHolder;
import androidx.preference.SwitchPreference;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
@@ -144,7 +145,7 @@
switchPreference.setSummary(Html.fromHtml(summary + flag.description));
}
- private void updateMenu() {
+ public void updateMenu() {
mFlagsApplyButton.setVisibility(anyChanged() ? View.VISIBLE : View.INVISIBLE);
}
@@ -161,12 +162,22 @@
return mDataStore.getBoolean(flag.key, defaultValue);
}
+ private int getIntFlagStateFromSharedPrefs(IntDebugFlag flag) {
+ LauncherPrefs prefs = LauncherPrefs.get(mContext);
+ return flag.launcherPrefFlag == null ? flag.get() : prefs.get(flag.launcherPrefFlag);
+ }
+
private boolean anyChanged() {
for (DebugFlag flag : FlagsFactory.getDebugFlags()) {
if (getFlagStateFromSharedPrefs(flag) != flag.get()) {
return true;
}
}
+ for (IntDebugFlag flag : FlagsFactory.getIntDebugFlags()) {
+ if (getIntFlagStateFromSharedPrefs(flag) != flag.get()) {
+ return true;
+ }
+ }
return false;
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
index 48d313e..686ed64 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
@@ -23,6 +23,8 @@
import static com.android.launcher3.config.FeatureFlags.FlagState.ENABLED;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static java.util.Collections.unmodifiableList;
+
import android.content.Context;
import android.content.SharedPreferences;
import android.provider.DeviceConfig;
@@ -30,7 +32,10 @@
import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import com.android.launcher3.ConstantItem;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.config.FeatureFlags.BooleanFlag;
import com.android.launcher3.config.FeatureFlags.FlagState;
import com.android.launcher3.config.FeatureFlags.IntFlag;
@@ -57,7 +62,7 @@
public static final String NAMESPACE_LAUNCHER = "launcher";
private static final List<DebugFlag> sDebugFlags = new ArrayList<>();
- private static final List<IntFlag> sIntFlags = new ArrayList<>();
+ private static final List<IntDebugFlag> sIntDebugFlags = new ArrayList<>();
private static SharedPreferences sSharedPreferences;
static final BooleanFlag TEAMFOOD_FLAG = getReleaseFlag(
@@ -132,11 +137,32 @@
*/
public static IntFlag getIntFlag(
int bugId, String key, int defaultValueInCode, String description) {
+ return getIntFlag(bugId, key, defaultValueInCode, description, null);
+ }
+
+ /**
+ * Creates a new integer flag.
+ *
+ * @param launcherPrefFlag Set launcherPrefFlag to non-null if you want
+ * to modify the int flag in Launcher Developer Options and IntDebugFlag
+ * will be backed up by LauncherPrefs. Modified int value will be saved
+ * in LauncherPrefs.
+ */
+ public static IntFlag getIntFlag(
+ int bugId, String key, int defaultValueInCode, String description,
+ @Nullable ConstantItem<Integer> launcherPrefFlag) {
INSTANCE.mKeySet.add(key);
int defaultValue = DeviceConfig.getInt(NAMESPACE_LAUNCHER, key, defaultValueInCode);
if (IS_DEBUG_DEVICE) {
- IntDeviceFlag flag = new IntDeviceFlag(key, defaultValue, defaultValueInCode);
- sIntFlags.add(flag);
+ int currentValue;
+ if (launcherPrefFlag == null) {
+ currentValue = defaultValue;
+ } else {
+ currentValue = LauncherPrefs.get(currentApplication()).get(launcherPrefFlag);
+ }
+ IntDebugFlag flag = new IntDebugFlag(key, currentValue, defaultValueInCode,
+ launcherPrefFlag);
+ sIntDebugFlags.add(flag);
return flag;
} else {
return new IntFlag(defaultValue);
@@ -152,6 +178,15 @@
}
}
+ static List<IntDebugFlag> getIntDebugFlags() {
+ if (!IS_DEBUG_DEVICE) {
+ return unmodifiableList(Collections.emptyList());
+ }
+ synchronized (sIntDebugFlags) {
+ return unmodifiableList(sIntDebugFlags);
+ }
+ }
+
/** Returns the SharedPreferences instance backing Debug FeatureFlags. */
@NonNull
static SharedPreferences getSharedPreferences() {
@@ -180,8 +215,8 @@
}
}
pw.println(" IntFlags:");
- synchronized (sIntFlags) {
- for (IntFlag flag : sIntFlags) {
+ synchronized (sIntDebugFlags) {
+ for (IntFlag flag : sIntDebugFlags) {
pw.println(" " + flag);
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/IntDeviceFlag.java b/quickstep/src/com/android/launcher3/uioverrides/flags/IntDebugFlag.java
similarity index 73%
rename from quickstep/src/com/android/launcher3/uioverrides/flags/IntDeviceFlag.java
rename to quickstep/src/com/android/launcher3/uioverrides/flags/IntDebugFlag.java
index 4f3b0ae..1350aa8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/IntDeviceFlag.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/IntDebugFlag.java
@@ -15,16 +15,23 @@
*/
package com.android.launcher3.uioverrides.flags;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.ConstantItem;
import com.android.launcher3.config.FeatureFlags.IntFlag;
-public class IntDeviceFlag extends IntFlag {
+public class IntDebugFlag extends IntFlag {
public final String key;
private final int mDefaultValueInCode;
+ @Nullable
+ public final ConstantItem<Integer> launcherPrefFlag;
- public IntDeviceFlag(String key, int currentValue, int defaultValueInCode) {
+ public IntDebugFlag(String key, int currentValue, int defaultValueInCode,
+ @Nullable ConstantItem<Integer> launcherPrefFlag) {
super(currentValue);
this.key = key;
mDefaultValueInCode = defaultValueInCode;
+ this.launcherPrefFlag = launcherPrefFlag;
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
index cda7855..d98e608 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
@@ -104,7 +104,7 @@
if (!mCanIntercept) {
return false;
}
- if (action == ACTION_MOVE) {
+ if (action == ACTION_MOVE && mDownEvents.contains(pid)) {
float dy = ev.getY(idx) - mDownEvents.get(pid).y;
float dx = ev.getX(idx) - mDownEvents.get(pid).x;
if (mIsTrackpadReverseScroll) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index ec84550..ef5096b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -36,13 +36,13 @@
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.touch.BaseSwipeDetector;
-import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.FlingBlockCheck;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.views.BaseDragLayer;
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.VibrationConstants;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -225,7 +225,8 @@
mCurrentAnimation.dispatchOnCancel();
}
- PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
+ RecentsPagedOrientationHandler orientationHandler =
+ mRecentsView.getPagedOrientationHandler();
mCurrentAnimationIsGoingUp = goingUp;
BaseDragLayer dl = mActivity.getDragLayer();
final int secondaryLayerDimension = orientationHandler.getSecondaryDimension(dl);
@@ -269,7 +270,8 @@
@Override
public void onDragStart(boolean start, float startDisplacement) {
- PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
+ RecentsPagedOrientationHandler orientationHandler =
+ mRecentsView.getPagedOrientationHandler();
if (mCurrentAnimation == null) {
reInitAnimationController(orientationHandler.isGoingUp(startDisplacement, mIsRtl));
mDisplacementShift = 0;
@@ -283,7 +285,8 @@
@Override
public boolean onDrag(float displacement) {
- PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
+ RecentsPagedOrientationHandler orientationHandler =
+ mRecentsView.getPagedOrientationHandler();
float totalDisplacement = displacement + mDisplacementShift;
boolean isGoingUp = totalDisplacement == 0 ? mCurrentAnimationIsGoingUp :
orientationHandler.isGoingUp(totalDisplacement, mIsRtl);
@@ -346,7 +349,8 @@
if (blockedFling) {
fling = false;
}
- PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
+ RecentsPagedOrientationHandler orientationHandler =
+ mRecentsView.getPagedOrientationHandler();
boolean goingUp = orientationHandler.isGoingUp(velocity, mIsRtl);
float progress = mCurrentAnimation.getProgressFraction();
float interpolatedProgress = mCurrentAnimation.getInterpolatedProgress();
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 60d0e2b..6698600 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -22,7 +22,9 @@
import static android.widget.Toast.LENGTH_SHORT;
import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
+import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.app.animation.Interpolators.DECELERATE;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.app.animation.Interpolators.OVERSHOOT_1_2;
import static com.android.launcher3.BaseActivity.EVENT_DESTROYED;
import static com.android.launcher3.BaseActivity.EVENT_STARTED;
@@ -134,6 +136,7 @@
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.SwipePipToHomeAnimator;
import com.android.quickstep.util.TaskViewSimulator;
+import com.android.quickstep.util.TransformParams;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
@@ -167,6 +170,9 @@
private static final ArrayList<String> STATE_NAMES = new ArrayList<>();
+ // Fraction of the scroll and transform animation in which the current task fades out
+ private static final float KQS_TASK_FADE_ANIMATION_FRACTION = 0.4f;
+
protected final BaseActivityInterface<S, T> mActivityInterface;
protected final InputConsumerProxy mInputConsumerProxy;
protected final ActivityInitListener mActivityInitListener;
@@ -900,7 +906,10 @@
return;
}
mLauncherTransitionController.setProgress(
- Math.max(mCurrentShift.value, getScaleProgressDueToScroll()), mDragLengthFactor);
+ // Immediately finish the grid transition
+ isKeyboardTaskFocusPending()
+ ? 1f : Math.max(mCurrentShift.value, getScaleProgressDueToScroll()),
+ mDragLengthFactor);
}
/**
@@ -1349,7 +1358,9 @@
}
Interpolator interpolator;
S state = mActivityInterface.stateFromGestureEndTarget(endTarget);
- if (state.displayOverviewTasksAsGrid(mDp)) {
+ if (isKeyboardTaskFocusPending()) {
+ interpolator = EMPHASIZED;
+ } else if (state.displayOverviewTasksAsGrid(mDp)) {
interpolator = ACCELERATE_DECELERATE;
} else if (endTarget == RECENTS) {
interpolator = OVERSHOOT_1_2;
@@ -1653,7 +1664,8 @@
animatorSet.play(windowAnim);
if (mRecentsView != null) {
mRecentsView.onPrepareGestureEndAnimation(
- animatorSet, mGestureState.getEndTarget(),
+ mGestureState.isHandlingAtomicEvent() ? null : animatorSet,
+ mGestureState.getEndTarget(),
getRemoteTaskViewSimulators());
}
animatorSet.setDuration(duration).setInterpolator(interpolator);
@@ -2225,6 +2237,14 @@
}
}
+ private boolean shouldLinkRecentsViewScroll() {
+ return mRecentsViewScrollLinked && !isKeyboardTaskFocusPending();
+ }
+
+ private boolean isKeyboardTaskFocusPending() {
+ return mRecentsView != null && mRecentsView.isKeyboardTaskFocusPending();
+ }
+
private void onRecentsViewScroll() {
if (moveWindowWithRecentsScroll()) {
onCurrentShiftUpdated();
@@ -2457,6 +2477,44 @@
mActivityInitListener.register();
}
+ private boolean shouldFadeOutTargetsForKeyboardQuickSwitch(
+ TransformParams transformParams,
+ TaskViewSimulator taskViewSimulator,
+ float progress) {
+ RemoteAnimationTargets targets = transformParams.getTargetSet();
+ boolean fadeAppTargets = isKeyboardTaskFocusPending()
+ && targets != null
+ && targets.apps != null
+ && targets.apps.length > 0;
+ float fadeProgress = Utilities.mapBoundToRange(
+ progress,
+ /* lowerBound= */ 0f,
+ /* upperBound= */ KQS_TASK_FADE_ANIMATION_FRACTION,
+ /* toMin= */ 0f,
+ /* toMax= */ 1f,
+ LINEAR);
+ if (!fadeAppTargets || Float.compare(fadeProgress, 1f) == 0) {
+ return false;
+ }
+ SurfaceTransaction surfaceTransaction =
+ transformParams.createSurfaceParams(taskViewSimulator);
+ SurfaceControl.Transaction transaction = surfaceTransaction.getTransaction();
+
+ for (RemoteAnimationTarget app : targets.apps) {
+ transaction.setAlpha(app.leash, 1f - fadeProgress);
+ transaction.setPosition(app.leash,
+ /* x= */ app.startBounds.left
+ + (mActivity.getDeviceProfile().overviewPageSpacing
+ * (mRecentsView.isRtl() ? fadeProgress : -fadeProgress)),
+ /* y= */ 0f);
+ transaction.setScale(app.leash, 1f, 1f);
+ taskViewSimulator.taskPrimaryTranslation.value =
+ mRecentsView.getScrollOffsetForKeyboardTaskFocus();
+ taskViewSimulator.apply(transformParams, surfaceTransaction);
+ }
+ return true;
+ }
+
/**
* Applies the transform on the recents animation
*/
@@ -2466,7 +2524,7 @@
// swipe-to-icon animation is handled by RectFSpringAnim anim
boolean notSwipingToHome = mRecentsAnimationTargets != null
&& mGestureState.getEndTarget() != HOME;
- boolean setRecentsScroll = mRecentsViewScrollLinked && mRecentsView != null;
+ boolean setRecentsScroll = shouldLinkRecentsViewScroll() && mRecentsView != null;
float progress = Math.max(mCurrentShift.value, getScaleProgressDueToScroll());
int scrollOffset = setRecentsScroll ? mRecentsView.getScrollOffset() : 0;
if (!mStartMovingTasks && (progress > 0 || scrollOffset != 0)) {
@@ -2485,7 +2543,12 @@
if (setRecentsScroll) {
taskViewSimulator.setScroll(scrollOffset);
}
- taskViewSimulator.apply(remoteHandle.getTransformParams());
+ TransformParams transformParams = remoteHandle.getTransformParams();
+ if (shouldFadeOutTargetsForKeyboardQuickSwitch(
+ transformParams, taskViewSimulator, progress)) {
+ continue;
+ }
+ taskViewSimulator.apply(transformParams);
}
}
}
@@ -2493,7 +2556,7 @@
// Scaling of RecentsView during quick switch based on amount of recents scroll
private float getScaleProgressDueToScroll() {
if (mActivity == null || !mActivity.getDeviceProfile().isTablet || mRecentsView == null
- || !mRecentsViewScrollLinked) {
+ || !shouldLinkRecentsViewScroll()) {
return 0;
}
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 377b866..b89d20c 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -62,6 +62,7 @@
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.NavigationMode;
import com.android.launcher3.views.ScrimView;
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.views.RecentsView;
@@ -128,7 +129,7 @@
public abstract int getSwipeUpDestinationAndLength(
DeviceProfile dp, Context context, Rect outRect,
- PagedOrientationHandler orientationHandler);
+ RecentsPagedOrientationHandler orientationHandler);
/** Called when the animation to home has fully settled. */
public void onSwipeUpToHomeComplete(RecentsAnimationDeviceState deviceState) {}
@@ -177,7 +178,7 @@
public abstract <T extends RecentsView> T getVisibleRecentsView();
@UiThread
- public abstract boolean switchToRecentsIfVisible(Runnable onCompleteCallback);
+ public abstract boolean switchToRecentsIfVisible(Animator.AnimatorListener animatorListener);
public abstract Rect getOverviewWindowBounds(
Rect homeBounds, RemoteAnimationTarget target);
@@ -519,7 +520,7 @@
// Since we are changing the start position of the UI, reapply the state, at the end
controller.setEndAction(() -> mActivity.getStateManager().goToState(
controller.getInterpolatedProgress() > 0.5 ? mTargetState : mBackgroundState,
- false));
+ /* animated= */ false));
RecentsView recentsView = mActivity.getOverviewPanel();
AnimatorControllerWithResistance controllerWithResistance =
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index 5c96000..27e8726 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -32,10 +32,10 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.taskbar.FallbackTaskbarUIController;
-import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.DisplayController;
import com.android.quickstep.GestureState.GestureEndTarget;
import com.android.quickstep.fallback.RecentsState;
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.views.RecentsView;
@@ -60,7 +60,7 @@
/** 2 */
@Override
public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect,
- PagedOrientationHandler orientationHandler) {
+ RecentsPagedOrientationHandler orientationHandler) {
calculateTaskSize(context, dp, outRect, orientationHandler);
if (dp.isVerticalBarLayout() && DisplayController.getNavigationMode(context) != NO_BUTTON) {
return dp.isSeascape() ? outRect.left : (dp.widthPx - outRect.right);
@@ -122,7 +122,7 @@
}
@Override
- public boolean switchToRecentsIfVisible(Runnable onCompleteCallback) {
+ public boolean switchToRecentsIfVisible(Animator.AnimatorListener animatorListener) {
return false;
}
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index feaa063..97c48e6 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -21,7 +21,6 @@
import static com.android.launcher3.LauncherState.FLOATING_SEARCH_BAR;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
@@ -46,11 +45,11 @@
import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.taskbar.LauncherTaskbarUIController;
-import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.NavigationMode;
import com.android.quickstep.GestureState.GestureEndTarget;
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.LayoutUtils;
@@ -74,7 +73,7 @@
@Override
public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect,
- PagedOrientationHandler orientationHandler) {
+ RecentsPagedOrientationHandler orientationHandler) {
calculateTaskSize(context, dp, outRect, orientationHandler);
if (dp.isVerticalBarLayout()
&& DisplayController.getNavigationMode(context) != NavigationMode.NO_BUTTON) {
@@ -212,7 +211,7 @@
}
@Override
- public boolean switchToRecentsIfVisible(Runnable onCompleteCallback) {
+ public boolean switchToRecentsIfVisible(Animator.AnimatorListener animatorListener) {
Launcher launcher = getVisibleLauncher();
if (launcher == null) {
return false;
@@ -227,7 +226,7 @@
closeOverlay();
launcher.getStateManager().goToState(OVERVIEW,
launcher.getStateManager().shouldAnimateStateChange(),
- onCompleteCallback == null ? null : forEndCallback(onCompleteCallback));
+ animatorListener);
return true;
}
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 315316d..e448a14 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -15,9 +15,12 @@
*/
package com.android.quickstep;
+import static com.android.launcher3.PagedView.INVALID_PAGE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.content.Intent;
import android.graphics.PointF;
import android.os.SystemClock;
@@ -25,7 +28,6 @@
import android.view.View;
import androidx.annotation.BinderThread;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
@@ -75,7 +77,7 @@
* do not lose the focus across multiple calls of
* {@link OverviewCommandHelper#executeCommand(CommandInfo)} for the same command
*/
- private int mTaskFocusIndexOverride = -1;
+ private int mKeyboardTaskFocusIndex = -1;
/**
* Whether we should incoming toggle commands while a previous toggle command is still ongoing.
@@ -195,9 +197,11 @@
}
BaseActivityInterface<?, T> activityInterface =
mOverviewComponentObserver.getActivityInterface();
- RecentsView recents = activityInterface.getVisibleRecentsView();
- if (recents == null) {
+ RecentsView visibleRecentsView = activityInterface.getVisibleRecentsView();
+ RecentsView createdRecentsView;
+ if (visibleRecentsView == null) {
T activity = activityInterface.getCreatedActivity();
+ createdRecentsView = activity == null ? null : activity.getOverviewPanel();
DeviceProfile dp = activity == null ? null : activity.getDeviceProfile();
TaskbarUIController uiController = activityInterface.getTaskbarController();
boolean allowQuickSwitch = FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get()
@@ -209,8 +213,8 @@
if (!allowQuickSwitch) {
return true;
}
- mTaskFocusIndexOverride = uiController.launchFocusedTask();
- if (mTaskFocusIndexOverride == -1) {
+ mKeyboardTaskFocusIndex = uiController.launchFocusedTask();
+ if (mKeyboardTaskFocusIndex == -1) {
return true;
}
}
@@ -224,34 +228,47 @@
return true;
}
} else {
+ createdRecentsView = visibleRecentsView;
switch (cmd.type) {
case TYPE_SHOW:
// already visible
return true;
case TYPE_HIDE: {
- mTaskFocusIndexOverride = -1;
- int currentPage = recents.getNextPage();
- TaskView tv = (currentPage >= 0 && currentPage < recents.getTaskViewCount())
- ? (TaskView) recents.getPageAt(currentPage)
+ mKeyboardTaskFocusIndex = INVALID_PAGE;
+ int currentPage = visibleRecentsView.getNextPage();
+ TaskView tv = (currentPage >= 0
+ && currentPage < visibleRecentsView.getTaskViewCount())
+ ? (TaskView) visibleRecentsView.getPageAt(currentPage)
: null;
- return launchTask(recents, tv, cmd);
+ return launchTask(visibleRecentsView, tv, cmd);
}
case TYPE_TOGGLE:
- return launchTask(recents, getNextTask(recents), cmd);
+ return launchTask(visibleRecentsView, getNextTask(visibleRecentsView), cmd);
case TYPE_HOME:
- recents.startHome();
+ visibleRecentsView.startHome();
return true;
}
}
- final Runnable completeCallback = () -> {
- RecentsView rv = activityInterface.getVisibleRecentsView();
- if (rv != null && (cmd.type == TYPE_KEYBOARD_INPUT || cmd.type == TYPE_HIDE)) {
- updateRecentsViewFocus(rv);
+ if (createdRecentsView != null) {
+ createdRecentsView.setKeyboardTaskFocusIndex(mKeyboardTaskFocusIndex);
+ }
+ // Handle recents view focus when launching from home
+ Animator.AnimatorListener animatorListener = new AnimatorListenerAdapter() {
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ updateRecentsViewFocus(cmd);
}
- scheduleNextTask(cmd);
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ onRecentsViewFocusUpdated(cmd);
+ scheduleNextTask(cmd);
+ }
};
- if (activityInterface.switchToRecentsIfVisible(completeCallback)) {
+ if (activityInterface.switchToRecentsIfVisible(animatorListener)) {
// If successfully switched, wait until animation finishes
return false;
}
@@ -276,6 +293,7 @@
@Override
public void onRecentsAnimationStart(RecentsAnimationController controller,
RecentsAnimationTargets targets) {
+ updateRecentsViewFocus(cmd);
activityInterface.runOnInitBackgroundStateUI(() ->
interactionHandler.onGestureEnded(0, new PointF()));
cmd.removeListener(this);
@@ -290,14 +308,12 @@
if (createdActivity == null) {
return;
}
- RecentsView createdRecents = createdActivity.getOverviewPanel();
- if (createdRecents != null) {
- createdRecents.onRecentsAnimationComplete();
+ if (createdRecentsView != null) {
+ createdRecentsView.onRecentsAnimationComplete();
}
}
};
- RecentsView<?, ?> visibleRecentsView = activityInterface.getVisibleRecentsView();
if (visibleRecentsView != null) {
visibleRecentsView.moveRunningTaskToFront();
}
@@ -317,7 +333,6 @@
interactionHandler.onGestureStarted(false /*isLikelyToStartNewTask*/);
cmd.mActiveCallbacks.addListener(recentAnimListener);
}
-
Trace.beginAsyncSection(TRANSITION_NAME, 0);
return false;
}
@@ -325,47 +340,58 @@
private void onTransitionComplete(CommandInfo cmd, AbsSwipeUpHandler handler) {
cmd.removeListener(handler);
Trace.endAsyncSection(TRANSITION_NAME, 0);
-
- RecentsView rv =
- mOverviewComponentObserver.getActivityInterface().getVisibleRecentsView();
- if (rv != null && (cmd.type == TYPE_KEYBOARD_INPUT || cmd.type == TYPE_HIDE)) {
- updateRecentsViewFocus(rv);
- }
+ onRecentsViewFocusUpdated(cmd);
scheduleNextTask(cmd);
}
- private void updateRecentsViewFocus(@NonNull RecentsView rv) {
+ private void updateRecentsViewFocus(CommandInfo cmd) {
+ RecentsView recentsView =
+ mOverviewComponentObserver.getActivityInterface().getVisibleRecentsView();
+ if (recentsView == null || (cmd.type != TYPE_KEYBOARD_INPUT && cmd.type != TYPE_HIDE)) {
+ return;
+ }
// When the overview is launched via alt tab (cmd type is TYPE_KEYBOARD_INPUT),
// the touch mode somehow is not change to false by the Android framework.
// The subsequent tab to go through tasks in overview can only be dispatched to
// focuses views, while focus can only be requested in
// {@link View#requestFocusNoSearch(int, Rect)} when touch mode is false. To note,
// here we launch overview with live tile.
- rv.getViewRootImpl().touchModeChanged(false);
+ recentsView.getViewRootImpl().touchModeChanged(false);
// Ensure that recents view has focus so that it receives the followup key inputs
- TaskView taskView = rv.getTaskViewAt(mTaskFocusIndexOverride);
- if (taskView != null) {
- requestFocus(taskView);
+ if (requestFocus(recentsView.getTaskViewAt(mKeyboardTaskFocusIndex))) {
return;
}
- taskView = rv.getNextTaskView();
- if (taskView != null) {
- requestFocus(taskView);
+ if (requestFocus(recentsView.getNextTaskView())) {
return;
}
- taskView = rv.getTaskViewAt(0);
- if (taskView != null) {
- requestFocus(taskView);
+ if (requestFocus(recentsView.getTaskViewAt(0))) {
return;
}
- requestFocus(rv);
+ requestFocus(recentsView);
}
- private void requestFocus(@NonNull View view) {
- view.post(() -> {
- view.requestFocus();
- view.requestAccessibilityFocus();
+ private void onRecentsViewFocusUpdated(CommandInfo cmd) {
+ RecentsView recentsView =
+ mOverviewComponentObserver.getActivityInterface().getVisibleRecentsView();
+ if (recentsView == null
+ || cmd.type != TYPE_HIDE
+ || mKeyboardTaskFocusIndex == INVALID_PAGE) {
+ return;
+ }
+ recentsView.setKeyboardTaskFocusIndex(INVALID_PAGE);
+ recentsView.setCurrentPage(mKeyboardTaskFocusIndex);
+ mKeyboardTaskFocusIndex = INVALID_PAGE;
+ }
+
+ private boolean requestFocus(@Nullable View taskView) {
+ if (taskView == null) {
+ return false;
+ }
+ taskView.post(() -> {
+ taskView.requestFocus();
+ taskView.requestAccessibilityFocus();
});
+ return true;
}
public void dump(PrintWriter pw) {
@@ -374,7 +400,7 @@
if (!mPendingCommands.isEmpty()) {
pw.println(" pendingCommandType=" + mPendingCommands.get(0).type);
}
- pw.println(" mTaskFocusIndexOverride=" + mTaskFocusIndexOverride);
+ pw.println(" mKeyboardTaskFocusIndex=" + mKeyboardTaskFocusIndex);
pw.println(" mWaitForToggleCommandComplete=" + mWaitForToggleCommandComplete);
}
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 65c8662..dffb882 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -14,8 +14,8 @@
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.testing.TestInformationHandler;
import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.DisplayController;
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.TISBindHelper;
import com.android.quickstep.views.RecentsView;
@@ -56,7 +56,7 @@
}
Rect focusedTaskRect = new Rect();
LauncherActivityInterface.INSTANCE.calculateTaskSize(mContext, mDeviceProfile,
- focusedTaskRect, PagedOrientationHandler.PORTRAIT);
+ focusedTaskRect, RecentsPagedOrientationHandler.PORTRAIT);
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, focusedTaskRect.height());
return response;
}
@@ -67,7 +67,7 @@
}
Rect gridTaskRect = new Rect();
LauncherActivityInterface.INSTANCE.calculateGridTaskSize(mContext, mDeviceProfile,
- gridTaskRect, PagedOrientationHandler.PORTRAIT);
+ gridTaskRect, RecentsPagedOrientationHandler.PORTRAIT);
response.putParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD, gridTaskRect);
return response;
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 179612b..d617828 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -19,7 +19,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.Display.DEFAULT_DISPLAY;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE;
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;
@@ -66,7 +65,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
-import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
@@ -608,7 +606,7 @@
if (FeatureFlags.CUSTOM_LPNH_THRESHOLDS.get()) {
float customSlopMultiplier =
- LauncherPrefs.get(mContext).get(LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE) / 100f;
+ FeatureFlags.LPNH_SLOP_PERCENTAGE.get() / 100f;
return customSlopMultiplier * slopMultiplier * touchSlop;
} else {
return slopMultiplier * touchSlop;
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
index 2e70703..6a9caf7 100644
--- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -29,6 +29,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.TaskViewSimulator;
@@ -68,13 +69,16 @@
*/
public RemoteTargetGluer(Context context, BaseActivityInterface sizingStrategy) {
if (isDesktopModeSupported()) {
- // TODO(279931899): binder call, only for prototyping. Creating the gluer should be
- // postponed so we can create it when we have the remote animation targets ready.
- int desktopTasks = SystemUiProxy.INSTANCE.get(context).getVisibleDesktopTaskCount(
- context.getDisplayId());
- if (desktopTasks > 0) {
- init(context, sizingStrategy, desktopTasks, true /* forDesktop */);
- return;
+ DesktopVisibilityController desktopVisibilityController =
+ LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
+ if (desktopVisibilityController != null) {
+ int visibleTasksCount = desktopVisibilityController.getVisibleFreeformTasksCount();
+ if (visibleTasksCount > 0) {
+ // Allocate +1 to account for a new task added to the desktop mode
+ int numHandles = visibleTasksCount + 1;
+ init(context, sizingStrategy, numHandles, true /* forDesktop */);
+ return;
+ }
}
}
@@ -135,18 +139,7 @@
* the left/top task, index 1 right/bottom.
*/
public RemoteTargetHandle[] assignTargetsForSplitScreen(RemoteAnimationTargets targets) {
- // Resize the mRemoteTargetHandles array since we started assuming split screen, but
- // targets.apps is the ultimate source of truth here
- long appCount = Arrays.stream(targets.apps)
- .filter(app -> app.mode == targets.targetMode)
- .count();
- Log.d(TAG, "appCount: " + appCount + " handleLength: " + mRemoteTargetHandles.length);
- if (appCount < mRemoteTargetHandles.length) {
- Log.d(TAG, "resizing handles");
- RemoteTargetHandle[] newHandles = new RemoteTargetHandle[(int) appCount];
- System.arraycopy(mRemoteTargetHandles, 0/*src*/, newHandles, 0/*dst*/, (int) appCount);
- mRemoteTargetHandles = newHandles;
- }
+ resizeRemoteTargetHandles(targets);
// If we are in a true split screen case (2 apps running on screen), either:
// a) mSplitBounds was already set (from the clicked GroupedTaskView)
@@ -228,6 +221,8 @@
* transform params per app in {@code targets.apps} list.
*/
public RemoteTargetHandle[] assignTargetsForDesktop(RemoteAnimationTargets targets) {
+ resizeRemoteTargetHandles(targets);
+
for (int i = 0; i < mRemoteTargetHandles.length; i++) {
RemoteAnimationTarget primaryTaskTarget = targets.apps[i];
mRemoteTargetHandles[i].mTransformParams.setTargetSet(
@@ -237,6 +232,23 @@
return mRemoteTargetHandles;
}
+ /**
+ * Resize the `mRemoteTargetHandles` array since we assumed initial size, but
+ * `targets.apps` is the ultimate source of truth here
+ */
+ private void resizeRemoteTargetHandles(RemoteAnimationTargets targets) {
+ long appCount = Arrays.stream(targets.apps)
+ .filter(app -> app.mode == targets.targetMode)
+ .count();
+ Log.d(TAG, "appCount: " + appCount + " handleLength: " + mRemoteTargetHandles.length);
+ if (appCount < mRemoteTargetHandles.length) {
+ Log.d(TAG, "resizing handles");
+ RemoteTargetHandle[] newHandles = new RemoteTargetHandle[(int) appCount];
+ System.arraycopy(mRemoteTargetHandles, 0/*src*/, newHandles, 0/*dst*/, (int) appCount);
+ mRemoteTargetHandles = newHandles;
+ }
+ }
+
private Rect getStartBounds(RemoteAnimationTarget target) {
return target.startBounds == null ? target.screenSpaceBounds : target.startBounds;
}
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index e481165..b920c10 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -37,6 +37,7 @@
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.RectFSpringAnim.DefaultSpringConfig;
@@ -151,7 +152,7 @@
@UiThread
public abstract void onCurrentShiftUpdated();
- protected PagedOrientationHandler getOrientationHandler() {
+ protected RecentsPagedOrientationHandler getOrientationHandler() {
// OrientationHandler should be independent of remote target, can directly take one
return mRemoteTargetHandles[0].getTaskViewSimulator()
.getOrientationState().getOrientationHandler();
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 56a4024..52f9d8d 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -731,15 +731,12 @@
/**
* Tells SysUI to show the bubble with the provided key.
* @param key the key of the bubble to show.
- * @param bubbleBarOffsetX the offset of the bubble bar from the edge of the screen on the X
- * axis.
- * @param bubbleBarOffsetY the offset of the bubble bar from the edge of the screen on the Y
- * axis.
+ * @param bubbleBarBounds bounds of the bubble bar in display coordinates
*/
- public void showBubble(String key, int bubbleBarOffsetX, int bubbleBarOffsetY) {
+ public void showBubble(String key, Rect bubbleBarBounds) {
if (mBubbles != null) {
try {
- mBubbles.showBubble(key, bubbleBarOffsetX, bubbleBarOffsetY);
+ mBubbles.showBubble(key, bubbleBarBounds);
} catch (RemoteException e) {
Log.w(TAG, "Failed call showBubble");
}
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 4f885af..a1a3145 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -66,6 +66,8 @@
private Runnable mLiveTileCleanUpHandler;
private Context mCtx;
+ private boolean mRecentsAnimationStartPending = false;
+
private final TaskStackChangeListener mLiveTileRestartListener = new TaskStackChangeListener() {
@Override
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
@@ -100,6 +102,10 @@
.startRecentsActivity(intent, 0, null, null, null));
}
+ public boolean isRecentsAnimationStartPending() {
+ return mRecentsAnimationStartPending;
+ }
+
/**
* Starts a new recents animation for the activity with the given {@param intent}.
*/
@@ -109,6 +115,7 @@
ActiveGestureLog.INSTANCE.addLog(
/* event= */ "startRecentsAnimation",
/* gestureEvent= */ START_RECENTS_ANIMATION);
+ mRecentsAnimationStartPending = true;
// Notify if recents animation is still running
if (mController != null) {
String msg = "New recents animation started before old animation completed";
@@ -138,6 +145,7 @@
@Override
public void onRecentsAnimationStart(RecentsAnimationController controller,
RecentsAnimationTargets targets) {
+ mRecentsAnimationStartPending = false;
if (mCallbacks == null) {
// It's possible for the recents animation to have finished and be cleaned up
// by the time we process the start callback, and in that case, just we can skip
@@ -178,11 +186,13 @@
@Override
public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
+ mRecentsAnimationStartPending = false;
cleanUpRecentsAnimation(newCallbacks);
}
@Override
public void onRecentsAnimationFinished(RecentsAnimationController controller) {
+ mRecentsAnimationStartPending = false;
cleanUpRecentsAnimation(newCallbacks);
}
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 73b786f..9c84df8 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -47,9 +47,9 @@
import com.android.launcher3.model.WellbeingModel;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.popup.SystemShortcut.AppInfo;
-import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.InstantAppResolver;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.views.GroupedTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskThumbnailView;
@@ -281,7 +281,7 @@
final int intentFlags = task.key.baseIntent.getFlags();
final TaskView taskView = taskContainer.getTaskView();
final RecentsView recentsView = taskView.getRecentsView();
- final PagedOrientationHandler orientationHandler =
+ final RecentsPagedOrientationHandler orientationHandler =
recentsView.getPagedOrientationHandler();
boolean notEnoughTasksToSplit =
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 55a7985..5228420 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -24,6 +24,7 @@
import static android.view.MotionEvent.ACTION_UP;
import static com.android.launcher3.Flags.enableCursorHoverStates;
+import static com.android.launcher3.Flags.useActivityOverlay;
import static com.android.launcher3.Launcher.INTENT_ACTION_ALL_APPS_TOGGLE;
import static com.android.launcher3.LauncherPrefs.backedUpItem;
import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent;
@@ -39,6 +40,7 @@
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_DOWN;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_MOVE;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_UP;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.RECENTS_ANIMATION_START_PENDING;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
@@ -697,7 +699,7 @@
private void onInputEvent(InputEvent ev) {
if (!(ev instanceof MotionEvent)) {
ActiveGestureLog.INSTANCE.addLog(new CompoundString("TIS.onInputEvent: ")
- .append("Cannot process input event, received unknown event ")
+ .append("Cannot process input event: received unknown event ")
.append(ev.toString()));
return;
}
@@ -710,22 +712,32 @@
if (!isUserUnlocked || (mDeviceState.isButtonNavMode()
&& !isTrackpadMotionEvent(event))) {
ActiveGestureLog.INSTANCE.addLog(new CompoundString("TIS.onInputEvent: ")
- .append("Cannot process input event, ")
+ .append("Cannot process input event: ")
.append(!isUserUnlocked
? "user is locked"
: "using 3-button nav and event is not a trackpad event"));
return;
}
- SafeCloseable traceToken = TraceHelper.INSTANCE.allowIpcs("TIS.onInputEvent");
-
final int action = event.getActionMasked();
// Note this will create a new consumer every mouse click, as after ACTION_UP from the click
// an ACTION_HOVER_ENTER will fire as well.
boolean isHoverActionWithoutConsumer = enableCursorHoverStates()
&& isHoverActionWithoutConsumer(event);
+ if (mTaskAnimationManager.isRecentsAnimationStartPending()
+ && (action == ACTION_DOWN || isHoverActionWithoutConsumer)) {
+ ActiveGestureLog.INSTANCE.addLog(
+ new CompoundString("TIS.onInputEvent: ")
+ .append("Cannot process input event: a recents animation has been ")
+ .append("requested, but hasn't started."),
+ RECENTS_ANIMATION_START_PENDING);
+ return;
+ }
+
+ SafeCloseable traceToken = TraceHelper.INSTANCE.allowIpcs("TIS.onInputEvent");
+
CompoundString reasonString = action == ACTION_DOWN
- ? new CompoundString("onMotionEvent: ") : CompoundString.NO_OP;
+ ? new CompoundString("TIS.onMotionEvent: ") : CompoundString.NO_OP;
if (action == ACTION_DOWN || isHoverActionWithoutConsumer) {
mRotationTouchHelper.setOrientationTransformIfNeeded(event);
@@ -1148,6 +1160,14 @@
boolean launcherResumedThroughShellTransition =
gestureState.getActivityInterface().isResumed()
&& !previousGestureState.isRecentsAnimationRunning();
+ // If a task fragment within Launcher is resumed
+ boolean launcherChildActivityResumed = useActivityOverlay()
+ && runningTask != null
+ && runningTask.isHomeTask()
+ && mOverviewComponentObserver.isHomeAndOverviewSame()
+ && !launcherResumedThroughShellTransition
+ && !previousGestureState.isRecentsAnimationRunning();
+
if (gestureState.getActivityInterface().isInLiveTileMode()) {
return createOverviewInputConsumer(
previousGestureState,
@@ -1174,9 +1194,11 @@
? "launcher resumed through a shell transition"
: "forceOverviewInputConsumer == true"))
.append(", trying to use overview input consumer"));
- } else if (mDeviceState.isGestureBlockedTask(runningTask)) {
+ } else if (mDeviceState.isGestureBlockedTask(runningTask) || launcherChildActivityResumed) {
return getDefaultInputConsumer(reasonString.append(SUBSTRING_PREFIX)
- .append("is gesture-blocked task, trying to use default input consumer"));
+ .append(launcherChildActivityResumed
+ ? "is launcher child-task, trying to use default input consumer"
+ : "is gesture-blocked task, trying to use default input consumer"));
} else {
reasonString.append(SUBSTRING_PREFIX)
.append("using OtherActivityInputConsumer");
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
index 4c0b550..cf8750f 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -15,7 +15,6 @@
*/
package com.android.quickstep.inputconsumers;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_TIMEOUT_MS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DEEP_PRESS_NAVBAR;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DEEP_PRESS_STASHED_TASKBAR;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LONG_PRESS_NAVBAR;
@@ -26,7 +25,6 @@
import android.view.MotionEvent;
import android.view.ViewConfiguration;
-import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.util.DisplayController;
@@ -63,7 +61,7 @@
mScreenWidth = DisplayController.INSTANCE.get(context).getInfo().currentSize.x;
mDeepPressEnabled = FeatureFlags.ENABLE_LPNH_DEEP_PRESS.get();
if (FeatureFlags.CUSTOM_LPNH_THRESHOLDS.get()) {
- mLongPressTimeout = LauncherPrefs.get(context).get(LONG_PRESS_NAV_HANDLE_TIMEOUT_MS);
+ mLongPressTimeout = FeatureFlags.LPNH_TIMEOUT_MS.get();
} else {
mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
}
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.java
similarity index 96%
rename from src/com/android/launcher3/touch/LandscapePagedViewHandler.java
rename to quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.java
index f7afcb9..e41832b 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.launcher3.touch;
+package com.android.quickstep.orientation;
import static android.view.Gravity.BOTTOM;
import static android.view.Gravity.CENTER_VERTICAL;
@@ -54,16 +54,18 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
import com.android.launcher3.views.BaseDragLayer;
+import com.android.quickstep.views.IconAppChipView;
import java.util.Collections;
import java.util.List;
-public class LandscapePagedViewHandler implements PagedOrientationHandler {
+public class LandscapePagedViewHandler implements RecentsPagedOrientationHandler {
@Override
public <T> T getPrimaryValue(T x, T y) {
@@ -398,7 +400,7 @@
@Override
public ChildBounds getChildBounds(View child, int childStart, int pageCenter,
- boolean layoutChild) {
+ boolean layoutChild) {
final int childHeight = child.getMeasuredHeight();
final int childBottom = childStart + childHeight;
final int childWidth = child.getMeasuredWidth();
@@ -574,21 +576,21 @@
}
@Override
- public void setIconAppChipMenuParams(View iconAppChipMenuView,
+ public void setIconAppChipMenuParams(IconAppChipView iconAppChipView,
FrameLayout.LayoutParams iconMenuParams, int iconMenuMargin, int thumbnailTopMargin) {
- boolean isRtl = iconAppChipMenuView.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+ boolean isRtl = iconAppChipView.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
iconMenuParams.gravity = (isRtl ? START : END) | (isRtl ? BOTTOM : TOP);
iconMenuParams.setMarginStart(isRtl ? iconMenuMargin : 0);
iconMenuParams.topMargin = iconMenuMargin;
iconMenuParams.bottomMargin = isRtl ? iconMenuMargin : 0;
iconMenuParams.setMarginEnd(iconMenuMargin);
- iconAppChipMenuView.setPivotX(isRtl ? iconMenuParams.width - (iconMenuParams.height / 2f)
+ iconAppChipView.setPivotX(isRtl ? iconMenuParams.width - (iconMenuParams.height / 2f)
: iconMenuParams.width / 2f);
- iconAppChipMenuView.setPivotY(
+ iconAppChipView.setPivotY(
isRtl ? (iconMenuParams.height / 2f) : iconMenuParams.width / 2f);
- iconAppChipMenuView.setTranslationY(0);
- iconAppChipMenuView.setRotation(getDegreesRotated());
+ iconAppChipView.setTranslationY(0);
+ iconAppChipView.setRotation(getDegreesRotated());
}
@Override
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
similarity index 90%
rename from src/com/android/launcher3/touch/PortraitPagedViewHandler.java
rename to quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
index 8301981..3d7065b 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.launcher3.touch;
+package com.android.quickstep.orientation;
import static android.view.Gravity.BOTTOM;
import static android.view.Gravity.CENTER_HORIZONTAL;
@@ -33,7 +33,6 @@
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
-import android.content.res.Resources;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -42,26 +41,27 @@
import android.util.FloatProperty;
import android.util.Pair;
import android.view.Gravity;
-import android.view.MotionEvent;
import android.view.Surface;
-import android.view.VelocityTracker;
import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.touch.DefaultPagedViewHandler;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
+import com.android.quickstep.views.IconAppChipView;
import java.util.ArrayList;
import java.util.List;
-public class PortraitPagedViewHandler implements PagedOrientationHandler {
+public class PortraitPagedViewHandler extends DefaultPagedViewHandler implements
+ RecentsPagedOrientationHandler {
private final Matrix mTmpMatrix = new Matrix();
private final RectF mTmpRectF = new RectF();
@@ -75,27 +75,6 @@
public <T> T getSecondaryValue(T x, T y) {
return y;
}
-
- @Override
- public int getPrimaryValue(int x, int y) {
- return x;
- }
-
- @Override
- public int getSecondaryValue(int x, int y) {
- return y;
- }
-
- @Override
- public float getPrimaryValue(float x, float y) {
- return x;
- }
-
- @Override
- public float getSecondaryValue(float x, float y) {
- return y;
- }
-
@Override
public boolean isLayoutNaturalToLauncher() {
return true;
@@ -116,16 +95,6 @@
}
@Override
- public <T> void setPrimary(T target, Int2DAction<T> action, int param) {
- action.call(target, param, 0);
- }
-
- @Override
- public <T> void setPrimary(T target, Float2DAction<T> action, float param) {
- action.call(target, param, 0);
- }
-
- @Override
public <T> void setSecondary(T target, Float2DAction<T> action, float param) {
action.call(target, 0, param);
}
@@ -137,21 +106,6 @@
}
@Override
- public float getPrimaryDirection(MotionEvent event, int pointerIndex) {
- return event.getX(pointerIndex);
- }
-
- @Override
- public float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId) {
- return velocityTracker.getXVelocity(pointerId);
- }
-
- @Override
- public int getMeasuredSize(View view) {
- return view.getMeasuredWidth();
- }
-
- @Override
public int getPrimarySize(View view) {
return view.getWidth();
}
@@ -192,26 +146,6 @@
}
@Override
- public int getPrimaryScroll(View view) {
- return view.getScrollX();
- }
-
- @Override
- public float getPrimaryScale(View view) {
- return view.getScaleX();
- }
-
- @Override
- public void setMaxScroll(AccessibilityEvent event, int maxScroll) {
- event.setMaxScrollX(maxScroll);
- }
-
- @Override
- public boolean getRecentsRtlSetting(Resources resources) {
- return !Utilities.isRtl(resources);
- }
-
- @Override
public float getDegreesRotated() {
return 0;
}
@@ -231,27 +165,6 @@
view.setScaleY(scale);
}
- @Override
- public int getChildStart(View view) {
- return view.getLeft();
- }
-
- @Override
- public int getCenterForPage(View view, Rect insets) {
- return (view.getPaddingTop() + view.getMeasuredHeight() + insets.top
- - insets.bottom - view.getPaddingBottom()) / 2;
- }
-
- @Override
- public int getScrollOffsetStart(View view, Rect insets) {
- return insets.left + view.getPaddingLeft();
- }
-
- @Override
- public int getScrollOffsetEnd(View view, Rect insets) {
- return view.getWidth() - view.getPaddingRight() - insets.right;
- }
-
public int getSecondaryTranslationDirectionFactor() {
return -1;
}
@@ -400,20 +313,6 @@
}
/* -------------------- */
-
- @Override
- public ChildBounds getChildBounds(View child, int childStart, int pageCenter,
- boolean layoutChild) {
- final int childWidth = child.getMeasuredWidth();
- final int childRight = childStart + childWidth;
- final int childHeight = child.getMeasuredHeight();
- final int childTop = pageCenter - childHeight / 2;
- if (layoutChild) {
- child.layout(childStart, childTop, childRight, childTop + childHeight);
- }
- return new ChildBounds(childWidth, childHeight, childRight, childTop);
- }
-
@Override
public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
return dp.heightPx - rect.bottom;
@@ -713,7 +612,7 @@
}
@Override
- public void setIconAppChipMenuParams(View iconAppChipMenuView,
+ public void setIconAppChipMenuParams(IconAppChipView iconAppChipView,
FrameLayout.LayoutParams iconMenuParams, int iconMenuMargin, int thumbnailTopMargin) {
iconMenuParams.gravity = TOP | START;
iconMenuParams.setMarginStart(iconMenuMargin);
@@ -721,10 +620,10 @@
iconMenuParams.bottomMargin = 0;
iconMenuParams.setMarginEnd(0);
- iconAppChipMenuView.setPivotX(0);
- iconAppChipMenuView.setPivotY(0);
- iconAppChipMenuView.setTranslationY(0);
- iconAppChipMenuView.setRotation(getDegreesRotated());
+ iconAppChipView.setPivotX(0);
+ iconAppChipView.setPivotY(0);
+ iconAppChipView.setTranslationY(0);
+ iconAppChipView.setRotation(getDegreesRotated());
}
@Override
diff --git a/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.java b/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.java
new file mode 100644
index 0000000..34756b4
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.java
@@ -0,0 +1,241 @@
+/*
+ * 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.orientation;
+
+
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.ShapeDrawable;
+import android.util.FloatProperty;
+import android.util.Pair;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
+import com.android.launcher3.util.SplitConfigurationOptions;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
+import com.android.quickstep.views.IconAppChipView;
+
+import java.util.List;
+
+/**
+ * Abstraction layer to separate horizontal and vertical specific implementations
+ * for {@link com.android.quickstep.views.RecentsView}. Majority of these implementations are
+ * (should be) as simple as choosing the correct X and Y analogous methods.
+ */
+public interface RecentsPagedOrientationHandler extends PagedOrientationHandler {
+
+ RecentsPagedOrientationHandler PORTRAIT = new PortraitPagedViewHandler();
+ RecentsPagedOrientationHandler LANDSCAPE = new LandscapePagedViewHandler();
+ RecentsPagedOrientationHandler SEASCAPE = new SeascapePagedViewHandler();
+
+ <T> void setSecondary(T target, Float2DAction<T> action, float param);
+ <T> void set(T target, Int2DAction<T> action, int primaryParam, int secondaryParam);
+ int getPrimarySize(View view);
+ float getPrimarySize(RectF rect);
+ int getSecondaryTranslationDirectionFactor();
+ float getDegreesRotated();
+ int getRotation();
+ boolean isLayoutNaturalToLauncher();
+
+ <T> T getPrimaryValue(T x, T y);
+ <T> T getSecondaryValue(T x, T y);
+ void setPrimaryScale(View view, float scale);
+ void setSecondaryScale(View view, float scale);
+ float getStart(RectF rect);
+ float getEnd(RectF rect);
+ int getClearAllSidePadding(View view, boolean isRtl);
+ int getSecondaryDimension(View view);
+ FloatProperty<View> getPrimaryViewTranslate();
+ FloatProperty<View> getSecondaryViewTranslate();
+ int getSplitTranslationDirectionFactor(@StagePosition int stagePosition,
+ DeviceProfile deviceProfile);
+ Pair<FloatProperty, FloatProperty> getSplitSelectTaskOffset(FloatProperty primary,
+ FloatProperty secondary, DeviceProfile deviceProfile);
+ int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect);
+ List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp);
+ /**
+ * @param placeholderHeight height of placeholder view in portrait, width in landscape
+ */
+ void getInitialSplitPlaceholderBounds(int placeholderHeight, int placeholderInset,
+ DeviceProfile dp, @StagePosition int stagePosition, Rect out);
+
+ /**
+ * Centers an icon in the split staging area, accounting for insets.
+ * @param out The icon that needs to be centered.
+ * @param onScreenRectCenterX The x-center of the on-screen staging area (most of the Rect is
+ * offscreen).
+ * @param onScreenRectCenterY The y-center of the on-screen staging area (most of the Rect is
+ * offscreen).
+ * @param fullscreenScaleX A x-scaling factor used to convert coordinates back into pixels.
+ * @param fullscreenScaleY A y-scaling factor used to convert coordinates back into pixels.
+ * @param drawableWidth The icon's drawable (final) width.
+ * @param drawableHeight The icon's drawable (final) height.
+ * @param dp The device profile, used to report rotation and hardware insets.
+ * @param stagePosition 0 if the staging area is pinned to top/left, 1 for bottom/right.
+ */
+ void updateSplitIconParams(View out, float onScreenRectCenterX,
+ float onScreenRectCenterY, float fullscreenScaleX, float fullscreenScaleY,
+ int drawableWidth, int drawableHeight, DeviceProfile dp,
+ @StagePosition int stagePosition);
+
+ /**
+ * Sets positioning and rotation for a SplitInstructionsView.
+ * @param out The SplitInstructionsView that needs to be positioned.
+ * @param dp The device profile, used to report rotation and device type.
+ * @param splitInstructionsHeight The SplitInstructionView's height.
+ * @param splitInstructionsWidth The SplitInstructionView's width.
+ */
+ void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
+ int splitInstructionsWidth);
+
+ /**
+ * @param splitDividerSize height of split screen drag handle in portrait, width in landscape
+ * @param stagePosition the split position option (top/left, bottom/right) of the first
+ * task selected for entering split
+ * @param out1 the bounds for where the first selected app will be
+ * @param out2 the bounds for where the second selected app will be, complimentary to
+ * {@param out1} based on {@param initialSplitOption}
+ */
+ void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
+ @StagePosition int stagePosition, Rect out1, Rect out2);
+
+ int getDefaultSplitPosition(DeviceProfile deviceProfile);
+
+ /**
+ * @param outRect This is expected to be the rect that has the dimensions for a non-split,
+ * fullscreen task in overview. This will directly be modified.
+ * @param desiredStagePosition Which stage position (topLeft/rightBottom) we want to resize
+ * outRect for
+ */
+ void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect, SplitBounds splitInfo,
+ @SplitConfigurationOptions.StagePosition int desiredStagePosition);
+
+ void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
+ int parentWidth, int parentHeight,
+ SplitBounds splitBoundsConfig, DeviceProfile dp, boolean isRtl);
+
+ // Overview TaskMenuView methods
+ void setTaskIconParams(FrameLayout.LayoutParams iconParams,
+ int taskIconMargin, int taskIconHeight, int thumbnailTopMargin, boolean isRtl);
+
+ void setIconAppChipMenuParams(IconAppChipView iconAppChipView,
+ FrameLayout.LayoutParams iconMenuParams,
+ int iconMenuMargin, int thumbnailTopMargin);
+ void setSplitIconParams(View primaryIconView, View secondaryIconView,
+ int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
+ int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
+ DeviceProfile deviceProfile, SplitBounds splitConfig);
+
+ /*
+ * The following two methods try to center the TaskMenuView in landscape by finding the center
+ * of the thumbnail view and then subtracting half of the taskMenu width. In this case, the
+ * taskMenu width is the same size as the thumbnail width (what got set below in
+ * getTaskMenuWidth()), so we directly use that in the calculations.
+ */
+ float getTaskMenuX(float x, View thumbnailView, DeviceProfile deviceProfile,
+ float taskInsetMargin, View taskViewIcon);
+ float getTaskMenuY(float y, View thumbnailView, int stagePosition,
+ View taskMenuView, float taskInsetMargin, View taskViewIcon);
+ int getTaskMenuWidth(View thumbnailView, DeviceProfile deviceProfile,
+ @StagePosition int stagePosition);
+ /**
+ * Sets linear layout orientation for {@link com.android.launcher3.popup.SystemShortcut} items
+ * inside task menu view.
+ */
+ void setTaskOptionsMenuLayoutOrientation(DeviceProfile deviceProfile,
+ LinearLayout taskMenuLayout, int dividerSpacing,
+ ShapeDrawable dividerDrawable);
+ /**
+ * Sets layout param attributes for {@link com.android.launcher3.popup.SystemShortcut} child
+ * views inside task menu view.
+ */
+ void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp,
+ LinearLayout viewGroup, DeviceProfile deviceProfile);
+
+ /**
+ * Calculates the position where a Digital Wellbeing Banner should be placed on its parent
+ * TaskView.
+ * @return A Pair of Floats representing the proper x and y translations.
+ */
+ Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth,
+ int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile,
+ View[] thumbnailViews, int desiredTaskId, View banner);
+
+ // The following are only used by TaskViewTouchHandler.
+ /** @return Either VERTICAL or HORIZONTAL. */
+ SingleAxisSwipeDetector.Direction getUpDownSwipeDirection();
+ /** @return Given {@link #getUpDownSwipeDirection()}, whether POSITIVE or NEGATIVE is up. */
+ int getUpDirection(boolean isRtl);
+ /** @return Whether the displacement is going towards the top of the screen. */
+ boolean isGoingUp(float displacement, boolean isRtl);
+ /** @return Either 1 or -1, a factor to multiply by so the animation goes the correct way. */
+ int getTaskDragDisplacementFactor(boolean isRtl);
+
+ /**
+ * Maps the velocity from the coordinate plane of the foreground app to that
+ * of Launcher's (which now will always be portrait)
+ */
+ void adjustFloatingIconStartVelocity(PointF velocity);
+
+ /**
+ * Ensures that outStartRect left bound is within the DeviceProfile's visual boundaries
+ * @param outStartRect The start rect that will directly be modified
+ */
+ void fixBoundsForHomeAnimStartRect(RectF outStartRect, DeviceProfile deviceProfile);
+
+ /**
+ * Determine the target translation for animating the FloatingTaskView out. This value could
+ * either be an x-coordinate or a y-coordinate, depending on which way the FloatingTaskView was
+ * docked.
+ *
+ * @param floatingTask The FloatingTaskView.
+ * @param onScreenRect The current on-screen dimensions of the FloatingTaskView.
+ * @param stagePosition STAGE_POSITION_TOP_OR_LEFT or STAGE_POSITION_BOTTOM_OR_RIGHT.
+ * @param dp The device profile.
+ * @return A float. When an animation translates the FloatingTaskView to this position, it will
+ * appear to tuck away off the edge of the screen.
+ */
+ float getFloatingTaskOffscreenTranslationTarget(View floatingTask, RectF onScreenRect,
+ @StagePosition int stagePosition, DeviceProfile dp);
+
+ /**
+ * Sets the translation of a FloatingTaskView along its "slide-in/slide-out" axis (could be
+ * either x or y), depending on how the view is oriented.
+ *
+ * @param floatingTask The FloatingTaskView to be translated.
+ * @param translation The target translation value.
+ * @param dp The current device profile.
+ */
+ void setFloatingTaskPrimaryTranslation(View floatingTask, float translation, DeviceProfile dp);
+
+ /**
+ * Gets the translation of a FloatingTaskView along its "slide-in/slide-out" axis (could be
+ * either x or y), depending on how the view is oriented.
+ *
+ * @param floatingTask The FloatingTaskView in question.
+ * @param dp The current device profile.
+ * @return The current translation value.
+ */
+ Float getFloatingTaskPrimaryTranslation(View floatingTask, DeviceProfile dp);
+}
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.java
similarity index 95%
rename from src/com/android/launcher3/touch/SeascapePagedViewHandler.java
rename to quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.java
index 7077ad9..7e53cf1 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.launcher3.touch;
+package com.android.quickstep.orientation;
import static android.view.Gravity.BOTTOM;
import static android.view.Gravity.CENTER_VERTICAL;
@@ -42,10 +42,12 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.views.BaseDragLayer;
+import com.android.quickstep.views.IconAppChipView;
import java.util.Collections;
import java.util.List;
@@ -234,9 +236,9 @@
}
@Override
- public void setIconAppChipMenuParams(View iconAppChipMenuView,
+ public void setIconAppChipMenuParams(IconAppChipView iconAppChipView,
FrameLayout.LayoutParams iconMenuParams, int iconMenuMargin, int thumbnailTopMargin) {
- boolean isRtl = iconAppChipMenuView.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+ boolean isRtl = iconAppChipView.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
iconMenuParams.gravity = (isRtl ? TOP : BOTTOM) | (isRtl ? END : START);
iconMenuParams.setMarginStart(0);
iconMenuParams.topMargin = isRtl ? iconMenuMargin : 0;
@@ -244,12 +246,12 @@
iconMenuParams.setMarginEnd(isRtl ? thumbnailTopMargin : 0);
// Use half menu height to place the pivot within the X/Y center of icon in the menu.
- float iconCenter = iconAppChipMenuView.getHeight() / 2f;
- iconAppChipMenuView.setPivotX(isRtl ? iconMenuParams.width / 2f : iconCenter);
- iconAppChipMenuView.setPivotY(
+ float iconCenter = iconAppChipView.getHeight() / 2f;
+ iconAppChipView.setPivotX(isRtl ? iconMenuParams.width / 2f : iconCenter);
+ iconAppChipView.setPivotY(
isRtl ? iconMenuParams.width / 2f : iconCenter - iconMenuMargin);
- iconAppChipMenuView.setTranslationY(0);
- iconAppChipMenuView.setRotation(getDegreesRotated());
+ iconAppChipView.setTranslationY(0);
+ iconAppChipView.setRotation(getDegreesRotated());
}
@Override
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
index 4359294..e3772bd 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
@@ -39,7 +39,7 @@
SET_ON_PAGE_TRANSITION_END_CALLBACK, CANCEL_CURRENT_ANIMATION, CLEANUP_SCREENSHOT,
SCROLLER_ANIMATION_ABORTED, TASK_APPEARED, EXPECTING_TASK_APPEARED,
FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER, LAUNCHER_DESTROYED, RECENT_TASKS_MISSING,
- INVALID_VELOCITY_ON_SWIPE_UP,
+ INVALID_VELOCITY_ON_SWIPE_UP, RECENTS_ANIMATION_START_PENDING,
/**
* These GestureEvents are specifically associated to state flags that get set in
@@ -273,6 +273,14 @@
case START_RECENTS_ANIMATION:
lastStartRecentAnimationEventEntryTime = eventEntry.getTime();
break;
+ case RECENTS_ANIMATION_START_PENDING:
+ errorDetected |= printErrorIfTrue(
+ true,
+ prefix,
+ /* errorMessage= */ "new gesture attempted while a requested recents"
+ + " animation is still pending.",
+ writer);
+ break;
case EXPECTING_TASK_APPEARED:
case MOTION_DOWN:
case SET_END_TARGET:
diff --git a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
index cb35ec8..bc8b571 100644
--- a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
+++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
@@ -43,7 +43,7 @@
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.AllAppsSwipeController;
-import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.views.RecentsView;
/**
@@ -200,7 +200,7 @@
public static <SCALE, TRANSLATION> PendingAnimation createRecentsResistanceAnim(
RecentsParams<SCALE, TRANSLATION> params) {
Rect startRect = new Rect();
- PagedOrientationHandler orientationHandler = params.recentsOrientedState
+ RecentsPagedOrientationHandler orientationHandler = params.recentsOrientedState
.getOrientationHandler();
params.recentsOrientedState.getActivityInterface()
.calculateTaskSize(params.context, params.dp, startRect, orientationHandler);
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index 0f3c029..839320e 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -27,6 +27,7 @@
import android.app.ActivityTaskManager;
import android.content.Context;
import android.content.Intent;
+import android.util.Log;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
@@ -58,6 +59,8 @@
* ratio.
*/
public class AppPairsController {
+ private static final String TAG = "AppPairsController";
+
// Used for encoding and decoding the "rank" attribute
private static final int BITMASK_SIZE = 16;
private static final int BITMASK_FOR_SNAP_POSITION = (1 << BITMASK_SIZE) - 1;
@@ -89,13 +92,23 @@
@PersistentSnapPosition int snapPosition = gtv.getSnapPosition();
if (!isPersistentSnapPosition(snapPosition)) {
- throw new RuntimeException("tried to save an app pair with illegal snapPosition");
+ // if we received an illegal snap position, log an error and do not create the app pair.
+ Log.wtf(TAG, "tried to save an app pair with illegal snapPosition " + snapPosition);
+ return;
}
app1.rank = encodeRank(SPLIT_POSITION_TOP_OR_LEFT, snapPosition);
app2.rank = encodeRank(SPLIT_POSITION_BOTTOM_OR_RIGHT, snapPosition);
FolderInfo newAppPair = FolderInfo.createAppPair(app1, app2);
+ if (newAppPair.contents.size() != 2) {
+ // if app pair doesn't have exactly 2 members, log an error and do not create the app
+ // pair.
+ Log.wtf(TAG,
+ "tried to save an app pair with " + newAppPair.contents.size() + " members");
+ return;
+ }
+
IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
MODEL_EXECUTOR.execute(() -> {
newAppPair.contents.forEach(member -> {
diff --git a/quickstep/src/com/android/quickstep/util/FadeOutRemoteTransition.kt b/quickstep/src/com/android/quickstep/util/FadeOutRemoteTransition.kt
index 5cce728..32a15a2 100644
--- a/quickstep/src/com/android/quickstep/util/FadeOutRemoteTransition.kt
+++ b/quickstep/src/com/android/quickstep/util/FadeOutRemoteTransition.kt
@@ -25,7 +25,7 @@
import android.window.TransitionInfo
import com.android.launcher3.anim.AnimatorListeners.forEndCallback
import com.android.launcher3.util.Executors
-import com.android.wm.shell.util.TransitionUtil
+import com.android.wm.shell.shared.TransitionUtil
/** Remote animation which fades out the closing targets */
class FadeOutRemoteTransition : IRemoteTransition.Stub() {
@@ -84,6 +84,5 @@
Executors.MAIN_EXECUTOR.execute { anim.start() }
}
- override fun onTransitionConsumed(transition: IBinder?, aborted: Boolean) {
- }
+ override fun onTransitionConsumed(transition: IBinder?, aborted: Boolean) {}
}
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index 79656c2..ec1eeb1 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -21,10 +21,10 @@
import android.view.ViewGroup;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.NavigationMode;
import com.android.quickstep.LauncherActivityInterface;
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
public class LayoutUtils {
@@ -40,7 +40,7 @@
}
public static int getShelfTrackingDistance(Context context, DeviceProfile dp,
- PagedOrientationHandler orientationHandler) {
+ RecentsPagedOrientationHandler orientationHandler) {
// Track the bottom of the window.
Rect taskSize = new Rect();
LauncherActivityInterface.INSTANCE.calculateTaskSize(context, dp, taskSize,
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index f6ad692..cba628b 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -53,6 +53,7 @@
import com.android.quickstep.BaseActivityInterface;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskAnimationManager;
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import java.lang.annotation.Retention;
import java.util.function.IntConsumer;
@@ -75,7 +76,8 @@
@IntDef({ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270})
public @interface SurfaceRotation {}
- private PagedOrientationHandler mOrientationHandler = PagedOrientationHandler.PORTRAIT;
+ private RecentsPagedOrientationHandler mOrientationHandler =
+ RecentsPagedOrientationHandler.PORTRAIT;
private @SurfaceRotation int mTouchRotation = ROTATION_0;
private @SurfaceRotation int mDisplayRotation = ROTATION_0;
@@ -225,13 +227,13 @@
private boolean updateHandler() {
mRecentsActivityRotation = inferRecentsActivityRotation(mDisplayRotation);
if (mRecentsActivityRotation == mTouchRotation || isRecentsActivityRotationAllowed()) {
- mOrientationHandler = PagedOrientationHandler.PORTRAIT;
+ mOrientationHandler = RecentsPagedOrientationHandler.PORTRAIT;
} else if (mTouchRotation == ROTATION_90) {
- mOrientationHandler = PagedOrientationHandler.LANDSCAPE;
+ mOrientationHandler = RecentsPagedOrientationHandler.LANDSCAPE;
} else if (mTouchRotation == ROTATION_270) {
- mOrientationHandler = PagedOrientationHandler.SEASCAPE;
+ mOrientationHandler = RecentsPagedOrientationHandler.SEASCAPE;
} else {
- mOrientationHandler = PagedOrientationHandler.PORTRAIT;
+ mOrientationHandler = RecentsPagedOrientationHandler.PORTRAIT;
}
if (DEBUG) {
Log.d(TAG, "current RecentsOrientedState: " + this);
@@ -413,7 +415,7 @@
return scale;
}
- public PagedOrientationHandler getOrientationHandler() {
+ public RecentsPagedOrientationHandler getOrientationHandler() {
return mOrientationHandler;
}
diff --git a/quickstep/src/com/android/quickstep/util/SlideInRemoteTransition.kt b/quickstep/src/com/android/quickstep/util/SlideInRemoteTransition.kt
index 6544ba7..9f7b46d 100644
--- a/quickstep/src/com/android/quickstep/util/SlideInRemoteTransition.kt
+++ b/quickstep/src/com/android/quickstep/util/SlideInRemoteTransition.kt
@@ -28,7 +28,7 @@
import android.window.TransitionInfo
import com.android.launcher3.anim.AnimatorListeners.forEndCallback
import com.android.launcher3.util.Executors
-import com.android.wm.shell.util.TransitionUtil
+import com.android.wm.shell.shared.TransitionUtil
/** Remote animation which slides the opening targets in and the closing targets out */
class SlideInRemoteTransition(
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 065a9c5..0bb6b23 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -40,6 +40,7 @@
import android.view.RemoteAnimationTarget;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Utilities;
@@ -344,6 +345,14 @@
* Applies the target to the previously set parameters
*/
public void apply(TransformParams params) {
+ apply(params, null);
+ }
+
+ /**
+ * Applies the target to the previously set parameters, optionally with an overridden
+ * surface transaction
+ */
+ public void apply(TransformParams params, @Nullable SurfaceTransaction surfaceTransaction) {
if (mDp == null || mThumbnailPosition.isEmpty()) {
return;
}
@@ -404,7 +413,8 @@
mTempRectF.roundOut(mTmpCropRect);
params.setProgress(1f - fullScreenProgress);
- params.applySurfaceParams(params.createSurfaceParams(this));
+ params.applySurfaceParams(surfaceTransaction == null
+ ? params.createSurfaceParams(this) : surfaceTransaction);
if (!DEBUG) {
return;
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
index fba847f..32ef904 100644
--- a/quickstep/src/com/android/quickstep/views/ClearAllButton.java
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
@@ -25,7 +25,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
public class ClearAllButton extends Button {
@@ -81,7 +81,8 @@
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
- PagedOrientationHandler orientationHandler = getRecentsView().getPagedOrientationHandler();
+ RecentsPagedOrientationHandler orientationHandler =
+ getRecentsView().getPagedOrientationHandler();
mSidePadding = orientationHandler.getClearAllSidePadding(getRecentsView(), mIsRtl);
}
@@ -131,7 +132,8 @@
return;
}
- PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler();
+ RecentsPagedOrientationHandler orientationHandler =
+ recentsView.getPagedOrientationHandler();
float orientationSize = orientationHandler.getPrimaryValue(getWidth(), getHeight());
if (orientationSize == 0) {
return;
@@ -219,7 +221,8 @@
return;
}
- PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler();
+ RecentsPagedOrientationHandler orientationHandler =
+ recentsView.getPagedOrientationHandler();
orientationHandler.getPrimaryViewTranslate().set(this,
orientationHandler.getPrimaryValue(0f, getOriginalTranslationY())
+ mNormalTranslationPrimary + getFullscreenTrans(
@@ -232,7 +235,8 @@
return;
}
- PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler();
+ RecentsPagedOrientationHandler orientationHandler =
+ recentsView.getPagedOrientationHandler();
orientationHandler.getSecondaryViewTranslate().set(this,
orientationHandler.getSecondaryValue(0f, getOriginalTranslationY()));
}
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 937a728..840382d 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -50,8 +50,8 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.systemui.shared.recents.model.Task;
import java.lang.annotation.Retention;
@@ -321,7 +321,7 @@
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
layoutParams.bottomMargin = ((ViewGroup.MarginLayoutParams)
mTaskView.getThumbnail().getLayoutParams()).bottomMargin;
- PagedOrientationHandler orientationHandler = mTaskView.getPagedOrientationHandler();
+ RecentsPagedOrientationHandler orientationHandler = mTaskView.getPagedOrientationHandler();
Pair<Float, Float> translations = orientationHandler
.getDwbLayoutTranslations(mTaskView.getMeasuredWidth(),
mTaskView.getMeasuredHeight(), mSplitBounds, deviceProfile,
diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
index efc0a35..12a073f 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
@@ -43,9 +43,9 @@
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.taskbar.TaskbarActivityContext;
-import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.views.BaseDragLayer;
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.AnimUtils;
import com.android.quickstep.util.MultiValueUpdateListener;
import com.android.quickstep.util.SplitAnimationTimings;
@@ -95,7 +95,7 @@
private final StatefulActivity mActivity;
private final boolean mIsRtl;
private final FullscreenDrawParams mFullscreenParams;
- private PagedOrientationHandler mOrientationHandler;
+ private RecentsPagedOrientationHandler mOrientationHandler;
@SplitConfigurationOptions.StagePosition
private int mStagePosition;
private final Rect mTmpRect = new Rect();
@@ -220,7 +220,7 @@
mOrientationHandler.setSecondaryScale(mSplitPlaceholderView.getIconView(), childScaleY);
}
- public void updateOrientationHandler(PagedOrientationHandler orientationHandler) {
+ public void updateOrientationHandler(RecentsPagedOrientationHandler orientationHandler) {
mOrientationHandler = orientationHandler;
mSplitPlaceholderView.getIconView().setRotation(mOrientationHandler.getDegreesRotated());
}
diff --git a/quickstep/src/com/android/quickstep/views/IconAppChipView.java b/quickstep/src/com/android/quickstep/views/IconAppChipView.java
index b064405..0a261ef 100644
--- a/quickstep/src/com/android/quickstep/views/IconAppChipView.java
+++ b/quickstep/src/com/android/quickstep/views/IconAppChipView.java
@@ -39,9 +39,9 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.views.ActivityContext;
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.RecentsOrientedState;
/**
@@ -212,7 +212,8 @@
@Override
public void setIconOrientation(RecentsOrientedState orientationState, boolean isGridTask) {
- PagedOrientationHandler orientationHandler = orientationState.getOrientationHandler();
+ RecentsPagedOrientationHandler orientationHandler =
+ orientationState.getOrientationHandler();
boolean isRtl = isLayoutRtl();
DeviceProfile deviceProfile =
ActivityContext.lookupContext(getContext()).getDeviceProfile();
diff --git a/quickstep/src/com/android/quickstep/views/IconView.java b/quickstep/src/com/android/quickstep/views/IconView.java
index a4bda7f..042f581 100644
--- a/quickstep/src/com/android/quickstep/views/IconView.java
+++ b/quickstep/src/com/android/quickstep/views/IconView.java
@@ -30,8 +30,8 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Utilities;
-import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.views.ActivityContext;
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.RecentsOrientedState;
/**
@@ -173,7 +173,8 @@
@Override
public void setIconOrientation(RecentsOrientedState orientationState, boolean isGridTask) {
- PagedOrientationHandler orientationHandler = orientationState.getOrientationHandler();
+ RecentsPagedOrientationHandler orientationHandler =
+ orientationState.getOrientationHandler();
boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
DeviceProfile deviceProfile =
ActivityContext.lookupContext(getContext()).getDeviceProfile();
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 6b3484d..d10541a 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -156,7 +156,6 @@
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.touch.OverScroll;
-import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.DynamicResource;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
@@ -187,6 +186,7 @@
import com.android.quickstep.TaskViewUtils;
import com.android.quickstep.TopTaskTracker;
import com.android.quickstep.ViewUtils;
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.ActiveGestureErrorDetector;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.AnimUtils;
@@ -542,6 +542,9 @@
private int mOverScrollShift = 0;
private long mScrollLastHapticTimestamp;
+ private int mKeyboardTaskFocusSnapAnimationDuration;
+ private int mKeyboardTaskFocusIndex = INVALID_PAGE;
+
/**
* TODO: Call reloadIdNeeded in onTaskStackChanged.
*/
@@ -788,7 +791,8 @@
mDesktopTaskViewPool = new ViewPool<>(context, this, R.layout.task_desktop,
5 /* max size */, 1 /* initial size */);
- mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
+ setOrientationHandler(mOrientationState.getOrientationHandler());
+ mIsRtl = getPagedOrientationHandler().getRecentsRtlSetting(getResources());
setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
mSplitPlaceholderSize = getResources().getDimensionPixelSize(
R.dimen.split_placeholder_size);
@@ -812,7 +816,6 @@
.getDimensionPixelSize(R.dimen.recents_empty_message_text_padding);
setWillNotDraw(false);
updateEmptyMessage();
- mOrientationHandler = mOrientationState.getOrientationHandler();
mTaskOverlayFactory = Overrides.getObject(
TaskOverlayFactory.class,
@@ -918,9 +921,9 @@
if (mAllowOverScroll && (!mEdgeGlowRight.isFinished() || !mEdgeGlowLeft.isFinished())) {
final int restoreCount = canvas.save();
- int primarySize = mOrientationHandler.getPrimaryValue(getWidth(), getHeight());
+ int primarySize = getPagedOrientationHandler().getPrimaryValue(getWidth(), getHeight());
int scroll = OverScroll.dampedScroll(getUndampedOverScrollShift(), primarySize);
- mOrientationHandler.setPrimary(canvas, CANVAS_TRANSLATE, scroll);
+ getPagedOrientationHandler().setPrimary(canvas, CANVAS_TRANSLATE, scroll);
if (mOverScrollShift != scroll) {
mOverScrollShift = scroll;
@@ -944,8 +947,8 @@
private float getUndampedOverScrollShift() {
final int width = getWidth();
final int height = getHeight();
- int primarySize = mOrientationHandler.getPrimaryValue(width, height);
- int secondarySize = mOrientationHandler.getSecondaryValue(width, height);
+ int primarySize = getPagedOrientationHandler().getPrimaryValue(width, height);
+ int secondarySize = getPagedOrientationHandler().getSecondaryValue(width, height);
float effectiveShift = 0;
if (!mEdgeGlowLeft.isFinished()) {
@@ -1270,8 +1273,8 @@
public boolean isTaskViewVisible(TaskView tv) {
if (showAsGrid()) {
- int screenStart = mOrientationHandler.getPrimaryScroll(this);
- int screenEnd = screenStart + mOrientationHandler.getMeasuredSize(this);
+ int screenStart = getPagedOrientationHandler().getPrimaryScroll(this);
+ int screenEnd = screenStart + getPagedOrientationHandler().getMeasuredSize(this);
return isTaskViewWithinBounds(tv, screenStart, screenEnd);
} else {
// For now, just check if it's the active task or an adjacent task
@@ -1281,8 +1284,8 @@
public boolean isTaskViewFullyVisible(TaskView tv) {
if (showAsGrid()) {
- int screenStart = mOrientationHandler.getPrimaryScroll(this);
- int screenEnd = screenStart + mOrientationHandler.getMeasuredSize(this);
+ int screenStart = getPagedOrientationHandler().getPrimaryScroll(this);
+ int screenEnd = screenStart + getPagedOrientationHandler().getMeasuredSize(this);
return isTaskViewFullyWithinBounds(tv, screenStart, screenEnd);
} else {
// For now, just check if it's the active task
@@ -1307,9 +1310,9 @@
private int getSnapToLastTaskScrollDiff() {
// Snap to a position where ClearAll is just invisible.
- int screenStart = mOrientationHandler.getPrimaryScroll(this);
+ int screenStart = getPagedOrientationHandler().getPrimaryScroll(this);
int clearAllScroll = getScrollForPage(indexOfChild(mClearAllButton));
- int clearAllWidth = mOrientationHandler.getPrimarySize(mClearAllButton);
+ int clearAllWidth = getPagedOrientationHandler().getPrimarySize(mClearAllButton);
int lastTaskScroll = getLastTaskScroll(clearAllScroll, clearAllWidth);
return screenStart - lastTaskScroll;
}
@@ -1320,20 +1323,20 @@
}
private boolean isTaskViewWithinBounds(TaskView tv, int start, int end) {
- int taskStart = mOrientationHandler.getChildStart(tv) + (int) tv.getOffsetAdjustment(
- showAsGrid());
- int taskSize = (int) (mOrientationHandler.getMeasuredSize(tv) * tv.getSizeAdjustment(
- showAsFullscreen()));
+ int taskStart = getPagedOrientationHandler().getChildStart(tv)
+ + (int) tv.getOffsetAdjustment(showAsGrid());
+ int taskSize = (int) (getPagedOrientationHandler().getMeasuredSize(tv)
+ * tv.getSizeAdjustment(showAsFullscreen()));
int taskEnd = taskStart + taskSize;
return (taskStart >= start && taskStart <= end) || (taskEnd >= start
&& taskEnd <= end);
}
private boolean isTaskViewFullyWithinBounds(TaskView tv, int start, int end) {
- int taskStart = mOrientationHandler.getChildStart(tv) + (int) tv.getOffsetAdjustment(
- showAsGrid());
- int taskSize = (int) (mOrientationHandler.getMeasuredSize(tv) * tv.getSizeAdjustment(
- showAsFullscreen()));
+ int taskStart = getPagedOrientationHandler().getChildStart(tv)
+ + (int) tv.getOffsetAdjustment(showAsGrid());
+ int taskSize = (int) (getPagedOrientationHandler().getMeasuredSize(tv)
+ * tv.getSizeAdjustment(showAsFullscreen()));
int taskEnd = taskStart + taskSize;
return taskStart >= start && taskEnd <= end;
}
@@ -1628,7 +1631,7 @@
return;
}
- int primaryScroll = mOrientationHandler.getPrimaryScroll(this);
+ int primaryScroll = getPagedOrientationHandler().getPrimaryScroll(this);
int currentPageScroll = getScrollForPage(mCurrentPage);
mCurrentPageScrollDiff = primaryScroll - currentPageScroll;
@@ -1704,6 +1707,7 @@
// Removing views sets the currentPage to 0, so we save this and restore it after
// the new set of views are added
int previousCurrentPage = mCurrentPage;
+ int previousFocusedPage = indexOfChild(getFocusedChild());
removeAllViews();
// If we are entering Overview as a result of initiating a split from somewhere else
@@ -1833,6 +1837,8 @@
targetPage = indexOfChild(currentTaskView);
}
}
+ } else if (previousFocusedPage != INVALID_PAGE) {
+ targetPage = previousFocusedPage;
} else {
// Set the current page to the running task, but not if settling on new task.
if (hasAnyValidTaskIds(runningTaskId)) {
@@ -2021,20 +2027,20 @@
private void updateOrientationHandler(boolean forceRecreateDragLayerControllers) {
// Handle orientation changes.
- PagedOrientationHandler oldOrientationHandler = mOrientationHandler;
- mOrientationHandler = mOrientationState.getOrientationHandler();
+ RecentsPagedOrientationHandler oldOrientationHandler = getPagedOrientationHandler();
+ setOrientationHandler(mOrientationState.getOrientationHandler());
- mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
+ mIsRtl = getPagedOrientationHandler().getRecentsRtlSetting(getResources());
setLayoutDirection(mIsRtl
? View.LAYOUT_DIRECTION_RTL
: View.LAYOUT_DIRECTION_LTR);
mClearAllButton.setLayoutDirection(mIsRtl
? View.LAYOUT_DIRECTION_LTR
: View.LAYOUT_DIRECTION_RTL);
- mClearAllButton.setRotation(mOrientationHandler.getDegreesRotated());
+ mClearAllButton.setRotation(getPagedOrientationHandler().getDegreesRotated());
if (forceRecreateDragLayerControllers
- || !mOrientationHandler.equals(oldOrientationHandler)) {
+ || !getPagedOrientationHandler().equals(oldOrientationHandler)) {
// Changed orientations, update controllers so they intercept accordingly.
mActivity.getDragLayer().recreateControllers();
onOrientationChanged();
@@ -2081,7 +2087,7 @@
mSizeStrategy.calculateGridSize(mActivity.getDeviceProfile(), mActivity,
mLastComputedGridSize);
mSizeStrategy.calculateGridTaskSize(mActivity, mActivity.getDeviceProfile(),
- mLastComputedGridTaskSize, mOrientationHandler);
+ mLastComputedGridTaskSize, getPagedOrientationHandler());
if (isDesktopModeSupported()) {
mSizeStrategy.calculateDesktopTaskSize(mActivity, mActivity.getDeviceProfile(),
mLastComputedDesktopTaskSize);
@@ -2136,7 +2142,7 @@
public void getTaskSize(Rect outRect) {
mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), outRect,
- mOrientationHandler);
+ getPagedOrientationHandler());
mLastComputedTaskSize.set(outRect);
}
@@ -2159,7 +2165,7 @@
private Rect getTaskBounds(TaskView taskView) {
int selectedPage = indexOfChild(taskView);
- int primaryScroll = mOrientationHandler.getPrimaryScroll(this);
+ int primaryScroll = getPagedOrientationHandler().getPrimaryScroll(this);
int selectedPageScroll = getScrollForPage(selectedPage);
boolean isTopRow = taskView != null && mTopRowIdSet.contains(taskView.getTaskViewId());
Rect outRect = new Rect(mLastComputedTaskSize);
@@ -2187,7 +2193,7 @@
/** Gets the task size for modal state. */
public void getModalTaskSize(Rect outRect) {
mSizeStrategy.calculateModalTaskSize(mActivity, mActivity.getDeviceProfile(), outRect,
- mOrientationHandler);
+ getPagedOrientationHandler());
}
@Override
@@ -2243,7 +2249,7 @@
if (getPageCount() == 0 || getPageAt(0).getMeasuredWidth() == 0) {
return;
}
- int scroll = mOrientationHandler.getPrimaryScroll(this);
+ int scroll = getPagedOrientationHandler().getPrimaryScroll(this);
mClearAllButton.onRecentsViewScroll(scroll, mOverviewGridEnabled);
// Clear all button alpha was set by the previous line.
@@ -2293,8 +2299,8 @@
int visibleStart = 0;
int visibleEnd = 0;
if (showAsGrid()) {
- int screenStart = mOrientationHandler.getPrimaryScroll(this);
- int pageOrientedSize = mOrientationHandler.getMeasuredSize(this);
+ int screenStart = getPagedOrientationHandler().getPrimaryScroll(this);
+ int pageOrientedSize = getPagedOrientationHandler().getMeasuredSize(this);
// For GRID_ONLY_OVERVIEW, use +/- 1 task column as visible area for preloading
// adjacent thumbnails, otherwise use +/-50% screen width
int extraWidth = enableGridOnlyOverview() ? getLastComputedTaskSize().width()
@@ -2914,7 +2920,7 @@
int focusedTaskShift = 0;
int focusedTaskWidthAndSpacing = 0;
int snappedTaskRowWidth = 0;
- int snappedPage = getNextPage();
+ int snappedPage = isKeyboardTaskFocusPending() ? mKeyboardTaskFocusIndex : getNextPage();
TaskView snappedTaskView = getTaskViewAt(snappedPage);
TaskView homeTaskView = getHomeTaskView();
TaskView nextFocusedTaskView = null;
@@ -3232,8 +3238,8 @@
clampToProgress(isOnGridBottomRow(taskView) ? ACCELERATE : FINAL_FRAME, 0, 0.5f));
FloatProperty<TaskView> secondaryViewTranslate =
taskView.getSecondaryDismissTranslationProperty();
- int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView);
- int verticalFactor = mOrientationHandler.getSecondaryTranslationDirectionFactor();
+ int secondaryTaskDimension = getPagedOrientationHandler().getSecondaryDimension(taskView);
+ int verticalFactor = getPagedOrientationHandler().getSecondaryTranslationDirectionFactor();
ResourceProvider rp = DynamicResource.provider(mActivity);
SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_START)
@@ -3248,7 +3254,7 @@
if (!mEnableDrawingLiveTile) return;
runActionOnRemoteHandles(
remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
- .taskSecondaryTranslation.value = mOrientationHandler
+ .taskSecondaryTranslation.value = getPagedOrientationHandler()
.getSecondaryValue(taskView.getTranslationX(),
taskView.getTranslationY()
));
@@ -3262,7 +3268,7 @@
* and then animates it into the split position that was desired
*/
private void createInitialSplitSelectAnimation(PendingAnimation anim) {
- mOrientationHandler.getInitialSplitPlaceholderBounds(mSplitPlaceholderSize,
+ getPagedOrientationHandler().getInitialSplitPlaceholderBounds(mSplitPlaceholderSize,
mSplitPlaceholderInset, mActivity.getDeviceProfile(),
mSplitSelectStateController.getActiveSplitStagePosition(), mTempRect);
SplitAnimationTimings timings =
@@ -3491,7 +3497,7 @@
// beyond that, we'll need to snap to last task instead.
TaskView lastTask = getLastGridTaskView();
if (lastTask != null) {
- int primaryScroll = mOrientationHandler.getPrimaryScroll(this);
+ int primaryScroll = getPagedOrientationHandler().getPrimaryScroll(this);
int lastTaskScroll = getScrollForPage(indexOfChild(lastTask));
if ((mIsRtl && primaryScroll < lastTaskScroll)
|| (!mIsRtl && primaryScroll > lastTaskScroll)) {
@@ -3597,7 +3603,7 @@
if (scrollDiff != 0) {
FloatProperty translationProperty = child instanceof TaskView
? ((TaskView) child).getPrimaryDismissTranslationProperty()
- : mOrientationHandler.getPrimaryViewTranslate();
+ : getPagedOrientationHandler().getPrimaryViewTranslate();
float additionalDismissDuration =
ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET * Math.abs(
@@ -3633,7 +3639,7 @@
remoteTargetHandle ->
remoteTargetHandle.getTaskViewSimulator()
.taskPrimaryTranslation.value =
- mOrientationHandler.getPrimaryValue(
+ getPagedOrientationHandler().getPrimaryValue(
child.getTranslationX(),
child.getTranslationY()
));
@@ -3709,7 +3715,7 @@
if (isStagingFocusedTask) {
// Moves less if focused task is not in scroll position.
int focusedTaskScroll = getScrollForPage(dismissedIndex);
- int primaryScroll = mOrientationHandler.getPrimaryScroll(this);
+ int primaryScroll = getPagedOrientationHandler().getPrimaryScroll(this);
int focusedTaskScrollDiff = primaryScroll - focusedTaskScroll;
primaryTranslation +=
mIsRtl ? focusedTaskScrollDiff : -focusedTaskScrollDiff;
@@ -3837,7 +3843,7 @@
}
if (calculateScrollDiff) {
- int primaryScroll = mOrientationHandler.getPrimaryScroll(
+ int primaryScroll = getPagedOrientationHandler().getPrimaryScroll(
RecentsView.this);
int currentPageScroll = getScrollForPage(mCurrentPage);
mCurrentPageScrollDiff = primaryScroll - currentPageScroll;
@@ -3878,9 +3884,9 @@
TaskView taskView = requireTaskViewAt(highestVisibleTaskIndex);
boolean shouldRebalance;
- int screenStart = mOrientationHandler.getPrimaryScroll(
+ int screenStart = getPagedOrientationHandler().getPrimaryScroll(
RecentsView.this);
- int taskStart = mOrientationHandler.getChildStart(taskView)
+ int taskStart = getPagedOrientationHandler().getChildStart(taskView)
+ (int) taskView.getOffsetAdjustment(/*gridEnabled=*/ true);
// Rebalance only if there is a maximum gap between the task and the
@@ -3889,10 +3895,11 @@
if (mIsRtl) {
shouldRebalance = taskStart <= screenStart + mPageSpacing;
} else {
- int screenEnd =
- screenStart + mOrientationHandler.getMeasuredSize(
- RecentsView.this);
- int taskSize = (int) (mOrientationHandler.getMeasuredSize(
+ int screenEnd = screenStart
+ + getPagedOrientationHandler().getMeasuredSize(
+ RecentsView.this);
+ int taskSize = (int) (
+ getPagedOrientationHandler().getMeasuredSize(
taskView) * taskView
.getSizeAdjustment(/*fullscreenEnabled=*/false));
int taskEnd = taskStart + taskSize;
@@ -4287,8 +4294,8 @@
return mOrientationState;
}
- public PagedOrientationHandler getPagedOrientationHandler() {
- return mOrientationHandler;
+ public RecentsPagedOrientationHandler getPagedOrientationHandler() {
+ return (RecentsPagedOrientationHandler) super.getPagedOrientationHandler();
}
@Nullable
@@ -4466,7 +4473,7 @@
View child = getChildAt(i);
FloatProperty translationPropertyX = child instanceof TaskView
? ((TaskView) child).getPrimaryTaskOffsetTranslationProperty()
- : mOrientationHandler.getPrimaryViewTranslate();
+ : getPagedOrientationHandler().getPrimaryViewTranslate();
translationPropertyX.set(child, totalTranslationX);
if (mEnableDrawingLiveTile && i == getRunningTaskIndex()) {
runActionOnRemoteHandles(
@@ -4504,8 +4511,8 @@
mIsRtl ? outRect.right : outRect.left, outRect.top);
mTempMatrix.mapRect(outRect);
}
- outRect.offset(mOrientationHandler.getPrimaryValue(-midPointScroll, 0),
- mOrientationHandler.getSecondaryValue(-midPointScroll, 0));
+ outRect.offset(getPagedOrientationHandler().getPrimaryValue(-midPointScroll, 0),
+ getPagedOrientationHandler().getSecondaryValue(-midPointScroll, 0));
}
/**
@@ -4530,14 +4537,15 @@
// to reach offscreen. Offset the task position to the task's starting point, and offset
// by current page's scroll diff.
int midpointScroll = getScrollForPage(midpointIndex)
- + mOrientationHandler.getPrimaryScroll(this) - getScrollForPage(mCurrentPage);
+ + getPagedOrientationHandler().getPrimaryScroll(this)
+ - getScrollForPage(mCurrentPage);
getPersistentChildPosition(midpointIndex, midpointScroll, taskPosition);
- float midpointStart = mOrientationHandler.getStart(taskPosition);
+ float midpointStart = getPagedOrientationHandler().getStart(taskPosition);
getPersistentChildPosition(childIndex, midpointScroll, taskPosition);
// Assume child does not overlap with midPointChild.
- isStartShift = mOrientationHandler.getStart(taskPosition) < midpointStart;
+ isStartShift = getPagedOrientationHandler().getStart(taskPosition) < midpointStart;
} else {
// Position the task at scroll position.
getPersistentChildPosition(childIndex, getScrollForPage(childIndex), taskPosition);
@@ -4551,27 +4559,29 @@
// desired position, and adjust the computed distance accordingly.
float distanceToOffscreen;
if (isStartShift) {
- float desiredStart = -mOrientationHandler.getPrimarySize(taskPosition);
- distanceToOffscreen = -mOrientationHandler.getEnd(taskPosition);
+ float desiredStart = -getPagedOrientationHandler().getPrimarySize(taskPosition);
+ distanceToOffscreen = -getPagedOrientationHandler().getEnd(taskPosition);
if (mLastComputedTaskStartPushOutDistance == null) {
taskPosition.offsetTo(
- mOrientationHandler.getPrimaryValue(desiredStart, 0f),
- mOrientationHandler.getSecondaryValue(desiredStart, 0f));
+ getPagedOrientationHandler().getPrimaryValue(desiredStart, 0f),
+ getPagedOrientationHandler().getSecondaryValue(desiredStart, 0f));
getMatrix().mapRect(taskPosition);
- mLastComputedTaskStartPushOutDistance = mOrientationHandler.getEnd(taskPosition)
- / mOrientationHandler.getPrimaryScale(this);
+ mLastComputedTaskStartPushOutDistance = getPagedOrientationHandler().getEnd(
+ taskPosition) / getPagedOrientationHandler().getPrimaryScale(this);
}
distanceToOffscreen -= mLastComputedTaskStartPushOutDistance;
} else {
- float desiredStart = mOrientationHandler.getPrimarySize(this);
- distanceToOffscreen = desiredStart - mOrientationHandler.getStart(taskPosition);
+ float desiredStart = getPagedOrientationHandler().getPrimarySize(this);
+ distanceToOffscreen = desiredStart - getPagedOrientationHandler().getStart(
+ taskPosition);
if (mLastComputedTaskEndPushOutDistance == null) {
taskPosition.offsetTo(
- mOrientationHandler.getPrimaryValue(desiredStart, 0f),
- mOrientationHandler.getSecondaryValue(desiredStart, 0f));
+ getPagedOrientationHandler().getPrimaryValue(desiredStart, 0f),
+ getPagedOrientationHandler().getSecondaryValue(desiredStart, 0f));
getMatrix().mapRect(taskPosition);
- mLastComputedTaskEndPushOutDistance = (mOrientationHandler.getStart(taskPosition)
- - desiredStart) / mOrientationHandler.getPrimaryScale(this);
+ mLastComputedTaskEndPushOutDistance = (getPagedOrientationHandler().getStart(
+ taskPosition) - desiredStart)
+ / getPagedOrientationHandler().getPrimaryScale(this);
}
distanceToOffscreen -= mLastComputedTaskEndPushOutDistance;
}
@@ -4660,7 +4670,7 @@
* of split invocation as such.
*/
public void initiateSplitSelect(TaskView taskView) {
- int defaultSplitPosition = mOrientationHandler
+ int defaultSplitPosition = getPagedOrientationHandler()
.getDefaultSplitPosition(mActivity.getDeviceProfile());
initiateSplitSelect(taskView, defaultSplitPosition, LAUNCHER_OVERVIEW_ACTIONS_SPLIT);
}
@@ -4803,7 +4813,7 @@
int halfDividerSize = getResources()
.getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
- mOrientationHandler.getFinalSplitPlaceholderBounds(halfDividerSize,
+ getPagedOrientationHandler().getFinalSplitPlaceholderBounds(halfDividerSize,
mActivity.getDeviceProfile(),
mSplitSelectStateController.getActiveSplitStagePosition(), firstTaskEndingBounds,
secondTaskEndingBounds);
@@ -4920,7 +4930,7 @@
*/
public float getSplitSelectTranslation() {
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
- PagedOrientationHandler orientationHandler = getPagedOrientationHandler();
+ RecentsPagedOrientationHandler orientationHandler = getPagedOrientationHandler();
int splitPosition = getSplitSelectController().getActiveSplitStagePosition();
int splitPlaceholderSize =
mActivity.getResources().getDimensionPixelSize(R.dimen.split_placeholder_size);
@@ -4945,16 +4955,16 @@
}
protected void onRotateInSplitSelectionState() {
- mOrientationHandler.getInitialSplitPlaceholderBounds(mSplitPlaceholderSize,
+ getPagedOrientationHandler().getInitialSplitPlaceholderBounds(mSplitPlaceholderSize,
mSplitPlaceholderInset, mActivity.getDeviceProfile(),
mSplitSelectStateController.getActiveSplitStagePosition(), mTempRect);
mTempRectF.set(mTempRect);
FloatingTaskView firstFloatingTaskView =
mSplitSelectStateController.getFirstFloatingTaskView();
- firstFloatingTaskView.updateOrientationHandler(mOrientationHandler);
+ firstFloatingTaskView.updateOrientationHandler(getPagedOrientationHandler());
firstFloatingTaskView.update(mTempRectF, /*progress=*/1f);
- PagedOrientationHandler orientationHandler = getPagedOrientationHandler();
+ RecentsPagedOrientationHandler orientationHandler = getPagedOrientationHandler();
Pair<FloatProperty, FloatProperty> taskViewsFloat =
orientationHandler.getSplitSelectTaskOffset(
TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
@@ -5059,7 +5069,7 @@
float displacementX = tv.getWidth() * (toScale - 1f);
float primaryTranslation = mIsRtl ? -displacementX : displacementX;
anim.play(ObjectAnimator.ofFloat(getPageAt(centerTaskIndex),
- mOrientationHandler.getPrimaryViewTranslate(), primaryTranslation));
+ getPagedOrientationHandler().getPrimaryViewTranslate(), primaryTranslation));
int runningTaskIndex = getRunningTaskIndex();
if (runningTaskIndex != -1 && runningTaskIndex != taskIndex
&& getRemoteTargetHandles() != null) {
@@ -5075,7 +5085,7 @@
if (otherAdjacentTaskIndex >= 0 && otherAdjacentTaskIndex < getPageCount()) {
PropertyValuesHolder[] properties = new PropertyValuesHolder[3];
properties[0] = PropertyValuesHolder.ofFloat(
- mOrientationHandler.getPrimaryViewTranslate(), primaryTranslation);
+ getPagedOrientationHandler().getPrimaryViewTranslate(), primaryTranslation);
properties[1] = PropertyValuesHolder.ofFloat(View.SCALE_X, 1);
properties[2] = PropertyValuesHolder.ofFloat(View.SCALE_Y, 1);
@@ -5495,8 +5505,8 @@
// Align ClearAllButton to the left (RTL) or right (non-RTL), which is different from other
// TaskViews. This must be called after laying out ClearAllButton.
if (layoutChildren) {
- int clearAllWidthDiff = mOrientationHandler.getPrimaryValue(mTaskWidth, mTaskHeight)
- - mOrientationHandler.getPrimarySize(mClearAllButton);
+ int clearAllWidthDiff = getPagedOrientationHandler().getPrimaryValue(mTaskWidth,
+ mTaskHeight) - getPagedOrientationHandler().getPrimarySize(mClearAllButton);
mClearAllButton.setScrollOffsetPrimary(mIsRtl ? clearAllWidthDiff : -clearAllWidthDiff);
}
@@ -5504,7 +5514,7 @@
int clearAllIndex = indexOfChild(mClearAllButton);
int clearAllScroll = 0;
- int clearAllWidth = mOrientationHandler.getPrimarySize(mClearAllButton);
+ int clearAllWidth = getPagedOrientationHandler().getPrimarySize(mClearAllButton);
if (clearAllIndex != -1 && clearAllIndex < outPageScrolls.length) {
float scrollDiff = mClearAllButton.getScrollAdjustment(showAsFullscreen, showAsGrid);
clearAllScroll = newPageScrolls[clearAllIndex] + (int) scrollDiff;
@@ -5573,6 +5583,19 @@
}
/**
+ * Returns how many pixels the running task is offset on the currently laid out dominant axis
+ * specifically during a Keyboard task focus.
+ */
+ public int getScrollOffsetForKeyboardTaskFocus() {
+ if (!isKeyboardTaskFocusPending()) {
+ return getScrollOffset(getRunningTaskIndex());
+ }
+ return getPagedOrientationHandler().getPrimaryScroll(this)
+ - getScrollForPage(mKeyboardTaskFocusIndex)
+ + getScrollOffset(getRunningTaskIndex());
+ }
+
+ /**
* Sets whether or not we should clamp the scroll offset.
* This is used to avoid x-axis movement when swiping up transient taskbar.
* Should only be set at the beginning and end of the gesture, otherwise a jump may occur.
@@ -5605,15 +5628,15 @@
if (pageIndex == -1) {
return 0;
}
-
- int overScrollShift = getOverScrollShift();
- if (mAdjacentPageHorizontalOffset > 0) {
- // Don't dampen the scroll (due to overscroll) if the adjacent tasks are offscreen, so
- // that the page can move freely given there's no visual indication why it shouldn't.
- overScrollShift = (int) Utilities.mapRange(mAdjacentPageHorizontalOffset,
- overScrollShift, getUndampedOverScrollShift());
- }
- return getScrollForPage(pageIndex) - mOrientationHandler.getPrimaryScroll(this)
+ // Don't dampen the scroll (due to overscroll) if the adjacent tasks are offscreen, so that
+ // the page can move freely given there's no visual indication why it shouldn't.
+ int overScrollShift = mAdjacentPageHorizontalOffset > 0
+ ? (int) Utilities.mapRange(
+ mAdjacentPageHorizontalOffset,
+ getOverScrollShift(),
+ getUndampedOverScrollShift())
+ : getOverScrollShift();
+ return getScrollForPage(pageIndex) - getPagedOrientationHandler().getPrimaryScroll(this)
+ overScrollShift + getOffsetFromScrollPosition(pageIndex);
}
@@ -5682,7 +5705,7 @@
public Consumer<MotionEvent> getEventDispatcher(float navbarRotation) {
float degreesRotated;
if (navbarRotation == 0) {
- degreesRotated = mOrientationHandler.getDegreesRotated();
+ degreesRotated = getPagedOrientationHandler().getDegreesRotated();
} else {
degreesRotated = -navbarRotation;
}
@@ -6021,11 +6044,50 @@
dispatchScrollChanged();
}
+ /**
+ * Prepares this RecentsView to scroll properly for an upcoming child view focus request from
+ * keyboard quick switching
+ */
+ public void setKeyboardTaskFocusIndex(int taskIndex) {
+ mKeyboardTaskFocusIndex = taskIndex;
+ }
+
+ /** Returns whether this RecentsView will be scrolling to a child view for a focus request */
+ public boolean isKeyboardTaskFocusPending() {
+ return mKeyboardTaskFocusIndex != INVALID_PAGE;
+ }
+
+ private boolean isKeyboardTaskFocusPendingForChild(View child) {
+ return isKeyboardTaskFocusPending() && mKeyboardTaskFocusIndex == indexOfChild(child);
+ }
+
@Override
- protected boolean shouldHandleRequestChildFocus() {
- // If we are already scrolling to a task view, then the focus request has already been
- // handled
- return mScroller.isFinished();
+ protected int getSnapAnimationDuration() {
+ return isKeyboardTaskFocusPending()
+ ? mKeyboardTaskFocusSnapAnimationDuration : super.getSnapAnimationDuration();
+ }
+
+ @Override
+ protected void onVelocityValuesUpdated() {
+ super.onVelocityValuesUpdated();
+ mKeyboardTaskFocusSnapAnimationDuration =
+ getResources().getInteger(R.integer.config_keyboardTaskFocusSnapAnimationDuration);
+ }
+
+ @Override
+ protected boolean shouldHandleRequestChildFocus(View child) {
+ // If we are already scrolling to a task view and we aren't focusing to this child from
+ // keyboard quick switch, then the focus request has already been handled
+ return mScroller.isFinished() || isKeyboardTaskFocusPendingForChild(child);
+ }
+
+ @Override
+ public void requestChildFocus(View child, View focused) {
+ if (isKeyboardTaskFocusPendingForChild(child)) {
+ updateGridProperties();
+ updateScrollSynchronously();
+ }
+ super.requestChildFocus(child, focused);
}
private void dispatchScrollChanged() {
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 3a4056a..cf50835 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -49,10 +49,10 @@
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.popup.SystemShortcut;
-import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskUtils;
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.TaskCornerRadius;
import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
@@ -218,7 +218,8 @@
private void orientAroundTaskView(TaskIdAttributeContainer taskContainer) {
RecentsView recentsView = mActivity.getOverviewPanel();
- PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler();
+ RecentsPagedOrientationHandler orientationHandler =
+ recentsView.getPagedOrientationHandler();
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
// Get Position
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index dff0580..077247b 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -51,11 +51,11 @@
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Utilities;
-import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.SystemUiController.SystemUiControllerFlags;
import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.views.TaskView.FullscreenDrawParams;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -513,7 +513,7 @@
return false;
}
- if (recents.getPagedOrientationHandler() == PagedOrientationHandler.PORTRAIT) {
+ if (recents.getPagedOrientationHandler() == RecentsPagedOrientationHandler.PORTRAIT) {
int currentRotation = recents.getPagedViewOrientedState().getRecentsActivityRotation();
return (currentRotation - mThumbnailData.rotation) % 2 != 0;
} else {
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 65020bc..b971f67 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -87,7 +87,6 @@
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.ActivityOptionsWrapper;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.RunnableList;
@@ -104,6 +103,7 @@
import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.TaskViewUtils;
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.BorderAnimator;
import com.android.quickstep.util.CancellableTask;
@@ -116,6 +116,8 @@
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
+import kotlin.Unit;
+
import java.lang.annotation.Retention;
import java.util.Arrays;
import java.util.Collections;
@@ -124,8 +126,6 @@
import java.util.function.Consumer;
import java.util.stream.Stream;
-import kotlin.Unit;
-
/**
* A task in the Recents view.
*/
@@ -1659,7 +1659,7 @@
return (RecentsView) getParent();
}
- PagedOrientationHandler getPagedOrientationHandler() {
+ RecentsPagedOrientationHandler getPagedOrientationHandler() {
return getRecentsView().mOrientationState.getOrientationHandler();
}
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
index 9c7fdc6..87cbdd1 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
@@ -9,6 +9,7 @@
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
+import android.widget.Space
import androidx.test.runner.AndroidJUnit4
import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
@@ -38,6 +39,7 @@
private val mockImeSwitcher: ImageView = mock()
private val mockRotationButton: RotationButton = mock()
private val mockA11yButton: ImageView = mock()
+ private val mockSpace: Space = mock()
private var surfaceRotation = Surface.ROTATION_0
@@ -201,7 +203,8 @@
surfaceRotation = surfaceRotation,
imeSwitcher = mockImeSwitcher,
rotationButton = mockRotationButton,
- a11yButton = mockA11yButton
+ a11yButton = mockA11yButton,
+ space = mockSpace,
)
}
}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java b/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java
index 7191f70..36c591e 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java
@@ -22,6 +22,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.tapl.KeyboardQuickSwitch;
+import com.android.launcher3.taskbar.KeyboardQuickSwitchController;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import org.junit.Assume;
@@ -49,7 +50,7 @@
DISMISS(0),
LAUNCH_LAST_APP(0),
LAUNCH_SELECTED_APP(1),
- LAUNCH_OVERVIEW(5);
+ LAUNCH_OVERVIEW(KeyboardQuickSwitchController.MAX_TASKS - 1);
private final int mNumAdditionalRunningTasks;
@@ -196,7 +197,9 @@
if (!testSurface.mInitialFocusAtZero) {
kqs.moveFocusBackward();
}
- kqs.launchFocusedOverviewTask();
+ kqs.launchFocusedOverviewTask()
+ // Check that the correct task was focused
+ .launchFocusedTaskByEnterKey(CALCULATOR_APP_PACKAGE);
break;
default:
throw new IllegalStateException("Cannot run test case: " + testCase);
diff --git a/res/drawable/ic_encrypted_with_background.xml b/res/drawable/ic_encrypted_with_background.xml
index 155e2be..c439b55 100644
--- a/res/drawable/ic_encrypted_with_background.xml
+++ b/res/drawable/ic_encrypted_with_background.xml
@@ -19,7 +19,7 @@
android:height="48dp">
<path
android:pathData="M48 24A24 24 0 0 1 0 24A24 24 0 0 1 48 24Z"
- android:fillColor="?attr/materialColorSurfaceBright" />
+ android:fillColor="?attr/materialColorOutlineVariant" />
<path
android:pathData="M24.0002 10.667L13.3335 14.667V22.787C13.3335 29.5203 17.8802 35.8003 24.0002 37.3337C30.1202 35.8003 34.6668 29.5203 34.6668 22.787V14.667L24.0002 10.667ZM32.0002 22.787C32.0002 28.1203 28.6002 33.0537 24.0002 34.5603C19.4002 33.0537 16.0002 28.1337 16.0002 22.787V16.5203L24.0002 13.5203L32.0002 16.5203V22.787ZM25.2402 23.6937L26.0002 28.0003H22.0002L22.7602 23.6937C21.9068 23.2537 21.3335 22.3603 21.3335 21.3337C21.3335 19.867 22.5335 18.667 24.0002 18.667C25.4668 18.667 26.6668 19.867 26.6668 21.3337C26.6668 22.3603 26.0935 23.2537 25.2402 23.6937Z"
android:fillColor="?attr/materialColorOnSurface" />
diff --git a/res/layout/widgets_two_pane_sheet_paged_view.xml b/res/layout/widgets_two_pane_sheet_paged_view.xml
index 442957a..4a7749b 100644
--- a/res/layout/widgets_two_pane_sheet_paged_view.xml
+++ b/res/layout/widgets_two_pane_sheet_paged_view.xml
@@ -66,7 +66,7 @@
<include layout="@layout/widgets_search_bar" />
</FrameLayout>
- <LinearLayout
+ <FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/suggestions_header"
@@ -74,7 +74,7 @@
android:orientation="horizontal"
android:background="?attr/widgetPickerPrimarySurfaceColor"
launcher:layout_sticky="true">
- </LinearLayout>
+ </FrameLayout>
<com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip
android:id="@+id/tabs"
diff --git a/res/values-sw600dp/config.xml b/res/values-sw600dp/config.xml
index e718d9c..bddfcfc 100644
--- a/res/values-sw600dp/config.xml
+++ b/res/values-sw600dp/config.xml
@@ -18,6 +18,9 @@
<!-- The duration of the PagedView page snap animation -->
<integer name="config_pageSnapAnimationDuration">550</integer>
+ <!-- The duration of the PagedView page snap animation -->
+ <integer name="config_keyboardTaskFocusSnapAnimationDuration">400</integer>
+
<!-- The duration of the Widget picker opening and closing animation -->
<integer name="config_bottomSheetOpenDuration">500</integer>
<integer name="config_bottomSheetCloseDuration">500</integer>
diff --git a/res/values/config.xml b/res/values/config.xml
index 5bdd7ebb..1b74238 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -43,6 +43,9 @@
<!-- The duration of the PagedView page snap animation -->
<integer name="config_pageSnapAnimationDuration">750</integer>
+ <!-- The duration of the PagedView page snap animation -->
+ <integer name="config_keyboardTaskFocusSnapAnimationDuration">750</integer>
+
<!-- View tag key used to store SpringAnimation data. -->
<item type="id" name="spring_animation_tag" />
@@ -194,6 +197,11 @@
<string-array name="filtered_components" ></string-array>
+ <!-- Widget component names to be included in weather category of widget suggestions. -->
+ <string-array name="weather_recommendations"></string-array>
+ <!-- Widget component names to be included in fitness category of widget suggestions. -->
+ <string-array name="fitness_recommendations"></string-array>
+
<!-- Name of the class used to generate colors from the wallpaper colors. Must be implementing the LauncherAppWidgetHostView.ColorGenerator interface. -->
<string name="color_generator_class" translatable="false"/>
@@ -252,6 +260,9 @@
<!-- Used for custom widgets -->
<array name="custom_widget_providers"/>
+ <!-- Used for determining category of a widget presented in widget recommendations. -->
+ <string name="widget_recommendation_category_provider_class" translatable="false"></string>
+
<!-- Embed parameters -->
<dimen name="activity_split_ratio" format="float">0.5</dimen>
<integer name="min_width_split">720</integer>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 3016559..7312164 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -56,7 +56,7 @@
<!-- App Widget resize frame -->
<dimen name="widget_handle_margin">13dp</dimen>
<dimen name="resize_frame_background_padding">24dp</dimen>
- <dimen name="resize_frame_margin">22dp</dimen>
+ <dimen name="resize_frame_margin">23dp</dimen>
<dimen name="resize_frame_invalid_drag_across_two_panel_opacity_margin">24dp</dimen>
<!-- App widget reconfigure button -->
diff --git a/res/values/id.xml b/res/values/id.xml
index 6156c91..198496f 100644
--- a/res/values/id.xml
+++ b/res/values/id.xml
@@ -32,6 +32,7 @@
<item type="id" name="ime_switcher" />
<item type="id" name="accessibility_button" />
<item type="id" name="rotate_suggestion" />
+ <item type="id" name="space" />
<!-- /Do not change, must be kept in sync with sysui navbar button IDs for tests! -->
<item type="id" name="quick_settings_button" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index b9fb024..5cc4616 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -71,6 +71,12 @@
<!-- Widget suggestions header title in the full widgets picker for large screen devices
in landscape mode. [CHAR_LIMIT=50] -->
<string name="suggested_widgets_header_title">Suggestions</string>
+ <string name="productivity_widget_recommendation_category_label">Boost your day</string>
+ <string name="news_widget_recommendation_category_label">News For You</string>
+ <string name="social_and_entertainment_widget_recommendation_category_label">Your Chill Zone</string>
+ <string name="fitness_widget_recommendation_category_label">Reach Your Fitness Goals</string>
+ <string name="weather_widget_recommendation_category_label">Stay Ahead of the Weather</string>
+ <string name="others_widget_recommendation_category_label">You Might Also Like</string>
<!-- Label for showing the number of widgets an app has in the full widgets picker.
[CHAR_LIMIT=25][ICU SYNTAX] -->
<string name="widgets_count">
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index ba65bea..baa1ee3 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -19,7 +19,6 @@
import static android.text.Layout.Alignment.ALIGN_NORMAL;
import static com.android.launcher3.Flags.enableCursorHoverStates;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_ICON_LABEL_AUTO_SCALING;
import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
import static com.android.launcher3.icons.BitmapInfo.FLAG_NO_BADGE;
import static com.android.launcher3.icons.BitmapInfo.FLAG_SKIP_USER_BADGE;
@@ -556,9 +555,6 @@
}
private void checkForEllipsis() {
- if (!ENABLE_ICON_LABEL_AUTO_SCALING.get()) {
- return;
- }
float width = getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight();
if (width <= 0) {
return;
@@ -830,7 +826,6 @@
float currentWordWidth, runningWidth = 0;
CharSequence currentWord;
StringBuilder newString = new StringBuilder();
- // TODO: Remove when ENABLE_ICON_LABEL_AUTO_SCALING feature flag is being cleaned up.
paint.setLetterSpacing(MIN_LETTER_SPACING);
int stringPtr = 0;
for (int i = 0; i < breakPoints.size()+1; i++) {
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 1b4edf8..941a793 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -16,7 +16,6 @@
package com.android.launcher3;
-import static com.android.launcher3.LauncherState.EDIT_MODE;
import static com.android.launcher3.dragndrop.DraggableView.DRAGGABLE_ICON;
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_REORDER_PREVIEW_OFFSET;
@@ -214,6 +213,7 @@
// Related to accessible drag and drop
DragAndDropAccessibilityDelegate mTouchHelper;
+ CellLayoutContainer mCellLayoutContainer;
public static final FloatProperty<CellLayout> SPRING_LOADED_PROGRESS =
new FloatProperty<CellLayout>("spring_loaded_progress") {
@@ -228,8 +228,9 @@
}
};
- public CellLayout(Context context) {
- this(context, null);
+ public CellLayout(Context context, CellLayoutContainer container) {
+ this(context, (AttributeSet) null);
+ this.mCellLayoutContainer = container;
}
public CellLayout(Context context, AttributeSet attrs) {
@@ -316,6 +317,14 @@
addView(mShortcutsAndWidgets);
}
+ public CellLayoutContainer getCellLayoutContainer() {
+ return mCellLayoutContainer;
+ }
+
+ public void setCellLayoutContainer(CellLayoutContainer cellLayoutContainer) {
+ mCellLayoutContainer = cellLayoutContainer;
+ }
+
/**
* Sets or clears a delegate used for accessible drag and drop
*/
@@ -573,9 +582,7 @@
}
protected void updateBgAlpha() {
- if (!getWorkspace().mLauncher.isInState(EDIT_MODE)) {
- mBackground.setAlpha((int) (mSpringLoadedProgress * 255));
- }
+ mBackground.setAlpha((int) (mSpringLoadedProgress * 255));
}
/**
@@ -1187,7 +1194,7 @@
// Apply local extracted color if the DragView is an AppWidgetHostViewDrawable.
View view = dragObject.dragView.getContentView();
if (view instanceof LauncherAppWidgetHostView) {
- int screenId = getWorkspace().getIdForScreen(this);
+ int screenId = mCellLayoutContainer.getCellLayoutId(this);
cellToRect(targetCell[0], targetCell[1], spanX, spanY, mTempRect);
((LauncherAppWidgetHostView) view).handleDrag(mTempRect, this, screenId);
@@ -1200,25 +1207,19 @@
return getContext().getString(R.string.move_to_hotseat_position,
Math.max(cellX, cellY) + 1);
} else {
- Workspace<?> workspace = getWorkspace();
int row = cellY + 1;
- int col = workspace.mIsRtl ? mCountX - cellX : cellX + 1;
- int panelCount = workspace.getPanelCount();
- int screenId = workspace.getIdForScreen(this);
- int pageIndex = workspace.getPageIndexForScreenId(screenId);
+ int col = Utilities.isRtl(getResources()) ? mCountX - cellX : cellX + 1;
+ int panelCount = mCellLayoutContainer.getPanelCount();
+ int pageIndex = mCellLayoutContainer.getCellLayoutIndex(this);
if (panelCount > 1) {
// Increment the column if the target is on the right side of a two panel home
col += (pageIndex % panelCount) * mCountX;
}
return getContext().getString(R.string.move_to_empty_cell_description, row, col,
- workspace.getPageDescription(pageIndex));
+ mCellLayoutContainer.getPageDescription(pageIndex));
}
}
- private Workspace<?> getWorkspace() {
- return Launcher.cast(mActivity).getWorkspace();
- }
-
public void clearDragOutlines() {
final int oldIndex = mDragOutlineCurrent;
mDragOutlineAnims[oldIndex].animateOut();
@@ -1452,7 +1453,7 @@
private void commitTempPlacement(View dragView) {
mTmpOccupied.copyTo(mOccupied);
- int screenId = getWorkspace().getIdForScreen(this);
+ int screenId = mCellLayoutContainer.getCellLayoutId(this);
int container = Favorites.CONTAINER_DESKTOP;
if (mContainerType == HOTSEAT) {
diff --git a/src/com/android/launcher3/CellLayoutContainer.java b/src/com/android/launcher3/CellLayoutContainer.java
new file mode 100644
index 0000000..9ee0f70
--- /dev/null
+++ b/src/com/android/launcher3/CellLayoutContainer.java
@@ -0,0 +1,44 @@
+/*
+ * 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;
+
+/**
+ * This interface should be implemented for any container/view that has a CellLayout as a children.
+ */
+public interface CellLayoutContainer {
+
+ /**
+ * Get the CellLayoutId for the given cellLayout.
+ */
+ int getCellLayoutId(CellLayout cellLayout);
+
+ /**
+ * Get the index of the given CellLayout out of all the other CellLayouts.
+ */
+ int getCellLayoutIndex(CellLayout cellLayout);
+
+ /**
+ * The total number of CellLayouts in the container.
+ */
+ int getPanelCount();
+
+ /**
+ * Used for accessibility, it returns the string that the assistant is going to say when
+ * referring to the given CellLayout.
+ */
+ String getPageDescription(int pageIndex);
+}
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 3b12b86..37737d8 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -207,6 +207,7 @@
public void setWorkspace(Workspace<?> w) {
mWorkspace = w;
+ setCellLayoutContainer(w);
}
@Override
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 44a1bf0..496cb4e 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -99,7 +99,6 @@
import static com.android.launcher3.util.SettingsCache.TOUCHPAD_NATURAL_SCROLLING;
import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
@@ -132,6 +131,7 @@
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.Log;
+import android.util.Pair;
import android.util.SparseArray;
import android.view.KeyEvent;
import android.view.KeyboardShortcutGroup;
@@ -264,7 +264,6 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -522,7 +521,7 @@
mAppWidgetHolder.startListening();
mAppWidgetHolder.addProviderChangeListener(() -> refreshAndBindWidgetsForPackageUser(null));
mItemInflater = new ItemInflater<>(this, mAppWidgetHolder, getItemOnClickListener(),
- mFocusHandler, new CellLayout(mWorkspace.getContext()));
+ mFocusHandler, new CellLayout(mWorkspace.getContext(), mWorkspace));
mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
@@ -2125,30 +2124,23 @@
*/
@Override
public void bindItems(final List<ItemInfo> items, final boolean forceAnimateIcons) {
- bindItems(items, forceAnimateIcons, /* focusFirstItemForAccessibility= */ false);
+ bindItems(items.stream().map(i -> Pair.create(
+ i, getItemInflater().inflateItem(i, getModelWriter()))).toList(),
+ forceAnimateIcons ? new AnimatorSet() : null);
}
-
/**
- * Bind the items start-end from the list.
+ * Bind all the items in the map, ignoring any null views
*
- * Implementation of the method from LauncherModel.Callbacks.
- *
- * @param focusFirstItemForAccessibility true iff the first item to be added to the workspace
- * should be focused for accessibility.
+ * @param boundAnim if non-null, uses it to create and play the bounce animation for added views
*/
- public void bindItems(
- final List<ItemInfo> items,
- final boolean forceAnimateIcons,
- final boolean focusFirstItemForAccessibility) {
+ public void bindItems(List<Pair<ItemInfo, View>> shortcuts, @Nullable AnimatorSet boundAnim) {
// Get the list of added items and intersect them with the set of items here
- final Collection<Animator> bounceAnims = new ArrayList<>();
Workspace<?> workspace = mWorkspace;
int newItemsScreenId = -1;
- int end = items.size();
- View newView = null;
- for (int i = 0; i < end; i++) {
- final ItemInfo item = items.get(i);
+ int index = 0;
+ for (Pair<ItemInfo, View> e : shortcuts) {
+ final ItemInfo item = e.first;
// Remove colliding items.
CellPos presenterPos = getCellPosMapper().mapModelToPresenter(item);
@@ -2167,42 +2159,26 @@
}
}
- final View view = mItemInflater.inflateItem(item, getModelWriter());
+ final View view = e.second;
if (view == null) {
continue;
}
workspace.addInScreenFromBind(view, item);
- if (forceAnimateIcons) {
+ if (boundAnim != null) {
// Animate all the applications up now
view.setAlpha(0f);
view.setScaleX(0f);
view.setScaleY(0f);
- bounceAnims.add(createNewAppBounceAnimation(view, i));
+ boundAnim.play(createNewAppBounceAnimation(view, index++));
newItemsScreenId = presenterPos.screenId;
}
-
- if (newView == null) {
- newView = view;
- }
}
- View viewToFocus = newView;
// Animate to the correct page
- if (forceAnimateIcons && newItemsScreenId > -1) {
- AnimatorSet anim = new AnimatorSet();
- anim.playTogether(bounceAnims);
- if (focusFirstItemForAccessibility && viewToFocus != null) {
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- viewToFocus.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
- }
- });
- }
-
+ if (boundAnim != null && newItemsScreenId > -1) {
int currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newItemsScreenId);
- final Runnable startBounceAnimRunnable = anim::start;
+ final Runnable startBounceAnimRunnable = boundAnim::start;
if (canAnimatePageChange() && newItemsScreenId != currentScreenId) {
// We post the animation slightly delayed to prevent slowdowns
@@ -2215,8 +2191,6 @@
} else {
mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
}
- } else if (focusFirstItemForAccessibility && viewToFocus != null) {
- viewToFocus.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
}
workspace.requestLayout();
}
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index e015021..1b2cebd 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -89,6 +89,7 @@
return mContext;
}
+ @SuppressWarnings("NewApi")
public LauncherAppState(Context context) {
this(context, LauncherFiles.APP_ICONS_DB);
Log.v(Launcher.TAG, "LauncherAppState initiated");
@@ -103,9 +104,14 @@
});
ModelLauncherCallbacks callbacks = mModel.newModelCallbacks();
- mContext.getSystemService(LauncherApps.class).registerCallback(callbacks);
+ LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
+ launcherApps.registerCallback(callbacks);
mOnTerminateCallback.add(() ->
mContext.getSystemService(LauncherApps.class).unregisterCallback(callbacks));
+ if (Flags.enableSupportForArchiving()) {
+ launcherApps.setArchiveCompatibilityOptions(/* enableIconOverlay= */ true,
+ /* enableUnarchivalConfirmation= */ false);
+ }
SimpleBroadcastReceiver modelChangeReceiver =
new SimpleBroadcastReceiver(mModel::onBroadcastIntent);
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 067d150..3b62ae1 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -20,17 +20,11 @@
import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.util.Log
+import android.view.ViewConfiguration
import androidx.annotation.VisibleForTesting
import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN
import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY
-import com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_DELAY
-import com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_END_SCALE_PERCENT
-import com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_ITERATIONS
-import com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_SCALE_EXPONENT
-import com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_START_SCALE_PERCENT
-import com.android.launcher3.config.FeatureFlags.LPNH_SLOP_PERCENTAGE
-import com.android.launcher3.config.FeatureFlags.LPNH_TIMEOUT_MS
import com.android.launcher3.model.DeviceGridState
import com.android.launcher3.pm.InstallSessionHelper
import com.android.launcher3.provider.RestoreDbTask
@@ -313,53 +307,45 @@
)
@JvmField
val LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE =
- nonRestorableItem(
- "pref_long_press_nav_handle_slop_percentage",
- LPNH_SLOP_PERCENTAGE.get(),
- EncryptionType.MOVE_TO_DEVICE_PROTECTED
- )
+ nonRestorableItem("LPNH_SLOP_PERCENTAGE", 100, EncryptionType.MOVE_TO_DEVICE_PROTECTED)
@JvmField
val LONG_PRESS_NAV_HANDLE_TIMEOUT_MS =
nonRestorableItem(
- "pref_long_press_nav_handle_timeout_ms",
- LPNH_TIMEOUT_MS.get(),
+ "LPNH_TIMEOUT_MS",
+ ViewConfiguration.getLongPressTimeout(),
EncryptionType.MOVE_TO_DEVICE_PROTECTED
)
@JvmField
val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT =
nonRestorableItem(
- "pref_long_press_nav_handle_haptic_hint_start_scale_percent",
- LPNH_HAPTIC_HINT_START_SCALE_PERCENT.get(),
+ "LPNH_HAPTIC_HINT_START_SCALE_PERCENT",
+ 0,
EncryptionType.MOVE_TO_DEVICE_PROTECTED
)
@JvmField
val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT =
nonRestorableItem(
- "pref_long_press_nav_handle_haptic_hint_end_scale_percent",
- LPNH_HAPTIC_HINT_END_SCALE_PERCENT.get(),
+ "LPNH_HAPTIC_HINT_END_SCALE_PERCENT",
+ 100,
EncryptionType.MOVE_TO_DEVICE_PROTECTED
)
@JvmField
val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT =
nonRestorableItem(
- "pref_long_press_nav_handle_haptic_hint_scale_exponent",
- LPNH_HAPTIC_HINT_SCALE_EXPONENT.get(),
+ "LPNH_HAPTIC_HINT_SCALE_EXPONENT",
+ 1,
EncryptionType.MOVE_TO_DEVICE_PROTECTED
)
@JvmField
val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS =
nonRestorableItem(
- "pref_long_press_nav_handle_haptic_hint_iterations",
- LPNH_HAPTIC_HINT_ITERATIONS.get(),
+ "LPNH_HAPTIC_HINT_ITERATIONS",
+ 50,
EncryptionType.MOVE_TO_DEVICE_PROTECTED
)
@JvmField
val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY =
- nonRestorableItem(
- "pref_long_press_nav_handle_haptic_hint_delay",
- LPNH_HAPTIC_HINT_DELAY.get(),
- EncryptionType.MOVE_TO_DEVICE_PROTECTED
- )
+ nonRestorableItem("LPNH_HAPTIC_HINT_DELAY", 0, EncryptionType.MOVE_TO_DEVICE_PROTECTED)
@JvmField
val PRIVATE_SPACE_APPS =
nonRestorableItem("pref_private_space_apps", 0, EncryptionType.MOVE_TO_DEVICE_PROTECTED)
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index f355ae7..ca83245 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -118,7 +118,8 @@
private float mTotalMotion;
// Used in special cases where the fling checks can be relaxed for an intentional gesture
private boolean mAllowEasyFling;
- protected PagedOrientationHandler mOrientationHandler = PagedOrientationHandler.PORTRAIT;
+ private PagedOrientationHandler mOrientationHandler =
+ PagedOrientationHandler.DEFAULT;
private final ArrayList<Runnable> mOnPageScrollsInitializedCallbacks = new ArrayList<>();
@@ -231,6 +232,14 @@
return getChildAt(index);
}
+ protected PagedOrientationHandler getPagedOrientationHandler() {
+ return mOrientationHandler;
+ }
+
+ protected void setOrientationHandler(PagedOrientationHandler orientationHandler) {
+ this.mOrientationHandler = orientationHandler;
+ }
+
/**
* Updates the scroll of the current page immediately to its final scroll position. We use this
* in CustomizePagedView to allow tabs to share the same PagedView while resetting the scroll of
@@ -628,6 +637,11 @@
mMinFlingVelocity = res.getDimensionPixelSize(R.dimen.min_fling_velocity);
mMinSnapVelocity = res.getDimensionPixelSize(R.dimen.min_page_snap_velocity);
mPageSnapAnimationDuration = res.getInteger(R.integer.config_pageSnapAnimationDuration);
+ onVelocityValuesUpdated();
+ }
+
+ protected void onVelocityValuesUpdated() {
+ // Overridden in RecentsView
}
@Override
@@ -1573,7 +1587,7 @@
@Override
public void requestChildFocus(View child, View focused) {
super.requestChildFocus(child, focused);
- if (!shouldHandleRequestChildFocus()) {
+ if (!shouldHandleRequestChildFocus(child)) {
return;
}
// In case the device is controlled by a controller, mCurrentPage isn't updated properly
@@ -1589,7 +1603,7 @@
}
}
- protected boolean shouldHandleRequestChildFocus() {
+ protected boolean shouldHandleRequestChildFocus(View child) {
return true;
}
@@ -1643,7 +1657,7 @@
}
protected void snapToDestination() {
- snapToPage(getDestinationPage(), mPageSnapAnimationDuration);
+ snapToPage(getDestinationPage(), getSnapAnimationDuration());
}
// We want the duration of the page snap animation to be influenced by the distance that
@@ -1667,7 +1681,7 @@
if (Math.abs(velocity) < mMinFlingVelocity) {
// If the velocity is low enough, then treat this more as an automatic page advance
// as opposed to an apparent physical response to flinging
- return snapToPage(whichPage, mPageSnapAnimationDuration);
+ return snapToPage(whichPage, getSnapAnimationDuration());
}
// Here we compute a "distance" that will be used in the computation of the overall
@@ -1689,12 +1703,16 @@
return snapToPage(whichPage, delta, duration);
}
+ protected int getSnapAnimationDuration() {
+ return mPageSnapAnimationDuration;
+ }
+
public boolean snapToPage(int whichPage) {
- return snapToPage(whichPage, mPageSnapAnimationDuration);
+ return snapToPage(whichPage, getSnapAnimationDuration());
}
public boolean snapToPageImmediately(int whichPage) {
- return snapToPage(whichPage, mPageSnapAnimationDuration, true);
+ return snapToPage(whichPage, getSnapAnimationDuration(), true);
}
public boolean snapToPage(int whichPage, int duration) {
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 0b92c28..1362586 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -34,6 +34,8 @@
import android.view.View;
import android.widget.Toast;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.logging.FileLog;
@@ -43,6 +45,7 @@
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
@@ -176,6 +179,10 @@
if (uninstallDisabled) {
return INVALID;
}
+ if (Flags.enablePrivateSpace() && UserCache.getInstance(getContext()).getUserInfo(
+ info.user).isPrivate()) {
+ return INVALID;
+ }
if (info instanceof ItemInfoWithIcon) {
ItemInfoWithIcon iconInfo = (ItemInfoWithIcon) info;
@@ -184,7 +191,7 @@
return INVALID;
}
}
- if (getUninstallTarget(info) == null) {
+ if (getUninstallTarget(getContext(), info) == null) {
return INVALID;
}
return UNINSTALL;
@@ -193,7 +200,7 @@
/**
* @return the component name that should be uninstalled or null.
*/
- private ComponentName getUninstallTarget(ItemInfo item) {
+ public static ComponentName getUninstallTarget(Context context, ItemInfo item) {
Intent intent = null;
UserHandle user = null;
if (item != null &&
@@ -202,7 +209,7 @@
user = item.user;
}
if (intent != null) {
- LauncherActivityInfo info = getContext().getSystemService(LauncherApps.class)
+ LauncherActivityInfo info = context.getSystemService(LauncherApps.class)
.resolveActivity(intent, user);
if (info != null
&& (info.getApplicationInfo().flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
@@ -280,32 +287,41 @@
if (FeatureFlags.ENABLE_DISMISS_PREDICTION_UNDO.get()) {
CharSequence announcement = getContext().getString(R.string.item_removed);
mDropTargetHandler
- .dismissPrediction(announcement, () -> {}, () -> {
- mStatsLogManager.logger()
- .withInstanceId(instanceId)
- .withItemInfo(info)
- .log(LAUNCHER_DISMISS_PREDICTION_UNDO);
- });
+ .dismissPrediction(announcement, () -> {
+ }, () -> {
+ mStatsLogManager.logger()
+ .withInstanceId(instanceId)
+ .withItemInfo(info)
+ .log(LAUNCHER_DISMISS_PREDICTION_UNDO);
+ });
}
return null;
}
- ComponentName cn = getUninstallTarget(info);
+ return performUninstall(getContext(), getUninstallTarget(getContext(), info), info);
+ }
+
+ /**
+ * Performs uninstall and returns the target component for the {@link ItemInfo} or null if
+ * the uninstall was not performed.
+ */
+ public static ComponentName performUninstall(Context context, @Nullable ComponentName cn,
+ ItemInfo info) {
if (cn == null) {
// System applications cannot be installed. For now, show a toast explaining that.
// We may give them the option of disabling apps this way.
Toast.makeText(
- getContext(),
+ context,
R.string.uninstall_system_app_text,
Toast.LENGTH_SHORT
- ).show();
+ ).show();
return null;
}
try {
- Intent i = Intent.parseUri(getContext().getString(R.string.delete_package_intent), 0)
+ Intent i = Intent.parseUri(context.getString(R.string.delete_package_intent), 0)
.setData(Uri.fromParts("package", cn.getPackageName(), cn.getClassName()))
.putExtra(Intent.EXTRA_USER, info.user);
- getContext().startActivity(i);
+ context.startActivity(i);
FileLog.d(TAG, "start uninstall activity " + cn.getPackageName());
return cn;
} catch (URISyntaxException e) {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 2eff8aa..984a9ae 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -143,7 +143,7 @@
* @param <T> Class that extends View and PageIndicator
*/
public class Workspace<T extends View & PageIndicator> extends PagedView<T>
- implements DropTarget, DragSource, View.OnTouchListener,
+ implements DropTarget, DragSource, View.OnTouchListener, CellLayoutContainer,
DragController.DragListener, Insettable, StateHandler<LauncherState>,
WorkspaceLayoutManager, LauncherBindableItemsContainer, LauncherOverlayCallbacks {
@@ -511,11 +511,6 @@
return !FOLDABLE_SINGLE_PAGE.get() && mLauncher.mDeviceProfile.isTwoPanels;
}
- @Override
- public int getPanelCount() {
- return isTwoPanelEnabled() ? 2 : super.getPanelCount();
- }
-
public void deferRemoveExtraEmptyScreen() {
mDeferRemoveExtraEmptyScreen = true;
}
@@ -683,6 +678,7 @@
newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
R.layout.workspace_screen, this, false /* attachToRoot */);
}
+ newScreen.setCellLayoutContainer(this);
mWorkspaceScreens.put(screenId, newScreen);
mScreenOrder.add(insertIndex, screenId);
@@ -949,7 +945,8 @@
return mWorkspaceScreens.get(screenId);
}
- public int getIdForScreen(CellLayout layout) {
+ @Override
+ public int getCellLayoutId(CellLayout layout) {
int index = mWorkspaceScreens.indexOfValue(layout);
if (index != -1) {
return mWorkspaceScreens.keyAt(index);
@@ -961,6 +958,16 @@
return indexOfChild(mWorkspaceScreens.get(screenId));
}
+ @Override
+ public int getCellLayoutIndex(CellLayout cellLayout) {
+ return indexOfChild(mWorkspaceScreens.get(getCellLayoutId(cellLayout)));
+ }
+
+ @Override
+ public int getPanelCount() {
+ return isTwoPanelEnabled() ? 2 : super.getPanelCount();
+ }
+
public IntSet getCurrentPageScreenIds() {
return IntSet.wrap(getScreenIdForPageIndex(getCurrentPage()));
}
@@ -1001,7 +1008,7 @@
if (!isTwoPanelEnabled()) {
return null;
}
- int screenId = getIdForScreen(cellLayout);
+ int screenId = getCellLayoutId(cellLayout);
if (screenId == -1) {
return null;
}
@@ -1826,7 +1833,7 @@
}
}
- int screenId = getIdForScreen(dropTargetLayout);
+ int screenId = getCellLayoutId(dropTargetLayout);
if (Workspace.EXTRA_EMPTY_SCREEN_IDS.contains(screenId)) {
commitExtraEmptyScreens();
}
@@ -1909,7 +1916,7 @@
if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
mCreateUserFolderOnDrop = false;
- final int screenId = getIdForScreen(target);
+ final int screenId = getCellLayoutId(target);
boolean aboveShortcut = (v.getTag() instanceof WorkspaceItemInfo);
boolean willBecomeShortcut = (newView.getTag() instanceof WorkspaceItemInfo);
@@ -2010,7 +2017,7 @@
LauncherSettings.Favorites.CONTAINER_HOTSEAT :
LauncherSettings.Favorites.CONTAINER_DESKTOP;
int screenId = (mTargetCell[0] < 0) ?
- mDragInfo.screenId : getIdForScreen(dropTargetLayout);
+ mDragInfo.screenId : getCellLayoutId(dropTargetLayout);
int spanX = mDragInfo != null ? mDragInfo.spanX : 1;
int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
// First we find the cell nearest to point at which the item is
@@ -2766,7 +2773,7 @@
final int container = mLauncher.isHotseatLayout(cellLayout)
? LauncherSettings.Favorites.CONTAINER_HOTSEAT
: LauncherSettings.Favorites.CONTAINER_DESKTOP;
- final int screenId = getIdForScreen(cellLayout);
+ final int screenId = getCellLayoutId(cellLayout);
if (!mLauncher.isHotseatLayout(cellLayout)
&& screenId != getScreenIdForPageIndex(mCurrentPage)
&& !mLauncher.isInState(SPRING_LOADED)
@@ -3480,14 +3487,15 @@
@Override
protected String getCurrentPageDescription() {
- int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
- return getPageDescription(page);
+ int pageIndex = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
+ return getPageDescription(pageIndex);
}
/**
* @param page page index.
* @return Description of the page at the given page index.
*/
+ @Override
public String getPageDescription(int page) {
int nScreens = getChildCount();
int extraScreenId = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 0844275..e65e614 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -5,18 +5,22 @@
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE;
+import android.animation.AnimatorSet;
import android.appwidget.AppWidgetProviderInfo;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Handler;
import android.util.Log;
+import android.util.Pair;
import android.view.KeyEvent;
import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.ButtonDropTarget;
@@ -396,10 +400,7 @@
LauncherSettings.Favorites.CONTAINER_DESKTOP,
screenId, coordinates[0], coordinates[1]);
- mContext.bindItems(
- Collections.singletonList(info),
- /* forceAnimateIcons= */ true,
- /* focusFirstItemForAccessibility= */ accessibility);
+ bindItem(item, accessibility);
announceConfirmation(R.string.item_added_to_workspace);
} else if (item instanceof PendingAddItemInfo) {
PendingAddItemInfo info = (PendingAddItemInfo) item;
@@ -412,7 +413,7 @@
mContext.getModelWriter().addItemToDatabase(info,
LauncherSettings.Favorites.CONTAINER_DESKTOP,
screenId, coordinates[0], coordinates[1]);
- mContext.bindItems(Collections.singletonList(info), true, accessibility);
+ bindItem(info, accessibility);
} else if (item instanceof FolderInfo fi) {
mContext.getModelWriter().addItemToDatabase(fi,
LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId, coordinates[0],
@@ -420,11 +421,26 @@
fi.contents.forEach(member -> {
mContext.getModelWriter().addItemToDatabase(member, fi.id, -1, -1, -1);
});
- mContext.bindItems(Collections.singletonList(fi), true, accessibility);
+ bindItem(fi, accessibility);
}
}));
return true;
}
+
+ private void bindItem(ItemInfo item, boolean focusForAccessibility) {
+ View view = mContext.getItemInflater().inflateItem(item, mContext.getModelWriter());
+ if (view == null) {
+ return;
+ }
+ AnimatorSet anim = null;
+ if (focusForAccessibility) {
+ anim = new AnimatorSet();
+ anim.addListener(forEndCallback(
+ () -> view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)));
+ }
+ mContext.bindItems(Collections.singletonList(Pair.create(item, view)), anim);
+ }
+
/**
* Functionality to move the item {@link ItemInfo} to the workspace
* @param item item to be moved
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 930196d..ae2849e 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -1156,13 +1156,15 @@
applyAdapterSideAndBottomPaddings(grid);
+ MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
// Ignore left/right insets on tablet because we are already centered in-screen.
- if (grid.isPhone) {
- MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
+ if (grid.isTablet) {
+ mlp.leftMargin = mlp.rightMargin = 0;
+ } else {
mlp.leftMargin = insets.left;
mlp.rightMargin = insets.right;
- setLayoutParams(mlp);
}
+ setLayoutParams(mlp);
if (!grid.isVerticalBarLayout() || FeatureFlags.enableResponsiveWorkspace()) {
int topPadding = grid.allAppsPadding.top;
diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java
index 557ec48..6422943 100644
--- a/src/com/android/launcher3/allapps/PrivateProfileManager.java
+++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java
@@ -22,6 +22,7 @@
import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_SYS_APPS_DIVIDER;
import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_NOTHING;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_PRIVATE_SPACE_INSTALL_APP;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
@@ -109,7 +110,7 @@
itemInfo.bitmap = bitmapInfo;
itemInfo.contentDescription = context.getResources().getString(
com.android.launcher3.R.string.ps_add_button_content_description);
- itemInfo.runtimeStatusFlags |= FLAG_PRIVATE_SPACE_INSTALL_APP;
+ itemInfo.runtimeStatusFlags |= FLAG_PRIVATE_SPACE_INSTALL_APP | FLAG_NOT_PINNABLE;
BaseAllAppsAdapter.AdapterItem item = new BaseAllAppsAdapter.AdapterItem(VIEW_TYPE_ICON);
item.itemInfo = itemInfo;
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index 4427a49..f9d047b 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -20,6 +20,7 @@
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.style.SuggestionSpan;
+import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnFocusChangeListener;
@@ -42,6 +43,7 @@
implements TextWatcher, OnEditorActionListener, ExtendedEditText.OnBackKeyListener,
OnFocusChangeListener {
+ private static final String TAG = "AllAppsSearchBarController";
protected ActivityContext mLauncher;
protected SearchCallback<AdapterItem> mCallback;
protected ExtendedEditText mInput;
@@ -122,6 +124,7 @@
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_GO) {
+ Log.i(TAG, "User tapped ime search button");
// selectFocusedView should return SearchTargetEvent that is passed onto onClick
return mLauncher.getAppsView().getMainAdapterProvider().launchHighlightedItem();
}
diff --git a/src/com/android/launcher3/apppairs/AppPairIcon.java b/src/com/android/launcher3/apppairs/AppPairIcon.java
index 1d73441..9b85a65 100644
--- a/src/com/android/launcher3/apppairs/AppPairIcon.java
+++ b/src/com/android/launcher3/apppairs/AppPairIcon.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -45,6 +46,8 @@
* member apps are set into these rectangles.
*/
public class AppPairIcon extends FrameLayout implements DraggableView, Reorderable {
+ private static final String TAG = "AppPairIcon";
+
// A view that holds the app pair icon graphic.
private AppPairIconGraphic mIconGraphic;
// A view that holds the app pair's title.
@@ -96,8 +99,7 @@
icon.mAppPairName.setText(appPairInfo.title);
// Set up accessibility
- icon.setContentDescription(icon.getAccessibilityTitle(
- appPairInfo.contents.get(0).title, appPairInfo.contents.get(1).title));
+ icon.setContentDescription(icon.getAccessibilityTitle(appPairInfo));
icon.setAccessibilityDelegate(activity.getAccessibilityDelegate());
return icon;
@@ -106,7 +108,14 @@
/**
* Returns a formatted accessibility title for app pairs.
*/
- public String getAccessibilityTitle(CharSequence app1, CharSequence app2) {
+ public String getAccessibilityTitle(FolderInfo appPairInfo) {
+ if (appPairInfo.contents.size() != 2) {
+ Log.wtf(TAG, "AppPair contents not 2, size: " + appPairInfo.contents.size());
+ return "";
+ }
+
+ CharSequence app1 = appPairInfo.contents.get(0).title;
+ CharSequence app2 = appPairInfo.contents.get(1).title;
return getContext().getString(R.string.app_pair_name_format, app1, app2);
}
diff --git a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
index b2497a3..65c270a 100644
--- a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
+++ b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
@@ -93,7 +93,7 @@
private fun applyIcons(contents: ArrayList<WorkspaceItemInfo>) {
// App pair should always contain 2 members; if not 2, return to avoid a crash loop
if (contents.size != 2) {
- Log.w(TAG, "AppPair contents not 2, size: " + contents.size, Throwable())
+ Log.wtf(TAG, "AppPair contents not 2, size: " + contents.size, Throwable())
return
}
@@ -112,7 +112,6 @@
appIcon2?.setBounds(0, 0, memberIconSize.toInt(), memberIconSize.toInt())
}
-
/** Gets this icon graphic's bounds, with respect to the parent icon's coordinate system. */
fun getIconBounds(outBounds: Rect) {
outBounds.set(0, 0, backgroundSize.toInt(), backgroundSize.toInt())
@@ -138,15 +137,8 @@
// Draw background
appPairBackground.draw(canvas)
- // Make sure icons are loaded
- if (
- appIcon1 == null ||
- appIcon2 == null ||
- appIcon1 is PlaceHolderIconDrawable ||
- appIcon2 is PlaceHolderIconDrawable
- ) {
- applyIcons(parentIcon.info.contents)
- }
+ // Make sure icons are loaded and fresh
+ applyIcons(parentIcon.info.contents)
// Draw first icon
canvas.save()
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 1994bd1..40c368b 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -17,11 +17,17 @@
package com.android.launcher3.config;
import static com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN;
+import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY;
+import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT;
+import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS;
+import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT;
+import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT;
+import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE;
+import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_TIMEOUT_MS;
import static com.android.launcher3.config.FeatureFlags.FlagState.DISABLED;
import static com.android.launcher3.config.FeatureFlags.FlagState.ENABLED;
import static com.android.launcher3.config.FeatureFlags.FlagState.TEAMFOOD;
import static com.android.launcher3.uioverrides.flags.FlagsFactory.getDebugFlag;
-import static com.android.launcher3.uioverrides.flags.FlagsFactory.getIntFlag;
import static com.android.launcher3.uioverrides.flags.FlagsFactory.getReleaseFlag;
import static com.android.wm.shell.Flags.enableTaskbarNavbarUnification;
@@ -31,6 +37,7 @@
import com.android.launcher3.BuildConfig;
import com.android.launcher3.Flags;
+import com.android.launcher3.uioverrides.flags.FlagsFactory;
import java.util.function.Predicate;
import java.util.function.ToIntFunction;
@@ -93,6 +100,12 @@
"ENABLE_DISMISS_PREDICTION_UNDO", DISABLED,
"Show an 'Undo' snackbar when users dismiss a predicted hotseat item");
+ public static final BooleanFlag MOVE_STARTUP_DATA_TO_DEVICE_PROTECTED_STORAGE = getDebugFlag(
+ 251502424, "ENABLE_BOOT_AWARE_STARTUP_DATA", DISABLED,
+ "Marks LauncherPref data as (and allows it to) available while the device is"
+ + " locked. Enabling this causes a 1-time movement of certain SharedPreferences"
+ + " data. Improves startup latency.");
+
public static final BooleanFlag CONTINUOUS_VIEW_TREE_CAPTURE = getDebugFlag(270395171,
"CONTINUOUS_VIEW_TREE_CAPTURE", ENABLED, "Capture View tree every frame");
@@ -129,12 +142,14 @@
"Shrinks navbar when long pressing if ANIMATE_LPNH is enabled");
public static final IntFlag LPNH_SLOP_PERCENTAGE =
- getIntFlag(301680992, "LPNH_SLOP_PERCENTAGE", 100,
- "Controls touch slop percentage for lpnh");
+ FlagsFactory.getIntFlag(301680992, "LPNH_SLOP_PERCENTAGE", 100,
+ "Controls touch slop percentage for lpnh",
+ LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE);
public static final IntFlag LPNH_TIMEOUT_MS =
- getIntFlag(301680992, "LPNH_TIMEOUT_MS", ViewConfiguration.getLongPressTimeout(),
- "Controls lpnh timeout in milliseconds");
+ FlagsFactory.getIntFlag(301680992, "LPNH_TIMEOUT_MS",
+ ViewConfiguration.getLongPressTimeout(),
+ "Controls lpnh timeout in milliseconds", LONG_PRESS_NAV_HANDLE_TIMEOUT_MS);
public static final BooleanFlag ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS = getReleaseFlag(
270394468, "ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS", ENABLED,
@@ -280,28 +295,35 @@
"Enables haptic hint at end of long pressing on the bottom bar nav handle.");
public static final IntFlag LPNH_HAPTIC_HINT_START_SCALE_PERCENT =
- getIntFlag(309972570, "LPNH_HAPTIC_HINT_START_SCALE_PERCENT", 0,
- "Haptic hint start scale.");
+ FlagsFactory.getIntFlag(309972570,
+ "LPNH_HAPTIC_HINT_START_SCALE_PERCENT", 0,
+ "Haptic hint start scale.",
+ LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT);
public static final IntFlag LPNH_HAPTIC_HINT_END_SCALE_PERCENT =
- getIntFlag(309972570, "LPNH_HAPTIC_HINT_END_SCALE_PERCENT", 100,
- "Haptic hint end scale.");
+ FlagsFactory.getIntFlag(309972570,
+ "LPNH_HAPTIC_HINT_END_SCALE_PERCENT", 100,
+ "Haptic hint end scale.", LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT);
public static final IntFlag LPNH_HAPTIC_HINT_SCALE_EXPONENT =
- getIntFlag(309972570, "LPNH_HAPTIC_HINT_SCALE_EXPONENT", 1,
- "Haptic hint scale exponent.");
+ FlagsFactory.getIntFlag(309972570,
+ "LPNH_HAPTIC_HINT_SCALE_EXPONENT", 1,
+ "Haptic hint scale exponent.",
+ LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT);
public static final IntFlag LPNH_HAPTIC_HINT_ITERATIONS =
- getIntFlag(309972570, "LPNH_HAPTIC_HINT_ITERATIONS", 50,
- "Haptic hint number of iterations.");
+ FlagsFactory.getIntFlag(309972570, "LPNH_HAPTIC_HINT_ITERATIONS",
+ 50,
+ "Haptic hint number of iterations.",
+ LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS);
public static final BooleanFlag ENABLE_LPNH_DEEP_PRESS =
getReleaseFlag(310952290, "ENABLE_LPNH_DEEP_PRESS", ENABLED,
"Long press of nav handle is instantly triggered if deep press is detected.");
public static final IntFlag LPNH_HAPTIC_HINT_DELAY =
- getIntFlag(309972570, "LPNH_HAPTIC_HINT_DELAY", 0,
- "Delay before haptic hint starts.");
+ FlagsFactory.getIntFlag(309972570, "LPNH_HAPTIC_HINT_DELAY", 0,
+ "Delay before haptic hint starts.", LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY);
// TODO(Block 17): Clean up flags
// Aconfig migration complete for ENABLE_TASKBAR_PINNING.
@@ -314,12 +336,6 @@
return ENABLE_TASKBAR_PINNING.get() || Flags.enableTaskbarPinning();
}
- public static final BooleanFlag MOVE_STARTUP_DATA_TO_DEVICE_PROTECTED_STORAGE = getDebugFlag(
- 251502424, "ENABLE_BOOT_AWARE_STARTUP_DATA", DISABLED,
- "Marks LauncherPref data as (and allows it to) available while the device is"
- + " locked. Enabling this causes a 1-time movement of certain SharedPreferences"
- + " data. Improves startup latency.");
-
// Aconfig migration complete for ENABLE_APP_PAIRS.
public static final BooleanFlag ENABLE_APP_PAIRS = getDebugFlag(274189428,
"ENABLE_APP_PAIRS", DISABLED,
@@ -413,10 +429,6 @@
"ENABLE_ENFORCED_ROUNDED_CORNERS", ENABLED,
"Enforce rounded corners on all App Widgets");
- public static final BooleanFlag ENABLE_ICON_LABEL_AUTO_SCALING = getDebugFlag(270393294,
- "ENABLE_ICON_LABEL_AUTO_SCALING", ENABLED,
- "Enables scaling/spacing for icon labels to make more characters visible");
-
public static final BooleanFlag USE_LOCAL_ICON_OVERRIDES = getDebugFlag(270394973,
"USE_LOCAL_ICON_OVERRIDES", ENABLED,
"Use inbuilt monochrome icons if app doesn't provide one");
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 3ccde0a..b6e5977 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -16,6 +16,8 @@
package com.android.launcher3.dragndrop;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE;
+
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -29,8 +31,10 @@
import com.android.app.animation.Interpolators;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
+import com.android.launcher3.Flags;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.ActivityContext;
@@ -221,6 +225,12 @@
}
}
+ protected boolean isItemPinnable() {
+ return !Flags.privateSpaceRestrictItemDrag()
+ || !(mDragObject.dragInfo instanceof ItemInfoWithIcon itemInfoWithIcon)
+ || (itemInfoWithIcon.runtimeStatusFlags & FLAG_NOT_PINNABLE) == 0;
+ }
+
public Optional<InstanceId> getLogInstanceId() {
return Optional.ofNullable(mDragObject)
.map(dragObject -> dragObject.logInstanceId);
diff --git a/src/com/android/launcher3/dragndrop/LauncherDragController.java b/src/com/android/launcher3/dragndrop/LauncherDragController.java
index da6f446..f3708a2 100644
--- a/src/com/android/launcher3/dragndrop/LauncherDragController.java
+++ b/src/com/android/launcher3/dragndrop/LauncherDragController.java
@@ -149,9 +149,10 @@
handleMoveEvent(mLastTouch.x, mLastTouch.y);
- if (!mActivity.isTouchInProgress() && options.simulatedDndStartPoint == null) {
+ if (!isItemPinnable()
+ || (!mActivity.isTouchInProgress() && options.simulatedDndStartPoint == null)) {
// If it is an internal drag and the touch is already complete, cancel immediately
- MAIN_EXECUTOR.submit(this::cancelDrag);
+ MAIN_EXECUTOR.post(this::cancelDrag);
}
return dragView;
}
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 7ae70e0..2f3f029 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -800,6 +800,14 @@
return;
}
+ int size = getIconsInReadingOrder().size();
+ if (size <= 1) {
+ Log.d(TAG, "Couldn't animate folder closed because there's " + size + " icons");
+ closeComplete(false);
+ post(this::announceAccessibilityChanges);
+ return;
+ }
+
mContent.completePendingPageChanges();
mContent.snapToPageImmediately(mContent.getDestinationPage());
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index f9b7495..2a0f030 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -717,7 +717,10 @@
LAUNCHER_PRIVATE_SPACE_INSTALL_APP_BUTTON_TAP(1605),
@UiEvent(doc = "User attempted to create split screen with a widget")
- LAUNCHER_SPLIT_WIDGET_ATTEMPT(1604)
+ LAUNCHER_SPLIT_WIDGET_ATTEMPT(1604),
+
+ @UiEvent(doc = "User tapped on private space uninstall system shortcut.")
+ LAUNCHER_PRIVATE_SPACE_UNINSTALL_SYSTEM_SHORTCUT_TAP(1608),
// ADD MORE
;
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 5e86bd6..96a8da9 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -33,6 +33,7 @@
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemFactory;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -102,6 +103,11 @@
Objects.requireNonNull(item.getIntent()))) {
continue;
}
+
+ if (item instanceof ItemInfoWithIcon
+ && ((ItemInfoWithIcon) item).isArchived()) {
+ continue;
+ }
}
if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index ccb8900..b41f011 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -330,12 +330,15 @@
PackageManagerHelper.getLoadingProgress(info),
PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING);
applicationInfo.intent = launchIntent;
- if (enableSupportForArchiving() && info.getActivityInfo().isArchived) {
+ if (enableSupportForArchiving()) {
// In case an app is archived, the respective item flag corresponding to
// archiving should also be applied during package updates
- applicationInfo.runtimeStatusFlags |= FLAG_ARCHIVED;
+ if (info.getActivityInfo().isArchived) {
+ applicationInfo.runtimeStatusFlags |= FLAG_ARCHIVED;
+ } else {
+ applicationInfo.runtimeStatusFlags &= (~FLAG_ARCHIVED);
+ }
}
-
mDataChanged = true;
}
}
diff --git a/src/com/android/launcher3/model/ItemInstallQueue.java b/src/com/android/launcher3/model/ItemInstallQueue.java
index 9a3abd4..59f453a 100644
--- a/src/com/android/launcher3/model/ItemInstallQueue.java
+++ b/src/com/android/launcher3/model/ItemInstallQueue.java
@@ -18,10 +18,12 @@
import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
+import static com.android.launcher3.Flags.enableSupportForArchiving;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
import static com.android.launcher3.model.data.AppInfo.makeLaunchIntent;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.appwidget.AppWidgetManager;
@@ -276,6 +278,7 @@
return intent;
}
+ @SuppressWarnings("NewApi")
public Pair<ItemInfo, Object> getItemInfo(Context context) {
switch (itemType) {
case ITEM_TYPE_APPLICATION: {
@@ -297,6 +300,9 @@
} else {
lai = laiList.get(0);
si.intent = makeLaunchIntent(lai);
+ if (enableSupportForArchiving() && lai.getActivityInfo().isArchived) {
+ si.runtimeStatusFlags |= FLAG_ARCHIVED;
+ }
}
LauncherAppState.getInstance(context).getIconCache()
.getTitleAndIcon(si, () -> lai, usePackageIcon, false);
diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
index f98cab6..44c41c1 100644
--- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
+++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
@@ -26,7 +26,6 @@
import android.text.TextUtils
import android.util.Log
import android.util.LongSparseArray
-import androidx.annotation.VisibleForTesting
import com.android.launcher3.Flags
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.LauncherAppState
@@ -89,7 +88,10 @@
try {
if (c.user == null) {
// User has been deleted, remove the item.
- c.markDeleted("User has been deleted", RestoreError.PROFILE_DELETED)
+ c.markDeleted(
+ "User has been deleted for item id=${c.id}",
+ RestoreError.PROFILE_DELETED
+ )
return
}
when (c.itemType) {
@@ -127,29 +129,24 @@
* data model to be bound to the launcher’s data model.
*/
@SuppressLint("NewApi")
- @VisibleForTesting
- fun processAppOrDeepShortcut() {
+ private fun processAppOrDeepShortcut() {
var allowMissingTarget = false
var intent = c.parseIntent()
if (intent == null) {
- c.markDeleted("Invalid or null intent", RestoreError.MISSING_INFO)
+ c.markDeleted("Null intent for item id=${c.id}", RestoreError.MISSING_INFO)
return
}
var disabledState =
if (userManagerState.isUserQuiet(c.serialNumber))
WorkspaceItemInfo.FLAG_DISABLED_QUIET_USER
else 0
- var cn = intent.component
- val targetPkg = if (cn == null) intent.getPackage() else cn.packageName
- if (TextUtils.isEmpty(targetPkg)) {
- c.markDeleted("Shortcuts can't have null package", RestoreError.MISSING_INFO)
+ val cn = intent.component
+ val targetPkg = cn?.packageName ?: intent.getPackage()
+ if (targetPkg.isNullOrEmpty()) {
+ c.markDeleted("No target package for item id=${c.id}", RestoreError.MISSING_INFO)
return
}
-
- // If there is no target package, it's an implicit intent
- // (legacy shortcut) which is always valid
- var validTarget =
- (TextUtils.isEmpty(targetPkg) || launcherApps.isPackageEnabled(targetPkg, c.user))
+ var validTarget = launcherApps.isPackageEnabled(targetPkg, c.user)
// If it's a deep shortcut, we'll use pinned shortcuts to restore it
if (cn != null && validTarget && (c.itemType != Favorites.ITEM_TYPE_DEEP_SHORTCUT)) {
@@ -359,8 +356,7 @@
* processing for folder content items is done in LoaderTask after all the items in the
* workspace have been loaded. The loaded FolderInfos are stored in the BgDataModel.
*/
- @VisibleForTesting
- fun processFolderOrAppPair() {
+ private fun processFolderOrAppPair() {
val folderInfo =
bgDataModel.findOrMakeFolder(c.id).apply {
c.applyCommonProperties(this)
@@ -394,8 +390,7 @@
* depending on the type of widget. Custom widgets are treated differently than non-custom
* widgets, installing / restoring widgets are treated differently, etc.
*/
- @VisibleForTesting
- fun processWidget() {
+ private fun processWidget() {
val component = ComponentName.unflattenFromString(c.appWidgetProvider)!!
val appWidgetInfo = LauncherAppWidgetInfo(c.appWidgetId, component)
c.applyCommonProperties(appWidgetInfo)
@@ -496,6 +491,7 @@
companion object {
private const val TAG = "WorkspaceItemProcessor"
+
private fun logWidgetInfo(
idp: InvariantDeviceProfile,
widgetProviderInfo: LauncherAppWidgetProviderInfo
diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java
index 5b541d0..83ba2b3 100644
--- a/src/com/android/launcher3/model/data/FolderInfo.java
+++ b/src/com/android/launcher3/model/data/FolderInfo.java
@@ -371,4 +371,13 @@
}
return LauncherAtom.ToState.TO_STATE_UNSPECIFIED;
}
+
+ @Override
+ public boolean isDisabled() {
+ if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR) {
+ return contents.stream().anyMatch((WorkspaceItemInfo::isDisabled));
+ }
+
+ return super.isDisabled();
+ }
}
diff --git a/src/com/android/launcher3/pm/UserCache.java b/src/com/android/launcher3/pm/UserCache.java
index 8708d5a..032de31 100644
--- a/src/com/android/launcher3/pm/UserCache.java
+++ b/src/com/android/launcher3/pm/UserCache.java
@@ -28,9 +28,13 @@
import androidx.annotation.AnyThread;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.UserBadgeDrawable;
+import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.SimpleBroadcastReceiver;
@@ -166,4 +170,14 @@
public List<UserHandle> getUserProfiles() {
return List.copyOf(mUserToSerialMap.keySet());
}
+
+ /**
+ * Get a non-themed {@link UserBadgeDrawable} based on the provided {@link UserHandle}.
+ */
+ @Nullable
+ public static UserBadgeDrawable getBadgeDrawable(Context context, UserHandle userHandle) {
+ return (UserBadgeDrawable) BitmapInfo.LOW_RES_INFO.withFlags(UserCache.getInstance(context)
+ .getUserInfo(userHandle).applyBitmapInfoFlags(FlagOp.NO_OP))
+ .getBadgeDrawable(context, false /* isThemed */);
+ }
}
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index c9c5fd3..1c9db17 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -19,6 +19,7 @@
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS;
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.Utilities.squaredTouchSlop;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE;
import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -44,6 +45,7 @@
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
import com.android.launcher3.DropTarget.DragObject;
+import com.android.launcher3.Flags;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
@@ -53,6 +55,7 @@
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
@@ -204,17 +207,21 @@
.collect(Collectors.toList());
container = (PopupContainerWithArrow) launcher.getLayoutInflater().inflate(
R.layout.popup_container, launcher.getDragLayer(), false);
- container.configureForLauncher(launcher);
+ container.configureForLauncher(launcher, item);
container.populateAndShowRows(icon, deepShortcutCount, systemShortcuts);
launcher.refreshAndBindWidgetsForPackageUser(PackageUserKey.fromItemInfo(item));
container.requestFocus();
return container;
}
- private void configureForLauncher(Launcher launcher) {
+ private void configureForLauncher(Launcher launcher, ItemInfo itemInfo) {
addOnAttachStateChangeListener(new LauncherPopupLiveUpdateHandler(
launcher, (PopupContainerWithArrow<Launcher>) this));
- mPopupItemDragHandler = new LauncherPopupItemDragHandler(launcher, this);
+ if (!Flags.privateSpaceRestrictItemDrag()
+ || !(itemInfo instanceof ItemInfoWithIcon itemInfoWithIcon)
+ || (itemInfoWithIcon.runtimeStatusFlags & FLAG_NOT_PINNABLE) == 0) {
+ mPopupItemDragHandler = new LauncherPopupItemDragHandler(launcher, this);
+ }
mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(launcher);
launcher.getDragController().addDragListener(this);
}
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 3030ed4..fbbfea9 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -1,6 +1,7 @@
package com.android.launcher3.popup;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_INSTALL_SYSTEM_SHORTCUT_TAP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_UNINSTALL_SYSTEM_SHORTCUT_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_DONT_SUGGEST_APP_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_WIDGETS_TAP;
@@ -17,17 +18,21 @@
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.Flags;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
+import com.android.launcher3.SecondaryDropTarget;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.PrivateProfileManager;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.pm.UserCache;
import com.android.launcher3.uioverrides.ApiWrapper;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.InstantAppResolver;
@@ -301,16 +306,11 @@
}
}
- public static final Factory<Launcher> DONT_SUGGEST_APP = new Factory<Launcher>() {
- @Nullable
- @Override
- public SystemShortcut<Launcher> getShortcut(Launcher activity, ItemInfo itemInfo,
- View originalView) {
- if (!itemInfo.isPredictedItem()) {
- return null;
- }
- return new DontSuggestApp(activity, itemInfo, originalView);
+ public static final Factory<Launcher> DONT_SUGGEST_APP = (activity, itemInfo, originalView) -> {
+ if (!itemInfo.isPredictedItem()) {
+ return null;
}
+ return new DontSuggestApp(activity, itemInfo, originalView);
};
private static class DontSuggestApp extends SystemShortcut<Launcher> {
@@ -329,6 +329,51 @@
}
}
+ public static final Factory<Launcher> UNINSTALL_APP = (activity, itemInfo, originalView) -> {
+ if (!Flags.enablePrivateSpace()) {
+ return null;
+ }
+ if (!UserCache.getInstance(activity.getApplicationContext()).getUserInfo(
+ itemInfo.user).isPrivate()) {
+ // If app is not Private Space app.
+ return null;
+ }
+ ComponentName cn = SecondaryDropTarget.getUninstallTarget(activity.getApplicationContext(),
+ itemInfo);
+ if (cn == null) {
+ // If component name is null, don't show uninstall shortcut.
+ // System apps will have component name as null.
+ return null;
+ }
+ return new UninstallApp(activity, itemInfo, originalView, cn);
+ };
+
+ private static class UninstallApp extends SystemShortcut<Launcher> {
+ private static final String TAG = "UninstallApp";
+ Context mContext;
+ @NonNull
+ ComponentName mComponentName;
+
+ UninstallApp(Launcher target, ItemInfo itemInfo, View originalView,
+ @NonNull ComponentName cn) {
+ super(R.drawable.ic_uninstall_no_shadow, R.string.uninstall_drop_target_label, target,
+ itemInfo, originalView);
+ mContext = target.getApplicationContext();
+ mComponentName = cn;
+
+ }
+
+ @Override
+ public void onClick(View view) {
+ dismissTaskMenuView(mTarget);
+ SecondaryDropTarget.performUninstall(mContext, mComponentName, mItemInfo);
+ mTarget.getStatsLogManager()
+ .logger()
+ .withItemInfo(mItemInfo)
+ .log(LAUNCHER_PRIVATE_SPACE_UNINSTALL_SYSTEM_SHORTCUT_TAP);
+ }
+ }
+
public static <T extends Context & ActivityContext> void dismissTaskMenuView(T activity) {
AbstractFloatingView.closeOpenViews(activity, true,
AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDragController.java b/src/com/android/launcher3/secondarydisplay/SecondaryDragController.java
index 8d1d96b..79b25a4 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDragController.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDragController.java
@@ -16,6 +16,8 @@
package com.android.launcher3.secondarydisplay;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
import android.content.res.Resources;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -129,6 +131,10 @@
dragView.show(mLastTouch.x, mLastTouch.y);
mDistanceSinceScroll = 0;
+ if (!isItemPinnable()) {
+ MAIN_EXECUTOR.post(this:: cancelDrag);
+ }
+
if (!mIsInPreDrag) {
callOnDragStart();
} else if (mOptions.preDragCondition != null) {
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 315b5e3..5636405 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -150,6 +150,12 @@
}, this::getCurrentActivity);
}
+ case TestProtocol.REQUEST_CELL_LAYOUT_BOARDER_HEIGHT: {
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+ mDeviceProfile.cellLayoutBorderSpacePx.y);
+ return response;
+ }
+
case TestProtocol.REQUEST_SYSTEM_GESTURE_REGION: {
return getUIProperty(Bundle::putParcelable, activity -> {
WindowInsetsCompat insets = WindowInsetsCompat.toWindowInsetsCompat(
diff --git a/src/com/android/launcher3/touch/DefaultPagedViewHandler.java b/src/com/android/launcher3/touch/DefaultPagedViewHandler.java
new file mode 100644
index 0000000..272ed10
--- /dev/null
+++ b/src/com/android/launcher3/touch/DefaultPagedViewHandler.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.touch;
+
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.launcher3.Utilities;
+
+public class DefaultPagedViewHandler implements PagedOrientationHandler {
+ @Override
+ public int getPrimaryValue(int x, int y) {
+ return x;
+ }
+
+ @Override
+ public int getSecondaryValue(int x, int y) {
+ return y;
+ }
+
+ @Override
+ public float getPrimaryValue(float x, float y) {
+ return x;
+ }
+
+ @Override
+ public float getSecondaryValue(float x, float y) {
+ return y;
+ }
+
+ @Override
+ public <T> void setPrimary(T target, Int2DAction<T> action, int param) {
+ action.call(target, param, 0);
+ }
+
+ @Override
+ public <T> void setPrimary(T target, Float2DAction<T> action, float param) {
+ action.call(target, param, 0);
+ }
+
+ @Override
+ public float getPrimaryDirection(MotionEvent event, int pointerIndex) {
+ return event.getX(pointerIndex);
+ }
+
+ @Override
+ public float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId) {
+ return velocityTracker.getXVelocity(pointerId);
+ }
+
+ @Override
+ public int getMeasuredSize(View view) {
+ return view.getMeasuredWidth();
+ }
+
+ @Override
+ public int getPrimaryScroll(View view) {
+ return view.getScrollX();
+ }
+
+ @Override
+ public float getPrimaryScale(View view) {
+ return view.getScaleX();
+ }
+
+ @Override
+ public void setMaxScroll(AccessibilityEvent event, int maxScroll) {
+ event.setMaxScrollX(maxScroll);
+ }
+
+ @Override
+ public boolean getRecentsRtlSetting(Resources resources) {
+ return !Utilities.isRtl(resources);
+ }
+
+ @Override
+ public int getChildStart(View view) {
+ return view.getLeft();
+ }
+
+ @Override
+ public int getCenterForPage(View view, Rect insets) {
+ return (view.getPaddingTop() + view.getMeasuredHeight() + insets.top
+ - insets.bottom - view.getPaddingBottom()) / 2;
+ }
+
+ @Override
+ public int getScrollOffsetStart(View view, Rect insets) {
+ return insets.left + view.getPaddingLeft();
+ }
+
+ @Override
+ public int getScrollOffsetEnd(View view, Rect insets) {
+ return view.getWidth() - view.getPaddingRight() - insets.right;
+ }
+
+ @Override
+ public ChildBounds getChildBounds(View child, int childStart, int pageCenter,
+ boolean layoutChild) {
+ final int childWidth = child.getMeasuredWidth();
+ final int childRight = childStart + childWidth;
+ final int childHeight = child.getMeasuredHeight();
+ final int childTop = pageCenter - childHeight / 2;
+ if (layoutChild) {
+ child.layout(childStart, childTop, childRight, childTop + childHeight);
+ }
+ return new ChildBounds(childWidth, childHeight, childRight, childTop);
+ }
+
+}
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 9d50e6a..f47ab90 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -149,7 +149,20 @@
private static void onClickAppPairIcon(View v) {
Launcher launcher = Launcher.getLauncher(v.getContext());
AppPairIcon appPairIcon = (AppPairIcon) v;
- launcher.launchAppPair(appPairIcon);
+ if (appPairIcon.getInfo().isDisabled()) {
+ WorkspaceItemInfo app1 = appPairIcon.getInfo().contents.get(0);
+ WorkspaceItemInfo app2 = appPairIcon.getInfo().contents.get(1);
+ // Show the user why the app pair is disabled.
+ if (app1.isDisabled() && !handleDisabledItemClicked(app1, launcher)) {
+ // If handleDisabledItemClicked() did not handle the error message, we initiate an
+ // app launch so Framework can tell the user why the app is suspended.
+ onClickAppShortcut(v, app1, launcher);
+ } else if (app2.isDisabled() && !handleDisabledItemClicked(app2, launcher)) {
+ onClickAppShortcut(v, app2, launcher);
+ }
+ } else {
+ launcher.launchAppPair(appPairIcon);
+ }
}
/**
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index 74d88ba..e0c4e3c 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -19,26 +19,11 @@
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Matrix;
-import android.graphics.PointF;
import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.ShapeDrawable;
-import android.util.FloatProperty;
-import android.util.Pair;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.util.SplitConfigurationOptions;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
-import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
-
-import java.util.List;
/**
* Abstraction layer to separate horizontal and vertical specific implementations
@@ -47,9 +32,7 @@
*/
public interface PagedOrientationHandler {
- PagedOrientationHandler PORTRAIT = new PortraitPagedViewHandler();
- PagedOrientationHandler LANDSCAPE = new LandscapePagedViewHandler();
- PagedOrientationHandler SEASCAPE = new SeascapePagedViewHandler();
+ PagedOrientationHandler DEFAULT = new DefaultPagedViewHandler();
interface Int2DAction<T> {
void call(T target, int x, int y);
@@ -64,39 +47,18 @@
<T> void setPrimary(T target, Int2DAction<T> action, int param);
<T> void setPrimary(T target, Float2DAction<T> action, float param);
- <T> void setSecondary(T target, Float2DAction<T> action, float param);
- <T> void set(T target, Int2DAction<T> action, int primaryParam, int secondaryParam);
float getPrimaryDirection(MotionEvent event, int pointerIndex);
float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId);
int getMeasuredSize(View view);
- int getPrimarySize(View view);
- float getPrimarySize(RectF rect);
- float getStart(RectF rect);
- float getEnd(RectF rect);
- int getClearAllSidePadding(View view, boolean isRtl);
- int getSecondaryDimension(View view);
- FloatProperty<View> getPrimaryViewTranslate();
- FloatProperty<View> getSecondaryViewTranslate();
-
int getPrimaryScroll(View view);
float getPrimaryScale(View view);
int getChildStart(View view);
int getCenterForPage(View view, Rect insets);
int getScrollOffsetStart(View view, Rect insets);
int getScrollOffsetEnd(View view, Rect insets);
- int getSecondaryTranslationDirectionFactor();
- int getSplitTranslationDirectionFactor(@StagePosition int stagePosition,
- DeviceProfile deviceProfile);
ChildBounds getChildBounds(View child, int childStart, int pageCenter, boolean layoutChild);
void setMaxScroll(AccessibilityEvent event, int maxScroll);
boolean getRecentsRtlSetting(Resources resources);
- float getDegreesRotated();
- int getRotation();
- void setPrimaryScale(View view, float scale);
- void setSecondaryScale(View view, float scale);
-
- <T> T getPrimaryValue(T x, T y);
- <T> T getSecondaryValue(T x, T y);
int getPrimaryValue(int x, int y);
int getSecondaryValue(int x, int y);
@@ -104,174 +66,6 @@
float getPrimaryValue(float x, float y);
float getSecondaryValue(float x, float y);
- boolean isLayoutNaturalToLauncher();
- Pair<FloatProperty, FloatProperty> getSplitSelectTaskOffset(FloatProperty primary,
- FloatProperty secondary, DeviceProfile deviceProfile);
- int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect);
- List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp);
- /**
- * @param placeholderHeight height of placeholder view in portrait, width in landscape
- */
- void getInitialSplitPlaceholderBounds(int placeholderHeight, int placeholderInset,
- DeviceProfile dp, @StagePosition int stagePosition, Rect out);
-
- /**
- * Centers an icon in the split staging area, accounting for insets.
- * @param out The icon that needs to be centered.
- * @param onScreenRectCenterX The x-center of the on-screen staging area (most of the Rect is
- * offscreen).
- * @param onScreenRectCenterY The y-center of the on-screen staging area (most of the Rect is
- * offscreen).
- * @param fullscreenScaleX A x-scaling factor used to convert coordinates back into pixels.
- * @param fullscreenScaleY A y-scaling factor used to convert coordinates back into pixels.
- * @param drawableWidth The icon's drawable (final) width.
- * @param drawableHeight The icon's drawable (final) height.
- * @param dp The device profile, used to report rotation and hardware insets.
- * @param stagePosition 0 if the staging area is pinned to top/left, 1 for bottom/right.
- */
- void updateSplitIconParams(View out, float onScreenRectCenterX,
- float onScreenRectCenterY, float fullscreenScaleX, float fullscreenScaleY,
- int drawableWidth, int drawableHeight, DeviceProfile dp,
- @StagePosition int stagePosition);
-
- /**
- * Sets positioning and rotation for a SplitInstructionsView.
- * @param out The SplitInstructionsView that needs to be positioned.
- * @param dp The device profile, used to report rotation and device type.
- * @param splitInstructionsHeight The SplitInstructionView's height.
- * @param splitInstructionsWidth The SplitInstructionView's width.
- */
- void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
- int splitInstructionsWidth);
-
- /**
- * @param splitDividerSize height of split screen drag handle in portrait, width in landscape
- * @param stagePosition the split position option (top/left, bottom/right) of the first
- * task selected for entering split
- * @param out1 the bounds for where the first selected app will be
- * @param out2 the bounds for where the second selected app will be, complimentary to
- * {@param out1} based on {@param initialSplitOption}
- */
- void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
- @StagePosition int stagePosition, Rect out1, Rect out2);
-
- int getDefaultSplitPosition(DeviceProfile deviceProfile);
-
- /**
- * @param outRect This is expected to be the rect that has the dimensions for a non-split,
- * fullscreen task in overview. This will directly be modified.
- * @param desiredStagePosition Which stage position (topLeft/rightBottom) we want to resize
- * outRect for
- */
- void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect, SplitBounds splitInfo,
- @SplitConfigurationOptions.StagePosition int desiredStagePosition);
-
- void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
- int parentWidth, int parentHeight,
- SplitBounds splitBoundsConfig, DeviceProfile dp, boolean isRtl);
-
- // Overview TaskMenuView methods
- void setTaskIconParams(FrameLayout.LayoutParams iconParams,
- int taskIconMargin, int taskIconHeight, int thumbnailTopMargin, boolean isRtl);
- void setIconAppChipMenuParams(View iconAppChipMenuView, FrameLayout.LayoutParams iconMenuParams,
- int iconMenuMargin, int thumbnailTopMargin);
- void setSplitIconParams(View primaryIconView, View secondaryIconView,
- int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
- int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
- DeviceProfile deviceProfile, SplitBounds splitConfig);
-
- /*
- * The following two methods try to center the TaskMenuView in landscape by finding the center
- * of the thumbnail view and then subtracting half of the taskMenu width. In this case, the
- * taskMenu width is the same size as the thumbnail width (what got set below in
- * getTaskMenuWidth()), so we directly use that in the calculations.
- */
- float getTaskMenuX(float x, View thumbnailView, DeviceProfile deviceProfile,
- float taskInsetMargin, View taskViewIcon);
- float getTaskMenuY(float y, View thumbnailView, int stagePosition,
- View taskMenuView, float taskInsetMargin, View taskViewIcon);
- int getTaskMenuWidth(View thumbnailView, DeviceProfile deviceProfile,
- @StagePosition int stagePosition);
- /**
- * Sets linear layout orientation for {@link com.android.launcher3.popup.SystemShortcut} items
- * inside task menu view.
- */
- void setTaskOptionsMenuLayoutOrientation(DeviceProfile deviceProfile,
- LinearLayout taskMenuLayout, int dividerSpacing,
- ShapeDrawable dividerDrawable);
- /**
- * Sets layout param attributes for {@link com.android.launcher3.popup.SystemShortcut} child
- * views inside task menu view.
- */
- void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp,
- LinearLayout viewGroup, DeviceProfile deviceProfile);
-
- /**
- * Calculates the position where a Digital Wellbeing Banner should be placed on its parent
- * TaskView.
- * @return A Pair of Floats representing the proper x and y translations.
- */
- Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth,
- int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile,
- View[] thumbnailViews, int desiredTaskId, View banner);
-
- // The following are only used by TaskViewTouchHandler.
- /** @return Either VERTICAL or HORIZONTAL. */
- SingleAxisSwipeDetector.Direction getUpDownSwipeDirection();
- /** @return Given {@link #getUpDownSwipeDirection()}, whether POSITIVE or NEGATIVE is up. */
- int getUpDirection(boolean isRtl);
- /** @return Whether the displacement is going towards the top of the screen. */
- boolean isGoingUp(float displacement, boolean isRtl);
- /** @return Either 1 or -1, a factor to multiply by so the animation goes the correct way. */
- int getTaskDragDisplacementFactor(boolean isRtl);
-
- /**
- * Maps the velocity from the coordinate plane of the foreground app to that
- * of Launcher's (which now will always be portrait)
- */
- void adjustFloatingIconStartVelocity(PointF velocity);
-
- /**
- * Ensures that outStartRect left bound is within the DeviceProfile's visual boundaries
- * @param outStartRect The start rect that will directly be modified
- */
- void fixBoundsForHomeAnimStartRect(RectF outStartRect, DeviceProfile deviceProfile);
-
- /**
- * Determine the target translation for animating the FloatingTaskView out. This value could
- * either be an x-coordinate or a y-coordinate, depending on which way the FloatingTaskView was
- * docked.
- *
- * @param floatingTask The FloatingTaskView.
- * @param onScreenRect The current on-screen dimensions of the FloatingTaskView.
- * @param stagePosition STAGE_POSITION_TOP_OR_LEFT or STAGE_POSITION_BOTTOM_OR_RIGHT.
- * @param dp The device profile.
- * @return A float. When an animation translates the FloatingTaskView to this position, it will
- * appear to tuck away off the edge of the screen.
- */
- float getFloatingTaskOffscreenTranslationTarget(View floatingTask, RectF onScreenRect,
- @StagePosition int stagePosition, DeviceProfile dp);
-
- /**
- * Sets the translation of a FloatingTaskView along its "slide-in/slide-out" axis (could be
- * either x or y), depending on how the view is oriented.
- *
- * @param floatingTask The FloatingTaskView to be translated.
- * @param translation The target translation value.
- * @param dp The current device profile.
- */
- void setFloatingTaskPrimaryTranslation(View floatingTask, float translation, DeviceProfile dp);
-
- /**
- * Gets the translation of a FloatingTaskView along its "slide-in/slide-out" axis (could be
- * either x or y), depending on how the view is oriented.
- *
- * @param floatingTask The FloatingTaskView in question.
- * @param dp The current device profile.
- * @return The current translation value.
- */
- Float getFloatingTaskPrimaryTranslation(View floatingTask, DeviceProfile dp);
-
class ChildBounds {
public final int primaryDimension;
@@ -279,8 +73,8 @@
public final int childPrimaryEnd;
public final int childSecondaryEnd;
- ChildBounds(int primaryDimension, int secondaryDimension, int childPrimaryEnd,
- int childSecondaryEnd) {
+ public ChildBounds(int primaryDimension, int secondaryDimension, int childPrimaryEnd,
+ int childSecondaryEnd) {
this.primaryDimension = primaryDimension;
this.secondaryDimension = secondaryDimension;
this.childPrimaryEnd = childPrimaryEnd;
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 2b5aaf5..50d8886 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -16,6 +16,8 @@
package com.android.launcher3.util;
+import static com.android.launcher3.Flags.enableSupportForArchiving;
+
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
@@ -112,8 +114,7 @@
@NonNull final UserHandle user, final int flags) {
try {
ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, flags, user);
- return (info.flags & ApplicationInfo.FLAG_INSTALLED) == 0 || !info.enabled
- ? null : info;
+ return !isPackageInstalledOrArchived(info) || !info.enabled ? null : info;
} catch (PackageManager.NameNotFoundException e) {
return null;
}
@@ -253,4 +254,11 @@
}
return 100;
}
+
+ /** Returns true in case app is installed on the device or in archived state. */
+ @SuppressWarnings("NewApi")
+ private boolean isPackageInstalledOrArchived(ApplicationInfo info) {
+ return (info.flags & ApplicationInfo.FLAG_INSTALLED) != 0 || (
+ enableSupportForArchiving() && info.isArchived);
+ }
}
diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java
index fef1c72..e1695e9 100644
--- a/src/com/android/launcher3/util/VibratorWrapper.java
+++ b/src/com/android/launcher3/util/VibratorWrapper.java
@@ -19,11 +19,11 @@
import static android.os.VibrationEffect.createPredefined;
import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT;
+import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_DELAY;
+import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_END_SCALE_PERCENT;
+import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_ITERATIONS;
+import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_SCALE_EXPONENT;
+import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_START_SCALE_PERCENT;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
@@ -39,7 +39,6 @@
import androidx.annotation.Nullable;
-import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
@@ -257,17 +256,11 @@
public void vibrateForSearchHint() {
if (FeatureFlags.ENABLE_SEARCH_HAPTIC_HINT.get() && Utilities.ATLEAST_S
&& mVibrator.areAllPrimitivesSupported(PRIMITIVE_LOW_TICK)) {
- LauncherPrefs launcherPrefs = LauncherPrefs.get(mContext);
- float startScale = launcherPrefs.get(
- LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT) / 100f;
- float endScale = launcherPrefs.get(
- LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT) / 100f;
- int scaleExponent = launcherPrefs.get(
- LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT);
- int iterations = launcherPrefs.get(
- LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS);
- int delayMs = launcherPrefs.get(
- LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY);
+ float startScale = LPNH_HAPTIC_HINT_START_SCALE_PERCENT.get() / 100f;
+ float endScale = LPNH_HAPTIC_HINT_END_SCALE_PERCENT.get() / 100f;
+ int scaleExponent = LPNH_HAPTIC_HINT_SCALE_EXPONENT.get();
+ int iterations = LPNH_HAPTIC_HINT_ITERATIONS.get();
+ int delayMs = LPNH_HAPTIC_HINT_DELAY.get();
VibrationEffect.Composition composition = VibrationEffect.startComposition();
for (int i = 0; i < iterations; i++) {
diff --git a/src/com/android/launcher3/widget/picker/OWNERS b/src/com/android/launcher3/widget/picker/OWNERS
new file mode 100644
index 0000000..6aabbfa
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/OWNERS
@@ -0,0 +1,16 @@
+set noparent
+
+# Bug component: 1481801
+
+# People who can approve changes for submission
+#
+
+# Widget Picker OWNERS
+zakcohen@google.com
+shamalip@google.com
+wvk@google.com
+
+# Launcher OWNERS
+captaincole@google.com
+sunnygoyal@google.com
+
diff --git a/src/com/android/launcher3/widget/picker/WidgetRecommendationCategory.java b/src/com/android/launcher3/widget/picker/WidgetRecommendationCategory.java
new file mode 100644
index 0000000..072d1d5
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetRecommendationCategory.java
@@ -0,0 +1,62 @@
+/*
+ * 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.widget.picker;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+
+import java.util.Objects;
+
+/**
+ * A category of widget recommendations displayed in the widget picker (launched from "Widgets"
+ * option in the pop-up opened on long press of launcher workspace).
+ */
+public class WidgetRecommendationCategory implements Comparable<WidgetRecommendationCategory> {
+ /** Resource id that holds the user friendly label for the category. */
+ @StringRes
+ public final int categoryTitleRes;
+ /**
+ * Relative order of this category with respect to other categories.
+ *
+ * <p>Category with lowest order is displayed first in the recommendations section.</p>
+ */
+ public final int order;
+
+ public WidgetRecommendationCategory(@StringRes int categoryTitleRes, int order) {
+ this.categoryTitleRes = categoryTitleRes;
+ this.order = order;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(categoryTitleRes, order);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof WidgetRecommendationCategory category)) {
+ return false;
+ }
+ return categoryTitleRes == category.categoryTitleRes
+ && order == category.order;
+ }
+
+ @Override
+ public int compareTo(WidgetRecommendationCategory widgetRecommendationCategory) {
+ return order - widgetRecommendationCategory.order;
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProvider.java b/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProvider.java
new file mode 100644
index 0000000..d99c5d1
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProvider.java
@@ -0,0 +1,126 @@
+/*
+ * 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.widget.picker;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+import androidx.annotation.WorkerThread;
+
+import com.android.launcher3.R;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.ResourceBasedOverride;
+
+/**
+ * A {@link ResourceBasedOverride} that categorizes widget recommendations.
+ *
+ * <p>Override the {@code widget_recommendation_category_provider_class} resource to provide your
+ * own implementation. Method {@code getWidgetRecommendationCategory} is called per widget to get
+ * the category.</p>
+ */
+public class WidgetRecommendationCategoryProvider implements ResourceBasedOverride {
+ private static final String TAG = "WidgetRecommendationCategoryProvider";
+
+ /**
+ * Retrieve instance of this object that can be overridden in runtime based on the build
+ * variant of the application.
+ */
+ public static WidgetRecommendationCategoryProvider newInstance(Context context) {
+ Preconditions.assertWorkerThread();
+ return Overrides.getObject(
+ WidgetRecommendationCategoryProvider.class, context.getApplicationContext(),
+ R.string.widget_recommendation_category_provider_class);
+ }
+
+ /**
+ * Returns a {@link WidgetRecommendationCategory} for the provided widget item that can be used
+ * to display the recommendation grouped by categories.
+ */
+ @WorkerThread
+ public WidgetRecommendationCategory getWidgetRecommendationCategory(Context context,
+ WidgetItem item) {
+ // This is a default implementation that uses application category to derive the category to
+ // be displayed. The implementation can be overridden in individual launcher customization
+ // via the overridden WidgetRecommendationCategoryProvider resource.
+
+ Preconditions.assertWorkerThread();
+ PackageManager pm = context.getPackageManager();
+ if (item.widgetInfo != null && item.widgetInfo.getComponent() != null) {
+ String widgetComponentName = item.widgetInfo.getComponent().getClassName();
+ try {
+ int predictionCategory = pm.getApplicationInfo(
+ item.widgetInfo.getComponent().getPackageName(), 0 /* flags */).category;
+ return getCategoryFromApplicationCategory(context, predictionCategory,
+ widgetComponentName);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Failed to retrieve application category when determining the "
+ + "widget category for " + widgetComponentName, e);
+ }
+ }
+ return null;
+ }
+
+ /** Maps application category to an appropriate displayable category. */
+ private static WidgetRecommendationCategory getCategoryFromApplicationCategory(
+ Context context, int applicationCategory, String componentName) {
+ if (applicationCategory == ApplicationInfo.CATEGORY_PRODUCTIVITY) {
+ return new WidgetRecommendationCategory(
+ R.string.productivity_widget_recommendation_category_label, /*order=*/0);
+ }
+
+ if (applicationCategory == ApplicationInfo.CATEGORY_NEWS) {
+ return new WidgetRecommendationCategory(
+ R.string.news_widget_recommendation_category_label, /*order=*/1);
+ }
+
+ if (applicationCategory == ApplicationInfo.CATEGORY_SOCIAL
+ || applicationCategory == ApplicationInfo.CATEGORY_AUDIO
+ || applicationCategory == ApplicationInfo.CATEGORY_VIDEO
+ || applicationCategory == ApplicationInfo.CATEGORY_IMAGE) {
+ return new WidgetRecommendationCategory(
+ R.string.social_and_entertainment_widget_recommendation_category_label,
+ /*order=*/4);
+ }
+
+ // Fitness & weather categories don't map to a specific application category, so, we
+ // maintain an allowlist.
+ String[] weatherRecommendationAllowlist =
+ context.getResources().getStringArray(R.array.weather_recommendations);
+ for (String allowedWeatherComponentName : weatherRecommendationAllowlist) {
+ if (componentName.equalsIgnoreCase(allowedWeatherComponentName)) {
+ return new WidgetRecommendationCategory(
+ R.string.weather_widget_recommendation_category_label, /*order=*/2);
+ }
+ }
+
+ String[] fitnessRecommendationAllowlist =
+ context.getResources().getStringArray(R.array.fitness_recommendations);
+ for (String allowedFitnessComponentName : fitnessRecommendationAllowlist) {
+ if (componentName.equalsIgnoreCase(allowedFitnessComponentName)) {
+ return new WidgetRecommendationCategory(
+ R.string.fitness_widget_recommendation_category_label, /*order=*/3);
+ }
+ }
+
+ return new WidgetRecommendationCategory(
+ R.string.others_widget_recommendation_category_label, /*order=*/5);
+ }
+
+}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/flags/FlagsFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/flags/FlagsFactory.java
index eb0494e..b193d37 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/flags/FlagsFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/flags/FlagsFactory.java
@@ -18,6 +18,9 @@
import static com.android.launcher3.config.FeatureFlags.FlagState.ENABLED;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.ConstantItem;
import com.android.launcher3.config.FeatureFlags.BooleanFlag;
import com.android.launcher3.config.FeatureFlags.FlagState;
import com.android.launcher3.config.FeatureFlags.IntFlag;
@@ -55,6 +58,15 @@
}
/**
+ * Creates a new debug integer flag and it is saved in LauncherPrefs.
+ */
+ public static IntFlag getIntFlag(
+ int bugId, String key, int defaultValueInCode, String description,
+ @Nullable ConstantItem<Integer> launcherPrefFlag) {
+ return new IntFlag(defaultValueInCode);
+ }
+
+ /**
* Dumps the current flags state to the print writer
*/
public static void dump(PrintWriter pw) { }
diff --git a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index 3e188e6..2f9945d 100644
--- a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -121,6 +121,7 @@
public static final String REQUEST_IS_TABLET = "is-tablet";
public static final String REQUEST_NUM_ALL_APPS_COLUMNS = "num-all-apps-columns";
public static final String REQUEST_IS_TWO_PANELS = "is-two-panel";
+ public static final String REQUEST_CELL_LAYOUT_BOARDER_HEIGHT = "cell-layout-boarder-height";
public static final String REQUEST_START_DRAG_THRESHOLD = "start-drag-threshold";
public static final String REQUEST_SHELL_DRAG_READY = "shell-drag-ready";
public static final String REQUEST_GET_ACTIVITIES_CREATED_COUNT =
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestUtils.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestUtils.java
index d8ae74b..9fb1989 100644
--- a/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestUtils.java
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestUtils.java
@@ -41,7 +41,7 @@
CellPosMapper.CellPos pos = launcher.getCellPosMapper().mapPresenterToModel(
params.getCellX(), params.getCellY(),
- launcher.getWorkspace().getIdForScreen(cellLayout), CONTAINER_DESKTOP);
+ launcher.getWorkspace().getCellLayoutId(cellLayout), CONTAINER_DESKTOP);
int screenId = pos.screenId;
for (int j = boards.size(); j <= screenId; j++) {
boards.add(new CellLayoutBoard(cellLayout.getCountX(), cellLayout.getCountY()));
diff --git a/tests/multivalentTests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/multivalentTests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 79d8c60..f0d2e20 100644
--- a/tests/multivalentTests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -370,7 +370,8 @@
Assert.assertTrue("Setup wizard is still visible", wizardDismissed);
}
- private static void verifyKeyguardInvisible() {
+ /** Asserts that keyguard is not visible */
+ public static void verifyKeyguardInvisible() {
final boolean keyguardAlreadyVisible = sSeenKeyguard;
sSeenKeyguard = sSeenKeyguard
diff --git a/tests/multivalentTests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/multivalentTests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
index d94e4a5..8eebdb2 100644
--- a/tests/multivalentTests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
+++ b/tests/multivalentTests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
@@ -44,6 +44,9 @@
@Override
public void evaluate() throws Throwable {
try {
+ // we expect to begin unlocked...
+ AbstractLauncherUiTest.verifyKeyguardInvisible();
+
mTest.mDevice.pressHome();
mTest.waitForLauncherCondition("Launcher activity wasn't created",
launcher -> launcher != null);
@@ -66,6 +69,9 @@
}
});
mTest.mLauncher.setExpectedRotation(Surface.ROTATION_0);
+
+ // and end unlocked...
+ AbstractLauncherUiTest.verifyKeyguardInvisible();
}
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/TestConstants.java b/tests/multivalentTests/src/com/android/launcher3/util/TestConstants.java
index 6f3c63a..bf5ccd0 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/TestConstants.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/TestConstants.java
@@ -23,6 +23,7 @@
public static final String MAPS_APP_NAME = "Maps";
public static final String STORE_APP_NAME = "Play Store";
public static final String GMAIL_APP_NAME = "Gmail";
+ public static final String PHOTOS_APP_NAME = "Photos";
public static final String CHROME_APP_NAME = "Chrome";
public static final String MESSAGES_APP_NAME = "Messages";
}
diff --git a/tests/multivalentTests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/multivalentTests/tapl/com/android/launcher3/tapl/BaseOverview.java
index 1bc489c..d337b91 100644
--- a/tests/multivalentTests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/multivalentTests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -19,9 +19,11 @@
import static android.view.KeyEvent.KEYCODE_ESCAPE;
import static com.android.launcher3.tapl.LauncherInstrumentation.TASKBAR_RES_ID;
+import static com.android.launcher3.tapl.OverviewTask.TASK_START_EVENT;
import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
import android.graphics.Rect;
+import android.view.KeyEvent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -47,6 +49,10 @@
"Key event: KeyEvent.*?action=ACTION_DOWN.*?keyCode=KEYCODE_ESCAPE.*?metaState=0");
private static final Pattern EVENT_ALT_ESC_UP = Pattern.compile(
"Key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_ESCAPE.*?metaState=0");
+ private static final Pattern EVENT_ENTER_DOWN = Pattern.compile(
+ "Key event: KeyEvent.*?action=ACTION_DOWN.*?keyCode=KEYCODE_ENTER");
+ private static final Pattern EVENT_ENTER_UP = Pattern.compile(
+ "Key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_ENTER");
private static final int FLINGS_FOR_DISMISS_LIMIT = 40;
@@ -414,6 +420,31 @@
}
}
+ /**
+ * Presses the enter key to launch the focused task
+ * <p>
+ * If no task is focused, this will fail.
+ */
+ public LaunchedAppState launchFocusedTaskByEnterKey(@NonNull String expectedPackageName) {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ENTER_DOWN);
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ENTER_UP);
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
+
+ mLauncher.executeAndWaitForLauncherStop(
+ () -> mLauncher.assertTrue(
+ "Failed to press enter",
+ mLauncher.getDevice().pressKeyCode(KeyEvent.KEYCODE_ENTER)),
+ "pressing enter");
+ mLauncher.assertAppLaunched(expectedPackageName);
+
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "pressed enter")) {
+ return new LaunchedAppState(mLauncher);
+ }
+ }
+ }
+
private void verifyActionsViewVisibility() {
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"want to assert overview actions view visibility")) {
diff --git a/tests/multivalentTests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/multivalentTests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index fef93b7..6c9f5ed 100644
--- a/tests/multivalentTests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/multivalentTests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -405,6 +405,11 @@
.getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
+ int getCellLayoutBoarderHeight() {
+ return getTestInfo(TestProtocol.REQUEST_CELL_LAYOUT_BOARDER_HEIGHT)
+ .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+ }
+
int getFocusedTaskHeightForTablet() {
return getTestInfo(TestProtocol.REQUEST_GET_FOCUSED_TASK_HEIGHT_FOR_TABLET).getInt(
TestProtocol.TEST_INFO_RESPONSE_FIELD);
diff --git a/tests/multivalentTests/tapl/com/android/launcher3/tapl/WidgetResizeFrame.java b/tests/multivalentTests/tapl/com/android/launcher3/tapl/WidgetResizeFrame.java
index d0573e0..3895302 100644
--- a/tests/multivalentTests/tapl/com/android/launcher3/tapl/WidgetResizeFrame.java
+++ b/tests/multivalentTests/tapl/com/android/launcher3/tapl/WidgetResizeFrame.java
@@ -57,7 +57,8 @@
Rect originalWidgetSize = widget.getVisibleBounds();
Point targetStart = bottomResizeHandle.getVisibleCenter();
Point targetDest = bottomResizeHandle.getVisibleCenter();
- targetDest.offset(0, originalWidgetSize.height());
+ targetDest.offset(0,
+ originalWidgetSize.height() + mLauncher.getCellLayoutBoarderHeight());
final long downTime = SystemClock.uptimeMillis();
mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetStart,
diff --git a/tests/src/com/android/launcher3/celllayout/UnitTestCellLayoutBuilderRule.kt b/tests/src/com/android/launcher3/celllayout/UnitTestCellLayoutBuilderRule.kt
index 0f0dd65..b63966d 100644
--- a/tests/src/com/android/launcher3/celllayout/UnitTestCellLayoutBuilderRule.kt
+++ b/tests/src/com/android/launcher3/celllayout/UnitTestCellLayoutBuilderRule.kt
@@ -21,6 +21,7 @@
import android.view.View
import androidx.test.core.app.ApplicationProvider
import com.android.launcher3.CellLayout
+import com.android.launcher3.CellLayoutContainer
import com.android.launcher3.DeviceProfile
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.MultipageCellLayout
@@ -40,6 +41,17 @@
private val applicationContext =
ActivityContextWrapper(ApplicationProvider.getApplicationContext())
+ private val container =
+ object : CellLayoutContainer {
+ override fun getCellLayoutId(cellLayout: CellLayout): Int = 0
+
+ override fun getCellLayoutIndex(cellLayout: CellLayout): Int = 0
+
+ override fun getPanelCount(): Int = 1
+
+ override fun getPageDescription(pageIndex: Int): String = ""
+ }
+
override fun starting(description: Description?) {
val dp = getDeviceProfile()
prevNumColumns = dp.inv.numColumns
@@ -60,7 +72,7 @@
dp.cellLayoutBorderSpacePx = Point(0, 0)
val cl =
if (isMulti) MultipageCellLayout(getWrappedContext(applicationContext, dp))
- else CellLayout(getWrappedContext(applicationContext, dp))
+ else CellLayout(getWrappedContext(applicationContext, dp), container)
// I put a very large number for width and height so that all the items can fit, it doesn't
// need to be exact, just bigger than the sum of cell border
cl.measure(
diff --git a/tests/src/com/android/launcher3/dragging/TaplDragTest.java b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
index 94a96aa..b633452 100644
--- a/tests/src/com/android/launcher3/dragging/TaplDragTest.java
+++ b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
@@ -15,11 +15,11 @@
*/
package com.android.launcher3.dragging;
-import static com.android.launcher3.util.TestConstants.AppNames.TEST_APP_NAME;
import static com.android.launcher3.util.TestConstants.AppNames.GMAIL_APP_NAME;
+import static com.android.launcher3.util.TestConstants.AppNames.PHOTOS_APP_NAME;
import static com.android.launcher3.util.TestConstants.AppNames.MAPS_APP_NAME;
import static com.android.launcher3.util.TestConstants.AppNames.STORE_APP_NAME;
-import static com.android.launcher3.ui.AbstractLauncherUiTest.initialize;
+import static com.android.launcher3.util.TestConstants.AppNames.TEST_APP_NAME;
import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
@@ -80,18 +80,18 @@
// TODO: add the use case to drag an icon to an existing folder. Currently it either fails
// on tablets or phones due to difference in resolution.
final HomeAppIcon playStoreIcon = createShortcutIfNotExist(STORE_APP_NAME, 0, 1);
- final HomeAppIcon gmailIcon = createShortcutInCenterIfNotExist(GMAIL_APP_NAME);
+ final HomeAppIcon photosIcon = createShortcutInCenterIfNotExist(PHOTOS_APP_NAME);
- FolderIcon folderIcon = gmailIcon.dragToIcon(playStoreIcon);
+ FolderIcon folderIcon = photosIcon.dragToIcon(playStoreIcon);
Folder folder = folderIcon.open();
folder.getAppIcon(STORE_APP_NAME);
- folder.getAppIcon(GMAIL_APP_NAME);
+ folder.getAppIcon(PHOTOS_APP_NAME);
Workspace workspace = folder.close();
workspace.verifyWorkspaceAppIconIsGone(STORE_APP_NAME + " should be moved to a folder.",
STORE_APP_NAME);
- workspace.verifyWorkspaceAppIconIsGone(GMAIL_APP_NAME + " should be moved to a folder.",
- GMAIL_APP_NAME);
+ workspace.verifyWorkspaceAppIconIsGone(PHOTOS_APP_NAME + " should be moved to a folder.",
+ PHOTOS_APP_NAME);
final HomeAppIcon mapIcon = createShortcutInCenterIfNotExist(MAPS_APP_NAME);
folderIcon = mapIcon.dragToIcon(folderIcon);
diff --git a/tests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt b/tests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
new file mode 100644
index 0000000..e94dc02
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
@@ -0,0 +1,290 @@
+/*
+ * 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.model
+
+import android.appwidget.AppWidgetProviderInfo
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.LauncherApps
+import android.content.pm.PackageInstaller
+import android.content.pm.ShortcutInfo
+import android.os.UserHandle
+import android.util.LongSparseArray
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherSettings.Favorites
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError.Companion.MISSING_INFO
+import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError.Companion.PROFILE_DELETED
+import com.android.launcher3.model.data.IconRequestInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.shortcuts.ShortcutKey
+import com.android.launcher3.util.ComponentKey
+import com.android.launcher3.util.PackageManagerHelper
+import com.android.launcher3.util.PackageUserKey
+import com.android.launcher3.widget.WidgetInflater
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.RETURNS_DEEP_STUBS
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+class WorkspaceItemProcessorTest {
+ private var itemProcessor = createTestWorkspaceItemProcessor()
+
+ @Before
+ fun setup() {
+ itemProcessor = createTestWorkspaceItemProcessor()
+ }
+
+ @Test
+ fun `When user is null then mark item deleted`() {
+ // Given
+ val mockCursor = mock<LoaderCursor>().apply { id = 1 }
+ val itemProcessor = createTestWorkspaceItemProcessor(cursor = mockCursor)
+ // When
+ itemProcessor.processItem()
+ // Then
+ verify(mockCursor).markDeleted("User has been deleted for item id=1", PROFILE_DELETED)
+ }
+
+ @Test
+ fun `When app has null intent then mark deleted`() {
+ // Given
+ val mockCursor =
+ mock<LoaderCursor>().apply {
+ user = UserHandle(0)
+ id = 1
+ itemType = ITEM_TYPE_APPLICATION
+ }
+ val itemProcessor = createTestWorkspaceItemProcessor(cursor = mockCursor)
+ // When
+ itemProcessor.processItem()
+ // Then
+ verify(mockCursor).markDeleted("Null intent for item id=1", MISSING_INFO)
+ }
+
+ @Test
+ fun `When app has null target package then mark deleted`() {
+ // Given
+ val mockCursor =
+ mock<LoaderCursor>().apply {
+ user = UserHandle(0)
+ itemType = ITEM_TYPE_APPLICATION
+ id = 1
+ whenever(parseIntent()).thenReturn(Intent())
+ }
+ val itemProcessor = createTestWorkspaceItemProcessor(cursor = mockCursor)
+ // When
+ itemProcessor.processItem()
+ // Then
+ verify(mockCursor).markDeleted("No target package for item id=1", MISSING_INFO)
+ }
+
+ @Test
+ fun `When app has empty String target package then mark deleted`() {
+ // Given
+ val mockIntent =
+ mock<Intent>().apply {
+ whenever(component).thenReturn(null)
+ whenever(`package`).thenReturn("")
+ }
+ val mockCursor =
+ mock<LoaderCursor>().apply {
+ user = UserHandle(0)
+ itemType = ITEM_TYPE_APPLICATION
+ id = 1
+ whenever(parseIntent()).thenReturn(mockIntent)
+ }
+ val itemProcessor = createTestWorkspaceItemProcessor(cursor = mockCursor)
+ // When
+ itemProcessor.processItem()
+ // Then
+ verify(mockCursor).markDeleted("No target package for item id=1", MISSING_INFO)
+ }
+
+ @Test
+ fun `When valid app then mark restored`() {
+ // Given
+ val userHandle = UserHandle(0)
+ val componentName = ComponentName("package", "class")
+ val mockIntent =
+ mock<Intent>().apply {
+ whenever(component).thenReturn(componentName)
+ whenever(`package`).thenReturn("")
+ }
+ val mockLauncherApps =
+ mock<LauncherApps>().apply {
+ whenever(isPackageEnabled("package", userHandle)).thenReturn(true)
+ whenever(isActivityEnabled(componentName, userHandle)).thenReturn(true)
+ }
+ val mockCursor =
+ mock<LoaderCursor>().apply {
+ user = userHandle
+ itemType = ITEM_TYPE_APPLICATION
+ id = 1
+ restoreFlag = 1
+ whenever(parseIntent()).thenReturn(mockIntent)
+ whenever(markRestored()).doAnswer { restoreFlag = 0 }
+ }
+ val itemProcessor =
+ createTestWorkspaceItemProcessor(cursor = mockCursor, launcherApps = mockLauncherApps)
+ // When
+ itemProcessor.processItem()
+ // Then
+ assertWithMessage("item restoreFlag should be set to 0")
+ .that(mockCursor.restoreFlag)
+ .isEqualTo(0)
+ // currently gets marked restored twice, although markRestore() has check for restoreFlag
+ verify(mockCursor, times(2)).markRestored()
+ }
+
+ @Test
+ fun `When fallback Activity found for app then mark restored`() {
+ // Given
+ val userHandle = UserHandle(0)
+ val componentName = ComponentName("package", "class")
+ val mockIntent =
+ mock<Intent>().apply {
+ whenever(component).thenReturn(componentName)
+ whenever(`package`).thenReturn("")
+ whenever(toUri(0)).thenReturn("")
+ }
+ val mockLauncherApps =
+ mock<LauncherApps>().apply {
+ whenever(isPackageEnabled("package", userHandle)).thenReturn(true)
+ whenever(isActivityEnabled(componentName, userHandle)).thenReturn(false)
+ }
+ val mockPmHelper =
+ mock<PackageManagerHelper>().apply {
+ whenever(getAppLaunchIntent(componentName.packageName, userHandle))
+ .thenReturn(mockIntent)
+ }
+ val mockCursor =
+ mock(LoaderCursor::class.java, RETURNS_DEEP_STUBS).apply {
+ user = userHandle
+ itemType = ITEM_TYPE_APPLICATION
+ id = 1
+ restoreFlag = 1
+ whenever(parseIntent()).thenReturn(mockIntent)
+ whenever(markRestored()).doAnswer { restoreFlag = 0 }
+ whenever(updater().put(Favorites.INTENT, mockIntent.toUri(0)).commit())
+ .thenReturn(1)
+ }
+ val itemProcessor =
+ createTestWorkspaceItemProcessor(
+ cursor = mockCursor,
+ launcherApps = mockLauncherApps,
+ pmHelper = mockPmHelper
+ )
+ // When
+ itemProcessor.processItem()
+ // Then
+ assertWithMessage("item restoreFlag should be set to 0")
+ .that(mockCursor.restoreFlag)
+ .isEqualTo(0)
+ verify(mockCursor.updater().put(Favorites.INTENT, mockIntent.toUri(0))).commit()
+ }
+
+ @Test
+ fun `When app with disabled activity and no fallback found then mark deleted`() {
+ // Given
+ val userHandle = UserHandle(0)
+ val componentName = ComponentName("package", "class")
+ val mockIntent =
+ mock<Intent>().apply {
+ whenever(component).thenReturn(componentName)
+ whenever(`package`).thenReturn("")
+ }
+ val mockLauncherApps =
+ mock<LauncherApps>().apply {
+ whenever(isPackageEnabled("package", userHandle)).thenReturn(true)
+ whenever(isActivityEnabled(componentName, userHandle)).thenReturn(false)
+ }
+ val mockPmHelper =
+ mock<PackageManagerHelper>().apply {
+ whenever(getAppLaunchIntent(componentName.packageName, userHandle)).thenReturn(null)
+ }
+ val mockCursor =
+ mock<LoaderCursor>().apply {
+ user = userHandle
+ itemType = ITEM_TYPE_APPLICATION
+ id = 1
+ restoreFlag = 1
+ whenever(parseIntent()).thenReturn(mockIntent)
+ }
+ val itemProcessor =
+ createTestWorkspaceItemProcessor(
+ cursor = mockCursor,
+ launcherApps = mockLauncherApps,
+ pmHelper = mockPmHelper
+ )
+ // When
+ itemProcessor.processItem()
+ // Then
+ assertWithMessage("item restoreFlag should be unchanged")
+ .that(mockCursor.restoreFlag)
+ .isEqualTo(1)
+ verify(mockCursor).markDeleted("Intent null, unable to find a launch target", MISSING_INFO)
+ }
+
+ /**
+ * Helper to create WorkspaceItemProcessor with defaults. WorkspaceItemProcessor has a lot of
+ * dependencies, so this method can be used to inject concrete arguments while keeping the rest
+ * as mocks/defaults.
+ */
+ private fun createTestWorkspaceItemProcessor(
+ cursor: LoaderCursor = mock(),
+ memoryLogger: LoaderMemoryLogger? = null,
+ userManagerState: UserManagerState = mock(),
+ launcherApps: LauncherApps = mock(),
+ shortcutKeyToPinnedShortcuts: Map<ShortcutKey, ShortcutInfo> = mapOf(),
+ app: LauncherAppState = mock(),
+ bgDataModel: BgDataModel = mock(),
+ widgetProvidersMap: MutableMap<ComponentKey, AppWidgetProviderInfo?> = mutableMapOf(),
+ widgetInflater: WidgetInflater = mock(),
+ pmHelper: PackageManagerHelper = mock(),
+ iconRequestInfos: MutableList<IconRequestInfo<WorkspaceItemInfo>> = mutableListOf(),
+ isSdCardReady: Boolean = false,
+ pendingPackages: MutableSet<PackageUserKey> = mutableSetOf(),
+ unlockedUsers: LongSparseArray<Boolean> = LongSparseArray(),
+ installingPkgs: HashMap<PackageUserKey, PackageInstaller.SessionInfo> = hashMapOf(),
+ allDeepShortcuts: MutableList<ShortcutInfo> = mutableListOf()
+ ) =
+ WorkspaceItemProcessor(
+ c = cursor,
+ memoryLogger = memoryLogger,
+ userManagerState = userManagerState,
+ launcherApps = launcherApps,
+ app = app,
+ bgDataModel = bgDataModel,
+ widgetProvidersMap = widgetProvidersMap,
+ widgetInflater = widgetInflater,
+ pmHelper = pmHelper,
+ unlockedUsers = unlockedUsers,
+ iconRequestInfos = iconRequestInfos,
+ pendingPackages = pendingPackages,
+ isSdCardReady = isSdCardReady,
+ shortcutKeyToPinnedShortcuts = shortcutKeyToPinnedShortcuts,
+ installingPkgs = installingPkgs,
+ allDeepShortcuts = allDeepShortcuts
+ )
+}
diff --git a/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
index db38c68..ec226af 100644
--- a/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
@@ -15,6 +15,9 @@
*/
package com.android.launcher3.ui.widget;
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
+
import static org.junit.Assert.assertNotNull;
import android.platform.test.annotations.PlatinumTest;
@@ -30,10 +33,11 @@
import com.android.launcher3.ui.TestViewHelpers;
import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
import com.android.launcher3.util.rule.ShellCommandRule;
+import com.android.launcher3.util.rule.TestStabilityRule.Stability;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+import org.junit.Assume;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -89,10 +93,13 @@
* A custom shortcut is a 1x1 widget that launches a specific intent when user tap on it.
* Custom shortcuts are replaced by deep shortcuts after api 25.
*/
- @Ignore
+ @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
@Test
@PortraitLandscape
public void testDragCustomShortcut() throws Throwable {
+ // TODO(b/322820039): Enable test for tablets - the picker UI has changed and test needs to
+ // be updated to look for appropriate UI elements.
+ Assume.assumeFalse(mLauncher.isTablet());
new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
mLauncher.getWorkspace().openAllWidgets()
diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
index 3693163..43fc8ff 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
@@ -349,7 +349,7 @@
assertEquals("Existing page count does NOT match.", pageIds.length, pageCount);
for (int i = 0; i < pageCount; i++) {
CellLayout page = (CellLayout) launcher.getWorkspace().getPageAt(i);
- int pageId = launcher.getWorkspace().getIdForScreen(page);
+ int pageId = launcher.getWorkspace().getCellLayoutId(page);
assertEquals("The page's id at index " + i + " does NOT match.", pageId,
pageIds[i]);
}
diff --git a/tests/src/com/android/launcher3/util/PackageManagerHelperTest.java b/tests/src/com/android/launcher3/util/PackageManagerHelperTest.java
new file mode 100644
index 0000000..d1da5f4
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/PackageManagerHelperTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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;
+
+import static com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link PackageManagerHelper}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class PackageManagerHelperTest {
+ @Rule
+ public ExpectedException exception = ExpectedException.none();
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ private static final String TEST_PACKAGE = "com.android.test.package";
+ private static final int TEST_USER = 2;
+
+ private Context mContext;
+ private LauncherApps mLauncherApps;
+ private PackageManagerHelper mPackageManagerHelper;
+
+ @Before
+ public void setup() {
+ mContext = mock(Context.class);
+ mLauncherApps = mock(LauncherApps.class);
+ when(mContext.getSystemService(eq(LauncherApps.class))).thenReturn(mLauncherApps);
+ mPackageManagerHelper = new PackageManagerHelper(mContext);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_ENABLE_SUPPORT_FOR_ARCHIVING)
+ public void getApplicationInfo_archivedApp_appInfoIsNotNull()
+ throws PackageManager.NameNotFoundException {
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.isArchived = true;
+ when(mLauncherApps.getApplicationInfo(TEST_PACKAGE, 0 /* flags */,
+ UserHandle.of(TEST_USER)))
+ .thenReturn(applicationInfo);
+
+ assertThat(mPackageManagerHelper.getApplicationInfo(TEST_PACKAGE, UserHandle.of(TEST_USER),
+ 0 /* flags */))
+ .isNotNull();
+ }
+}
diff --git a/tests/src/com/android/launcher3/widget/picker/OWNERS b/tests/src/com/android/launcher3/widget/picker/OWNERS
new file mode 100644
index 0000000..775b0c7
--- /dev/null
+++ b/tests/src/com/android/launcher3/widget/picker/OWNERS
@@ -0,0 +1,18 @@
+set noparent
+
+# Bug component: 1481801
+# People who can approve changes for submission
+#
+
+# Widget Picker OWNERS
+zakcohen@google.com
+shamalip@google.com
+wvk@google.com
+
+# For Tests
+vadimt@google.com
+
+# Launcher OWNERS
+captaincole@google.com
+sunnygoyal@google.com
+
diff --git a/tests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java b/tests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
new file mode 100644
index 0000000..c807771
--- /dev/null
+++ b/tests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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.widget.picker;
+
+import static android.content.pm.ApplicationInfo.CATEGORY_AUDIO;
+import static android.content.pm.ApplicationInfo.CATEGORY_IMAGE;
+import static android.content.pm.ApplicationInfo.CATEGORY_NEWS;
+import static android.content.pm.ApplicationInfo.CATEGORY_PRODUCTIVITY;
+import static android.content.pm.ApplicationInfo.CATEGORY_SOCIAL;
+import static android.content.pm.ApplicationInfo.CATEGORY_UNDEFINED;
+import static android.content.pm.ApplicationInfo.CATEGORY_VIDEO;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Process;
+
+import androidx.test.core.content.pm.ApplicationInfoBuilder;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.util.Executors;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+
+import com.google.common.collect.ImmutableMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Map;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class WidgetRecommendationCategoryProviderTest {
+ private static final String TEST_PACKAGE = "com.foo.test";
+ private static final String TEST_APP_NAME = "foo";
+ public static final WidgetRecommendationCategory SOCIAL_AND_ENTERTAINMENT_CATEGORY =
+ new WidgetRecommendationCategory(
+ R.string.social_and_entertainment_widget_recommendation_category_label,
+ /*order=*/4);
+ private final ApplicationInfo mTestAppInfo = ApplicationInfoBuilder.newBuilder().setPackageName(
+ TEST_PACKAGE).setName(TEST_APP_NAME).build();
+ private Context mContext;
+ @Mock
+ private IconCache mIconCache;
+
+ private WidgetItem mTestWidgetItem;
+ @Mock
+ private PackageManager mPackageManager;
+ private InvariantDeviceProfile mTestProfile;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = new ContextWrapper(getInstrumentation().getTargetContext()) {
+ @Override
+ public PackageManager getPackageManager() {
+ return mPackageManager;
+ }
+ };
+ mTestProfile = new InvariantDeviceProfile();
+ mTestProfile.numRows = 5;
+ mTestProfile.numColumns = 5;
+ createTestWidgetItem();
+ }
+
+ @Test
+ public void getWidgetRecommendationCategory_returnsMappedCategory() throws Exception {
+ ImmutableMap<Integer, WidgetRecommendationCategory> testCategories = ImmutableMap.of(
+ CATEGORY_PRODUCTIVITY, new WidgetRecommendationCategory(
+ R.string.productivity_widget_recommendation_category_label,
+ /*order=*/
+ 0),
+ CATEGORY_NEWS, new WidgetRecommendationCategory(
+ R.string.news_widget_recommendation_category_label, /*order=*/1),
+ CATEGORY_SOCIAL, SOCIAL_AND_ENTERTAINMENT_CATEGORY,
+ CATEGORY_AUDIO, SOCIAL_AND_ENTERTAINMENT_CATEGORY,
+ CATEGORY_IMAGE, SOCIAL_AND_ENTERTAINMENT_CATEGORY,
+ CATEGORY_VIDEO, SOCIAL_AND_ENTERTAINMENT_CATEGORY,
+ CATEGORY_UNDEFINED, new WidgetRecommendationCategory(
+ R.string.others_widget_recommendation_category_label, /*order=*/5));
+
+ for (Map.Entry<Integer, WidgetRecommendationCategory> testCategory :
+ testCategories.entrySet()) {
+
+ mTestAppInfo.category = testCategory.getKey();
+ when(mPackageManager.getApplicationInfo(anyString(), anyInt())).thenReturn(
+ mTestAppInfo);
+
+ WidgetRecommendationCategory category = Executors.MODEL_EXECUTOR.submit(() ->
+ new WidgetRecommendationCategoryProvider().getWidgetRecommendationCategory(
+ mContext,
+ mTestWidgetItem)).get();
+
+ assertThat(category).isEqualTo(testCategory.getValue());
+ }
+ }
+
+ private void createTestWidgetItem() {
+ String widgetLabel = "Foo Widget";
+ String widgetClassName = ".mWidget";
+
+ doAnswer(invocation -> widgetLabel).when(mIconCache).getTitleNoCache(any());
+
+ AppWidgetProviderInfo providerInfo = AppWidgetManager.getInstance(getApplicationContext())
+ .getInstalledProvidersForPackage(
+ getInstrumentation().getContext().getPackageName(), Process.myUserHandle())
+ .get(0);
+ providerInfo.provider = ComponentName.createRelative(TEST_PACKAGE, widgetClassName);
+
+ LauncherAppWidgetProviderInfo launcherAppWidgetProviderInfo =
+ LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, providerInfo);
+ launcherAppWidgetProviderInfo.spanX = 2;
+ launcherAppWidgetProviderInfo.spanY = 2;
+ launcherAppWidgetProviderInfo.label = widgetLabel;
+ mTestWidgetItem = new WidgetItem(launcherAppWidgetProviderInfo, mTestProfile, mIconCache,
+ mContext
+ );
+ }
+}