Merge "Prevent taskbar animation for contextual split from home" into main
diff --git a/Android.bp b/Android.bp
index 39b1ae0..eca0778 100644
--- a/Android.bp
+++ b/Android.bp
@@ -309,6 +309,8 @@
"AndroidManifest-common.xml",
],
min_sdk_version: "current",
+ // TODO(b/319712088): re-enable use_resource_processor
+ use_resource_processor: false,
}
// Build rule for Quickstep library
@@ -337,6 +339,8 @@
manifest: "quickstep/AndroidManifest.xml",
platform_apis: true,
min_sdk_version: "current",
+ // TODO(b/319712088): re-enable use_resource_processor
+ use_resource_processor: false,
}
// Build rule for Launcher3 Go app for Android Go devices.
diff --git a/OWNERS b/OWNERS
index b8aae78..4409b33 100644
--- a/OWNERS
+++ b/OWNERS
@@ -25,5 +25,10 @@
andonian@google.com
sihua@google.com
+# Multitasking eng team
+tracyzhou@google.com
+peanutbutter@google.com
+jeremysim@google.com
+
per-file FeatureFlags.java, globs = set noparent
per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, captaincole@google.com
diff --git a/aconfig/launcher_search.aconfig b/aconfig/launcher_search.aconfig
index 97e56b7..b846604 100644
--- a/aconfig/launcher_search.aconfig
+++ b/aconfig/launcher_search.aconfig
@@ -26,4 +26,11 @@
namespace: "launcher_search"
description: "This flag enables addition of App Installer button in Private Space container."
bug: "308064949"
-}
\ No newline at end of file
+}
+
+flag {
+ name: "private_space_restrict_accessibility_drag"
+ namespace: "launcher_search"
+ description: "This flag disables accessibility drag for Private Space Apps."
+ bug: "289223923"
+}
diff --git a/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml b/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml
index 672440f..e4942ae 100644
--- a/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml
+++ b/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml
@@ -159,7 +159,7 @@
style="@style/TextAppearance.GestureTutorial.ButtonLabel"
android:id="@+id/gesture_tutorial_menu_done_button"
android:layout_width="wrap_content"
- android:layout_height="40dp"
+ android:layout_height="48dp"
android:layout_marginVertical="16dp"
android:text="@string/gesture_tutorial_action_button_label"
android:background="@drawable/gesture_tutorial_action_button_background"
diff --git a/quickstep/res/layout/gesture_tutorial_step_menu.xml b/quickstep/res/layout/gesture_tutorial_step_menu.xml
index c8ee6e9..668a2e1 100644
--- a/quickstep/res/layout/gesture_tutorial_step_menu.xml
+++ b/quickstep/res/layout/gesture_tutorial_step_menu.xml
@@ -157,7 +157,7 @@
style="@style/TextAppearance.GestureTutorial.ButtonLabel"
android:id="@+id/gesture_tutorial_menu_done_button"
android:layout_width="wrap_content"
- android:layout_height="40dp"
+ android:layout_height="48dp"
android:layout_marginVertical="16dp"
android:text="@string/gesture_tutorial_action_button_label"
android:background="@drawable/gesture_tutorial_action_button_background"
diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml
index 72d7485..736706a 100644
--- a/quickstep/res/layout/taskbar.xml
+++ b/quickstep/res/layout/taskbar.xml
@@ -35,7 +35,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"/>
- <FrameLayout
+ <com.android.launcher3.taskbar.navbutton.NearestTouchFrame
android:id="@+id/navbuttons_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -62,7 +62,7 @@
android:layout_height="match_parent"
android:gravity="center_vertical"
android:layout_gravity="end"/>
- </FrameLayout>
+ </com.android.launcher3.taskbar.navbutton.NearestTouchFrame>
<com.android.launcher3.taskbar.StashedHandleView
android:id="@+id/stashed_handle"
diff --git a/quickstep/res/layout/transient_taskbar.xml b/quickstep/res/layout/transient_taskbar.xml
index 0890a4e..6af7cf4 100644
--- a/quickstep/res/layout/transient_taskbar.xml
+++ b/quickstep/res/layout/transient_taskbar.xml
@@ -52,7 +52,7 @@
android:elevation="@dimen/bubblebar_elevation"
/>
- <FrameLayout
+ <com.android.launcher3.taskbar.navbutton.NearestTouchFrame
android:id="@+id/navbuttons_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -83,7 +83,7 @@
android:paddingTop="@dimen/taskbar_contextual_padding_top"
android:gravity="center_vertical"
android:layout_gravity="end"/>
- </FrameLayout>
+ </com.android.launcher3.taskbar.navbutton.NearestTouchFrame>
<com.android.launcher3.taskbar.StashedHandleView
android:id="@+id/stashed_handle"
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index 667f784..0ce1cb8 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -68,6 +68,7 @@
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.IntSparseArrayMap;
@@ -553,7 +554,10 @@
if (lai == null) {
return null;
}
- AppInfo info = new AppInfo(lai, user, mUMS.isUserQuiet(user));
+ AppInfo info = new AppInfo(
+ lai,
+ UserCache.INSTANCE.get(mAppState.getContext()).getUserInfo(user),
+ mUMS.isUserQuiet(user));
info.container = mContainer;
mAppState.getIconCache().getTitleAndIcon(info, lai, false);
mReadCount++;
diff --git a/quickstep/src/com/android/launcher3/proxy/ProxyActivityStarter.java b/quickstep/src/com/android/launcher3/proxy/ProxyActivityStarter.java
index 6d90b035..212a5ff 100644
--- a/quickstep/src/com/android/launcher3/proxy/ProxyActivityStarter.java
+++ b/quickstep/src/com/android/launcher3/proxy/ProxyActivityStarter.java
@@ -53,13 +53,10 @@
try {
if (mParams.intent != null) {
- startActivityForResult(mParams.intent, mParams.requestCode, mParams.options);
+ startActivity();
return;
} else if (mParams.intentSender != null) {
- startIntentSenderForResult(mParams.intentSender, mParams.requestCode,
- mParams.fillInIntent, mParams.flagsMask, mParams.flagsValues,
- mParams.extraFlags,
- mParams.options);
+ startIntentSender();
return;
}
} catch (NullPointerException | ActivityNotFoundException | SecurityException
@@ -83,4 +80,26 @@
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
}
+
+ private void startActivity() throws SendIntentException {
+ if (mParams.requireActivityResult) {
+ startActivityForResult(mParams.intent, mParams.requestCode, mParams.options);
+ } else {
+ startActivity(mParams.intent, mParams.options);
+ finishAndRemoveTask();
+ }
+ }
+
+ private void startIntentSender() throws SendIntentException {
+ if (mParams.requireActivityResult) {
+ startIntentSenderForResult(mParams.intentSender, mParams.requestCode,
+ mParams.fillInIntent, mParams.flagsMask, mParams.flagsValues,
+ mParams.extraFlags,
+ mParams.options);
+ } else {
+ startIntentSender(mParams.intentSender, mParams.fillInIntent, mParams.flagsMask,
+ mParams.flagsValues, mParams.extraFlags, mParams.options);
+ finishAndRemoveTask();
+ }
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/DesktopNavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/DesktopNavbarButtonsViewController.java
index 0a9dfff..3635827 100644
--- a/quickstep/src/com/android/launcher3/taskbar/DesktopNavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/DesktopNavbarButtonsViewController.java
@@ -28,6 +28,7 @@
import androidx.annotation.Nullable;
import com.android.launcher3.R;
+import com.android.launcher3.taskbar.navbutton.NearestTouchFrame;
/**
* Controller for managing buttons and status icons in taskbar in a desktop environment.
@@ -43,7 +44,7 @@
private TaskbarControllers mControllers;
public DesktopNavbarButtonsViewController(TaskbarActivityContext context,
- @Nullable Context navigationBarPanelContext, FrameLayout navButtonsView) {
+ @Nullable Context navigationBarPanelContext, NearestTouchFrame navButtonsView) {
super(context, navigationBarPanelContext, navButtonsView);
mContext = context;
mNavButtonsView = navButtonsView;
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index bd44a35..1a1c64d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -91,6 +91,7 @@
import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory;
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter;
+import com.android.launcher3.taskbar.navbutton.NearestTouchFrame;
import com.android.launcher3.util.DimensionUtils;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
import com.android.launcher3.util.MultiValueAlpha;
@@ -151,7 +152,7 @@
private final TaskbarActivityContext mContext;
private final @Nullable Context mNavigationBarPanelContext;
private final WindowManagerProxy mWindowManagerProxy;
- private final FrameLayout mNavButtonsView;
+ private final NearestTouchFrame mNavButtonsView;
private final LinearLayout mNavButtonContainer;
// Used for IME+A11Y buttons
private final ViewGroup mEndContextualContainer;
@@ -208,7 +209,7 @@
private ImageView mRecentsButton;
public NavbarButtonsViewController(TaskbarActivityContext context,
- @Nullable Context navigationBarPanelContext, FrameLayout navButtonsView) {
+ @Nullable Context navigationBarPanelContext, NearestTouchFrame navButtonsView) {
mContext = context;
mNavigationBarPanelContext = navigationBarPanelContext;
mWindowManagerProxy = WindowManagerProxy.INSTANCE.get(mContext);
@@ -517,6 +518,10 @@
return (mState & FLAG_IME_VISIBLE) != 0;
}
+ public boolean isImeRenderingNavButtons() {
+ return mIsImeRenderingNavButtons;
+ }
+
/**
* Returns true if the home button is disabled
*/
@@ -1003,6 +1008,8 @@
+ mOnTaskbarBackgroundNavButtonColorOverride.value);
pw.println(prefix + "\t\tmOnBackgroundNavButtonColorOverrideMultiplier="
+ mOnBackgroundNavButtonColorOverrideMultiplier.value);
+
+ mNavButtonsView.dumpLogs(prefix + "\t", pw);
}
private static String getStateString(int flags) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 9f65f81..2b88f02 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -104,6 +104,7 @@
import com.android.launcher3.taskbar.bubbles.BubbleDragController;
import com.android.launcher3.taskbar.bubbles.BubbleStashController;
import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController;
+import com.android.launcher3.taskbar.navbutton.NearestTouchFrame;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayController;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
@@ -236,7 +237,7 @@
mDragLayer = (TaskbarDragLayer) mLayoutInflater.inflate(taskbarLayout, null, false);
TaskbarView taskbarView = mDragLayer.findViewById(R.id.taskbar_view);
TaskbarScrimView taskbarScrimView = mDragLayer.findViewById(R.id.taskbar_scrim);
- FrameLayout navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view);
+ NearestTouchFrame navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view);
StashedHandleView stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle);
BubbleBarView bubbleBarView = mDragLayer.findViewById(R.id.taskbar_bubbles);
StashedHandleView bubbleHandleView = mDragLayer.findViewById(R.id.stashed_bubble_handle);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index 633383d..b8e6889 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -309,7 +309,12 @@
controllers.bubbleControllers.isPresent &&
controllers.bubbleControllers.get().bubbleBarViewController.isBubbleBarVisible()
var insetsIsTouchableRegion = true
- if (context.dragLayer.alpha < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
+ if (context.isPhoneButtonNavMode &&
+ (!controllers.navbarButtonsViewController.isImeVisible
+ || !controllers.navbarButtonsViewController.isImeRenderingNavButtons)) {
+ insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME)
+ insetsIsTouchableRegion = false
+ } else if (context.dragLayer.alpha < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
// Let touches pass through us.
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
debugTouchableRegion.lastSetTouchableReason = "Taskbar is invisible"
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index a850680..8e4a78f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -207,7 +207,7 @@
applyState();
boolean disallowLongClick =
FeatureFlags.enableSplitContextually()
- ? mLauncher.isSplitSelectionEnabled()
+ ? mLauncher.isSplitSelectionActive()
: finalState == LauncherState.OVERVIEW_SPLIT_SELECT;
com.android.launcher3.taskbar.Utilities.setOverviewDragState(
mControllers, finalState.disallowTaskbarGlobalDrag(),
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
index 1e3f4f1..aa2b29d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
@@ -50,6 +50,22 @@
var width: Float = 0f
+ /**
+ * Set whether the drawable is anchored to the left or right edge of the container.
+ *
+ * When `anchorLeft` is set to `true`, drawable left edge aligns up with the container left
+ * edge. Drawable can be drawn outside container bounds on the right edge. When it is set to
+ * `false` (the default), drawable right edge aligns up with the container right edge. Drawable
+ * can be drawn outside container bounds on the left edge.
+ */
+ var anchorLeft: Boolean = false
+ set(value) {
+ if (field != value) {
+ field = value
+ invalidateSelf()
+ }
+ }
+
init {
paint.color = context.getColor(R.color.taskbar_background)
paint.flags = Paint.ANTI_ALIAS_FLAG
@@ -106,15 +122,9 @@
// Draw background.
val radius = backgroundHeight / 2f
- canvas.drawRoundRect(
- canvas.width.toFloat() - width,
- 0f,
- canvas.width.toFloat(),
- canvas.height.toFloat(),
- radius,
- radius,
- paint
- )
+ val left = if (anchorLeft) 0f else canvas.width.toFloat() - width
+ val right = if (anchorLeft) width else canvas.width.toFloat()
+ canvas.drawRoundRect(left, 0f, right, canvas.height.toFloat(), radius, radius, paint)
if (showingArrow) {
// Draw arrow.
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index ec9f4e5..fbc7da1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -197,6 +197,18 @@
updateChildrenRenderNodeProperties();
}
+ @Override
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ // TODO(b/313661121): set this based on bubble bar position and not LTR or RTL
+ boolean onLeft = layoutDirection == LAYOUT_DIRECTION_RTL;
+ mBubbleBarBackground.setAnchorLeft(onLeft);
+ mRelativePivotX = onLeft ? 0f : 1f;
+ }
+
+ private boolean isOnLeft() {
+ return getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+ }
+
/**
* Updates the bounds with translation that may have been applied and returns the result.
*/
@@ -275,18 +287,31 @@
int bubbleCount = getChildCount();
final float ty = (mBubbleBarBounds.height() - mIconSize) / 2f;
final boolean animate = getVisibility() == VISIBLE;
+ final boolean onLeft = isOnLeft();
for (int i = 0; i < bubbleCount; i++) {
BubbleView bv = (BubbleView) getChildAt(i);
bv.setTranslationY(ty);
// the position of the bubble when the bar is fully expanded
- final float expandedX = i * (mIconSize + mIconSpacing);
+ final float expandedX;
// the position of the bubble when the bar is fully collapsed
- final float collapsedX = i == 0 ? 0 : mIconOverlapAmount;
+ final float collapsedX;
+ if (onLeft) {
+ // If bar is on the left, bubbles are ordered right to left
+ expandedX = (bubbleCount - i - 1) * (mIconSize + mIconSpacing);
+ // Shift the first bubble only if there are more bubbles in addition to overflow
+ collapsedX = i == 0 && bubbleCount > 2 ? mIconOverlapAmount : 0;
+ } else {
+ // Bubbles ordered left to right, don't move the first bubble
+ expandedX = i * (mIconSize + mIconSpacing);
+ collapsedX = i == 0 ? 0 : mIconOverlapAmount;
+ }
if (mIsBarExpanded) {
+ // If bar is on the right, account for bubble bar expanding and shifting left
+ final float expandedBarShift = onLeft ? 0 : currentWidth - expandedWidth;
// where the bubble will end up when the animation ends
- final float targetX = currentWidth - expandedWidth + expandedX;
+ final float targetX = expandedX + expandedBarShift;
bv.setTranslationX(widthState * (targetX - collapsedX) + collapsedX);
// if we're fully expanded, set the z level to 0 or to bubble elevation if dragged
if (widthState == 1f) {
@@ -296,7 +321,9 @@
bv.setBehindStack(false, animate);
bv.setAlpha(1);
} else {
- final float targetX = currentWidth - collapsedWidth + collapsedX;
+ // If bar is on the right, account for bubble bar expanding and shifting left
+ final float collapsedBarShift = onLeft ? 0 : currentWidth - collapsedWidth;
+ final float targetX = collapsedX + collapsedBarShift;
bv.setTranslationX(widthState * (expandedX - targetX) + targetX);
bv.setZ((MAX_BUBBLES * mBubbleElevation) - i);
// If we're not the first bubble we're behind the stack
@@ -318,18 +345,22 @@
final float expandedArrowPosition = arrowPositionForSelectedWhenExpanded();
final float interpolatedWidth =
widthState * (expandedWidth - collapsedWidth) + collapsedWidth;
- if (mIsBarExpanded) {
- // when the bar is expanding, the selected bubble is always the first, so the arrow
- // always shifts with the interpolated width.
- final float arrowPosition = currentWidth - interpolatedWidth + collapsedArrowPosition;
- mBubbleBarBackground.setArrowPosition(arrowPosition);
+ final float arrowPosition;
+ if (onLeft) {
+ float interpolatedShift = (expandedArrowPosition - collapsedArrowPosition) * widthState;
+ arrowPosition = collapsedArrowPosition + interpolatedShift;
} else {
- final float targetPosition = currentWidth - collapsedWidth + collapsedArrowPosition;
- final float arrowPosition =
- targetPosition + widthState * (expandedArrowPosition - targetPosition);
- mBubbleBarBackground.setArrowPosition(arrowPosition);
+ if (mIsBarExpanded) {
+ // when the bar is expanding, the selected bubble is always the first, so the arrow
+ // always shifts with the interpolated width.
+ arrowPosition = currentWidth - interpolatedWidth + collapsedArrowPosition;
+ } else {
+ final float targetPosition = currentWidth - collapsedWidth + collapsedArrowPosition;
+ arrowPosition =
+ targetPosition + widthState * (expandedArrowPosition - targetPosition);
+ }
}
-
+ mBubbleBarBackground.setArrowPosition(arrowPosition);
mBubbleBarBackground.setArrowAlpha((int) (255 * widthState));
mBubbleBarBackground.setWidth(interpolatedWidth);
}
@@ -394,12 +425,14 @@
Log.w(TAG, "trying to update selection arrow without a selected view!");
return;
}
- final int index = indexOfChild(mSelectedBubbleView);
// Find the center of the bubble when it's expanded, set the arrow position to it.
- final float tx = getPaddingStart() + index * (mIconSize + mIconSpacing) + mIconSize / 2f;
-
+ final float tx = arrowPositionForSelectedWhenExpanded();
+ final float currentArrowPosition = mBubbleBarBackground.getArrowPositionX();
+ if (shouldAnimate && currentArrowPosition > expandedWidth()) {
+ Log.d(TAG, "arrow out of bounds of expanded view, skip animation");
+ shouldAnimate = false;
+ }
if (shouldAnimate) {
- final float currentArrowPosition = mBubbleBarBackground.getArrowPositionX();
ValueAnimator animator = ValueAnimator.ofFloat(currentArrowPosition, tx);
animator.setDuration(ARROW_POSITION_ANIMATION_DURATION_MS);
animator.addUpdateListener(animation -> {
@@ -416,12 +449,27 @@
private float arrowPositionForSelectedWhenExpanded() {
final int index = indexOfChild(mSelectedBubbleView);
- return getPaddingStart() + index * (mIconSize + mIconSpacing) + mIconSize / 2f;
+ final int bubblePosition;
+ if (isOnLeft()) {
+ // Bubble positions are reversed. First bubble is on the right.
+ bubblePosition = getChildCount() - index - 1;
+ } else {
+ bubblePosition = index;
+ }
+ return getPaddingStart() + bubblePosition * (mIconSize + mIconSpacing) + mIconSize / 2f;
}
private float arrowPositionForSelectedWhenCollapsed() {
final int index = indexOfChild(mSelectedBubbleView);
- return getPaddingStart() + index * (mIconOverlapAmount) + mIconSize / 2f;
+ final int bubblePosition;
+ if (isOnLeft()) {
+ // Bubble positions are reversed. First bubble may be shifted, if there are more
+ // bubbles than the current bubble and overflow.
+ bubblePosition = index == 0 && getChildCount() > 2 ? 1 : 0;
+ } else {
+ bubblePosition = index;
+ }
+ return getPaddingStart() + bubblePosition * (mIconOverlapAmount) + mIconSize / 2f;
}
@Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 065dd58..6bb7b04 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -21,6 +21,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.util.Log;
+import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
@@ -289,7 +290,8 @@
*/
public void addBubble(BubbleBarItem b) {
if (b != null) {
- mBarView.addView(b.getView(), 0, new FrameLayout.LayoutParams(mIconSize, mIconSize));
+ mBarView.addView(b.getView(), 0,
+ new FrameLayout.LayoutParams(mIconSize, mIconSize, Gravity.LEFT));
b.getView().setOnClickListener(mBubbleClickListener);
mBubbleDragController.setupBubbleView(b.getView());
} else {
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
index c998d97..f88460f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
@@ -16,6 +16,7 @@
package com.android.launcher3.taskbar.bubbles;
import static android.view.View.INVISIBLE;
+import static android.view.View.LAYOUT_DIRECTION_RTL;
import static android.view.View.VISIBLE;
import android.animation.Animator;
@@ -124,22 +125,35 @@
private void updateBounds() {
// As more bubbles get added, the icon bounds become larger. To ensure a consistent
// handle bar position, we pin it to the edge of the screen.
- final int right =
- mActivity.getDeviceProfile().widthPx - mBarViewController.getHorizontalMargin();
-
final int stashedCenterY = mStashedHandleView.getHeight() - mStashedTaskbarHeight / 2;
+ if (isOnLeft()) {
+ final int left = mBarViewController.getHorizontalMargin();
+ mStashedHandleBounds.set(
+ left,
+ stashedCenterY - mStashedHandleHeight / 2,
+ left + mStashedHandleWidth,
+ stashedCenterY + mStashedHandleHeight / 2);
+ mStashedHandleView.setPivotX(0);
+ } else {
+ final int right =
+ mActivity.getDeviceProfile().widthPx - mBarViewController.getHorizontalMargin();
+ mStashedHandleBounds.set(
+ right - mStashedHandleWidth,
+ stashedCenterY - mStashedHandleHeight / 2,
+ right,
+ stashedCenterY + mStashedHandleHeight / 2);
+ mStashedHandleView.setPivotX(mStashedHandleView.getWidth());
+ }
- mStashedHandleBounds.set(
- right - mStashedHandleWidth,
- stashedCenterY - mStashedHandleHeight / 2,
- right,
- stashedCenterY + mStashedHandleHeight / 2);
mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
-
- mStashedHandleView.setPivotX(mStashedHandleView.getWidth());
mStashedHandleView.setPivotY(mStashedHandleView.getHeight() - mStashedTaskbarHeight / 2f);
}
+ private boolean isOnLeft() {
+ // TODO(b/313661121): set this based on bubble bar position and not LTR or RTL
+ return mStashedHandleView.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+ }
+
public void onDestroy() {
mRegionSamplingHelper.stopAndDestroy();
mRegionSamplingHelper = null;
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/OWNERS b/quickstep/src/com/android/launcher3/taskbar/bubbles/OWNERS
index 7af0389..edabae2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/OWNERS
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/OWNERS
@@ -1 +1,3 @@
+atsjenk@google.com
+liranb@google.com
madym@google.com
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
index 22f0131..672bc0d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
@@ -56,7 +56,7 @@
*/
fun getUiLayoutter(
deviceProfile: DeviceProfile,
- navButtonsView: FrameLayout,
+ navButtonsView: NearestTouchFrame,
imeSwitcher: ImageView?,
rotationButton: RotationButton?,
a11yButton: ImageView?,
@@ -78,6 +78,7 @@
return when {
isPhoneNavMode -> {
if (!deviceProfile.isLandscape) {
+ navButtonsView.setIsVertical(false)
PhonePortraitNavLayoutter(
resources,
navButtonContainer,
@@ -88,6 +89,7 @@
a11yButton
)
} else if (surfaceRotation == ROTATION_90) {
+ navButtonsView.setIsVertical(true)
PhoneLandscapeNavLayoutter(
resources,
navButtonContainer,
@@ -98,6 +100,7 @@
a11yButton
)
} else {
+ navButtonsView.setIsVertical(true)
PhoneSeascapeNavLayoutter(
resources,
navButtonContainer,
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/NearestTouchFrame.java b/quickstep/src/com/android/launcher3/taskbar/navbutton/NearestTouchFrame.java
new file mode 100644
index 0000000..a477303
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/NearestTouchFrame.java
@@ -0,0 +1,204 @@
+/*
+ * 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.taskbar.navbutton;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * Redirects touches that aren't handled by any child view to the nearest
+ * clickable child. Only takes effect on <sw600dp.
+ */
+public class NearestTouchFrame extends FrameLayout {
+
+ private final List<View> mClickableChildren = new ArrayList<>();
+ private final List<View> mAttachedChildren = new ArrayList<>();
+ private final boolean mIsActive;
+
+ private boolean mIsVertical;
+ private View mTouchingChild;
+ private final Map<View, Rect> mTouchableRegions = new HashMap<>();
+ /**
+ * Used to sort all child views either by their left position or their top position,
+ * depending on if this layout is used horizontally or vertically, respectively
+ */
+ private final Comparator<View> mChildRegionComparator =
+ (view1, view2) -> {
+ int startingCoordView1 = mIsVertical ? view1.getTop() : view1.getLeft();
+ int startingCoordView2 = mIsVertical ? view2.getTop() : view2.getLeft();
+
+ return startingCoordView1 - startingCoordView2;
+ };
+
+ public NearestTouchFrame(Context context, AttributeSet attrs) {
+ this(context, attrs, context.getResources().getConfiguration());
+ }
+
+ public NearestTouchFrame(Context context, AttributeSet attrs, Configuration c) {
+ super(context, attrs);
+ mIsActive = c.smallestScreenWidthDp < 600;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ mClickableChildren.clear();
+ mAttachedChildren.clear();
+ mTouchableRegions.clear();
+ addClickableChildren(this);
+ cacheClosestChildLocations();
+ }
+
+ /**
+ * Populates {@link #mTouchableRegions} with the regions where each clickable child is the
+ * closest for a given point on this layout.
+ */
+ private void cacheClosestChildLocations() {
+ if (getWidth() == 0 || getHeight() == 0) {
+ return;
+ }
+
+ // Sort by either top or left depending on mIsVertical, then take out all children
+ // that are not attached to window
+ mClickableChildren.sort(mChildRegionComparator);
+ mClickableChildren.stream()
+ .filter(View::isAttachedToWindow)
+ .forEachOrdered(mAttachedChildren::add);
+
+ // Cache bounds of children
+ // Mark coordinates where the actual child layout resides in this frame's window
+ for (int i = 0; i < mAttachedChildren.size(); i++) {
+ View child = mAttachedChildren.get(i);
+ if (!child.isAttachedToWindow()) {
+ continue;
+ }
+ Rect childRegion = getChildsBounds(child);
+
+ // We compute closest child from this child to the previous one
+ if (i == 0) {
+ // First child, nothing to the left/top of it
+ if (mIsVertical) {
+ childRegion.top = 0;
+ } else {
+ childRegion.left = 0;
+ }
+ mTouchableRegions.put(child, childRegion);
+ continue;
+ }
+
+ View previousChild = mAttachedChildren.get(i - 1);
+ Rect previousChildBounds = mTouchableRegions.get(previousChild);
+ int midPoint;
+ if (mIsVertical) {
+ int distance = childRegion.top - previousChildBounds.bottom;
+ midPoint = distance / 2;
+ childRegion.top -= midPoint;
+ previousChildBounds.bottom += midPoint - ((distance % 2) == 0 ? 1 : 0);
+ } else {
+ int distance = childRegion.left - previousChildBounds.right;
+ midPoint = distance / 2;
+ childRegion.left -= midPoint;
+ previousChildBounds.right += midPoint - ((distance % 2) == 0 ? 1 : 0);
+ }
+
+ if (i == mClickableChildren.size() - 1) {
+ // Last child, nothing to right/bottom of it
+ if (mIsVertical) {
+ childRegion.bottom = getHeight();
+ } else {
+ childRegion.right = getWidth();
+ }
+ }
+
+ mTouchableRegions.put(child, childRegion);
+ }
+ }
+
+ void setIsVertical(boolean isVertical) {
+ mIsVertical = isVertical;
+ }
+
+ private Rect getChildsBounds(View child) {
+ int left = child.getLeft();
+ int top = child.getTop();
+ int right = left + child.getWidth();
+ int bottom = top + child.getHeight();
+ return new Rect(left, top, right, bottom);
+ }
+
+ private void addClickableChildren(ViewGroup group) {
+ final int N = group.getChildCount();
+ for (int i = 0; i < N; i++) {
+ View child = group.getChildAt(i);
+ if (child.isClickable()) {
+ mClickableChildren.add(child);
+ } else if (child instanceof ViewGroup) {
+ addClickableChildren((ViewGroup) child);
+ }
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (mIsActive) {
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ mTouchingChild = mClickableChildren
+ .stream()
+ .filter(View::isAttachedToWindow)
+ .filter(view -> mTouchableRegions.get(view).contains(x, y))
+ .findFirst()
+ .orElse(null);
+
+ }
+ if (mTouchingChild != null) {
+ // Translate the touch event to the view center of the touching child.
+ event.offsetLocation(mTouchingChild.getWidth() / 2 - x,
+ mTouchingChild.getHeight() / 2 - y);
+ return mTouchingChild.getVisibility() == VISIBLE
+ && mTouchingChild.dispatchTouchEvent(event);
+ }
+ }
+ return super.onTouchEvent(event);
+ }
+
+ public void dumpLogs(String prefix, PrintWriter pw) {
+ pw.println(prefix + "NearestTouchFrame:");
+
+ 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,
+ mTouchableRegions.keySet().stream()
+ .map(key -> key + "=" + mTouchableRegions.get(key))
+ .collect(Collectors.joining(", ", "{", "}"))));
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
index 3ebc8ed..5c4eb9d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -138,6 +138,11 @@
|| Flags.enablePrivateSpaceInstallShortcut())) {
StartActivityParams params = new StartActivityParams((PendingIntent) null, 0);
params.intentSender = launcherApps.getAppMarketActivityIntent(packageName, user);
+ ActivityOptions options = ActivityOptions.makeBasic()
+ .setPendingIntentBackgroundActivityStartMode(ActivityOptions
+ .MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ params.options = options.toBundle();
+ params.requireActivityResult = false;
return ProxyActivityStarter.getLaunchIntent(context, params);
} else {
return new Intent(Intent.ACTION_VIEW)
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
index d834935..22f24f1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
@@ -59,7 +59,7 @@
return RemoteViews.startPendingIntent(hostView, pendingIntent,
remoteResponse.getLaunchOptions(view));
}
- if (mLauncher.isSplitSelectionEnabled()) {
+ if (mLauncher.isSplitSelectionActive()) {
// Log metric
StatsLogManager.StatsLogger logger = mLauncher.getStatsLogManager().logger();
logger.log(LAUNCHER_SPLIT_WIDGET_ATTEMPT);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index eb951f6..729609b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -725,7 +725,7 @@
}
@Override
- public boolean isSplitSelectionEnabled() {
+ public boolean isSplitSelectionActive() {
return mSplitSelectStateController.isSplitSelectActive();
}
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 4e84f4a..2e40117 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -196,12 +196,13 @@
remoteTargetHandles = gluer.assignTargets(targets);
}
}
+
final int recentsActivityRotation =
recentsView.getPagedViewOrientedState().getRecentsActivityRotation();
- for (RemoteTargetHandle remoteTargetGluer : remoteTargetHandles) {
- remoteTargetGluer.getTaskViewSimulator().getOrientationState().setRecentsRotation(
- recentsActivityRotation);
- remoteTargetGluer.getTransformParams().setSyncTransactionApplier(applier);
+ for (RemoteTargetHandle remoteTargetHandle : remoteTargetHandles) {
+ remoteTargetHandle.getTaskViewSimulator().getOrientationState()
+ .setRecentsRotation(recentsActivityRotation);
+ remoteTargetHandle.getTransformParams().setSyncTransactionApplier(applier);
}
int taskIndex = recentsView.indexOfChild(v);
@@ -394,6 +395,13 @@
out.addListener(new AnimationSuccessListener() {
@Override
+ public void onAnimationStart(Animator animation) {
+ for (RemoteTargetHandle remoteTargetHandle : remoteTargetHandles) {
+ remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(false);
+ }
+ }
+
+ @Override
public void onAnimationSuccess(Animator animator) {
if (isQuickSwitch) {
InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
index a36f501..4198e2d 100644
--- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
@@ -54,6 +54,8 @@
static final String KEY_TUTORIAL_TYPE = "tutorial_type";
static final String KEY_GESTURE_COMPLETE = "gesture_complete";
static final String KEY_USE_TUTORIAL_MENU = "use_tutorial_menu";
+ public static final double SQUARE_ASPECT_RATIO_BOTTOM_BOUND = 0.95;
+ public static final double SQUARE_ASPECT_RATIO_UPPER_BOUND = 1.05;
@Nullable private TutorialType[] mTutorialSteps;
private GestureSandboxFragment mCurrentFragment;
@@ -159,14 +161,20 @@
* Gesture animations are only in landscape for large screens and portrait for mobile. This
* method enforces the following flows:
* 1) phone / two-panel closed -> lock to portrait
- * 2) two-panel open / tablet + portrait -> prompt the user to rotate the screen
- * 3) two-panel open / tablet + landscape -> hide potential rotating prompt
+ * 2) Large screen + portrait -> prompt the user to rotate the screen
+ * 3) Large screen + landscape -> hide potential rotating prompt
+ * 4) Square aspect ratio -> no action taken as the animations will fit both orientations
*/
private void correctUserOrientation() {
DeviceProfile deviceProfile = InvariantDeviceProfile.INSTANCE.get(
getApplicationContext()).getDeviceProfile(this);
if (deviceProfile.isTablet) {
- boolean showRotationPrompt = getResources().getConfiguration().orientation
+ // The tutorial will work in either orientation if the height and width are similar
+ boolean isAspectRatioSquare =
+ deviceProfile.aspectRatio > SQUARE_ASPECT_RATIO_BOTTOM_BOUND
+ && deviceProfile.aspectRatio < SQUARE_ASPECT_RATIO_UPPER_BOUND;
+ boolean showRotationPrompt = !isAspectRatioSquare
+ && getResources().getConfiguration().orientation
== ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
GestureSandboxFragment recreatedFragment =
diff --git a/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java b/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
index b663970..1abedb7 100644
--- a/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
@@ -43,6 +43,7 @@
import com.android.quickstep.RecentsAnimationTargets;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.TopTaskTracker;
import com.android.quickstep.views.FloatingTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.recents.model.Task;
@@ -75,7 +76,9 @@
@BinderThread
public void enterStageSplit(boolean leftOrTop) {
- if (!enableSplitContextually()) {
+ if (!enableSplitContextually() ||
+ // Do not enter stage split from keyboard shortcuts if the user is already in split
+ TopTaskTracker.INSTANCE.get(mLauncher).getRunningSplitTaskIds().length == 2) {
return;
}
RecentsAnimationCallbacks callbacks = new RecentsAnimationCallbacks(
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 66a880b..c8d631d 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -1034,15 +1034,6 @@
recentsView.getDepthController());
anim.addListener(new AnimatorListenerAdapter() {
@Override
- public void onAnimationStart(Animator animation) {
- recentsView.runActionOnRemoteHandles(
- (Consumer<RemoteTargetHandle>) remoteTargetHandle ->
- remoteTargetHandle
- .getTaskViewSimulator()
- .setDrawsBelowRecents(false));
- }
-
- @Override
public void onAnimationEnd(Animator animator) {
if (mTask != null && mTask.key.displayId != getRootViewDisplayId()) {
launchTaskAnimated();
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 9c7f014..9c7fdc6 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
@@ -27,7 +27,7 @@
class NavButtonLayoutFactoryTest {
private val mockDeviceProfile: DeviceProfile = mock()
- private val mockParentButtonContainer: FrameLayout = mock()
+ private val mockParentButtonContainer: NearestTouchFrame = mock()
private val mockNavLayout: LinearLayout = mock()
private val mockStartContextualLayout: ViewGroup = mock()
private val mockEndContextualLayout: ViewGroup = mock()
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 1b8866a..fcb8320 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -55,7 +55,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -334,9 +333,6 @@
@Test
@TaskbarModeSwitch
- @ScreenRecord // b/314873201
- // Staging; will be promoted to presubmit if stable
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
public void testQuickSwitchToPreviousAppForTablet() throws Exception {
assumeTrue(mLauncher.isTablet());
startTestActivity(2);
diff --git a/res/drawable/encrypted_24px.xml b/res/drawable/encrypted_24px.xml
deleted file mode 100644
index cf4d2df..0000000
--- a/res/drawable/encrypted_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="960"
- android:viewportHeight="960"
- android:tint="?attr/colorControlNormal">
- <path
- android:fillColor="@android:color/white"
- android:pathData="M420,600L540,600L517,471Q537,461 548.5,442Q560,423 560,400Q560,367 536.5,343.5Q513,320 480,320Q447,320 423.5,343.5Q400,367 400,400Q400,423 411.5,442Q423,461 443,471L420,600ZM480,880Q341,845 250.5,720.5Q160,596 160,444L160,200L480,80L800,200L800,444Q800,596 709.5,720.5Q619,845 480,880ZM480,796Q584,763 652,664Q720,565 720,444L720,255L480,165L240,255L240,444Q240,565 308,664Q376,763 480,796ZM480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Z"/>
-</vector>
diff --git a/res/drawable/ic_encrypted_with_background.xml b/res/drawable/ic_encrypted_with_background.xml
new file mode 100644
index 0000000..155e2be
--- /dev/null
+++ b/res/drawable/ic_encrypted_with_background.xml
@@ -0,0 +1,26 @@
+<!-- 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
+ android:viewportWidth="48"
+ android:viewportHeight="48"
+ android:width="48dp"
+ android:height="48dp">
+ <path
+ android:pathData="M48 24A24 24 0 0 1 0 24A24 24 0 0 1 48 24Z"
+ android:fillColor="?attr/materialColorSurfaceBright" />
+ <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" />
+</vector>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5efe940..b9fb024 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -447,8 +447,10 @@
<string name="remote_action_failed">Failed: <xliff:g id="what" example="Pause">%1$s</xliff:g></string>
<!-- Strings for Private Space -->
- <!-- Private space label -->
+ <!-- Private space tile label -->
<string name="private_space_label">Private space</string>
+ <!-- Private space tile secondary label -->
+ <string name="private_space_secondary_label">Keep private apps locked and hidden</string>
<!-- Title for Private Space Container shown at the bottom of all apps drawer -->
<string name="ps_container_title">Private</string>
<!-- Description for Private Space Settings button -->
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 5a51d8e..1b4edf8 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -16,18 +16,13 @@
package com.android.launcher3;
-import static android.animation.ValueAnimator.areAnimatorsEnabled;
-
-import static com.android.app.animation.Interpolators.DECELERATE_1_5;
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_BOUNCE_OFFSET;
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_REORDER_PREVIEW_OFFSET;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
@@ -48,7 +43,6 @@
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.Log;
-import android.util.Property;
import android.util.SparseArray;
import android.view.MotionEvent;
import android.view.View;
@@ -71,6 +65,7 @@
import com.android.launcher3.celllayout.ItemConfiguration;
import com.android.launcher3.celllayout.ReorderAlgorithm;
import com.android.launcher3.celllayout.ReorderParameters;
+import com.android.launcher3.celllayout.ReorderPreviewAnimation;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.folder.PreviewBackground;
@@ -189,7 +184,7 @@
@ContainerType private final int mContainerType;
- private final float mChildScale = 1f;
+ public static final float DEFAULT_SCALE = 1f;
public static final int MODE_SHOW_REORDER_HINT = 0;
public static final int MODE_DRAG_OVER = 1;
@@ -199,8 +194,8 @@
private static final boolean DESTRUCTIVE_REORDER = false;
private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
- private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
- private static final int REORDER_ANIMATION_DURATION = 150;
+ public static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
+ public static final int REORDER_ANIMATION_DURATION = 150;
@Thunk final float mReorderPreviewAnimationMagnitude;
private final ArrayList<View> mIntersectingViews = new ArrayList<>();
@@ -762,8 +757,8 @@
bubbleChild.setTextVisibility(mContainerType != HOTSEAT);
}
- child.setScaleX(mChildScale);
- child.setScaleY(mChildScale);
+ child.setScaleX(DEFAULT_SCALE);
+ child.setScaleY(DEFAULT_SCALE);
// Generate an id for each view, this assumes we have at most 256x256 cells
// per workspace screen
@@ -1439,174 +1434,14 @@
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
if (c != null && !skip && (child instanceof Reorderable)) {
- ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child,
- mode, lp.getCellX(), lp.getCellY(), c.cellX, c.cellY, c.spanX, c.spanY);
+ ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode,
+ lp.getCellX(), lp.getCellY(), c.cellX, c.cellY, c.spanX, c.spanY,
+ mReorderPreviewAnimationMagnitude, this, mShakeAnimators);
rha.animate();
}
}
}
- private static final Property<ReorderPreviewAnimation, Float> ANIMATION_PROGRESS =
- new Property<ReorderPreviewAnimation, Float>(float.class, "animationProgress") {
- @Override
- public Float get(ReorderPreviewAnimation anim) {
- return anim.animationProgress;
- }
-
- @Override
- public void set(ReorderPreviewAnimation anim, Float progress) {
- anim.setAnimationProgress(progress);
- }
- };
-
- // Class which represents the reorder preview animations. These animations show that an item is
- // in a temporary state, and hint at where the item will return to.
- class ReorderPreviewAnimation<T extends View & Reorderable> {
- final T child;
- float finalDeltaX;
- float finalDeltaY;
- float initDeltaX;
- float initDeltaY;
- final float finalScale;
- float initScale;
- final int mode;
- boolean repeating = false;
- private static final int PREVIEW_DURATION = 300;
- private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
-
- private static final float CHILD_DIVIDEND = 4.0f;
-
- public static final int MODE_HINT = 0;
- public static final int MODE_PREVIEW = 1;
-
- float animationProgress = 0;
- ValueAnimator a;
-
- ReorderPreviewAnimation(View childView, int mode, int cellX0, int cellY0,
- int cellX1, int cellY1, int spanX, int spanY) {
- regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
- final int x0 = mTmpPoint[0];
- final int y0 = mTmpPoint[1];
- regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
- final int x1 = mTmpPoint[0];
- final int y1 = mTmpPoint[1];
- final int dX = x1 - x0;
- final int dY = y1 - y0;
-
- this.child = (T) childView;
- this.mode = mode;
- finalDeltaX = 0;
- finalDeltaY = 0;
-
- MultiTranslateDelegate mtd = child.getTranslateDelegate();
- initDeltaX = mtd.getTranslationX(INDEX_REORDER_BOUNCE_OFFSET).getValue();
- initDeltaY = mtd.getTranslationY(INDEX_REORDER_BOUNCE_OFFSET).getValue();
- initScale = child.getReorderBounceScale();
- finalScale = mChildScale - (CHILD_DIVIDEND / child.getWidth()) * initScale;
-
- int dir = mode == MODE_HINT ? -1 : 1;
- if (dX == dY && dX == 0) {
- } else {
- if (dY == 0) {
- finalDeltaX = -dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
- } else if (dX == 0) {
- finalDeltaY = -dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
- } else {
- double angle = Math.atan( (float) (dY) / dX);
- finalDeltaX = (int) (-dir * Math.signum(dX)
- * Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
- finalDeltaY = (int) (-dir * Math.signum(dY)
- * Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
- }
- }
- }
-
- void setInitialAnimationValuesToBaseline() {
- initScale = mChildScale;
- initDeltaX = 0;
- initDeltaY = 0;
- }
-
- void animate() {
- boolean noMovement = (finalDeltaX == 0) && (finalDeltaY == 0);
-
- if (mShakeAnimators.containsKey(child)) {
- ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
- mShakeAnimators.remove(child);
-
- if (noMovement) {
- // A previous animation for this item exists, and no new animation will exist.
- // Finish the old animation smoothly.
- oldAnimation.finishAnimation();
- return;
- } else {
- // A previous animation for this item exists, and a new one will exist. Stop
- // the old animation in its tracks, and proceed with the new one.
- oldAnimation.cancel();
- }
- }
- if (noMovement) {
- return;
- }
-
- ValueAnimator va = ObjectAnimator.ofFloat(this, ANIMATION_PROGRESS, 0, 1);
- a = va;
-
- // Animations are disabled in power save mode, causing the repeated animation to jump
- // spastically between beginning and end states. Since this looks bad, we don't repeat
- // the animation in power save mode.
- if (areAnimatorsEnabled()) {
- va.setRepeatMode(ValueAnimator.REVERSE);
- va.setRepeatCount(ValueAnimator.INFINITE);
- }
-
- va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
- va.setStartDelay((int) (Math.random() * 60));
- va.addListener(new AnimatorListenerAdapter() {
- public void onAnimationRepeat(Animator animation) {
- // We make sure to end only after a full period
- setInitialAnimationValuesToBaseline();
- repeating = true;
- }
- });
- mShakeAnimators.put(child, this);
- va.start();
- }
-
- private void setAnimationProgress(float progress) {
- animationProgress = progress;
- float r1 = (mode == MODE_HINT && repeating) ? 1.0f : animationProgress;
- float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
- float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
- child.getTranslateDelegate().setTranslation(INDEX_REORDER_BOUNCE_OFFSET, x, y);
- float s = animationProgress * finalScale + (1 - animationProgress) * initScale;
- child.setReorderBounceScale(s);
- }
-
- private void cancel() {
- if (a != null) {
- a.cancel();
- }
- }
-
- /**
- * Smoothly returns the item to its baseline position / scale
- */
- @Thunk void finishAnimation() {
- if (a != null) {
- a.cancel();
- }
-
- setInitialAnimationValuesToBaseline();
- ValueAnimator va = ObjectAnimator.ofFloat(this, ANIMATION_PROGRESS,
- animationProgress, 0);
- a = va;
- a.setInterpolator(DECELERATE_1_5);
- a.setDuration(REORDER_ANIMATION_DURATION);
- a.start();
- }
- }
-
private void completeAndClearReorderPreviewAnimations() {
for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
a.finishAnimation();
diff --git a/src/com/android/launcher3/FastScrollRecyclerView.java b/src/com/android/launcher3/FastScrollRecyclerView.java
index a13dcc1..7ec0a89 100644
--- a/src/com/android/launcher3/FastScrollRecyclerView.java
+++ b/src/com/android/launcher3/FastScrollRecyclerView.java
@@ -197,11 +197,11 @@
/**
* Scrolls this recycler view to the bottom with easing and duration.
*/
- public void scrollToBottomWithMotion() {
+ public void scrollToBottomWithMotion(int duration) {
if (mScrollbar != null) {
mScrollbar.reattachThumbToScroll();
}
// Emphasized interpolators with 500ms duration
- smoothScrollBy(0, getAvailableScrollHeight(), Interpolators.EMPHASIZED, 500);
+ smoothScrollBy(0, getAvailableScrollHeight(), Interpolators.EMPHASIZED, duration);
}
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 39b8de1..b531780 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -54,7 +54,6 @@
import static com.android.launcher3.LauncherConstants.TraceEvents.ON_START_EVT;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
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.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.EDIT_MODE;
import static com.android.launcher3.LauncherState.FLAG_MULTI_PAGE;
@@ -219,6 +218,7 @@
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
+import com.android.launcher3.util.ItemInflater;
import com.android.launcher3.util.KeyboardShortcutsDelegate;
import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.PackageUserKey;
@@ -246,8 +246,6 @@
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.PendingAppWidgetHostView;
import com.android.launcher3.widget.WidgetAddFlowHandler;
-import com.android.launcher3.widget.WidgetInflater;
-import com.android.launcher3.widget.WidgetInflater.InflationResult;
import com.android.launcher3.widget.WidgetManagerHelper;
import com.android.launcher3.widget.custom.CustomWidgetManager;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
@@ -329,7 +327,7 @@
private WidgetManagerHelper mAppWidgetManager;
private LauncherWidgetHolder mAppWidgetHolder;
- private WidgetInflater mWidgetInflater;
+ private ItemInflater<Launcher> mItemInflater;
private final int[] mTmpAddItemCellCoordinates = new int[2];
@@ -515,10 +513,11 @@
updateDisallowBack();
mAppWidgetManager = new WidgetManagerHelper(this);
- mWidgetInflater = new WidgetInflater(this);
mAppWidgetHolder = createAppWidgetHolder();
mAppWidgetHolder.startListening();
mAppWidgetHolder.addProviderChangeListener(() -> refreshAndBindWidgetsForPackageUser(null));
+ mItemInflater = new ItemInflater<>(this, mAppWidgetHolder, getItemOnClickListener(),
+ mFocusHandler, new CellLayout(mWorkspace.getContext()));
mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
@@ -1354,35 +1353,6 @@
}
/**
- * Creates a view representing a shortcut.
- *
- * @param info The data structure describing the shortcut.
- */
- View createShortcut(WorkspaceItemInfo info) {
- // This can be called before PagedView#pageScrollsInitialized returns true, so use the
- // first page, which we always assume to be present.
- return createShortcut((ViewGroup) mWorkspace.getChildAt(0), info);
- }
-
- /**
- * Creates a view representing a shortcut inflated from the specified resource.
- *
- * @param parent The group the shortcut belongs to. This is not necessarily the group where
- * the shortcut should be added.
- * @param info The data structure describing the shortcut.
- * @return A View inflated from layoutResId.
- */
- public View createShortcut(@Nullable ViewGroup parent, WorkspaceItemInfo info) {
- BubbleTextView favorite =
- (BubbleTextView) LayoutInflater.from(parent != null ? parent.getContext() : this)
- .inflate(R.layout.app_icon, parent, false);
- favorite.applyFromWorkspaceItem(info);
- favorite.setOnClickListener(getItemOnClickListener());
- favorite.setOnFocusChangeListener(mFocusHandler);
- return favorite;
- }
-
- /**
* Add a shortcut to the workspace or to a Folder.
*
* @param data The intent describing the shortcut.
@@ -1405,7 +1375,7 @@
if (container < 0) {
// Adding a shortcut to the Workspace.
- final View view = createShortcut(info);
+ final View view = mItemInflater.inflateItem(info, getModelWriter());
boolean foundCellSpan = false;
// First we check if we already know the exact location where we want to add this item.
if (cellX >= 0 && cellY >= 0) {
@@ -1491,7 +1461,7 @@
itemInfo.container, presenterPos.screenId, presenterPos.cellX, presenterPos.cellY);
hostView.setVisibility(View.VISIBLE);
- prepareAppWidget(hostView, launcherInfo);
+ mItemInflater.prepareAppWidget(hostView, launcherInfo);
mWorkspace.addInScreen(hostView, launcherInfo);
announceForAccessibility(R.string.item_added_to_workspace);
@@ -1516,12 +1486,6 @@
}
}
- private void prepareAppWidget(AppWidgetHostView hostView, LauncherAppWidgetInfo item) {
- hostView.setTag(item);
- hostView.setFocusable(true);
- hostView.setOnFocusChangeListener(mFocusHandler);
- }
-
private final ScreenOnListener mScreenOnListener = this::onScreenOnChanged;
private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
@@ -2157,63 +2121,19 @@
final boolean focusFirstItemForAccessibility) {
// Get the list of added items and intersect them with the set of items here
final Collection<Animator> bounceAnims = new ArrayList<>();
- boolean canAnimatePageChange = canAnimatePageChange();
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);
- // Short circuit if we are loading dock items for a configuration which has no dock
- if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
- mHotseat == null) {
- continue;
- }
- final View view;
- switch (item.itemType) {
- case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
- case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
- WorkspaceItemInfo info = (WorkspaceItemInfo) item;
- view = createShortcut(info);
- break;
- }
- case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: {
- view = FolderIcon.inflateFolderAndIcon(R.layout.folder_icon, this,
- (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
- (FolderInfo) item);
- break;
- }
- case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR: {
- view = AppPairIcon.inflateIcon(R.layout.app_pair_icon, this,
- (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
- (FolderInfo) item);
- break;
- }
- case ITEM_TYPE_APPWIDGET:
- case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: {
- view = inflateAppWidget((LauncherAppWidgetInfo) item);
- if (view == null) {
- continue;
- }
- break;
- }
- default:
- throw new RuntimeException("Invalid Item Type");
- }
-
- /*
- * Remove colliding items.
- */
+ // Remove colliding items.
CellPos presenterPos = getCellPosMapper().mapModelToPresenter(item);
if (item.container == CONTAINER_DESKTOP) {
CellLayout cl = mWorkspace.getScreenWithId(presenterPos.screenId);
if (cl != null && cl.isOccupied(presenterPos.cellX, presenterPos.cellY)) {
- View v = cl.getChildAt(presenterPos.cellX, presenterPos.cellY);
- if (v == null) {
- Log.e(TAG, "bindItems failed when removing colliding item=" + item);
- }
- Object tag = v.getTag();
+ Object tag = cl.getChildAt(presenterPos.cellX, presenterPos.cellY).getTag();
String desc = "Collision while binding workspace item: " + item
+ ". Collides with " + tag;
if (FeatureFlags.IS_STUDIO_BUILD) {
@@ -2224,6 +2144,11 @@
}
}
}
+
+ final View view = mItemInflater.inflateItem(item, getModelWriter());
+ if (view == null) {
+ continue;
+ }
workspace.addInScreenFromBind(view, item);
if (forceAnimateIcons) {
// Animate all the applications up now
@@ -2240,7 +2165,7 @@
}
View viewToFocus = newView;
- // Animate to the correct pager
+ // Animate to the correct page
if (forceAnimateIcons && newItemsScreenId > -1) {
AnimatorSet anim = new AnimatorSet();
anim.playTogether(bounceAnims);
@@ -2257,19 +2182,13 @@
final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newItemsScreenId);
final Runnable startBounceAnimRunnable = anim::start;
- if (canAnimatePageChange && newItemsScreenId != currentScreenId) {
+ if (canAnimatePageChange() && newItemsScreenId != currentScreenId) {
// We post the animation slightly delayed to prevent slowdowns
// when we are loading right after we return to launcher.
- mWorkspace.postDelayed(new Runnable() {
- public void run() {
- if (mWorkspace != null) {
- closeOpenViews(false);
-
- mWorkspace.snapToPage(newScreenIndex);
- mWorkspace.postDelayed(startBounceAnimRunnable,
- NEW_APPS_ANIMATION_DELAY);
- }
- }
+ mWorkspace.postDelayed(() -> {
+ closeOpenViews(false);
+ mWorkspace.snapToPage(newScreenIndex);
+ mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
}, NEW_APPS_PAGE_MOVE_DELAY);
} else {
mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
@@ -2284,36 +2203,13 @@
* Add the views for a widget to the workspace.
*/
public void bindAppWidget(LauncherAppWidgetInfo item) {
- View view = inflateAppWidget(item);
+ View view = mItemInflater.inflateItem(item, getModelWriter());
if (view != null) {
mWorkspace.addInScreen(view, item);
mWorkspace.requestLayout();
}
}
- private View inflateAppWidget(LauncherAppWidgetInfo item) {
- TraceHelper.INSTANCE.beginSection("BIND_WIDGET_id=" + item.appWidgetId);
- try {
- InflationResult inflationResult = mWidgetInflater.inflateAppWidget(item);
- if (inflationResult.getType() == WidgetInflater.TYPE_DELETE) {
- getModelWriter().deleteItemFromDatabase(item, inflationResult.getReason());
- return null;
- }
-
- if (inflationResult.isUpdate()) {
- getModelWriter().updateItemInDatabase(item);
- }
- AppWidgetHostView view = inflationResult.getType() == WidgetInflater.TYPE_PENDING
- ? new PendingAppWidgetHostView(this, item, inflationResult.getWidgetInfo())
- : mAppWidgetHolder.createView(
- item.appWidgetId, inflationResult.getWidgetInfo());
- prepareAppWidget(view, item);
- return view;
- } finally {
- TraceHelper.INSTANCE.endSection();
- }
- }
-
/**
* Restores a pending widget.
*
@@ -2760,7 +2656,7 @@
}
LauncherRootView rv = getRootView();
if (rv != null) {
- boolean isSplitSelectionEnabled = isSplitSelectionEnabled();
+ boolean isSplitSelectionEnabled = isSplitSelectionActive();
boolean disableBack = getStateManager().getState() == NORMAL
&& AbstractFloatingView.getTopOpenView(this) == null
&& !isSplitSelectionEnabled;
@@ -2769,7 +2665,7 @@
}
/** To be overridden by subclasses */
- public boolean isSplitSelectionEnabled() {
+ public boolean isSplitSelectionActive() {
// Overridden
return false;
}
@@ -3096,6 +2992,10 @@
return super.getStatsLogManager().withDefaultInstanceId(mAllAppsSessionLogId);
}
+ public ItemInflater<Launcher> getItemInflater() {
+ return mItemInflater;
+ }
+
/**
* Returns the current popup for testing, if any.
*/
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index be4168d..2eff8aa 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -73,7 +73,6 @@
import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.celllayout.CellInfo;
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.celllayout.CellPosMapper;
@@ -99,7 +98,6 @@
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
-import com.android.launcher3.model.data.WorkspaceItemFactory;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pageindicators.PageIndicator;
import com.android.launcher3.statemanager.StateManager;
@@ -2339,10 +2337,6 @@
}
}
- public CellLayout getCurrentDragOverlappingLayout() {
- return mDragOverlappingLayout;
- }
-
void setCurrentDropOverCell(int x, int y) {
if (x != mDragOverX || y != mDragOverY) {
mDragOverX = x;
@@ -2854,36 +2848,9 @@
} else {
// This is for other drag/drop cases, like dragging from All Apps
mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
- View view;
-
- switch (info.itemType) {
- case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
- case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
- case LauncherSettings.Favorites.ITEM_TYPE_SEARCH_ACTION:
- if (info instanceof WorkspaceItemFactory) {
- // Came from all apps -- make a copy
- info = ((WorkspaceItemFactory) info).makeWorkspaceItem(mLauncher);
- d.dragInfo = info;
- }
- if (info instanceof WorkspaceItemInfo
- && info.container == LauncherSettings.Favorites.CONTAINER_PREDICTION) {
- // Came from all apps prediction row -- make a copy
- info = new WorkspaceItemInfo((WorkspaceItemInfo) info);
- d.dragInfo = info;
- }
- view = mLauncher.createShortcut(cellLayout, (WorkspaceItemInfo) info);
- break;
- case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
- view = FolderIcon.inflateFolderAndIcon(R.layout.folder_icon, mLauncher, cellLayout,
- (FolderInfo) info);
- break;
- case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
- view = AppPairIcon.inflateIcon(R.layout.app_pair_icon, mLauncher, cellLayout,
- (FolderInfo) info);
- break;
- default:
- throw new IllegalStateException("Unknown item type: " + info.itemType);
- }
+ View view = mLauncher.getItemInflater()
+ .inflateItem(info, mLauncher.getModelWriter(), cellLayout);
+ d.dragInfo = info = (ItemInfo) view.getTag();
// First we find the cell nearest to point at which the item is
// dropped, without any consideration to whether there is an item there.
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index ac5b528..0844275 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -32,6 +32,7 @@
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.keyboard.KeyboardDragAndDropView;
+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.LauncherAppWidgetInfo;
@@ -126,7 +127,8 @@
}
private boolean supportAddToWorkSpace(ItemInfo item) {
- return (item instanceof WorkspaceItemFactory)
+ return ((item instanceof AppInfo)
+ && (((AppInfo) item).runtimeStatusFlags & FLAG_NOT_PINNABLE) == 0)
|| ((item instanceof WorkspaceItemInfo)
&& (((WorkspaceItemInfo) item).runtimeStatusFlags & FLAG_NOT_PINNABLE) == 0)
|| ((item instanceof PendingAddItemInfo)
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 55438fe..930196d 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -126,6 +126,7 @@
public static final float PULL_MULTIPLIER = .02f;
public static final float FLING_VELOCITY_MULTIPLIER = 1200f;
protected static final String BUNDLE_KEY_CURRENT_PAGE = "launcher.allapps.current_page";
+ private static final int SCROLL_TO_BOTTOM_DURATION = 500;
private static final long DEFAULT_SEARCH_TRANSITION_DURATION_MS = 300;
// Render the header protection at all times to debug clipping issues.
private static final boolean DEBUG_HEADER_PROTECTION = false;
@@ -515,7 +516,7 @@
// Switch to the main tab
switchToTab(ActivityAllAppsContainerView.AdapterHolder.MAIN);
// Scroll to bottom
- getActiveRecyclerView().scrollToBottomWithMotion();
+ getActiveRecyclerView().scrollToBottomWithMotion(SCROLL_TO_BOTTOM_DURATION);
});
}
diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java
index aee511c..1c46dac 100644
--- a/src/com/android/launcher3/allapps/PrivateProfileManager.java
+++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java
@@ -21,6 +21,7 @@
import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_HEADER;
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_PRIVATE_SPACE_INSTALL_APP;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SettingsCache.PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI;
@@ -105,6 +106,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;
BaseAllAppsAdapter.AdapterItem item = new BaseAllAppsAdapter.AdapterItem(VIEW_TYPE_ICON);
item.itemInfo = itemInfo;
diff --git a/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java b/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java
index bc3269d..bc55597 100644
--- a/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java
+++ b/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java
@@ -120,11 +120,7 @@
(ActivityAllAppsContainerView<?>.AdapterHolder) mAllApps.mAH.get(MAIN);
if (Flags.enablePrivateSpace() && Flags.privateSpaceAnimation()
&& mAllApps.getActiveRecyclerView() == mainAdapterHolder.mRecyclerView) {
- RecyclerViewAnimationController recyclerViewAnimationController =
- new RecyclerViewAnimationController(mAllApps);
- recyclerViewAnimationController.animateToState(true /* expand */,
- ANIMATION_DURATION, () -> {});
- mAllApps.getActiveRecyclerView().scrollToBottomWithMotion();
+ mAllApps.getActiveRecyclerView().scrollToBottomWithMotion(ANIMATION_DURATION);
}
}
diff --git a/src/com/android/launcher3/celllayout/ReorderPreviewAnimation.kt b/src/com/android/launcher3/celllayout/ReorderPreviewAnimation.kt
new file mode 100644
index 0000000..62b19d4
--- /dev/null
+++ b/src/com/android/launcher3/celllayout/ReorderPreviewAnimation.kt
@@ -0,0 +1,166 @@
+/*
+ * 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.celllayout
+
+import android.animation.ObjectAnimator
+import android.animation.ValueAnimator
+import android.animation.ValueAnimator.areAnimatorsEnabled
+import android.util.ArrayMap
+import android.view.View
+import com.android.app.animation.Interpolators.DECELERATE_1_5
+import com.android.launcher3.CellLayout
+import com.android.launcher3.CellLayout.REORDER_ANIMATION_DURATION
+import com.android.launcher3.Reorderable
+import com.android.launcher3.Workspace
+import com.android.launcher3.util.MultiTranslateDelegate.INDEX_REORDER_BOUNCE_OFFSET
+import com.android.launcher3.util.Thunk
+import kotlin.math.abs
+import kotlin.math.atan
+import kotlin.math.cos
+import kotlin.math.sign
+import kotlin.math.sin
+
+/**
+ * Class which represents the reorder preview animations. These animations show that an item is in a
+ * temporary state, and hint at where the item will return to.
+ */
+class ReorderPreviewAnimation<T>(
+ val child: T,
+ // If the mode is MODE_HINT it will only move one period and stop, it then will be going
+ // backwards to the initial position, otherwise it will oscillate.
+ val mode: Int,
+ cellX0: Int,
+ cellY0: Int,
+ cellX1: Int,
+ cellY1: Int,
+ spanX: Int,
+ spanY: Int,
+ reorderMagnitude: Float,
+ cellLayout: CellLayout,
+ private val shakeAnimators: ArrayMap<Reorderable, ReorderPreviewAnimation<T>>
+) : ValueAnimator.AnimatorUpdateListener where T : View, T : Reorderable {
+
+ private var finalDeltaX = 0f
+ private var finalDeltaY = 0f
+ private var initDeltaX =
+ child.getTranslateDelegate().getTranslationX(INDEX_REORDER_BOUNCE_OFFSET).value
+ private var initDeltaY =
+ child.getTranslateDelegate().getTranslationY(INDEX_REORDER_BOUNCE_OFFSET).value
+ private var initScale = child.getReorderBounceScale()
+ private val finalScale = CellLayout.DEFAULT_SCALE - CHILD_DIVIDEND / child.width * initScale
+
+ private val dir = if (mode == MODE_HINT) -1 else 1
+ var animator: ValueAnimator =
+ ObjectAnimator.ofFloat(0f, 1f).also {
+ it.addUpdateListener(this)
+ it.setDuration((if (mode == MODE_HINT) HINT_DURATION else PREVIEW_DURATION).toLong())
+ it.startDelay = (Math.random() * 60).toLong()
+ // Animations are disabled in power save mode, causing the repeated animation to jump
+ // spastically between beginning and end states. Since this looks bad, we don't repeat
+ // the animation in power save mode.
+ if (areAnimatorsEnabled() && mode == MODE_PREVIEW) {
+ it.repeatCount = ValueAnimator.INFINITE
+ it.repeatMode = ValueAnimator.REVERSE
+ }
+ }
+
+ init {
+ val tmpRes = intArrayOf(0, 0)
+ cellLayout.regionToCenterPoint(cellX0, cellY0, spanX, spanY, tmpRes)
+ val (x0, y0) = tmpRes
+ cellLayout.regionToCenterPoint(cellX1, cellY1, spanX, spanY, tmpRes)
+ val (x1, y1) = tmpRes
+ val dX = x1 - x0
+ val dY = y1 - y0
+
+ if (dX != 0 || dY != 0) {
+ if (dY == 0) {
+ finalDeltaX = -dir * sign(dX.toFloat()) * reorderMagnitude
+ } else if (dX == 0) {
+ finalDeltaY = -dir * sign(dY.toFloat()) * reorderMagnitude
+ } else {
+ val angle = atan((dY.toFloat() / dX))
+ finalDeltaX = (-dir * sign(dX.toFloat()) * abs(cos(angle) * reorderMagnitude))
+ finalDeltaY = (-dir * sign(dY.toFloat()) * abs(sin(angle) * reorderMagnitude))
+ }
+ }
+ }
+
+ private fun setInitialAnimationValuesToBaseline() {
+ initScale = CellLayout.DEFAULT_SCALE
+ initDeltaX = 0f
+ initDeltaY = 0f
+ }
+
+ fun animate() {
+ val noMovement = finalDeltaX == 0f && finalDeltaY == 0f
+ if (shakeAnimators.containsKey(child)) {
+ val oldAnimation: ReorderPreviewAnimation<T>? = shakeAnimators.remove(child)
+ if (noMovement) {
+ // A previous animation for this item exists, and no new animation will exist.
+ // Finish the old animation smoothly.
+ oldAnimation!!.finishAnimation()
+ return
+ } else {
+ // A previous animation for this item exists, and a new one will exist. Stop
+ // the old animation in its tracks, and proceed with the new one.
+ oldAnimation!!.cancel()
+ }
+ }
+ if (noMovement) {
+ return
+ }
+ shakeAnimators[child] = this
+ animator.start()
+ }
+
+ override fun onAnimationUpdate(updatedAnimation: ValueAnimator) {
+ val progress = updatedAnimation.animatedValue as Float
+ child
+ .getTranslateDelegate()
+ .setTranslation(
+ INDEX_REORDER_BOUNCE_OFFSET,
+ /* x = */ progress * finalDeltaX + (1 - progress) * initDeltaX,
+ /* y = */ progress * finalDeltaY + (1 - progress) * initDeltaY
+ )
+ child.setReorderBounceScale(progress * finalScale + (1 - progress) * initScale)
+ }
+
+ private fun cancel() {
+ animator.cancel()
+ }
+
+ /** Smoothly returns the item to its baseline position / scale */
+ @Thunk
+ fun finishAnimation() {
+ animator.cancel()
+ setInitialAnimationValuesToBaseline()
+ animator = ObjectAnimator.ofFloat((animator.animatedValue as Float), 0f)
+ animator.addUpdateListener(this)
+ animator.interpolator = DECELERATE_1_5
+ animator.setDuration(REORDER_ANIMATION_DURATION.toLong())
+ animator.start()
+ }
+
+ companion object {
+ const val PREVIEW_DURATION = 300
+ const val HINT_DURATION = Workspace.REORDER_TIMEOUT
+ private const val CHILD_DIVIDEND = 4.0f
+ const val MODE_HINT = 0
+ const val MODE_PREVIEW = 1
+ }
+}
diff --git a/src/com/android/launcher3/folder/LauncherDelegate.java b/src/com/android/launcher3/folder/LauncherDelegate.java
index 66c9109..78298b3 100644
--- a/src/com/android/launcher3/folder/LauncherDelegate.java
+++ b/src/com/android/launcher3/folder/LauncherDelegate.java
@@ -94,7 +94,8 @@
CellLayout cellLayout = mLauncher.getCellLayout(info.container,
mLauncher.getCellPosMapper().mapModelToPresenter(info).screenId);
finalItem = info.contents.remove(0);
- newIcon = mLauncher.createShortcut(cellLayout, finalItem);
+ newIcon = mLauncher.getItemInflater().inflateItem(
+ finalItem, mLauncher.getModelWriter(), cellLayout);
mLauncher.getModelWriter().addOrMoveItemInDatabase(finalItem,
info.container, info.screenId, info.cellX, info.cellY);
}
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 45ff33b..f9b7495 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -713,6 +713,9 @@
@UiEvent(doc = "User tapped on install to private space system shortcut.")
LAUNCHER_PRIVATE_SPACE_INSTALL_SYSTEM_SHORTCUT_TAP(1565),
+ @UiEvent(doc = "User tapped private space install app button.")
+ LAUNCHER_PRIVATE_SPACE_INSTALL_APP_BUTTON_TAP(1605),
+
@UiEvent(doc = "User attempted to create split screen with a widget")
LAUNCHER_SPLIT_WIDGET_ATTEMPT(1604)
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 1044dfb..2f678a8 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -50,11 +50,13 @@
import com.android.launcher3.model.data.IconRequestInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSparseArrayMap;
+import com.android.launcher3.util.UserIconInfo;
import java.net.URISyntaxException;
import java.security.InvalidParameterException;
@@ -353,6 +355,8 @@
final WorkspaceItemInfo info = new WorkspaceItemInfo();
info.user = user;
info.intent = newIntent;
+ UserCache userCache = UserCache.getInstance(mContext);
+ UserIconInfo userIconInfo = userCache.getUserInfo(user);
if (loadIcon) {
mIconCache.getTitleAndIcon(info, mActivityInfo, useLowResIcon);
@@ -362,7 +366,7 @@
}
if (mActivityInfo != null) {
- AppInfo.updateRuntimeFlagsForActivityTarget(info, mActivityInfo);
+ AppInfo.updateRuntimeFlagsForActivityTarget(info, mActivityInfo, userIconInfo);
}
// from the db
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index d0a1f10..736b80a 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -655,7 +655,7 @@
// Create the ApplicationInfos
for (int i = 0; i < apps.size(); i++) {
LauncherActivityInfo app = apps.get(i);
- AppInfo appInfo = new AppInfo(app, user, quietMode);
+ AppInfo appInfo = new AppInfo(app, mUserCache.getUserInfo(user), quietMode);
if (enableSupportForArchiving() && app.getApplicationInfo().isArchived) {
// For archived apps, include progress info in case there is a pending
// install session post restart of device.
diff --git a/src/com/android/launcher3/model/data/AppInfo.java b/src/com/android/launcher3/model/data/AppInfo.java
index ea8a7a1..72eda6c 100644
--- a/src/com/android/launcher3/model/data/AppInfo.java
+++ b/src/com/android/launcher3/model/data/AppInfo.java
@@ -31,10 +31,13 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.Flags;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
import com.android.launcher3.pm.PackageInstallInfo;
+import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.UserIconInfo;
import java.util.Comparator;
@@ -83,20 +86,21 @@
* Must not hold the Context.
*/
public AppInfo(Context context, LauncherActivityInfo info, UserHandle user) {
- this(info, user, context.getSystemService(UserManager.class).isQuietModeEnabled(user));
+ this(info, UserCache.INSTANCE.get(context).getUserInfo(user),
+ context.getSystemService(UserManager.class).isQuietModeEnabled(user));
}
- public AppInfo(LauncherActivityInfo info, UserHandle user, boolean quietModeEnabled) {
+ public AppInfo(LauncherActivityInfo info, UserIconInfo userIconInfo, boolean quietModeEnabled) {
this.componentName = info.getComponentName();
this.container = CONTAINER_ALL_APPS;
- this.user = user;
+ this.user = userIconInfo.user;
intent = makeLaunchIntent(info);
if (quietModeEnabled) {
runtimeStatusFlags |= FLAG_DISABLED_QUIET_USER;
}
uid = info.getApplicationInfo().uid;
- updateRuntimeFlagsForActivityTarget(this, info);
+ updateRuntimeFlagsForActivityTarget(this, info, userIconInfo);
}
public AppInfo(AppInfo info) {
@@ -170,7 +174,7 @@
}
public static void updateRuntimeFlagsForActivityTarget(
- ItemInfoWithIcon info, LauncherActivityInfo lai) {
+ ItemInfoWithIcon info, LauncherActivityInfo lai, UserIconInfo userIconInfo) {
ApplicationInfo appInfo = lai.getApplicationInfo();
if (PackageManagerHelper.isAppSuspended(appInfo)) {
info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;
@@ -181,6 +185,12 @@
info.runtimeStatusFlags |= (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0
? FLAG_SYSTEM_NO : FLAG_SYSTEM_YES;
+ if (Flags.privateSpaceRestrictAccessibilityDrag()) {
+ if (userIconInfo.isPrivate()) {
+ info.runtimeStatusFlags |= FLAG_NOT_PINNABLE;
+ }
+ }
+
// Sets the progress level, installation and incremental download flags.
info.setProgressLevel(
PackageManagerHelper.getLoadingProgress(lai),
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index 58b12b1..c8ab09c 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -122,6 +122,11 @@
public static final int FLAG_ARCHIVED = 1 << 14;
/**
+ * Flag indicating it's the Private Space Install App icon.
+ */
+ public static final int FLAG_PRIVATE_SPACE_INSTALL_APP = 1 << 15;
+
+ /**
* Status associated with the system state of the underlying item. This is calculated every
* time a new info is created and not persisted on the disk.
*/
diff --git a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
index c67ec5a..9e169cf 100644
--- a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
+++ b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
@@ -25,10 +25,12 @@
import androidx.annotation.NonNull;
+import com.android.launcher3.Flags;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.Utilities;
import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.uioverrides.ApiWrapper;
import com.android.launcher3.util.ContentWriter;
@@ -120,6 +122,11 @@
public WorkspaceItemInfo(ShortcutInfo shortcutInfo, Context context) {
user = shortcutInfo.getUserHandle();
itemType = Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+ if (Flags.privateSpaceRestrictAccessibilityDrag()) {
+ if (UserCache.INSTANCE.get(context).getUserInfo(user).isPrivate()) {
+ runtimeStatusFlags |= FLAG_NOT_PINNABLE;
+ }
+ }
updateFromDeepShortcutInfo(shortcutInfo, context);
}
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index ff8b381..369008d 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -19,6 +19,7 @@
import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_BIND_PENDING_APPWIDGET;
import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_RECONFIGURE_APPWIDGET;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_INSTALL_APP_BUTTON_TAP;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_BY_PUBLISHER;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_QUIET_USER;
@@ -352,6 +353,12 @@
appInfo.getTargetComponent().getPackageName(), Process.myUserHandle());
} else {
intent = item.getIntent();
+ if (item instanceof AppInfo
+ && (((ItemInfoWithIcon) item).runtimeStatusFlags
+ & ItemInfoWithIcon.FLAG_PRIVATE_SPACE_INSTALL_APP) != 0) {
+ launcher.getStatsLogManager().logger().log(
+ LAUNCHER_PRIVATE_SPACE_INSTALL_APP_BUTTON_TAP);
+ }
}
if (intent == null) {
throw new IllegalArgumentException("Input must have a valid intent");
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index 9e7d4dc..116f13a 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -184,7 +184,7 @@
// Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
if (launcher.getDragController().isDragging()) return false;
// Return early if user is in the middle of selecting split-screen apps
- if (FeatureFlags.enableSplitContextually() && launcher.isSplitSelectionEnabled()) {
+ if (FeatureFlags.enableSplitContextually() && launcher.isSplitSelectionActive()) {
return false;
}
diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
index 5b6c9e0..8c43f75 100644
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -206,7 +206,7 @@
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
mLauncher.getStatsLogManager().logger().log(LAUNCHER_WORKSPACE_LONGPRESS);
mLauncher.showDefaultOptions(mTouchDownPoint.x, mTouchDownPoint.y);
- if (FeatureFlags.enableSplitContextually() && mLauncher.isSplitSelectionEnabled()) {
+ if (FeatureFlags.enableSplitContextually() && mLauncher.isSplitSelectionActive()) {
mLauncher.dismissSplitSelection();
}
} else {
diff --git a/src/com/android/launcher3/util/ItemInflater.kt b/src/com/android/launcher3/util/ItemInflater.kt
new file mode 100644
index 0000000..79091ca
--- /dev/null
+++ b/src/com/android/launcher3/util/ItemInflater.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.appwidget.AppWidgetHostView
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.OnClickListener
+import android.view.View.OnFocusChangeListener
+import android.view.ViewGroup
+import com.android.launcher3.BubbleTextView
+import com.android.launcher3.LauncherSettings.Favorites
+import com.android.launcher3.R
+import com.android.launcher3.apppairs.AppPairIcon
+import com.android.launcher3.folder.FolderIcon
+import com.android.launcher3.model.ModelWriter
+import com.android.launcher3.model.data.FolderInfo
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.LauncherAppWidgetInfo
+import com.android.launcher3.model.data.WorkspaceItemFactory
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.views.ActivityContext
+import com.android.launcher3.widget.LauncherWidgetHolder
+import com.android.launcher3.widget.PendingAppWidgetHostView
+import com.android.launcher3.widget.WidgetInflater
+
+/** Utility class to inflate View for a model item */
+class ItemInflater<T>(
+ private val context: T,
+ private val widgetHolder: LauncherWidgetHolder,
+ private val clickListener: OnClickListener,
+ private val focusListener: OnFocusChangeListener,
+ private val defaultParent: ViewGroup
+) where T : Context, T : ActivityContext {
+
+ private val widgetInflater = WidgetInflater(context)
+
+ @JvmOverloads
+ fun inflateItem(item: ItemInfo, writer: ModelWriter, nullableParent: ViewGroup? = null): View? {
+ val parent = nullableParent ?: defaultParent
+ when (item.itemType) {
+ Favorites.ITEM_TYPE_APPLICATION,
+ Favorites.ITEM_TYPE_DEEP_SHORTCUT,
+ Favorites.ITEM_TYPE_SEARCH_ACTION -> {
+ var info =
+ if (item is WorkspaceItemFactory) {
+ (item as WorkspaceItemFactory).makeWorkspaceItem(context)
+ } else {
+ item as WorkspaceItemInfo
+ }
+ if (info.container == Favorites.CONTAINER_PREDICTION) {
+ // Came from all apps prediction row -- make a copy
+ info = WorkspaceItemInfo(info)
+ }
+ return createShortcut(info, parent)
+ }
+ Favorites.ITEM_TYPE_FOLDER ->
+ return FolderIcon.inflateFolderAndIcon(
+ R.layout.folder_icon,
+ context,
+ parent,
+ item as FolderInfo
+ )
+ Favorites.ITEM_TYPE_APP_PAIR ->
+ return AppPairIcon.inflateIcon(
+ R.layout.app_pair_icon,
+ context,
+ parent,
+ item as FolderInfo
+ )
+ Favorites.ITEM_TYPE_APPWIDGET,
+ Favorites.ITEM_TYPE_CUSTOM_APPWIDGET ->
+ return inflateAppWidget(item as LauncherAppWidgetInfo, writer)
+ else -> throw RuntimeException("Invalid Item Type")
+ }
+ }
+
+ /**
+ * Creates a view representing a shortcut inflated from the specified resource.
+ *
+ * @param parent The group the shortcut belongs to. This is not necessarily the group where the
+ * shortcut should be added.
+ * @param info The data structure describing the shortcut.
+ * @return A View inflated from layoutResId.
+ */
+ private fun createShortcut(info: WorkspaceItemInfo, parent: ViewGroup): View {
+ val favorite =
+ LayoutInflater.from(parent.context).inflate(R.layout.app_icon, parent, false)
+ as BubbleTextView
+ favorite.applyFromWorkspaceItem(info)
+ favorite.setOnClickListener(clickListener)
+ favorite.onFocusChangeListener = focusListener
+ return favorite
+ }
+
+ private fun inflateAppWidget(item: LauncherAppWidgetInfo, writer: ModelWriter): View? {
+ TraceHelper.INSTANCE.beginSection("BIND_WIDGET_id=" + item.appWidgetId)
+ try {
+ val (type, reason, _, isUpdate, widgetInfo) = widgetInflater.inflateAppWidget(item)
+ if (type == WidgetInflater.TYPE_DELETE) {
+ writer.deleteItemFromDatabase(item, reason)
+ return null
+ }
+ if (isUpdate) {
+ writer.updateItemInDatabase(item)
+ }
+ val view =
+ if (type == WidgetInflater.TYPE_PENDING || widgetInfo == null)
+ PendingAppWidgetHostView(context, item, widgetInfo)
+ else widgetHolder.createView(item.appWidgetId, widgetInfo)
+ prepareAppWidget(view, item)
+ return view
+ } finally {
+ TraceHelper.INSTANCE.endSection()
+ }
+ }
+
+ fun prepareAppWidget(hostView: AppWidgetHostView, item: LauncherAppWidgetInfo) {
+ hostView.tag = item
+ hostView.isFocusable = true
+ hostView.onFocusChangeListener = focusListener
+ }
+}
diff --git a/src/com/android/launcher3/util/StartActivityParams.java b/src/com/android/launcher3/util/StartActivityParams.java
index b48562f..d66b0a0 100644
--- a/src/com/android/launcher3/util/StartActivityParams.java
+++ b/src/com/android/launcher3/util/StartActivityParams.java
@@ -52,6 +52,7 @@
public int flagsValues;
public int extraFlags;
public Bundle options;
+ public boolean requireActivityResult = true;
public StartActivityParams(Activity activity, int requestCode) {
this(activity.createPendingResult(requestCode, new Intent(),
@@ -74,6 +75,7 @@
flagsValues = parcel.readInt();
extraFlags = parcel.readInt();
options = parcel.readBundle();
+ requireActivityResult = parcel.readInt() != 0;
}
@@ -94,6 +96,7 @@
parcel.writeInt(flagsValues);
parcel.writeInt(extraFlags);
parcel.writeBundle(options);
+ parcel.writeInt(requireActivityResult ? 1 : 0);
}
/** Perform the operation on the pendingIntent. */
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index bef84f7..0cddc97 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -150,7 +150,7 @@
* @return {@code true} if user has selected the first split app and is in the process of
* selecting the second
*/
- default boolean isSplitSelectionEnabled() {
+ default boolean isSplitSelectionActive() {
// Overridden
return false;
}
diff --git a/tests/Android.bp b/tests/Android.bp
index a236954..310e418 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -21,13 +21,13 @@
filegroup {
name: "launcher-tests-src",
srcs: [
- "src/**/*.java",
- "src/**/*.kt",
- "multivalentTests/src/**/*.java",
- "multivalentTests/src/**/*.kt",
+ "src/**/*.java",
+ "src/**/*.kt",
+ "multivalentTests/src/**/*.java",
+ "multivalentTests/src/**/*.kt",
],
exclude_srcs: [
- ":launcher-non-quickstep-tests-src"
+ ":launcher-non-quickstep-tests-src",
],
}
@@ -35,25 +35,25 @@
filegroup {
name: "launcher-image-tests-helpers",
srcs: [
- "multivalentTests/src/com/android/launcher3/celllayout/board/*.java",
- "multivalentTests/src/com/android/launcher3/celllayout/board/*.kt",
- "multivalentTests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java",
- "multivalentTests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java",
- "multivalentTests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java",
- "multivalentTests/src/com/android/launcher3/ui/TestViewHelpers.java",
- "multivalentTests/src/com/android/launcher3/util/LauncherLayoutBuilder.java",
- "multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt",
- "multivalentTests/src/com/android/launcher3/util/TestConstants.java",
- "multivalentTests/src/com/android/launcher3/util/TestUtil.java",
- "multivalentTests/src/com/android/launcher3/util/Wait.java",
- "multivalentTests/src/com/android/launcher3/util/WidgetUtils.java",
- "multivalentTests/src/com/android/launcher3/util/rule/*.java",
- "multivalentTests/src/com/android/launcher3/util/rule/*.kt",
- "multivalentTests/src/com/android/launcher3/util/rule/*.java",
- "multivalentTests/src/com/android/launcher3/util/rule/*.kt",
- "multivalentTests/src/com/android/launcher3/util/viewcapture_analysis/*.java",
- "multivalentTests/src/com/android/launcher3/testcomponent/*.java",
- "multivalentTests/src/com/android/launcher3/testcomponent/*.kt",
+ "multivalentTests/src/com/android/launcher3/celllayout/board/*.java",
+ "multivalentTests/src/com/android/launcher3/celllayout/board/*.kt",
+ "multivalentTests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java",
+ "multivalentTests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java",
+ "multivalentTests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java",
+ "multivalentTests/src/com/android/launcher3/ui/TestViewHelpers.java",
+ "multivalentTests/src/com/android/launcher3/util/LauncherLayoutBuilder.java",
+ "multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt",
+ "multivalentTests/src/com/android/launcher3/util/TestConstants.java",
+ "multivalentTests/src/com/android/launcher3/util/TestUtil.java",
+ "multivalentTests/src/com/android/launcher3/util/Wait.java",
+ "multivalentTests/src/com/android/launcher3/util/WidgetUtils.java",
+ "multivalentTests/src/com/android/launcher3/util/rule/*.java",
+ "multivalentTests/src/com/android/launcher3/util/rule/*.kt",
+ "multivalentTests/src/com/android/launcher3/util/rule/*.java",
+ "multivalentTests/src/com/android/launcher3/util/rule/*.kt",
+ "multivalentTests/src/com/android/launcher3/util/viewcapture_analysis/*.java",
+ "multivalentTests/src/com/android/launcher3/testcomponent/*.java",
+ "multivalentTests/src/com/android/launcher3/testcomponent/*.kt",
],
}
@@ -61,8 +61,8 @@
filegroup {
name: "launcher-non-quickstep-tests-src",
srcs: [
- "src/com/android/launcher3/nonquickstep/**/*.java",
- "src/com/android/launcher3/nonquickstep/**/*.kt",
+ "src/com/android/launcher3/nonquickstep/**/*.java",
+ "src/com/android/launcher3/nonquickstep/**/*.kt",
],
}
@@ -70,42 +70,42 @@
filegroup {
name: "launcher-oop-tests-src",
srcs: [
- "src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java",
- "src/com/android/launcher3/allapps/TaplAllAppsIconsWorkingTest.java",
- "src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java",
- "src/com/android/launcher3/dragging/TaplDragTest.java",
- "src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java",
- "multivalentTests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java",
- "multivalentTests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java",
- "src/com/android/launcher3/ui/TaplTestsLauncher3Test.java",
- "src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java",
- "src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java",
- "multivalentTests/src/com/android/launcher3/util/LauncherLayoutBuilder.java",
- "multivalentTests/src/com/android/launcher3/util/TestConstants.java",
- "multivalentTests/src/com/android/launcher3/util/TestUtil.java",
- "multivalentTests/src/com/android/launcher3/util/Wait.java",
- "multivalentTests/src/com/android/launcher3/util/WidgetUtils.java",
- "multivalentTests/src/com/android/launcher3/util/rule/FailureWatcher.java",
- "multivalentTests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt",
- "multivalentTests/src/com/android/launcher3/util/rule/SamplerRule.java",
- "multivalentTests/src/com/android/launcher3/util/rule/ScreenRecordRule.java",
- "multivalentTests/src/com/android/launcher3/util/rule/ShellCommandRule.java",
- "multivalentTests/src/com/android/launcher3/util/rule/TestIsolationRule.java",
- "multivalentTests/src/com/android/launcher3/util/rule/TestStabilityRule.java",
- "multivalentTests/src/com/android/launcher3/util/viewcapture_analysis/*.java",
- "multivalentTests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java",
- "multivalentTests/src/com/android/launcher3/testcomponent/OtherBaseTestingActivity.java",
- "multivalentTests/src/com/android/launcher3/testcomponent/CustomShortcutConfigActivity.java",
- "multivalentTests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java",
- "multivalentTests/src/com/android/launcher3/testcomponent/TestLauncherActivity.java",
- "multivalentTests/src/com/android/launcher3/testcomponent/ImeTestActivity.java",
+ "src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java",
+ "src/com/android/launcher3/allapps/TaplAllAppsIconsWorkingTest.java",
+ "src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java",
+ "src/com/android/launcher3/dragging/TaplDragTest.java",
+ "src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java",
+ "multivalentTests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java",
+ "multivalentTests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java",
+ "src/com/android/launcher3/ui/TaplTestsLauncher3Test.java",
+ "src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java",
+ "src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java",
+ "multivalentTests/src/com/android/launcher3/util/LauncherLayoutBuilder.java",
+ "multivalentTests/src/com/android/launcher3/util/TestConstants.java",
+ "multivalentTests/src/com/android/launcher3/util/TestUtil.java",
+ "multivalentTests/src/com/android/launcher3/util/Wait.java",
+ "multivalentTests/src/com/android/launcher3/util/WidgetUtils.java",
+ "multivalentTests/src/com/android/launcher3/util/rule/FailureWatcher.java",
+ "multivalentTests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt",
+ "multivalentTests/src/com/android/launcher3/util/rule/SamplerRule.java",
+ "multivalentTests/src/com/android/launcher3/util/rule/ScreenRecordRule.java",
+ "multivalentTests/src/com/android/launcher3/util/rule/ShellCommandRule.java",
+ "multivalentTests/src/com/android/launcher3/util/rule/TestIsolationRule.java",
+ "multivalentTests/src/com/android/launcher3/util/rule/TestStabilityRule.java",
+ "multivalentTests/src/com/android/launcher3/util/viewcapture_analysis/*.java",
+ "multivalentTests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java",
+ "multivalentTests/src/com/android/launcher3/testcomponent/OtherBaseTestingActivity.java",
+ "multivalentTests/src/com/android/launcher3/testcomponent/CustomShortcutConfigActivity.java",
+ "multivalentTests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java",
+ "multivalentTests/src/com/android/launcher3/testcomponent/TestLauncherActivity.java",
+ "multivalentTests/src/com/android/launcher3/testcomponent/ImeTestActivity.java",
],
}
// Library with all the dependencies for building quickstep
android_library {
name: "Launcher3TestLib",
- srcs: [ ],
+ srcs: [],
asset_dirs: ["assets"],
resource_dirs: ["res"],
static_libs: [
@@ -130,11 +130,15 @@
],
manifest: "AndroidManifest-common.xml",
platform_apis: true,
+ // TODO(b/319712088): re-enable use_resource_processor
+ use_resource_processor: false,
}
android_library {
name: "Launcher3TestResources",
resource_dirs: ["res"],
+ // TODO(b/319712088): re-enable use_resource_processor
+ use_resource_processor: false,
}
android_test {
@@ -171,24 +175,24 @@
android_library {
name: "launcher-testing-shared",
srcs: [
- "multivalentTests/shared/com/android/launcher3/testing/shared/**/*.java"
+ "multivalentTests/shared/com/android/launcher3/testing/shared/**/*.java",
],
- resource_dirs: [ ],
+ resource_dirs: [],
manifest: "multivalentTests/shared/AndroidManifest.xml",
sdk_version: "current",
min_sdk_version: min_launcher3_sdk_version,
- }
+}
filegroup {
name: "launcher-testing-helpers",
srcs: [
- "src/**/*.java",
- "src/**/*.kt",
- "multivalentTests/src/**/*.java",
- "multivalentTests/src/**/*.kt",
- "multivalentTests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java",
- "multivalentTests/tapl/com/android/launcher3/tapl/*.java",
- "multivalentTests/tapl/com/android/launcher3/tapl/*.kt",
+ "src/**/*.java",
+ "src/**/*.kt",
+ "multivalentTests/src/**/*.java",
+ "multivalentTests/src/**/*.kt",
+ "multivalentTests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java",
+ "multivalentTests/tapl/com/android/launcher3/tapl/*.java",
+ "multivalentTests/tapl/com/android/launcher3/tapl/*.kt",
],
exclude_srcs: [
// Test classes
@@ -209,6 +213,8 @@
static_libs: [
"Launcher3CommonDepsLib",
],
+ // TODO(b/319712088): re-enable use_resource_processor
+ use_resource_processor: false,
}
android_robolectric_test {
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 2596b75..daace8e 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -24,7 +24,7 @@
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
- <receiver android:name="com.android.launcher3.compat.PromiseIconUiTest$UnarchiveBroadcastReceiver"
+ <receiver android:name="com.android.launcher3.compat.TaplPromiseIconUiTest$UnarchiveBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
diff --git a/tests/multivalentTests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/multivalentTests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index f68e12c..fef93b7 100644
--- a/tests/multivalentTests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/multivalentTests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -1771,6 +1771,7 @@
}
endTime = movePointer(
start, end, steps, false, downTime, downTime, slowDown, gestureScope);
+ } finally {
if (mTrackpadGestureType != TrackpadGestureType.NONE) {
for (int i = mPointerCount; i >= 2; i--) {
sendPointer(downTime, downTime,
@@ -1778,7 +1779,6 @@
start, gestureScope);
}
}
- } finally {
sendPointer(downTime, endTime, MotionEvent.ACTION_UP, end, gestureScope);
}
}
@@ -2055,11 +2055,14 @@
final long downTime = SystemClock.uptimeMillis();
sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetCenter,
GestureScope.DONT_EXPECT_PILFER);
- expectEvent(TestProtocol.SEQUENCE_MAIN, longClickEvent);
- final UiObject2 result = waitForLauncherObject(resName);
- sendPointer(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, targetCenter,
- GestureScope.DONT_EXPECT_PILFER);
- return result;
+ try {
+ expectEvent(TestProtocol.SEQUENCE_MAIN, longClickEvent);
+ final UiObject2 result = waitForLauncherObject(resName);
+ return result;
+ } finally {
+ sendPointer(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, targetCenter,
+ GestureScope.DONT_EXPECT_PILFER);
+ }
}
@NonNull
@@ -2070,12 +2073,15 @@
sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetCenter,
GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE,
/* isRightClick= */ true);
- expectEvent(TestProtocol.SEQUENCE_MAIN, rightClickEvent);
- final UiObject2 result = waitForLauncherObject(resName);
- sendPointer(downTime, SystemClock.uptimeMillis(), ACTION_UP, targetCenter,
- GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE,
- /* isRightClick= */ true);
- return result;
+ try {
+ expectEvent(TestProtocol.SEQUENCE_MAIN, rightClickEvent);
+ final UiObject2 result = waitForLauncherObject(resName);
+ return result;
+ } finally {
+ sendPointer(downTime, SystemClock.uptimeMillis(), ACTION_UP, targetCenter,
+ GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE,
+ /* isRightClick= */ true);
+ }
}
private static int getSystemIntegerRes(Context context, String resName) {
diff --git a/tests/multivalentTests/tapl/com/android/launcher3/tapl/WidgetResizeFrame.java b/tests/multivalentTests/tapl/com/android/launcher3/tapl/WidgetResizeFrame.java
index ec1cbd8..d0573e0 100644
--- a/tests/multivalentTests/tapl/com/android/launcher3/tapl/WidgetResizeFrame.java
+++ b/tests/multivalentTests/tapl/com/android/launcher3/tapl/WidgetResizeFrame.java
@@ -16,6 +16,7 @@
package com.android.launcher3.tapl;
import static com.android.launcher3.tapl.Launchable.DEFAULT_DRAG_STEPS;
+
import static org.junit.Assert.assertTrue;
import android.graphics.Point;
@@ -61,11 +62,14 @@
final long downTime = SystemClock.uptimeMillis();
mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetStart,
LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
- mLauncher.movePointer(targetStart, targetDest, DEFAULT_DRAG_STEPS,
- true, downTime, downTime, true,
- LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
- mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_UP, targetDest,
- LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
+ try {
+ mLauncher.movePointer(targetStart, targetDest, DEFAULT_DRAG_STEPS,
+ true, downTime, downTime, true,
+ LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
+ } finally {
+ mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_UP, targetDest,
+ LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
+ }
try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
"want to return resized widget resize frame")) {
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java b/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
index e1af774..0ff7c20 100644
--- a/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
+++ b/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
@@ -29,8 +29,6 @@
import androidx.test.filters.SmallTest;
import com.android.launcher3.CellLayout;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.MultipageCellLayout;
import com.android.launcher3.celllayout.board.CellLayoutBoard;
import com.android.launcher3.celllayout.board.IconPoint;
@@ -41,8 +39,7 @@
import com.android.launcher3.util.ActivityContextWrapper;
import com.android.launcher3.views.DoubleShadowBubbleTextView;
-import org.junit.After;
-import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -70,7 +67,8 @@
private static final int TOTAL_OF_CASES_GENERATED = 300;
private Context mApplicationContext;
- private int mPrevNumColumns, mPrevNumRows;
+ @Rule
+ public UnitTestCellLayoutBuilderRule mCellLayoutBuilder = new UnitTestCellLayoutBuilderRule();
/**
* This test reads existing test cases and makes sure the CellLayout produces the same
@@ -144,34 +142,10 @@
(CellLayoutLayoutParams) cell.getLayoutParams(), true);
}
- public CellLayout createCellLayout(int width, int height, boolean isMulti) {
- Context c = mApplicationContext;
- DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(c).getDeviceProfile(c).copy(c);
- // modify the device profile.
- dp.inv.numColumns = isMulti ? width / 2 : width;
- dp.inv.numRows = height;
- dp.cellLayoutBorderSpacePx = new Point(0, 0);
-
- CellLayout cl = isMulti ? new MultipageCellLayout(getWrappedContext(c, dp))
- : new CellLayout(getWrappedContext(c, dp));
- // 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(View.MeasureSpec.makeMeasureSpec(10000, View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(10000, View.MeasureSpec.EXACTLY));
- return cl;
- }
-
- private Context getWrappedContext(Context context, DeviceProfile dp) {
- return new ActivityContextWrapper(context) {
- public DeviceProfile getDeviceProfile() {
- return dp;
- }
- };
- }
-
public ItemConfiguration solve(CellLayoutBoard board, int x, int y, int spanX,
int spanY, int minSpanX, int minSpanY, boolean isMulti) {
- CellLayout cl = createCellLayout(board.getWidth(), board.getHeight(), isMulti);
+ CellLayout cl = mCellLayoutBuilder.createCellLayout(board.getWidth(), board.getHeight(),
+ isMulti);
// The views have to be sorted or the result can vary
board.getIcons()
@@ -249,22 +223,6 @@
}
}
- @Before
- public void storePreviousValues() {
- Context c = new ActivityContextWrapper(getApplicationContext());
- DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(c).getDeviceProfile(c).copy(c);
- mPrevNumColumns = dp.inv.numColumns;
- mPrevNumRows = dp.inv.numRows;
- }
-
- @After
- public void restorePreviousValues() {
- Context c = new ActivityContextWrapper(getApplicationContext());
- DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(c).getDeviceProfile(c).copy(c);
- dp.inv.numColumns = mPrevNumColumns;
- dp.inv.numRows = mPrevNumRows;
- }
-
private ReorderAlgorithmUnitTestCase generateRandomTestCase(
RandomBoardGenerator boardGenerator) {
ReorderAlgorithmUnitTestCase testCase = new ReorderAlgorithmUnitTestCase();
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderPreviewAnimationTest.kt b/tests/src/com/android/launcher3/celllayout/ReorderPreviewAnimationTest.kt
new file mode 100644
index 0000000..0bec1b2
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/ReorderPreviewAnimationTest.kt
@@ -0,0 +1,201 @@
+/*
+ * 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.celllayout
+
+import android.content.Context
+import android.util.ArrayMap
+import android.view.View
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.CellLayout
+import com.android.launcher3.Reorderable
+import com.android.launcher3.celllayout.ReorderPreviewAnimation.Companion.HINT_DURATION
+import com.android.launcher3.celllayout.ReorderPreviewAnimation.Companion.PREVIEW_DURATION
+import com.android.launcher3.util.ActivityContextWrapper
+import com.android.launcher3.util.MultiTranslateDelegate
+import com.android.launcher3.util.MultiTranslateDelegate.INDEX_REORDER_BOUNCE_OFFSET
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+class Mock(context: Context) : Reorderable, View(context) {
+
+ init {
+ mLeft = 0
+ mRight = 100
+ }
+
+ private val translateDelegate = MultiTranslateDelegate(this)
+
+ private var scaleForReorderBounce = 1f
+ override fun getTranslateDelegate(): MultiTranslateDelegate {
+ return translateDelegate
+ }
+
+ override fun setReorderBounceScale(scale: Float) {
+ scaleForReorderBounce = scale
+ }
+
+ override fun getReorderBounceScale(): Float {
+ return scaleForReorderBounce
+ }
+
+ fun toAnimationValues(): AnimationValues {
+ return AnimationValues(
+ (translateDelegate.getTranslationX(INDEX_REORDER_BOUNCE_OFFSET).value * 100).toInt(),
+ (translateDelegate.getTranslationY(INDEX_REORDER_BOUNCE_OFFSET).value * 100).toInt(),
+ (scaleForReorderBounce * 100).toInt()
+ )
+ }
+}
+
+data class AnimationValues(val dx: Int, val dy: Int, val scale: Int)
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ReorderPreviewAnimationTest {
+
+ @JvmField @Rule var cellLayoutBuilder = UnitTestCellLayoutBuilderRule()
+
+ private val applicationContext =
+ ActivityContextWrapper(ApplicationProvider.getApplicationContext())
+
+ /**
+ * @param animationTime the time of the animation we will check the state against.
+ * @param mode the mode either PREVIEW_DURATION or HINT_DURATION.
+ * @param valueToMatch the state of the animation we expect to see at animationTime.
+ * @param isAfterReverse if the animation is finish and we are returning to the beginning.
+ */
+ private fun testAnimationAtGivenProgress(
+ animationTime: Int,
+ mode: Int,
+ valueToMatch: AnimationValues
+ ) {
+ val view = Mock(applicationContext)
+ val cellLayout = cellLayoutBuilder.createCellLayout(100, 100, false)
+ val map = ArrayMap<Reorderable, ReorderPreviewAnimation<Mock>>()
+ val animation =
+ ReorderPreviewAnimation(
+ view,
+ mode,
+ 3,
+ 3,
+ 1,
+ 7,
+ 1,
+ 1,
+ CellLayout.REORDER_PREVIEW_MAGNITUDE,
+ cellLayout,
+ map
+ )
+ // Remove delay because it's randomly generated and it can slightly change the results.
+ animation.animator.startDelay = 0
+ animation.animator.currentPlayTime = animationTime.toLong()
+ val currentValue = view.toAnimationValues()
+ assert(currentValue == valueToMatch) {
+ "The value of the animation $currentValue at $animationTime time (milliseconds) doesn't match the given value $valueToMatch"
+ }
+ }
+
+ @Test
+ fun testAnimationModePreview() {
+ testAnimationAtGivenProgress(
+ PREVIEW_DURATION * 0,
+ ReorderPreviewAnimation.MODE_PREVIEW,
+ AnimationValues(dx = 0, dy = 0, scale = 100)
+ )
+ testAnimationAtGivenProgress(
+ PREVIEW_DURATION / 2,
+ ReorderPreviewAnimation.MODE_PREVIEW,
+ AnimationValues(dx = 2, dy = -5, scale = 98)
+ )
+ testAnimationAtGivenProgress(
+ PREVIEW_DURATION / 3,
+ ReorderPreviewAnimation.MODE_PREVIEW,
+ AnimationValues(dx = 1, dy = -2, scale = 99)
+ )
+ testAnimationAtGivenProgress(
+ PREVIEW_DURATION,
+ ReorderPreviewAnimation.MODE_PREVIEW,
+ AnimationValues(dx = 5, dy = -10, scale = 96)
+ )
+
+ // MODE_PREVIEW oscillates and goes back to 0,0
+ testAnimationAtGivenProgress(
+ PREVIEW_DURATION * 2,
+ ReorderPreviewAnimation.MODE_PREVIEW,
+ AnimationValues(dx = 0, dy = 0, scale = 100)
+ )
+ testAnimationAtGivenProgress(
+ PREVIEW_DURATION * 99,
+ ReorderPreviewAnimation.MODE_PREVIEW,
+ AnimationValues(dx = 5, dy = -10, scale = 96)
+ )
+ testAnimationAtGivenProgress(
+ PREVIEW_DURATION * 98,
+ ReorderPreviewAnimation.MODE_PREVIEW,
+ AnimationValues(dx = 0, dy = 0, scale = 100)
+ )
+ testAnimationAtGivenProgress(
+ (PREVIEW_DURATION * 1.5).toInt(),
+ ReorderPreviewAnimation.MODE_PREVIEW,
+ AnimationValues(dx = 2, dy = -5, scale = 98)
+ )
+ }
+
+ @Test
+ fun testAnimationModeHint() {
+ testAnimationAtGivenProgress(
+ HINT_DURATION * 0,
+ ReorderPreviewAnimation.MODE_HINT,
+ AnimationValues(dx = 0, dy = 0, scale = 100)
+ )
+ testAnimationAtGivenProgress(
+ HINT_DURATION,
+ ReorderPreviewAnimation.MODE_HINT,
+ AnimationValues(dx = -5, dy = 10, scale = 96)
+ )
+ testAnimationAtGivenProgress(
+ HINT_DURATION / 2,
+ ReorderPreviewAnimation.MODE_HINT,
+ AnimationValues(dx = -2, dy = 5, scale = 98)
+ )
+ testAnimationAtGivenProgress(
+ HINT_DURATION / 3,
+ ReorderPreviewAnimation.MODE_HINT,
+ AnimationValues(dx = -1, dy = 2, scale = 99)
+ )
+ testAnimationAtGivenProgress(
+ HINT_DURATION,
+ ReorderPreviewAnimation.MODE_HINT,
+ AnimationValues(dx = -5, dy = 10, scale = 96)
+ )
+
+ // After one cycle the animationValues should always be the top values and don't cycle.
+ testAnimationAtGivenProgress(
+ HINT_DURATION * 2,
+ ReorderPreviewAnimation.MODE_HINT,
+ AnimationValues(dx = -5, dy = 10, scale = 96)
+ )
+ testAnimationAtGivenProgress(
+ HINT_DURATION * 99,
+ ReorderPreviewAnimation.MODE_HINT,
+ AnimationValues(dx = -5, dy = 10, scale = 96)
+ )
+ }
+}
diff --git a/tests/src/com/android/launcher3/celllayout/UnitTestCellLayoutBuilderRule.kt b/tests/src/com/android/launcher3/celllayout/UnitTestCellLayoutBuilderRule.kt
new file mode 100644
index 0000000..0f0dd65
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/UnitTestCellLayoutBuilderRule.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.celllayout
+
+import android.content.Context
+import android.graphics.Point
+import android.view.View
+import androidx.test.core.app.ApplicationProvider
+import com.android.launcher3.CellLayout
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.MultipageCellLayout
+import com.android.launcher3.util.ActivityContextWrapper
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+
+/**
+ * Create CellLayouts to be used in Unit testing and make sure to set the DeviceProfile back to
+ * normal.
+ */
+class UnitTestCellLayoutBuilderRule : TestWatcher() {
+
+ private var prevNumColumns = 0
+ private var prevNumRows = 0
+
+ private val applicationContext =
+ ActivityContextWrapper(ApplicationProvider.getApplicationContext())
+
+ override fun starting(description: Description?) {
+ val dp = getDeviceProfile()
+ prevNumColumns = dp.inv.numColumns
+ prevNumRows = dp.inv.numRows
+ }
+
+ override fun finished(description: Description?) {
+ val dp = getDeviceProfile()
+ dp.inv.numColumns = prevNumColumns
+ dp.inv.numRows = prevNumRows
+ }
+
+ fun createCellLayout(width: Int, height: Int, isMulti: Boolean): CellLayout {
+ val dp = getDeviceProfile()
+ // modify the device profile.
+ dp.inv.numColumns = if (isMulti) width / 2 else width
+ dp.inv.numRows = height
+ dp.cellLayoutBorderSpacePx = Point(0, 0)
+ val cl =
+ if (isMulti) MultipageCellLayout(getWrappedContext(applicationContext, dp))
+ else CellLayout(getWrappedContext(applicationContext, dp))
+ // 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(
+ View.MeasureSpec.makeMeasureSpec(10000, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(10000, View.MeasureSpec.EXACTLY)
+ )
+ return cl
+ }
+
+ private fun getDeviceProfile(): DeviceProfile =
+ InvariantDeviceProfile.INSTANCE[applicationContext].getDeviceProfile(applicationContext)
+ .copy(applicationContext)
+
+ private fun getWrappedContext(context: Context, dp: DeviceProfile): Context {
+ return object : ActivityContextWrapper(context) {
+ override fun getDeviceProfile(): DeviceProfile {
+ return dp
+ }
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
index cb57918..e2ca31f 100644
--- a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
@@ -95,6 +95,10 @@
@Test
fun loadsDataProperly() =
with(BgDataModel()) {
+ val MAIN_HANDLE = UserHandle.of(0)
+ val mockUserHandles = arrayListOf<UserHandle>(MAIN_HANDLE)
+ `when`(userCache.userProfiles).thenReturn(mockUserHandles)
+ `when`(userCache.getUserInfo(MAIN_HANDLE)).thenReturn(UserIconInfo(MAIN_HANDLE, 1))
LoaderTask(app, bgAllAppsList, this, modelDelegate, launcherBinder)
.runSyncOnBackgroundThread()
Truth.assertThat(workspaceItems.size).isAtLeast(25)