Merge "Fixed issue with shadow for overflow icon" into main
diff --git a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
index 694475a..84c2ed2 100644
--- a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
@@ -28,14 +28,15 @@
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.View;
+import android.view.accessibility.AccessibilityManager;
import androidx.annotation.ColorInt;
import androidx.core.content.ContextCompat;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.FloatingHeaderRow;
import com.android.launcher3.allapps.FloatingHeaderView;
-import com.android.launcher3.util.Themes;
/**
* A view which shows a horizontal divider
@@ -54,6 +55,7 @@
private final @ColorInt int mStrokeColor;
private final @ColorInt int mAllAppsLabelTextColor;
+ private final AccessibilityManager mAccessibilityManager;
private Layout mAllAppsLabelLayout;
private boolean mShowAllAppsLabel;
@@ -87,7 +89,8 @@
mAllAppsLabelTextColor = ContextCompat.getColor(context,
R.color.material_color_on_surface_variant);
- mShowAllAppsLabel = !ALL_APPS_VISITED_COUNT.hasReachedMax(context);
+ mAccessibilityManager = AccessibilityManager.getInstance(context);
+ setShowAllAppsLabel(!ALL_APPS_VISITED_COUNT.hasReachedMax(context));
}
public void setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden) {
@@ -99,6 +102,9 @@
/** {@code true} if all apps label should be shown in place of divider. */
public void setShowAllAppsLabel(boolean showAllAppsLabel) {
+ if (mAccessibilityManager.isEnabled() && !Utilities.isRunningInTestHarness()) {
+ showAllAppsLabel = true;
+ }
if (showAllAppsLabel != mShowAllAppsLabel) {
mShowAllAppsLabel = showAllAppsLabel;
updateDividerType();
@@ -148,6 +154,7 @@
mDividerType = dividerType;
int topPadding;
int bottomPadding;
+ setContentDescription(null);
switch (dividerType) {
case LINE:
topPadding = 0;
@@ -161,6 +168,7 @@
bottomPadding = getResources()
.getDimensionPixelSize(R.dimen.all_apps_label_bottom_padding);
mPaint.setColor(mAllAppsLabelTextColor);
+ setContentDescription(mAllAppsLabelLayout.getText());
break;
case NONE:
default:
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index 6163dad..95c4e25 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -153,6 +153,16 @@
context.deviceProfile.widthPx,
windowLayoutParams.height
)
+
+ // if there's an animating bubble add it to the touch region so that it's clickable
+ val animatingBubbleBounds =
+ controllers.bubbleControllers
+ .getOrNull()
+ ?.bubbleBarViewController
+ ?.animatingBubbleBounds
+ if (animatingBubbleBounds != null) {
+ defaultTouchableRegion.op(animatingBubbleBounds, Region.Op.UNION)
+ }
}
// Pre-calculate insets for different providers across different rotations for this gravity
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index e47640b..3196bfb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -62,6 +62,7 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks;
import com.android.launcher3.taskbar.unfold.NonDestroyableScopedUnfoldTransitionProgressProvider;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.DisplayController;
@@ -70,7 +71,6 @@
import com.android.quickstep.AllAppsActionManager;
import com.android.quickstep.RecentsActivity;
import com.android.quickstep.SystemUiProxy;
-import com.android.quickstep.TouchInteractionService;
import com.android.quickstep.util.AssistUtils;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
@@ -209,15 +209,18 @@
@SuppressLint("WrongConstant")
public TaskbarManager(
- TouchInteractionService service, AllAppsActionManager allAppsActionManager) {
+ Context context,
+ AllAppsActionManager allAppsActionManager,
+ TaskbarNavButtonCallbacks navCallbacks) {
+
Display display =
- service.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
- mContext = service.createWindowContext(display,
+ context.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
+ mContext = context.createWindowContext(display,
ENABLE_TASKBAR_NAVBAR_UNIFICATION ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL,
null);
mAllAppsActionManager = allAppsActionManager;
mNavigationBarPanelContext = ENABLE_TASKBAR_NAVBAR_UNIFICATION
- ? service.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null)
+ ? context.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null)
: null;
if (enableTaskbarNoRecreate()) {
mWindowManager = mContext.getSystemService(WindowManager.class);
@@ -234,8 +237,11 @@
}
};
}
- mNavButtonController = new TaskbarNavButtonController(service,
- SystemUiProxy.INSTANCE.get(mContext), new Handler(),
+ mNavButtonController = new TaskbarNavButtonController(
+ context,
+ navCallbacks,
+ SystemUiProxy.INSTANCE.get(mContext),
+ new Handler(),
AssistUtils.newInstance(mContext));
mComponentCallbacks = new ComponentCallbacks() {
private Configuration mOldConfig = mContext.getResources().getConfiguration();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index 03f55ca..ade4649 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -31,6 +31,7 @@
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
+import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
@@ -47,10 +48,8 @@
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.quickstep.LauncherActivityInterface;
-import com.android.quickstep.OverviewCommandHelper;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskUtils;
-import com.android.quickstep.TouchInteractionService;
import com.android.quickstep.util.AssistUtils;
import java.io.PrintWriter;
@@ -106,7 +105,8 @@
private static final int SCREEN_UNPIN_COMBO = BUTTON_BACK | BUTTON_RECENTS;
private int mLongPressedButtons = 0;
- private final TouchInteractionService mService;
+ private final Context mContext;
+ private final TaskbarNavButtonCallbacks mCallbacks;
private final SystemUiProxy mSystemUiProxy;
private final Handler mHandler;
private final AssistUtils mAssistUtils;
@@ -114,9 +114,14 @@
private final Runnable mResetLongPress = this::resetScreenUnpin;
- public TaskbarNavButtonController(TouchInteractionService service,
- SystemUiProxy systemUiProxy, Handler handler, AssistUtils assistUtils) {
- mService = service;
+ public TaskbarNavButtonController(
+ Context context,
+ TaskbarNavButtonCallbacks callbacks,
+ SystemUiProxy systemUiProxy,
+ Handler handler,
+ AssistUtils assistUtils) {
+ mContext = context;
+ mCallbacks = callbacks;
mSystemUiProxy = systemUiProxy;
mHandler = handler;
mAssistUtils = assistUtils;
@@ -286,7 +291,7 @@
desktopVisibilityController.onHomeActionTriggered();
}
- mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_HOME);
+ mCallbacks.onNavigateHome();
}
private void navigateToOverview() {
@@ -295,7 +300,7 @@
}
TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle");
TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
- mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_TOGGLE);
+ mCallbacks.onToggleOverview();
}
private void executeBack() {
@@ -310,7 +315,7 @@
if (longClick) {
mSystemUiProxy.notifyAccessibilityButtonLongClicked();
} else {
- mSystemUiProxy.notifyAccessibilityButtonClicked(mService.getDisplayId());
+ mSystemUiProxy.notifyAccessibilityButtonClicked(mContext.getDisplayId());
}
}
@@ -333,4 +338,13 @@
private void showNotifications() {
mSystemUiProxy.toggleNotificationPanel();
}
+
+ /** Callbacks for navigation buttons on Taskbar. */
+ public interface TaskbarNavButtonCallbacks {
+ /** Callback invoked when the home button is pressed. */
+ default void onNavigateHome() {}
+
+ /** Callback invoked when the overview button is pressed. */
+ default void onToggleOverview() {}
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index d8a8b29..5234936 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -104,6 +104,8 @@
* updates the bounds and accounts for translation.
*/
private final Rect mBubbleBarBounds = new Rect();
+ /** The bounds of the animating bubble in the coordinate space of the BubbleBarView. */
+ private final Rect mAnimatingBubbleBounds = new Rect();
// The amount the bubbles overlap when they are stacked in the bubble bar
private final float mIconOverlapAmount;
// The spacing between the bubbles when bubble bar is expanded
@@ -460,6 +462,30 @@
return mBubbleBarBounds;
}
+ /** Returns the bounds of the animating bubble, or {@code null} if no bubble is animating. */
+ @Nullable
+ public Rect getAnimatingBubbleBounds() {
+ if (mIsAnimatingNewBubble) {
+ return mAnimatingBubbleBounds;
+ }
+ return null;
+ }
+
+ /**
+ * Updates the animating bubble bounds. This should be called when the bubble is fully animated
+ * in so that we can include it in taskbar touchable region.
+ *
+ * <p>The bounds are adjusted to the coordinate space of BubbleBarView so that it can be used
+ * by taskbar.
+ */
+ public void updateAnimatingBubbleBounds(int left, int top, int width, int height) {
+ Rect bubbleBarBounds = getBubbleBarBounds();
+ mAnimatingBubbleBounds.left = bubbleBarBounds.left + left;
+ mAnimatingBubbleBounds.top = bubbleBarBounds.top + top;
+ mAnimatingBubbleBounds.right = mAnimatingBubbleBounds.left + width;
+ mAnimatingBubbleBounds.bottom = mAnimatingBubbleBounds.top + height;
+ }
+
/**
* Set bubble bar relative pivot value for X and Y, applied as a fraction of view width/height
* respectively. If the value is not in range of 0 to 1 it will be normalized.
@@ -856,10 +882,15 @@
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (!mIsBarExpanded) {
+ if (!mIsBarExpanded && !mIsAnimatingNewBubble) {
// When the bar is collapsed, all taps on it should expand it.
return true;
}
return super.onInterceptTouchEvent(ev);
}
+
+ /** Whether a new bubble is currently animating. */
+ public boolean isAnimatingNewBubble() {
+ return mIsAnimatingNewBubble;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 0e62eaf..3c46f32 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -138,6 +138,15 @@
if (bubble == null) {
Log.e(TAG, "bubble click listener, bubble was null");
}
+
+ if (mBarView.isAnimatingNewBubble()) {
+ mBubbleBarViewAnimator.onBubbleClickedWhileAnimating();
+ mBubbleStashController.showBubbleBarImmediate();
+ setExpanded(true);
+ mBubbleBarController.showAndSelectBubble(bubble);
+ return;
+ }
+
final String currentlySelected = mBubbleBarController.getSelectedBubbleKey();
if (mBarView.isExpanded() && Objects.equals(bubble.getKey(), currentlySelected)) {
// Tapping the currently selected bubble while expanded collapses the view.
@@ -213,6 +222,12 @@
return mBarView.getBubbleBarBounds();
}
+ /** The bounds of the animating bubble, or {@code null} if no bubble is animating. */
+ @Nullable
+ public Rect getAnimatingBubbleBounds() {
+ return mBarView.getAnimatingBubbleBounds();
+ }
+
/** The horizontal margin of the bubble bar from the edge of the screen. */
public int getHorizontalMargin() {
return mBarView.getHorizontalMargin();
@@ -373,7 +388,7 @@
boolean isInApp = mTaskbarStashController.isInApp();
// only animate the new bubble if we're in an app and not auto expanding
- if (b instanceof BubbleBarBubble && isInApp && !isExpanding) {
+ if (b instanceof BubbleBarBubble && isInApp && !isExpanding && !isExpanded()) {
mBubbleBarViewAnimator.animateBubbleInForStashed((BubbleBarBubble) b);
}
} else {
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
index 76d86de..bea0af8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
@@ -347,7 +347,7 @@
hotseatCellHeight - mUnstashedHeight) / 2;
}
- float getBubbleBarTranslationY() {
+ public float getBubbleBarTranslationY() {
// If we're on home, adjust the translation so the bubble bar aligns with hotseat.
// Otherwise we're either showing in an app or in overview. In either case adjust it so
// the bubble bar aligns with the taskbar.
@@ -374,4 +374,19 @@
public PhysicsAnimator<View> getStashedHandlePhysicsAnimator() {
return mHandleViewController.getPhysicsAnimator();
}
+
+ /** Notifies taskbar that it should update its touchable region. */
+ public void updateTaskbarTouchRegion() {
+ mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
+ }
+
+ /** Shows the bubble bar immediately without animation. */
+ public void showBubbleBarImmediate() {
+ mHandleViewController.setTranslationYForSwipe(0);
+ mIconTranslationYForStash.updateValue(getBubbleBarTranslationY());
+ mIconAlphaForStash.setValue(1);
+ mIconScaleForStash.updateValue(1);
+ mIsStashed = false;
+ onIsStashedChanged();
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
index 2d8983f..da36944 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
@@ -35,6 +35,8 @@
private val scheduler: Scheduler = HandlerScheduler(bubbleBarView)
) {
+ private var animatingBubble: AnimatingBubble? = null
+
private companion object {
/** The time to show the flyout. */
const val FLYOUT_DELAY_MS: Long = 2500
@@ -54,26 +56,40 @@
const val BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y = -20f
}
+ /** Wrapper around the animating bubble with its show and hide animations. */
+ private data class AnimatingBubble(
+ val bubbleView: BubbleView,
+ val showAnimation: Runnable,
+ val hideAnimation: Runnable
+ )
+
/** An interface for scheduling jobs. */
interface Scheduler {
/** Schedule the given [block] to run. */
- fun post(block: () -> Unit)
+ fun post(block: Runnable)
/** Schedule the given [block] to start with a delay of [delayMillis]. */
- fun postDelayed(delayMillis: Long, block: () -> Unit)
+ fun postDelayed(delayMillis: Long, block: Runnable)
+
+ /** Cancel the given [block] if it hasn't started yet. */
+ fun cancel(block: Runnable)
}
/** A [Scheduler] that uses a Handler to run jobs. */
private class HandlerScheduler(private val view: View) : Scheduler {
- override fun post(block: () -> Unit) {
+ override fun post(block: Runnable) {
view.post(block)
}
- override fun postDelayed(delayMillis: Long, block: () -> Unit) {
+ override fun postDelayed(delayMillis: Long, block: Runnable) {
view.postDelayed(block, delayMillis)
}
+
+ override fun cancel(block: Runnable) {
+ view.removeCallbacks(block)
+ }
}
private val springConfig =
@@ -91,6 +107,7 @@
// and the second part hides it after a delay.
val showAnimation = buildShowAnimation(bubbleView, b.key)
val hideAnimation = buildHideAnimation(bubbleView)
+ animatingBubble = AnimatingBubble(bubbleView, showAnimation, hideAnimation)
scheduler.post(showAnimation)
scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation)
}
@@ -113,7 +130,7 @@
private fun buildShowAnimation(
bubbleView: BubbleView,
key: String,
- ): () -> Unit = {
+ ) = Runnable {
bubbleBarView.prepareForAnimatingBubbleWhileStashed(key)
// calculate the initial translation x the bubble should have in order to align it with the
// stash handle.
@@ -140,7 +157,7 @@
// map the path [0, BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y] to [0,1]
val fraction = ty / BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y
- target.alpha = 1 - fraction / 2
+ target.alpha = 1 - fraction
}
ty >= totalTranslationY -> {
// this is the second leg of the animation. the handle should be completely
@@ -173,6 +190,16 @@
}
}
}
+ animator.addEndListener { _, _, _, _, _, _, _ ->
+ // the bubble is now fully settled in. make it touchable
+ bubbleBarView.updateAnimatingBubbleBounds(
+ bubbleView.left,
+ bubbleView.top,
+ bubbleView.width,
+ bubbleView.height
+ )
+ bubbleStashController.updateTaskbarTouchRegion()
+ }
animator.start()
}
@@ -189,7 +216,7 @@
* 1. In the second part the bubble is fully hidden and the handle animates in.
* 1. The third part is the overshoot. The handle is made fully visible.
*/
- private fun buildHideAnimation(bubbleView: BubbleView): () -> Unit = {
+ private fun buildHideAnimation(bubbleView: BubbleView) = Runnable {
// this is the total distance that both the stashed handle and the bubble will be traveling
val totalTranslationY =
BUBBLE_ANIMATION_BUBBLE_TRANSLATION_Y + BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y
@@ -230,6 +257,7 @@
}
}
animator.addEndListener { _, _, _, _, _, _, _ ->
+ animatingBubble = null
bubbleView.alpha = 0f
bubbleView.translationY = 0f
bubbleView.scaleY = 1f
@@ -237,9 +265,18 @@
bubbleBarView.alpha = 0f
}
bubbleBarView.onAnimatingBubbleCompleted()
+ bubbleStashController.updateTaskbarTouchRegion()
}
animator.start()
}
+
+ /** Handles clicking on the animating bubble while the animation is still playing. */
+ fun onBubbleClickedWhileAnimating() {
+ val hideAnimation = animatingBubble?.hideAnimation ?: return
+ scheduler.cancel(hideAnimation)
+ bubbleBarView.onAnimatingBubbleCompleted()
+ animatingBubble = null
+ }
}
/** The X position in screen coordinates of the center of the bubble. */
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index 79b09fd..225b127 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -220,6 +220,11 @@
}
});
}
+
+ @Override
+ public void setTriggerBack(boolean triggerBack) {
+ // TODO(b/261654570): track touch from the Launcher process.
+ }
}
private static class RemoteAnimationRunnerStub extends IRemoteAnimationRunner.Stub {
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index a842b51..832f4e1 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -94,6 +94,7 @@
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.taskbar.TaskbarManager;
+import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.ResourceUtils;
import com.android.launcher3.testing.shared.TestProtocol;
@@ -470,6 +471,18 @@
private final ScreenOnTracker.ScreenOnListener mScreenOnListener = this::onScreenOnChanged;
+ private final TaskbarNavButtonCallbacks mNavCallbacks = new TaskbarNavButtonCallbacks() {
+ @Override
+ public void onNavigateHome() {
+ mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_HOME);
+ }
+
+ @Override
+ public void onToggleOverview() {
+ mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_TOGGLE);
+ }
+ };
+
private ActivityManagerWrapper mAM;
private OverviewCommandHelper mOverviewCommandHelper;
private OverviewComponentObserver mOverviewComponentObserver;
@@ -500,7 +513,7 @@
mDeviceState = new RecentsAnimationDeviceState(this, true);
mAllAppsActionManager = new AllAppsActionManager(
this, UI_HELPER_EXECUTOR, this::createAllAppsPendingIntent);
- mTaskbarManager = new TaskbarManager(this, mAllAppsActionManager);
+ mTaskbarManager = new TaskbarManager(this, mAllAppsActionManager, mNavCallbacks);
mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
diff --git a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
index 9df568e..2a27dea 100644
--- a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
+++ b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
@@ -136,21 +136,16 @@
});
}
- if (!ALL_APPS_VISITED_COUNT.hasReachedMax(launcher)) {
+ if (!Utilities.isRunningInTestHarness()) {
launcher.getStateManager().addStateListener(new StateListener<LauncherState>() {
@Override
public void onStateTransitionComplete(LauncherState finalState) {
if (finalState == ALL_APPS) {
ALL_APPS_VISITED_COUNT.increment(launcher);
- return;
}
-
- boolean hasReachedMaxCount = ALL_APPS_VISITED_COUNT.hasReachedMax(launcher);
- launcher.getAppsView().getFloatingHeaderView().findFixedRowByType(
- AppsDividerView.class).setShowAllAppsLabel(!hasReachedMaxCount);
- if (hasReachedMaxCount) {
- launcher.getStateManager().removeStateListener(this);
- }
+ launcher.getAppsView().getFloatingHeaderView()
+ .findFixedRowByType(AppsDividerView.class)
+ .setShowAllAppsLabel(!ALL_APPS_VISITED_COUNT.hasReachedMax(launcher));
}
});
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
index 58be345..0f06d98 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
@@ -12,10 +12,10 @@
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.SCREEN_PIN_LONG_PRESS_THRESHOLD;
-import static com.android.quickstep.OverviewCommandHelper.TYPE_HOME;
-import static com.android.quickstep.OverviewCommandHelper.TYPE_TOGGLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
@@ -31,7 +31,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.logging.StatsLogManager;
-import com.android.quickstep.OverviewCommandHelper;
+import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TouchInteractionService;
import com.android.quickstep.util.AssistUtils;
@@ -52,8 +52,6 @@
@Mock
TouchInteractionService mockService;
@Mock
- OverviewCommandHelper mockCommandHelper;
- @Mock
Handler mockHandler;
@Mock
AssistUtils mockAssistUtils;
@@ -68,13 +66,26 @@
@Mock
View mockView;
+ private int mHomePressCount;
+ private int mOverviewToggleCount;
+ private final TaskbarNavButtonCallbacks mCallbacks = new TaskbarNavButtonCallbacks() {
+ @Override
+ public void onNavigateHome() {
+ mHomePressCount++;
+ }
+
+ @Override
+ public void onToggleOverview() {
+ mOverviewToggleCount++;
+ }
+ };
+
private TaskbarNavButtonController mNavButtonController;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
when(mockService.getDisplayId()).thenReturn(DISPLAY_ID);
- when(mockService.getOverviewCommandHelper()).thenReturn(mockCommandHelper);
when(mockService.getApplicationContext())
.thenReturn(InstrumentationRegistry.getInstrumentation().getTargetContext()
.getApplicationContext());
@@ -82,8 +93,12 @@
when(mockTaskbarControllers.getTaskbarActivityContext())
.thenReturn(mockTaskbarActivityContext);
doReturn(mockStatsLogManager).when(mockTaskbarActivityContext).getStatsLogManager();
- mNavButtonController = new TaskbarNavButtonController(mockService,
- mockSystemUiProxy, mockHandler, mockAssistUtils);
+ mNavButtonController = new TaskbarNavButtonController(
+ mockService,
+ mCallbacks,
+ mockSystemUiProxy,
+ mockHandler,
+ mockAssistUtils);
}
@Test
@@ -154,20 +169,20 @@
@Test
public void testPressHome() {
mNavButtonController.onButtonClick(BUTTON_HOME, mockView);
- verify(mockCommandHelper, times(1)).addCommand(TYPE_HOME);
+ assertThat(mHomePressCount).isEqualTo(1);
}
@Test
public void testPressRecents() {
mNavButtonController.onButtonClick(BUTTON_RECENTS, mockView);
- verify(mockCommandHelper, times(1)).addCommand(TYPE_TOGGLE);
+ assertThat(mOverviewToggleCount).isEqualTo(1);
}
@Test
- public void testPressRecentsWithScreenPinned() {
+ public void testPressRecentsWithScreenPinned_noNavigationToOverview() {
mNavButtonController.updateSysuiFlags(SYSUI_STATE_SCREEN_PINNING);
mNavButtonController.onButtonClick(BUTTON_RECENTS, mockView);
- verify(mockCommandHelper, times(0)).addCommand(TYPE_TOGGLE);
+ assertThat(mOverviewToggleCount).isEqualTo(0);
}
@Test
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
index d90e048..3d8484d 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
@@ -130,18 +130,90 @@
assertThat(handle.translationY).isEqualTo(0)
}
- private class TestBubbleBarViewAnimatorScheduler : BubbleBarViewAnimator.Scheduler {
+ @Test
+ fun animateBubbleInForStashed_tapAnimatingBubble() {
+ lateinit var overflowView: BubbleView
+ lateinit var bubbleView: BubbleView
+ lateinit var bubble: BubbleBarBubble
+ val bubbleBarView = BubbleBarView(context)
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleBarView.layoutParams = FrameLayout.LayoutParams(0, 0)
+ val inflater = LayoutInflater.from(context)
- var delayedBlock: (() -> Unit)? = null
- private set
+ val bitmap = ColorDrawable(Color.WHITE).toBitmap(width = 20, height = 20)
+ overflowView =
+ inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView
+ overflowView.setOverflow(BubbleBarOverflow(overflowView), bitmap)
+ bubbleBarView.addView(overflowView)
- override fun post(block: () -> Unit) {
- block.invoke()
+ val bubbleInfo = BubbleInfo("key", 0, null, null, 0, context.packageName, null, false)
+ bubbleView =
+ inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView
+ bubble =
+ BubbleBarBubble(bubbleInfo, bubbleView, bitmap, bitmap, Color.WHITE, Path(), "")
+ bubbleView.setBubble(bubble)
+ bubbleBarView.addView(bubbleView)
+ }
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+
+ val bubbleStashController = mock<BubbleStashController>()
+ whenever(bubbleStashController.isStashed).thenReturn(true)
+
+ val handle = View(context)
+ val handleAnimator = PhysicsAnimator.getInstance(handle)
+ whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator)
+
+ val animator =
+ BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ animator.animateBubbleInForStashed(bubble)
}
- override fun postDelayed(delayMillis: Long, block: () -> Unit) {
+ // let the animation start and wait for it to complete
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
+
+ assertThat(handle.alpha).isEqualTo(0)
+ assertThat(handle.translationY).isEqualTo(-70)
+ assertThat(overflowView.visibility).isEqualTo(INVISIBLE)
+ assertThat(bubbleBarView.visibility).isEqualTo(VISIBLE)
+ assertThat(bubbleView.visibility).isEqualTo(VISIBLE)
+ assertThat(bubbleView.alpha).isEqualTo(1)
+ assertThat(bubbleView.translationY).isEqualTo(-20)
+ assertThat(bubbleView.scaleY).isEqualTo(1)
+
+ // verify the hide bubble animation is pending
+ assertThat(animatorScheduler.delayedBlock).isNotNull()
+
+ animator.onBubbleClickedWhileAnimating()
+
+ assertThat(animatorScheduler.delayedBlock).isNull()
+ assertThat(overflowView.visibility).isEqualTo(VISIBLE)
+ assertThat(overflowView.alpha).isEqualTo(1)
+ assertThat(bubbleView.alpha).isEqualTo(1)
+ assertThat(bubbleView.visibility).isEqualTo(VISIBLE)
+ assertThat(bubbleBarView.background).isNotNull()
+ assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
+ }
+
+ private class TestBubbleBarViewAnimatorScheduler : BubbleBarViewAnimator.Scheduler {
+
+ var delayedBlock: Runnable? = null
+ private set
+
+ override fun post(block: Runnable) {
+ block.run()
+ }
+
+ override fun postDelayed(delayMillis: Long, block: Runnable) {
check(delayedBlock == null) { "there is already a pending block waiting to run" }
delayedBlock = block
}
+
+ override fun cancel(block: Runnable) {
+ check(delayedBlock == block) { "the pending block does not match the canceled block" }
+ delayedBlock = null
+ }
}
}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java b/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java
index edc0a6f..7708233 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java
@@ -123,6 +123,21 @@
psContainer.verifyInstalledAppIsPresent(INSTALLED_APP_NAME);
}
+ @Test
+ @ScreenRecordRule.ScreenRecord // b/334946529
+ public void testPrivateSpaceAppLongPressUninstallMenu() throws IOException {
+ // Ensure that the App is not installed in main user otherwise, it may not be found in
+ // PS container.
+ TestUtil.uninstallDummyApp();
+ // Install the app in Private Profile
+ TestUtil.installDummyAppForUser(mProfileUserId);
+ waitForLauncherUIUpdate();
+ // Scroll to the bottom of All Apps
+ executeOnLauncher(launcher -> launcher.getAppsView().resetAndScrollToPrivateSpaceHeader());
+ // Get the "uninstall" menu item.
+ mLauncher.getAllApps().getAppIcon(INSTALLED_APP_NAME).openMenu().getMenuItem("Uninstall");
+ }
+
private void waitForPrivateSpaceSetup() {
waitForLauncherCondition("Private Profile not setup",
launcher -> launcher.getAppsView().hasPrivateProfile(),
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index f16c69b..31def04 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -494,7 +494,7 @@
<!-- Private Space parameters -->
<dimen name="ps_container_corner_radius">24dp</dimen>
- <dimen name="ps_header_height">64dp</dimen>
+ <dimen name="ps_header_height">72dp</dimen>
<dimen name="ps_header_relative_layout_height">48dp</dimen>
<dimen name="ps_header_image_height">48dp</dimen>
<dimen name="ps_header_text_height">24dp</dimen>
diff --git a/tests/src/com/android/launcher3/dragging/TaplDragTest.java b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
index d64d096..1c41ded 100644
--- a/tests/src/com/android/launcher3/dragging/TaplDragTest.java
+++ b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
@@ -21,8 +21,6 @@
import static com.android.launcher3.util.TestConstants.AppNames.PHOTOS_APP_NAME;
import static com.android.launcher3.util.TestConstants.AppNames.STORE_APP_NAME;
import static com.android.launcher3.util.TestConstants.AppNames.TEST_APP_NAME;
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -43,8 +41,6 @@
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.util.TestUtil;
-import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
-import com.android.launcher3.util.rule.TestStabilityRule;
import org.junit.Test;
@@ -67,9 +63,6 @@
*/
@Test
@PortraitLandscape
- @ScreenRecord
- // Staging; will be promoted to presubmit if stable
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
@PlatinumTest(focusArea = "launcher")
public void testDragToFolder() {
// TODO: add the use case to drag an icon to an existing folder. Currently it either fails
diff --git a/tests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt b/tests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
index c2e73fc..6bbcf85 100644
--- a/tests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
+++ b/tests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
@@ -24,18 +24,29 @@
import android.content.pm.PackageInstaller
import android.content.pm.ShortcutInfo
import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
import android.util.LongSparseArray
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING
import com.android.launcher3.LauncherAppState
import com.android.launcher3.LauncherSettings.Favorites
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER
+import com.android.launcher3.Utilities
import com.android.launcher3.Utilities.EMPTY_PERSON_ARRAY
+import com.android.launcher3.backuprestore.LauncherRestoreEventLogger
import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError.Companion.MISSING_INFO
+import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError.Companion.MISSING_WIDGET_PROVIDER
import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError.Companion.PROFILE_DELETED
import com.android.launcher3.model.data.FolderInfo
import com.android.launcher3.model.data.IconRequestInfo
import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.LauncherAppWidgetInfo
+import com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_RESTORE_STARTED
+import com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_UI_NOT_READY
import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.pm.UserCache
import com.android.launcher3.shortcuts.ShortcutKey
@@ -43,11 +54,14 @@
import com.android.launcher3.util.PackageManagerHelper
import com.android.launcher3.util.PackageUserKey
import com.android.launcher3.util.UserIconInfo
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo
import com.android.launcher3.widget.WidgetInflater
+import com.android.launcher3.widget.WidgetSections
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
import org.junit.Test
+import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.RETURNS_DEEP_STUBS
import org.mockito.Mockito.mock
@@ -56,8 +70,10 @@
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
class WorkspaceItemProcessorTest {
@@ -73,29 +89,31 @@
@Mock private lateinit var mockUserManagerState: UserManagerState
@Mock private lateinit var mockWidgetInflater: WidgetInflater
- private lateinit var intent: Intent
- private lateinit var userHandle: UserHandle
- private lateinit var iconRequestInfos: MutableList<IconRequestInfo<WorkspaceItemInfo>>
- private lateinit var componentName: ComponentName
- private lateinit var unlockedUsersArray: LongSparseArray<Boolean>
- private lateinit var keyToPinnedShortcutsMap: MutableMap<ShortcutKey, ShortcutInfo>
- private lateinit var installingPkgs: HashMap<PackageUserKey, PackageInstaller.SessionInfo>
- private lateinit var allDeepShortcuts: MutableList<ShortcutInfo>
+ private var intent: Intent = Intent()
+ private var mUserHandle: UserHandle = UserHandle(0)
+ private var mIconRequestInfos: MutableList<IconRequestInfo<WorkspaceItemInfo>> = mutableListOf()
+ private var mComponentName: ComponentName = ComponentName("package", "class")
+ private var mUnlockedUsersArray: LongSparseArray<Boolean> = LongSparseArray()
+ private var mKeyToPinnedShortcutsMap: MutableMap<ShortcutKey, ShortcutInfo> = mutableMapOf()
+ private var mInstallingPkgs: HashMap<PackageUserKey, PackageInstaller.SessionInfo> = hashMapOf()
+ private var mAllDeepShortcuts: MutableList<ShortcutInfo> = mutableListOf()
+ private var mWidgetProvidersMap: MutableMap<ComponentKey, AppWidgetProviderInfo?> =
+ mutableMapOf()
+ private var mPendingPackages: MutableSet<PackageUserKey> = mutableSetOf()
private lateinit var itemProcessorUnderTest: WorkspaceItemProcessor
@Before
fun setup() {
- userHandle = UserHandle(0)
+ mUserHandle = UserHandle(0)
mockIconRequestInfo = mock<IconRequestInfo<WorkspaceItemInfo>>()
- iconRequestInfos = mutableListOf(mockIconRequestInfo)
mockWorkspaceInfo = mock<WorkspaceItemInfo>()
mockBgDataModel = mock<BgDataModel>()
- componentName = ComponentName("package", "class")
- unlockedUsersArray = LongSparseArray<Boolean>(1).apply { put(101, true) }
+ mComponentName = ComponentName("package", "class")
+ mUnlockedUsersArray = LongSparseArray<Boolean>(1).apply { put(101, true) }
intent =
Intent().apply {
- component = componentName
+ component = mComponentName
`package` = "pkg"
putExtra(ShortcutKey.EXTRA_SHORTCUT_ID, "")
}
@@ -112,17 +130,17 @@
}
mockPmHelper =
mock<PackageManagerHelper>().apply {
- whenever(getAppLaunchIntent(componentName.packageName, userHandle))
+ whenever(getAppLaunchIntent(mComponentName.packageName, mUserHandle))
.thenReturn(intent)
}
mockLauncherApps =
mock<LauncherApps>().apply {
- whenever(isPackageEnabled("package", userHandle)).thenReturn(true)
- whenever(isActivityEnabled(componentName, userHandle)).thenReturn(true)
+ whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true)
+ whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(true)
}
mockCursor =
mock(LoaderCursor::class.java, RETURNS_DEEP_STUBS).apply {
- user = userHandle
+ user = mUserHandle
itemType = ITEM_TYPE_APPLICATION
id = 1
restoreFlag = 1
@@ -137,15 +155,18 @@
mockUserCache =
mock<UserCache>().apply {
val userIconInfo =
- mock<UserIconInfo>().apply() { whenever(isPrivate).thenReturn(false) }
+ mock<UserIconInfo>().apply { whenever(isPrivate).thenReturn(false) }
whenever(getUserInfo(any())).thenReturn(userIconInfo)
}
mockUserManagerState = mock<UserManagerState>()
mockWidgetInflater = mock<WidgetInflater>()
- keyToPinnedShortcutsMap = mutableMapOf()
- installingPkgs = hashMapOf()
- allDeepShortcuts = mutableListOf()
+ mKeyToPinnedShortcutsMap = mutableMapOf()
+ mInstallingPkgs = hashMapOf()
+ mAllDeepShortcuts = mutableListOf()
+ mWidgetProvidersMap = mutableMapOf()
+ mIconRequestInfos = mutableListOf()
+ mPendingPackages = mutableSetOf()
}
/**
@@ -159,18 +180,18 @@
userCache: UserCache = mockUserCache,
userManagerState: UserManagerState = mockUserManagerState,
launcherApps: LauncherApps = mockLauncherApps,
- shortcutKeyToPinnedShortcuts: Map<ShortcutKey, ShortcutInfo> = keyToPinnedShortcutsMap,
+ shortcutKeyToPinnedShortcuts: Map<ShortcutKey, ShortcutInfo> = mKeyToPinnedShortcutsMap,
app: LauncherAppState = mockAppState,
bgDataModel: BgDataModel = mockBgDataModel,
- widgetProvidersMap: MutableMap<ComponentKey, AppWidgetProviderInfo?> = mutableMapOf(),
+ widgetProvidersMap: MutableMap<ComponentKey, AppWidgetProviderInfo?> = mWidgetProvidersMap,
widgetInflater: WidgetInflater = mockWidgetInflater,
pmHelper: PackageManagerHelper = mockPmHelper,
- iconRequestInfos: MutableList<IconRequestInfo<WorkspaceItemInfo>> = mutableListOf(),
+ iconRequestInfos: MutableList<IconRequestInfo<WorkspaceItemInfo>> = mIconRequestInfos,
isSdCardReady: Boolean = false,
- pendingPackages: MutableSet<PackageUserKey> = mutableSetOf(),
- unlockedUsers: LongSparseArray<Boolean> = unlockedUsersArray,
- installingPkgs: HashMap<PackageUserKey, PackageInstaller.SessionInfo> = hashMapOf(),
- allDeepShortcuts: MutableList<ShortcutInfo> = mutableListOf()
+ pendingPackages: MutableSet<PackageUserKey> = mPendingPackages,
+ unlockedUsers: LongSparseArray<Boolean> = mUnlockedUsersArray,
+ installingPkgs: HashMap<PackageUserKey, PackageInstaller.SessionInfo> = mInstallingPkgs,
+ allDeepShortcuts: MutableList<ShortcutInfo> = mAllDeepShortcuts
) =
WorkspaceItemProcessor(
c = cursor,
@@ -241,8 +262,8 @@
fun `When app has empty String target package then mark deleted`() {
// Given
- componentName = ComponentName("", "")
- intent.component = componentName
+ mComponentName = ComponentName("", "")
+ intent.component = mComponentName
intent.`package` = ""
// When
@@ -267,7 +288,7 @@
.isEqualTo(0)
// currently gets marked restored twice, although markRestore() has check for restoreFlag
verify(mockCursor, times(2)).markRestored()
- assertThat(iconRequestInfos).containsExactly(mockIconRequestInfo)
+ assertThat(mIconRequestInfos).containsExactly(mockIconRequestInfo)
verify(mockCursor).checkAndAddItem(mockWorkspaceInfo, mockBgDataModel, null)
}
@@ -277,12 +298,12 @@
// Given
mockLauncherApps =
mock<LauncherApps>().apply {
- whenever(isPackageEnabled("package", userHandle)).thenReturn(true)
- whenever(isActivityEnabled(componentName, userHandle)).thenReturn(false)
+ whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true)
+ whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(false)
}
mockPmHelper =
mock<PackageManagerHelper>().apply {
- whenever(getAppLaunchIntent(componentName.packageName, userHandle))
+ whenever(getAppLaunchIntent(mComponentName.packageName, mUserHandle))
.thenReturn(intent)
}
@@ -295,7 +316,7 @@
.that(mockCursor.restoreFlag)
.isEqualTo(0)
verify(mockCursor.updater().put(Favorites.INTENT, intent.toUri(0))).commit()
- assertThat(iconRequestInfos).containsExactly(mockIconRequestInfo)
+ assertThat(mIconRequestInfos).containsExactly(mockIconRequestInfo)
verify(mockCursor).checkAndAddItem(mockWorkspaceInfo, mockBgDataModel, null)
}
@@ -305,12 +326,13 @@
// Given
mockLauncherApps =
mock<LauncherApps>().apply {
- whenever(isPackageEnabled("package", userHandle)).thenReturn(true)
- whenever(isActivityEnabled(componentName, userHandle)).thenReturn(false)
+ whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true)
+ whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(false)
}
mockPmHelper =
mock<PackageManagerHelper>().apply {
- whenever(getAppLaunchIntent(componentName.packageName, userHandle)).thenReturn(null)
+ whenever(getAppLaunchIntent(mComponentName.packageName, mUserHandle))
+ .thenReturn(null)
}
// When
@@ -349,20 +371,20 @@
whenever(persons).thenReturn(EMPTY_PERSON_ARRAY)
}
val shortcutKey = ShortcutKey.fromIntent(intent, mockCursor.user)
- keyToPinnedShortcutsMap[shortcutKey] = expectedShortcutInfo
- iconRequestInfos = mutableListOf()
+ mKeyToPinnedShortcutsMap[shortcutKey] = expectedShortcutInfo
+ mIconRequestInfos = mutableListOf()
// When
itemProcessorUnderTest =
- createWorkspaceItemProcessorUnderTest(allDeepShortcuts = allDeepShortcuts)
+ createWorkspaceItemProcessorUnderTest(allDeepShortcuts = mAllDeepShortcuts)
itemProcessorUnderTest.processItem()
// Then
assertWithMessage("item restoreFlag should be set to 0")
.that(mockCursor.restoreFlag)
.isEqualTo(0)
- assertThat(iconRequestInfos).isEmpty()
- assertThat(allDeepShortcuts).containsExactly(expectedShortcutInfo)
+ assertThat(mIconRequestInfos).isEmpty()
+ assertThat(mAllDeepShortcuts).containsExactly(expectedShortcutInfo)
verify(mockCursor).markRestored()
verify(mockCursor).checkAndAddItem(any(), any(), anyOrNull())
}
@@ -372,8 +394,8 @@
// Given
mockCursor.itemType = ITEM_TYPE_DEEP_SHORTCUT
- iconRequestInfos = mutableListOf()
- keyToPinnedShortcutsMap = hashMapOf()
+ mIconRequestInfos = mutableListOf()
+ mKeyToPinnedShortcutsMap = hashMapOf()
// When
itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest()
@@ -383,7 +405,7 @@
assertWithMessage("item restoreFlag should be set to 0")
.that(mockCursor.restoreFlag)
.isEqualTo(0)
- assertThat(iconRequestInfos).isEmpty()
+ assertThat(mIconRequestInfos).isEmpty()
verify(mockCursor, times(0)).checkAndAddItem(any(), any(), anyOrNull())
verify(mockCursor)
.markDeleted(
@@ -408,25 +430,25 @@
whenever(disabledReason).thenReturn(0)
whenever(persons).thenReturn(EMPTY_PERSON_ARRAY)
}
- iconRequestInfos = mutableListOf()
+ mIconRequestInfos = mutableListOf()
// Make sure shortcuts map has expected key from expected package
- intent.`package` = componentName.packageName
+ intent.`package` = mComponentName.packageName
val shortcutKey = ShortcutKey.fromIntent(intent, mockCursor.user)
- keyToPinnedShortcutsMap[shortcutKey] = expectedShortcutInfo
+ mKeyToPinnedShortcutsMap[shortcutKey] = expectedShortcutInfo
// set intent package back to null to test scenario
intent.`package` = null
// When
itemProcessorUnderTest =
- createWorkspaceItemProcessorUnderTest(allDeepShortcuts = allDeepShortcuts)
+ createWorkspaceItemProcessorUnderTest(allDeepShortcuts = mAllDeepShortcuts)
itemProcessorUnderTest.processItem()
// Then
assertWithMessage("item restoreFlag should be set to 0")
.that(mockCursor.restoreFlag)
.isEqualTo(0)
- assertThat(iconRequestInfos).isEmpty()
- assertThat(allDeepShortcuts).containsExactly(expectedShortcutInfo)
+ assertThat(mIconRequestInfos).isEmpty()
+ assertThat(mAllDeepShortcuts).containsExactly(expectedShortcutInfo)
verify(mockCursor).markRestored()
verify(mockCursor).checkAndAddItem(any(), any(), anyOrNull())
}
@@ -478,4 +500,346 @@
assertThat(actualFolderInfo.options).isEqualTo(expectedFolderInfo.options)
verify(mockCursor).checkAndAddItem(actualFolderInfo, mockBgDataModel, null)
}
+
+ @Test
+ fun `When valid TYPE_REAL App Widget then add item`() {
+
+ // Given
+ val expectedProvider = "com.google.android.testApp/com.android.testApp.testAppProvider"
+ val expectedComponentName =
+ ComponentName.unflattenFromString(expectedProvider)!!.flattenToString()
+ val expectedRestoreStatus = FLAG_UI_NOT_READY
+ val expectedAppWidgetId = 0
+ mockCursor.apply {
+ itemType = ITEM_TYPE_APPWIDGET
+ user = mUserHandle
+ restoreFlag = FLAG_UI_NOT_READY
+ container = CONTAINER_DESKTOP
+ whenever(isOnWorkspaceOrHotseat).thenCallRealMethod()
+ whenever(appWidgetProvider).thenReturn(expectedProvider)
+ whenever(appWidgetId).thenReturn(expectedAppWidgetId)
+ whenever(spanX).thenReturn(2)
+ whenever(spanY).thenReturn(1)
+ whenever(options).thenReturn(0)
+ whenever(appWidgetSource).thenReturn(20)
+ whenever(applyCommonProperties(any())).thenCallRealMethod()
+ whenever(
+ updater()
+ .put(Favorites.APPWIDGET_PROVIDER, expectedComponentName)
+ .put(Favorites.APPWIDGET_ID, expectedAppWidgetId)
+ .put(Favorites.RESTORED, expectedRestoreStatus)
+ .commit()
+ )
+ .thenReturn(1)
+ }
+ val expectedWidgetInfo =
+ LauncherAppWidgetInfo().apply {
+ appWidgetId = expectedAppWidgetId
+ providerName = ComponentName.unflattenFromString(expectedProvider)
+ restoreStatus = expectedRestoreStatus
+ }
+ val expectedWidgetProviderInfo =
+ mock<LauncherAppWidgetProviderInfo>().apply {
+ provider = ComponentName.unflattenFromString(expectedProvider)
+ whenever(user).thenReturn(mUserHandle)
+ }
+ val inflationResult =
+ WidgetInflater.InflationResult(
+ type = WidgetInflater.TYPE_REAL,
+ widgetInfo = expectedWidgetProviderInfo
+ )
+ mockWidgetInflater =
+ mock<WidgetInflater>().apply {
+ whenever(inflateAppWidget(any())).thenReturn(inflationResult)
+ }
+ val packageUserKey = PackageUserKey("com.google.android.testApp", mUserHandle)
+ mInstallingPkgs[packageUserKey] = PackageInstaller.SessionInfo()
+
+ // When
+ itemProcessorUnderTest =
+ createWorkspaceItemProcessorUnderTest(widgetProvidersMap = mWidgetProvidersMap)
+ itemProcessorUnderTest.processItem()
+
+ // Then
+ val widgetInfoCaptor = ArgumentCaptor.forClass(LauncherAppWidgetInfo::class.java)
+ verify(mockCursor).checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel))
+ val actualWidgetInfo = widgetInfoCaptor.value
+ with(actualWidgetInfo) {
+ assertThat(providerName).isEqualTo(expectedWidgetInfo.providerName)
+ assertThat(restoreStatus).isEqualTo(expectedWidgetInfo.restoreStatus)
+ assertThat(targetComponent).isEqualTo(expectedWidgetInfo.targetComponent)
+ assertThat(appWidgetId).isEqualTo(expectedWidgetInfo.appWidgetId)
+ }
+ val expectedComponentKey =
+ ComponentKey(expectedWidgetProviderInfo.provider, expectedWidgetProviderInfo.user)
+ assertThat(mWidgetProvidersMap[expectedComponentKey]).isEqualTo(expectedWidgetProviderInfo)
+ }
+
+ @Test
+ fun `When valid Pending Widget then checkAndAddItem`() {
+
+ // Given
+ mockCursor =
+ mock<LoaderCursor>().apply {
+ itemType = ITEM_TYPE_APPWIDGET
+ id = 1
+ user = UserHandle(1)
+ restoreFlag = FLAG_UI_NOT_READY
+ container = CONTAINER_DESKTOP
+ whenever(isOnWorkspaceOrHotseat).thenCallRealMethod()
+ whenever(appWidgetProvider)
+ .thenReturn("com.google.android.testApp/com.android.testApp.testAppProvider")
+ whenever(appWidgetId).thenReturn(0)
+ whenever(spanX).thenReturn(2)
+ whenever(spanY).thenReturn(1)
+ whenever(options).thenReturn(0)
+ whenever(appWidgetSource).thenReturn(20)
+ whenever(applyCommonProperties(any())).thenCallRealMethod()
+ }
+ val mockProviderInfo =
+ mock<LauncherAppWidgetProviderInfo>().apply {
+ provider = mock()
+ whenever(user).thenReturn(UserHandle(1))
+ }
+ val inflationResult =
+ WidgetInflater.InflationResult(
+ type = WidgetInflater.TYPE_PENDING,
+ widgetInfo = mockProviderInfo
+ )
+ mockWidgetInflater =
+ mock<WidgetInflater>().apply {
+ whenever(inflateAppWidget(any())).thenReturn(inflationResult)
+ }
+ itemProcessorUnderTest =
+ createWorkspaceItemProcessorUnderTest(widgetProvidersMap = mWidgetProvidersMap)
+
+ // When
+ itemProcessorUnderTest.processItem()
+
+ // Then
+ verify(mockCursor).checkAndAddItem(any(), any())
+ }
+
+ @Test
+ fun `When Unrestored Pending App Widget then mark deleted`() {
+
+ // Given
+ val expectedProvider = "com.google.android.testApp/com.android.testApp.testAppProvider"
+ mockCursor =
+ mock<LoaderCursor>().apply {
+ itemType = ITEM_TYPE_APPWIDGET
+ id = 1
+ user = UserHandle(1)
+ restoreFlag = FLAG_UI_NOT_READY
+ container = CONTAINER_DESKTOP
+ whenever(isOnWorkspaceOrHotseat).thenCallRealMethod()
+ whenever(appWidgetProvider).thenReturn(expectedProvider)
+ whenever(appWidgetId).thenReturn(0)
+ whenever(spanX).thenReturn(2)
+ whenever(spanY).thenReturn(1)
+ whenever(options).thenReturn(0)
+ whenever(appWidgetSource).thenReturn(20)
+ whenever(applyCommonProperties(any())).thenCallRealMethod()
+ }
+ mInstallingPkgs = hashMapOf()
+ val inflationResult =
+ WidgetInflater.InflationResult(type = WidgetInflater.TYPE_PENDING, widgetInfo = null)
+ mockWidgetInflater =
+ mock<WidgetInflater>().apply {
+ whenever(inflateAppWidget(any())).thenReturn(inflationResult)
+ }
+ val expectedComponentName = ComponentName.unflattenFromString(expectedProvider)
+
+ // When
+ itemProcessorUnderTest =
+ createWorkspaceItemProcessorUnderTest(widgetProvidersMap = mWidgetProvidersMap)
+ itemProcessorUnderTest.processItem()
+
+ // Then
+ verify(mockCursor)
+ .markDeleted(
+ "processWidget: Unrestored Pending widget removed: id=1, appWidgetId=0, component=$expectedComponentName, restoreFlag:=4",
+ LauncherRestoreEventLogger.RestoreError.APP_NOT_INSTALLED
+ )
+ }
+
+ @Test
+ fun `When Pending App Widget has not started restore then update db and add item`() {
+
+ val mockitoSession =
+ ExtendedMockito.mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .mockStatic(WidgetSections::class.java)
+ .startMocking()
+ try {
+ // Given
+ val expectedProvider = "com.google.android.testApp/com.android.testApp.testAppProvider"
+ val expectedComponentName =
+ ComponentName.unflattenFromString(expectedProvider)!!.flattenToString()
+ val expectedRestoreStatus = FLAG_UI_NOT_READY or FLAG_RESTORE_STARTED
+ val expectedAppWidgetId = 0
+ mockCursor.apply {
+ itemType = ITEM_TYPE_APPWIDGET
+ user = mUserHandle
+ restoreFlag = FLAG_UI_NOT_READY
+ container = CONTAINER_DESKTOP
+ whenever(isOnWorkspaceOrHotseat).thenCallRealMethod()
+ whenever(appWidgetProvider).thenReturn(expectedProvider)
+ whenever(appWidgetId).thenReturn(expectedAppWidgetId)
+ whenever(spanX).thenReturn(2)
+ whenever(spanY).thenReturn(1)
+ whenever(options).thenReturn(0)
+ whenever(appWidgetSource).thenReturn(20)
+ whenever(applyCommonProperties(any())).thenCallRealMethod()
+ whenever(
+ updater()
+ .put(Favorites.APPWIDGET_PROVIDER, expectedComponentName)
+ .put(Favorites.APPWIDGET_ID, expectedAppWidgetId)
+ .put(Favorites.RESTORED, expectedRestoreStatus)
+ .commit()
+ )
+ .thenReturn(1)
+ }
+ val inflationResult =
+ WidgetInflater.InflationResult(
+ type = WidgetInflater.TYPE_PENDING,
+ widgetInfo = null
+ )
+ mockWidgetInflater =
+ mock<WidgetInflater>().apply {
+ whenever(inflateAppWidget(any())).thenReturn(inflationResult)
+ }
+ val packageUserKey = PackageUserKey("com.google.android.testApp", mUserHandle)
+ mInstallingPkgs[packageUserKey] = PackageInstaller.SessionInfo()
+
+ // When
+ itemProcessorUnderTest =
+ createWorkspaceItemProcessorUnderTest(widgetProvidersMap = mWidgetProvidersMap)
+ itemProcessorUnderTest.processItem()
+
+ // Then
+ val expectedWidgetInfo =
+ LauncherAppWidgetInfo().apply {
+ appWidgetId = expectedAppWidgetId
+ providerName = ComponentName.unflattenFromString(expectedProvider)
+ restoreStatus = expectedRestoreStatus
+ }
+ verify(
+ mockCursor
+ .updater()
+ .put(Favorites.APPWIDGET_PROVIDER, expectedProvider)
+ .put(Favorites.APPWIDGET_ID, expectedAppWidgetId)
+ .put(Favorites.RESTORED, expectedRestoreStatus)
+ )
+ .commit()
+ val widgetInfoCaptor = ArgumentCaptor.forClass(LauncherAppWidgetInfo::class.java)
+ verify(mockCursor).checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel))
+ val actualWidgetInfo = widgetInfoCaptor.value
+ with(actualWidgetInfo) {
+ assertThat(providerName).isEqualTo(expectedWidgetInfo.providerName)
+ assertThat(restoreStatus).isEqualTo(expectedWidgetInfo.restoreStatus)
+ assertThat(targetComponent).isEqualTo(expectedWidgetInfo.targetComponent)
+ assertThat(appWidgetId).isEqualTo(expectedWidgetInfo.appWidgetId)
+ }
+ } finally {
+ mockitoSession.finishMocking()
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_SUPPORT_FOR_ARCHIVING)
+ fun `When Archived Pending App Widget then checkAndAddItem`() {
+ val mockitoSession =
+ ExtendedMockito.mockitoSession().mockStatic(Utilities::class.java).startMocking()
+ try {
+ // Given
+ val expectedProvider = "com.google.android.testApp/com.android.testApp.testAppProvider"
+ val expectedComponentName = ComponentName.unflattenFromString(expectedProvider)
+ val expectedPackage = expectedComponentName!!.packageName
+ mockPmHelper =
+ mock<PackageManagerHelper>().apply {
+ whenever(isAppArchived(expectedPackage)).thenReturn(true)
+ }
+ mockCursor =
+ mock<LoaderCursor>().apply {
+ itemType = ITEM_TYPE_APPWIDGET
+ id = 1
+ user = UserHandle(1)
+ restoreFlag = FLAG_UI_NOT_READY
+ container = CONTAINER_DESKTOP
+ whenever(isOnWorkspaceOrHotseat).thenCallRealMethod()
+ whenever(appWidgetProvider).thenReturn(expectedProvider)
+ whenever(appWidgetId).thenReturn(0)
+ whenever(spanX).thenReturn(2)
+ whenever(spanY).thenReturn(1)
+ whenever(options).thenReturn(0)
+ whenever(appWidgetSource).thenReturn(20)
+ whenever(applyCommonProperties(any())).thenCallRealMethod()
+ }
+ mInstallingPkgs = hashMapOf()
+ val inflationResult =
+ WidgetInflater.InflationResult(
+ type = WidgetInflater.TYPE_PENDING,
+ widgetInfo = null
+ )
+ mockWidgetInflater =
+ mock<WidgetInflater>().apply {
+ whenever(inflateAppWidget(any())).thenReturn(inflationResult)
+ }
+ itemProcessorUnderTest =
+ createWorkspaceItemProcessorUnderTest(widgetProvidersMap = mWidgetProvidersMap)
+
+ // When
+ itemProcessorUnderTest.processItem()
+
+ // Then
+ verify(mockCursor).checkAndAddItem(any(), any())
+ } finally {
+ mockitoSession.finishMocking()
+ }
+ }
+
+ @Test
+ fun `When widget inflation result is TYPE_DELETE then mark deleted`() {
+ // Given
+ val expectedProvider = "com.google.android.testApp/com.android.testApp.testAppProvider"
+ val expectedComponentName = ComponentName.unflattenFromString(expectedProvider)
+ val expectedPackage = expectedComponentName!!.packageName
+ mockPmHelper =
+ mock<PackageManagerHelper>().apply {
+ whenever(isAppArchived(expectedPackage)).thenReturn(true)
+ }
+ mockCursor =
+ mock<LoaderCursor>().apply {
+ itemType = ITEM_TYPE_APPWIDGET
+ id = 1
+ user = UserHandle(1)
+ container = CONTAINER_DESKTOP
+ whenever(spanX).thenReturn(2)
+ whenever(spanY).thenReturn(1)
+ whenever(appWidgetProvider).thenReturn(expectedProvider)
+ whenever(isOnWorkspaceOrHotseat).thenCallRealMethod()
+ whenever(applyCommonProperties(any())).thenCallRealMethod()
+ }
+ mInstallingPkgs = hashMapOf()
+ val inflationResult =
+ WidgetInflater.InflationResult(
+ type = WidgetInflater.TYPE_DELETE,
+ widgetInfo = null,
+ reason = "test_delete_reason",
+ restoreErrorType = MISSING_WIDGET_PROVIDER
+ )
+ mockWidgetInflater =
+ mock<WidgetInflater>().apply {
+ whenever(inflateAppWidget(any())).thenReturn(inflationResult)
+ }
+ itemProcessorUnderTest =
+ createWorkspaceItemProcessorUnderTest(widgetProvidersMap = mWidgetProvidersMap)
+
+ // When
+ itemProcessorUnderTest.processItem()
+
+ // Then
+ verify(mockCursor).markDeleted(inflationResult.reason, inflationResult.restoreErrorType)
+ }
}
diff --git a/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
index 7845dec..9dbd866 100644
--- a/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
@@ -86,7 +86,6 @@
* A custom shortcut is a 1x1 widget that launches a specific intent when user tap on it.
* Custom shortcuts are replaced by deep shortcuts after api 25.
*/
- @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
@Test
@PortraitLandscape
public void testDragCustomShortcut() throws Throwable {