Merge "Add split instructions to stage split from keyboard shortcut" into main
diff --git a/aconfig/launcher_search.aconfig b/aconfig/launcher_search.aconfig
index b846604..4e16e7f 100644
--- a/aconfig/launcher_search.aconfig
+++ b/aconfig/launcher_search.aconfig
@@ -34,3 +34,10 @@
description: "This flag disables accessibility drag for Private Space Apps."
bug: "289223923"
}
+
+flag {
+ name: "private_space_restrict_item_drag"
+ namespace: "launcher_search"
+ description: "This flag disables drag and drop for Private Space Items."
+ bug: "289223923"
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index 5caf004..f15d12b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -22,6 +22,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.launcher3.R;
import com.android.launcher3.statehandlers.DesktopVisibilityController;
@@ -47,7 +48,8 @@
public final class KeyboardQuickSwitchController implements
TaskbarControllers.LoggableTaskbarController {
- static final int MAX_TASKS = 6;
+ @VisibleForTesting
+ public static final int MAX_TASKS = 6;
@NonNull private final ControllerCallbacks mControllerCallbacks = new ControllerCallbacks();
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 5819bb3..6dc7db7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -71,6 +71,7 @@
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.Executors.SimpleThreadFactory;
import com.android.quickstep.SystemUiProxy;
+import com.android.wm.shell.Flags;
import com.android.wm.shell.bubbles.IBubblesListener;
import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
import com.android.wm.shell.common.bubbles.BubbleInfo;
@@ -101,8 +102,8 @@
*
* @see #onTaskbarRecreated()
*/
- private static boolean sBubbleBarEnabled =
- SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
+ private static boolean sBubbleBarEnabled = Flags.enableBubbleBar()
+ || SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
/** Whether showing bubbles in the launcher bubble bar is enabled. */
public static boolean isBubbleBarEnabled() {
@@ -111,8 +112,10 @@
/** Re-reads the value of the flag from SystemProperties when taskbar is recreated. */
public static void onTaskbarRecreated() {
- sBubbleBarEnabled = SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
+ sBubbleBarEnabled = Flags.enableBubbleBar()
+ || SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
}
+
private static final int MASK_HIDE_BUBBLE_BAR = SYSUI_STATE_BOUNCER_SHOWING
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
index 22f24f1..039c0a0 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
@@ -28,10 +28,8 @@
import android.util.Pair;
import android.view.View;
import android.widget.RemoteViews;
-import android.widget.Toast;
import android.window.SplashScreen;
-import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.ItemInfo;
@@ -63,8 +61,7 @@
// Log metric
StatsLogManager.StatsLogger logger = mLauncher.getStatsLogManager().logger();
logger.log(LAUNCHER_SPLIT_WIDGET_ATTEMPT);
- Toast.makeText(hostView.getContext(), R.string.split_widgets_not_supported,
- Toast.LENGTH_SHORT).show();
+ mLauncher.handleIncorrectSplitTargetSelection();
return true;
}
Pair<Intent, ActivityOptions> options = remoteResponse.getLaunchOptions(view);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 32d10b0..ac0e53e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -36,6 +36,7 @@
import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
+import static com.android.launcher3.config.FeatureFlags.enableSplitContextually;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition;
@@ -676,7 +677,7 @@
splitSelectSource.alreadyRunningTaskId = taskWasFound
? foundTask.key.id
: INVALID_TASK_ID;
- if (FeatureFlags.enableSplitContextually()) {
+ if (enableSplitContextually()) {
startSplitToHome(splitSelectSource);
} else {
recentsView.initiateSplitSelect(splitSelectSource);
@@ -761,7 +762,7 @@
super.onPause();
- if (FeatureFlags.enableSplitContextually()) {
+ if (enableSplitContextually()) {
// If Launcher pauses before both split apps are selected, exit split screen.
if (!mSplitSelectStateController.isBothSplitAppsConfirmed() &&
!mSplitSelectStateController.isLaunchingFirstAppFullscreen()) {
@@ -1352,6 +1353,15 @@
return (mTaskbarUIController != null && mTaskbarUIController.hasBubbles());
}
+ @Override
+ public boolean handleIncorrectSplitTargetSelection() {
+ if (enableSplitContextually() && !mSplitSelectStateController.isSplitSelectActive()) {
+ return false;
+ }
+ mSplitSelectStateController.getSplitInstructionsView().goBoing();
+ return true;
+ }
+
private static final class LauncherTaskViewController extends
TaskViewTouchController<Launcher> {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 60d0e2b..6698600 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -22,7 +22,9 @@
import static android.widget.Toast.LENGTH_SHORT;
import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
+import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.app.animation.Interpolators.DECELERATE;
+import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.app.animation.Interpolators.OVERSHOOT_1_2;
import static com.android.launcher3.BaseActivity.EVENT_DESTROYED;
import static com.android.launcher3.BaseActivity.EVENT_STARTED;
@@ -134,6 +136,7 @@
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.SwipePipToHomeAnimator;
import com.android.quickstep.util.TaskViewSimulator;
+import com.android.quickstep.util.TransformParams;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
@@ -167,6 +170,9 @@
private static final ArrayList<String> STATE_NAMES = new ArrayList<>();
+ // Fraction of the scroll and transform animation in which the current task fades out
+ private static final float KQS_TASK_FADE_ANIMATION_FRACTION = 0.4f;
+
protected final BaseActivityInterface<S, T> mActivityInterface;
protected final InputConsumerProxy mInputConsumerProxy;
protected final ActivityInitListener mActivityInitListener;
@@ -900,7 +906,10 @@
return;
}
mLauncherTransitionController.setProgress(
- Math.max(mCurrentShift.value, getScaleProgressDueToScroll()), mDragLengthFactor);
+ // Immediately finish the grid transition
+ isKeyboardTaskFocusPending()
+ ? 1f : Math.max(mCurrentShift.value, getScaleProgressDueToScroll()),
+ mDragLengthFactor);
}
/**
@@ -1349,7 +1358,9 @@
}
Interpolator interpolator;
S state = mActivityInterface.stateFromGestureEndTarget(endTarget);
- if (state.displayOverviewTasksAsGrid(mDp)) {
+ if (isKeyboardTaskFocusPending()) {
+ interpolator = EMPHASIZED;
+ } else if (state.displayOverviewTasksAsGrid(mDp)) {
interpolator = ACCELERATE_DECELERATE;
} else if (endTarget == RECENTS) {
interpolator = OVERSHOOT_1_2;
@@ -1653,7 +1664,8 @@
animatorSet.play(windowAnim);
if (mRecentsView != null) {
mRecentsView.onPrepareGestureEndAnimation(
- animatorSet, mGestureState.getEndTarget(),
+ mGestureState.isHandlingAtomicEvent() ? null : animatorSet,
+ mGestureState.getEndTarget(),
getRemoteTaskViewSimulators());
}
animatorSet.setDuration(duration).setInterpolator(interpolator);
@@ -2225,6 +2237,14 @@
}
}
+ private boolean shouldLinkRecentsViewScroll() {
+ return mRecentsViewScrollLinked && !isKeyboardTaskFocusPending();
+ }
+
+ private boolean isKeyboardTaskFocusPending() {
+ return mRecentsView != null && mRecentsView.isKeyboardTaskFocusPending();
+ }
+
private void onRecentsViewScroll() {
if (moveWindowWithRecentsScroll()) {
onCurrentShiftUpdated();
@@ -2457,6 +2477,44 @@
mActivityInitListener.register();
}
+ private boolean shouldFadeOutTargetsForKeyboardQuickSwitch(
+ TransformParams transformParams,
+ TaskViewSimulator taskViewSimulator,
+ float progress) {
+ RemoteAnimationTargets targets = transformParams.getTargetSet();
+ boolean fadeAppTargets = isKeyboardTaskFocusPending()
+ && targets != null
+ && targets.apps != null
+ && targets.apps.length > 0;
+ float fadeProgress = Utilities.mapBoundToRange(
+ progress,
+ /* lowerBound= */ 0f,
+ /* upperBound= */ KQS_TASK_FADE_ANIMATION_FRACTION,
+ /* toMin= */ 0f,
+ /* toMax= */ 1f,
+ LINEAR);
+ if (!fadeAppTargets || Float.compare(fadeProgress, 1f) == 0) {
+ return false;
+ }
+ SurfaceTransaction surfaceTransaction =
+ transformParams.createSurfaceParams(taskViewSimulator);
+ SurfaceControl.Transaction transaction = surfaceTransaction.getTransaction();
+
+ for (RemoteAnimationTarget app : targets.apps) {
+ transaction.setAlpha(app.leash, 1f - fadeProgress);
+ transaction.setPosition(app.leash,
+ /* x= */ app.startBounds.left
+ + (mActivity.getDeviceProfile().overviewPageSpacing
+ * (mRecentsView.isRtl() ? fadeProgress : -fadeProgress)),
+ /* y= */ 0f);
+ transaction.setScale(app.leash, 1f, 1f);
+ taskViewSimulator.taskPrimaryTranslation.value =
+ mRecentsView.getScrollOffsetForKeyboardTaskFocus();
+ taskViewSimulator.apply(transformParams, surfaceTransaction);
+ }
+ return true;
+ }
+
/**
* Applies the transform on the recents animation
*/
@@ -2466,7 +2524,7 @@
// swipe-to-icon animation is handled by RectFSpringAnim anim
boolean notSwipingToHome = mRecentsAnimationTargets != null
&& mGestureState.getEndTarget() != HOME;
- boolean setRecentsScroll = mRecentsViewScrollLinked && mRecentsView != null;
+ boolean setRecentsScroll = shouldLinkRecentsViewScroll() && mRecentsView != null;
float progress = Math.max(mCurrentShift.value, getScaleProgressDueToScroll());
int scrollOffset = setRecentsScroll ? mRecentsView.getScrollOffset() : 0;
if (!mStartMovingTasks && (progress > 0 || scrollOffset != 0)) {
@@ -2485,7 +2543,12 @@
if (setRecentsScroll) {
taskViewSimulator.setScroll(scrollOffset);
}
- taskViewSimulator.apply(remoteHandle.getTransformParams());
+ TransformParams transformParams = remoteHandle.getTransformParams();
+ if (shouldFadeOutTargetsForKeyboardQuickSwitch(
+ transformParams, taskViewSimulator, progress)) {
+ continue;
+ }
+ taskViewSimulator.apply(transformParams);
}
}
}
@@ -2493,7 +2556,7 @@
// Scaling of RecentsView during quick switch based on amount of recents scroll
private float getScaleProgressDueToScroll() {
if (mActivity == null || !mActivity.getDeviceProfile().isTablet || mRecentsView == null
- || !mRecentsViewScrollLinked) {
+ || !shouldLinkRecentsViewScroll()) {
return 0;
}
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index ca82a2d..b89d20c 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -178,7 +178,7 @@
public abstract <T extends RecentsView> T getVisibleRecentsView();
@UiThread
- public abstract boolean switchToRecentsIfVisible(Runnable onCompleteCallback);
+ public abstract boolean switchToRecentsIfVisible(Animator.AnimatorListener animatorListener);
public abstract Rect getOverviewWindowBounds(
Rect homeBounds, RemoteAnimationTarget target);
@@ -520,7 +520,7 @@
// Since we are changing the start position of the UI, reapply the state, at the end
controller.setEndAction(() -> mActivity.getStateManager().goToState(
controller.getInterpolatedProgress() > 0.5 ? mTargetState : mBackgroundState,
- false));
+ /* animated= */ false));
RecentsView recentsView = mActivity.getOverviewPanel();
AnimatorControllerWithResistance controllerWithResistance =
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index 693d6ae..27e8726 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -122,7 +122,7 @@
}
@Override
- public boolean switchToRecentsIfVisible(Runnable onCompleteCallback) {
+ public boolean switchToRecentsIfVisible(Animator.AnimatorListener animatorListener) {
return false;
}
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index e77a450..b42eb06 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -371,8 +371,8 @@
if (mSpringAnim != null) {
mSpringAnim.onTargetPositionChanged();
}
- mOnFinishCallback = data.getParcelable(EXTRA_ON_FINISH_CALLBACK);
}
+ mOnFinishCallback = data.getParcelable(EXTRA_ON_FINISH_CALLBACK);
maybeSendEndMessage();
} catch (Exception e) {
// Ignore
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 70b9183..97c48e6 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -21,7 +21,6 @@
import static com.android.launcher3.LauncherState.FLOATING_SEARCH_BAR;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
@@ -212,7 +211,7 @@
}
@Override
- public boolean switchToRecentsIfVisible(Runnable onCompleteCallback) {
+ public boolean switchToRecentsIfVisible(Animator.AnimatorListener animatorListener) {
Launcher launcher = getVisibleLauncher();
if (launcher == null) {
return false;
@@ -227,7 +226,7 @@
closeOverlay();
launcher.getStateManager().goToState(OVERVIEW,
launcher.getStateManager().shouldAnimateStateChange(),
- onCompleteCallback == null ? null : forEndCallback(onCompleteCallback));
+ animatorListener);
return true;
}
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 315316d..e448a14 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -15,9 +15,12 @@
*/
package com.android.quickstep;
+import static com.android.launcher3.PagedView.INVALID_PAGE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.content.Intent;
import android.graphics.PointF;
import android.os.SystemClock;
@@ -25,7 +28,6 @@
import android.view.View;
import androidx.annotation.BinderThread;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
@@ -75,7 +77,7 @@
* do not lose the focus across multiple calls of
* {@link OverviewCommandHelper#executeCommand(CommandInfo)} for the same command
*/
- private int mTaskFocusIndexOverride = -1;
+ private int mKeyboardTaskFocusIndex = -1;
/**
* Whether we should incoming toggle commands while a previous toggle command is still ongoing.
@@ -195,9 +197,11 @@
}
BaseActivityInterface<?, T> activityInterface =
mOverviewComponentObserver.getActivityInterface();
- RecentsView recents = activityInterface.getVisibleRecentsView();
- if (recents == null) {
+ RecentsView visibleRecentsView = activityInterface.getVisibleRecentsView();
+ RecentsView createdRecentsView;
+ if (visibleRecentsView == null) {
T activity = activityInterface.getCreatedActivity();
+ createdRecentsView = activity == null ? null : activity.getOverviewPanel();
DeviceProfile dp = activity == null ? null : activity.getDeviceProfile();
TaskbarUIController uiController = activityInterface.getTaskbarController();
boolean allowQuickSwitch = FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get()
@@ -209,8 +213,8 @@
if (!allowQuickSwitch) {
return true;
}
- mTaskFocusIndexOverride = uiController.launchFocusedTask();
- if (mTaskFocusIndexOverride == -1) {
+ mKeyboardTaskFocusIndex = uiController.launchFocusedTask();
+ if (mKeyboardTaskFocusIndex == -1) {
return true;
}
}
@@ -224,34 +228,47 @@
return true;
}
} else {
+ createdRecentsView = visibleRecentsView;
switch (cmd.type) {
case TYPE_SHOW:
// already visible
return true;
case TYPE_HIDE: {
- mTaskFocusIndexOverride = -1;
- int currentPage = recents.getNextPage();
- TaskView tv = (currentPage >= 0 && currentPage < recents.getTaskViewCount())
- ? (TaskView) recents.getPageAt(currentPage)
+ mKeyboardTaskFocusIndex = INVALID_PAGE;
+ int currentPage = visibleRecentsView.getNextPage();
+ TaskView tv = (currentPage >= 0
+ && currentPage < visibleRecentsView.getTaskViewCount())
+ ? (TaskView) visibleRecentsView.getPageAt(currentPage)
: null;
- return launchTask(recents, tv, cmd);
+ return launchTask(visibleRecentsView, tv, cmd);
}
case TYPE_TOGGLE:
- return launchTask(recents, getNextTask(recents), cmd);
+ return launchTask(visibleRecentsView, getNextTask(visibleRecentsView), cmd);
case TYPE_HOME:
- recents.startHome();
+ visibleRecentsView.startHome();
return true;
}
}
- final Runnable completeCallback = () -> {
- RecentsView rv = activityInterface.getVisibleRecentsView();
- if (rv != null && (cmd.type == TYPE_KEYBOARD_INPUT || cmd.type == TYPE_HIDE)) {
- updateRecentsViewFocus(rv);
+ if (createdRecentsView != null) {
+ createdRecentsView.setKeyboardTaskFocusIndex(mKeyboardTaskFocusIndex);
+ }
+ // Handle recents view focus when launching from home
+ Animator.AnimatorListener animatorListener = new AnimatorListenerAdapter() {
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ updateRecentsViewFocus(cmd);
}
- scheduleNextTask(cmd);
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ onRecentsViewFocusUpdated(cmd);
+ scheduleNextTask(cmd);
+ }
};
- if (activityInterface.switchToRecentsIfVisible(completeCallback)) {
+ if (activityInterface.switchToRecentsIfVisible(animatorListener)) {
// If successfully switched, wait until animation finishes
return false;
}
@@ -276,6 +293,7 @@
@Override
public void onRecentsAnimationStart(RecentsAnimationController controller,
RecentsAnimationTargets targets) {
+ updateRecentsViewFocus(cmd);
activityInterface.runOnInitBackgroundStateUI(() ->
interactionHandler.onGestureEnded(0, new PointF()));
cmd.removeListener(this);
@@ -290,14 +308,12 @@
if (createdActivity == null) {
return;
}
- RecentsView createdRecents = createdActivity.getOverviewPanel();
- if (createdRecents != null) {
- createdRecents.onRecentsAnimationComplete();
+ if (createdRecentsView != null) {
+ createdRecentsView.onRecentsAnimationComplete();
}
}
};
- RecentsView<?, ?> visibleRecentsView = activityInterface.getVisibleRecentsView();
if (visibleRecentsView != null) {
visibleRecentsView.moveRunningTaskToFront();
}
@@ -317,7 +333,6 @@
interactionHandler.onGestureStarted(false /*isLikelyToStartNewTask*/);
cmd.mActiveCallbacks.addListener(recentAnimListener);
}
-
Trace.beginAsyncSection(TRANSITION_NAME, 0);
return false;
}
@@ -325,47 +340,58 @@
private void onTransitionComplete(CommandInfo cmd, AbsSwipeUpHandler handler) {
cmd.removeListener(handler);
Trace.endAsyncSection(TRANSITION_NAME, 0);
-
- RecentsView rv =
- mOverviewComponentObserver.getActivityInterface().getVisibleRecentsView();
- if (rv != null && (cmd.type == TYPE_KEYBOARD_INPUT || cmd.type == TYPE_HIDE)) {
- updateRecentsViewFocus(rv);
- }
+ onRecentsViewFocusUpdated(cmd);
scheduleNextTask(cmd);
}
- private void updateRecentsViewFocus(@NonNull RecentsView rv) {
+ private void updateRecentsViewFocus(CommandInfo cmd) {
+ RecentsView recentsView =
+ mOverviewComponentObserver.getActivityInterface().getVisibleRecentsView();
+ if (recentsView == null || (cmd.type != TYPE_KEYBOARD_INPUT && cmd.type != TYPE_HIDE)) {
+ return;
+ }
// When the overview is launched via alt tab (cmd type is TYPE_KEYBOARD_INPUT),
// the touch mode somehow is not change to false by the Android framework.
// The subsequent tab to go through tasks in overview can only be dispatched to
// focuses views, while focus can only be requested in
// {@link View#requestFocusNoSearch(int, Rect)} when touch mode is false. To note,
// here we launch overview with live tile.
- rv.getViewRootImpl().touchModeChanged(false);
+ recentsView.getViewRootImpl().touchModeChanged(false);
// Ensure that recents view has focus so that it receives the followup key inputs
- TaskView taskView = rv.getTaskViewAt(mTaskFocusIndexOverride);
- if (taskView != null) {
- requestFocus(taskView);
+ if (requestFocus(recentsView.getTaskViewAt(mKeyboardTaskFocusIndex))) {
return;
}
- taskView = rv.getNextTaskView();
- if (taskView != null) {
- requestFocus(taskView);
+ if (requestFocus(recentsView.getNextTaskView())) {
return;
}
- taskView = rv.getTaskViewAt(0);
- if (taskView != null) {
- requestFocus(taskView);
+ if (requestFocus(recentsView.getTaskViewAt(0))) {
return;
}
- requestFocus(rv);
+ requestFocus(recentsView);
}
- private void requestFocus(@NonNull View view) {
- view.post(() -> {
- view.requestFocus();
- view.requestAccessibilityFocus();
+ private void onRecentsViewFocusUpdated(CommandInfo cmd) {
+ RecentsView recentsView =
+ mOverviewComponentObserver.getActivityInterface().getVisibleRecentsView();
+ if (recentsView == null
+ || cmd.type != TYPE_HIDE
+ || mKeyboardTaskFocusIndex == INVALID_PAGE) {
+ return;
+ }
+ recentsView.setKeyboardTaskFocusIndex(INVALID_PAGE);
+ recentsView.setCurrentPage(mKeyboardTaskFocusIndex);
+ mKeyboardTaskFocusIndex = INVALID_PAGE;
+ }
+
+ private boolean requestFocus(@Nullable View taskView) {
+ if (taskView == null) {
+ return false;
+ }
+ taskView.post(() -> {
+ taskView.requestFocus();
+ taskView.requestAccessibilityFocus();
});
+ return true;
}
public void dump(PrintWriter pw) {
@@ -374,7 +400,7 @@
if (!mPendingCommands.isEmpty()) {
pw.println(" pendingCommandType=" + mPendingCommands.get(0).type);
}
- pw.println(" mTaskFocusIndexOverride=" + mTaskFocusIndexOverride);
+ pw.println(" mKeyboardTaskFocusIndex=" + mKeyboardTaskFocusIndex);
pw.println(" mWaitForToggleCommandComplete=" + mWaitForToggleCommandComplete);
}
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 4f885af..a1a3145 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -66,6 +66,8 @@
private Runnable mLiveTileCleanUpHandler;
private Context mCtx;
+ private boolean mRecentsAnimationStartPending = false;
+
private final TaskStackChangeListener mLiveTileRestartListener = new TaskStackChangeListener() {
@Override
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
@@ -100,6 +102,10 @@
.startRecentsActivity(intent, 0, null, null, null));
}
+ public boolean isRecentsAnimationStartPending() {
+ return mRecentsAnimationStartPending;
+ }
+
/**
* Starts a new recents animation for the activity with the given {@param intent}.
*/
@@ -109,6 +115,7 @@
ActiveGestureLog.INSTANCE.addLog(
/* event= */ "startRecentsAnimation",
/* gestureEvent= */ START_RECENTS_ANIMATION);
+ mRecentsAnimationStartPending = true;
// Notify if recents animation is still running
if (mController != null) {
String msg = "New recents animation started before old animation completed";
@@ -138,6 +145,7 @@
@Override
public void onRecentsAnimationStart(RecentsAnimationController controller,
RecentsAnimationTargets targets) {
+ mRecentsAnimationStartPending = false;
if (mCallbacks == null) {
// It's possible for the recents animation to have finished and be cleaned up
// by the time we process the start callback, and in that case, just we can skip
@@ -178,11 +186,13 @@
@Override
public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
+ mRecentsAnimationStartPending = false;
cleanUpRecentsAnimation(newCallbacks);
}
@Override
public void onRecentsAnimationFinished(RecentsAnimationController controller) {
+ mRecentsAnimationStartPending = false;
cleanUpRecentsAnimation(newCallbacks);
}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 55a7985..5228420 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -24,6 +24,7 @@
import static android.view.MotionEvent.ACTION_UP;
import static com.android.launcher3.Flags.enableCursorHoverStates;
+import static com.android.launcher3.Flags.useActivityOverlay;
import static com.android.launcher3.Launcher.INTENT_ACTION_ALL_APPS_TOGGLE;
import static com.android.launcher3.LauncherPrefs.backedUpItem;
import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent;
@@ -39,6 +40,7 @@
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_DOWN;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_MOVE;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_UP;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.RECENTS_ANIMATION_START_PENDING;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
@@ -697,7 +699,7 @@
private void onInputEvent(InputEvent ev) {
if (!(ev instanceof MotionEvent)) {
ActiveGestureLog.INSTANCE.addLog(new CompoundString("TIS.onInputEvent: ")
- .append("Cannot process input event, received unknown event ")
+ .append("Cannot process input event: received unknown event ")
.append(ev.toString()));
return;
}
@@ -710,22 +712,32 @@
if (!isUserUnlocked || (mDeviceState.isButtonNavMode()
&& !isTrackpadMotionEvent(event))) {
ActiveGestureLog.INSTANCE.addLog(new CompoundString("TIS.onInputEvent: ")
- .append("Cannot process input event, ")
+ .append("Cannot process input event: ")
.append(!isUserUnlocked
? "user is locked"
: "using 3-button nav and event is not a trackpad event"));
return;
}
- SafeCloseable traceToken = TraceHelper.INSTANCE.allowIpcs("TIS.onInputEvent");
-
final int action = event.getActionMasked();
// Note this will create a new consumer every mouse click, as after ACTION_UP from the click
// an ACTION_HOVER_ENTER will fire as well.
boolean isHoverActionWithoutConsumer = enableCursorHoverStates()
&& isHoverActionWithoutConsumer(event);
+ if (mTaskAnimationManager.isRecentsAnimationStartPending()
+ && (action == ACTION_DOWN || isHoverActionWithoutConsumer)) {
+ ActiveGestureLog.INSTANCE.addLog(
+ new CompoundString("TIS.onInputEvent: ")
+ .append("Cannot process input event: a recents animation has been ")
+ .append("requested, but hasn't started."),
+ RECENTS_ANIMATION_START_PENDING);
+ return;
+ }
+
+ SafeCloseable traceToken = TraceHelper.INSTANCE.allowIpcs("TIS.onInputEvent");
+
CompoundString reasonString = action == ACTION_DOWN
- ? new CompoundString("onMotionEvent: ") : CompoundString.NO_OP;
+ ? new CompoundString("TIS.onMotionEvent: ") : CompoundString.NO_OP;
if (action == ACTION_DOWN || isHoverActionWithoutConsumer) {
mRotationTouchHelper.setOrientationTransformIfNeeded(event);
@@ -1148,6 +1160,14 @@
boolean launcherResumedThroughShellTransition =
gestureState.getActivityInterface().isResumed()
&& !previousGestureState.isRecentsAnimationRunning();
+ // If a task fragment within Launcher is resumed
+ boolean launcherChildActivityResumed = useActivityOverlay()
+ && runningTask != null
+ && runningTask.isHomeTask()
+ && mOverviewComponentObserver.isHomeAndOverviewSame()
+ && !launcherResumedThroughShellTransition
+ && !previousGestureState.isRecentsAnimationRunning();
+
if (gestureState.getActivityInterface().isInLiveTileMode()) {
return createOverviewInputConsumer(
previousGestureState,
@@ -1174,9 +1194,11 @@
? "launcher resumed through a shell transition"
: "forceOverviewInputConsumer == true"))
.append(", trying to use overview input consumer"));
- } else if (mDeviceState.isGestureBlockedTask(runningTask)) {
+ } else if (mDeviceState.isGestureBlockedTask(runningTask) || launcherChildActivityResumed) {
return getDefaultInputConsumer(reasonString.append(SUBSTRING_PREFIX)
- .append("is gesture-blocked task, trying to use default input consumer"));
+ .append(launcherChildActivityResumed
+ ? "is launcher child-task, trying to use default input consumer"
+ : "is gesture-blocked task, trying to use default input consumer"));
} else {
reasonString.append(SUBSTRING_PREFIX)
.append("using OtherActivityInputConsumer");
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
index 4359294..e3772bd 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
@@ -39,7 +39,7 @@
SET_ON_PAGE_TRANSITION_END_CALLBACK, CANCEL_CURRENT_ANIMATION, CLEANUP_SCREENSHOT,
SCROLLER_ANIMATION_ABORTED, TASK_APPEARED, EXPECTING_TASK_APPEARED,
FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER, LAUNCHER_DESTROYED, RECENT_TASKS_MISSING,
- INVALID_VELOCITY_ON_SWIPE_UP,
+ INVALID_VELOCITY_ON_SWIPE_UP, RECENTS_ANIMATION_START_PENDING,
/**
* These GestureEvents are specifically associated to state flags that get set in
@@ -273,6 +273,14 @@
case START_RECENTS_ANIMATION:
lastStartRecentAnimationEventEntryTime = eventEntry.getTime();
break;
+ case RECENTS_ANIMATION_START_PENDING:
+ errorDetected |= printErrorIfTrue(
+ true,
+ prefix,
+ /* errorMessage= */ "new gesture attempted while a requested recents"
+ + " animation is still pending.",
+ writer);
+ break;
case EXPECTING_TASK_APPEARED:
case MOTION_DOWN:
case SET_END_TARGET:
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 065a9c5..0bb6b23 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -40,6 +40,7 @@
import android.view.RemoteAnimationTarget;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Utilities;
@@ -344,6 +345,14 @@
* Applies the target to the previously set parameters
*/
public void apply(TransformParams params) {
+ apply(params, null);
+ }
+
+ /**
+ * Applies the target to the previously set parameters, optionally with an overridden
+ * surface transaction
+ */
+ public void apply(TransformParams params, @Nullable SurfaceTransaction surfaceTransaction) {
if (mDp == null || mThumbnailPosition.isEmpty()) {
return;
}
@@ -404,7 +413,8 @@
mTempRectF.roundOut(mTmpCropRect);
params.setProgress(1f - fullScreenProgress);
- params.applySurfaceParams(params.createSurfaceParams(this));
+ params.applySurfaceParams(surfaceTransaction == null
+ ? params.createSurfaceParams(this) : surfaceTransaction);
if (!DEBUG) {
return;
diff --git a/quickstep/src/com/android/quickstep/views/IconAppChipView.java b/quickstep/src/com/android/quickstep/views/IconAppChipView.java
index d2b0540..0a261ef 100644
--- a/quickstep/src/com/android/quickstep/views/IconAppChipView.java
+++ b/quickstep/src/com/android/quickstep/views/IconAppChipView.java
@@ -86,6 +86,7 @@
private final int mMinIconBackgroundHeight;
private final int mMaxIconBackgroundCornerRadius;
private final float mMinIconBackgroundCornerRadius;
+ private AnimatorSet mAnimator;
private int mMaxWidth = Integer.MAX_VALUE;
@@ -316,11 +317,13 @@
}
protected void revealAnim(boolean isRevealing) {
+ cancelInProgressAnimations();
+
if (isRevealing) {
boolean isRtl = isLayoutRtl();
bringToFront();
((AnimatedVectorDrawable) mIconArrowView.getDrawable()).start();
- AnimatorSet anim = new AnimatorSet();
+ mAnimator = new AnimatorSet();
float backgroundScaleY = mMaxIconBackgroundHeight / (float) mMinIconBackgroundHeight;
float maxCornerSize = Math.min(mMaxIconBackgroundHeight / 2f,
mMaxIconBackgroundCornerRadius);
@@ -341,7 +344,7 @@
mIconTextMaxWidth + maxCornerSize);
}
});
- anim.playTogether(
+ mAnimator.playTogether(
expandedTextRevealAnim,
ObjectAnimator.ofFloat(mIconViewBackgroundCornersStart, SCALE_Y,
backgroundScaleY),
@@ -367,9 +370,9 @@
ObjectAnimator.ofFloat(mIconTextExpandedView, ALPHA, 1),
ObjectAnimator.ofFloat(mIconArrowView, TRANSLATION_X,
isRtl ? -arrowTranslationX : arrowTranslationX));
- anim.setDuration(MENU_BACKGROUND_REVEAL_DURATION);
- anim.setInterpolator(EMPHASIZED);
- anim.start();
+ mAnimator.setDuration(MENU_BACKGROUND_REVEAL_DURATION);
+ mAnimator.setInterpolator(EMPHASIZED);
+ mAnimator.start();
} else {
((AnimatedVectorDrawable) mIconArrowView.getDrawable()).reverse();
float maxCornerSize = Math.min(mMaxIconBackgroundHeight / 2f,
@@ -386,8 +389,8 @@
mIconTextExpandedView.getHeight() / 2f, 0);
}
});
- AnimatorSet anim = new AnimatorSet();
- anim.playTogether(
+ mAnimator = new AnimatorSet();
+ mAnimator.playTogether(
expandedTextClipAnim,
ObjectAnimator.ofFloat(mIconViewBackgroundCornersStart, SCALE_X, 1),
ObjectAnimator.ofFloat(mIconViewBackgroundCornersStart, SCALE_Y, 1),
@@ -403,9 +406,9 @@
ObjectAnimator.ofFloat(mIconTextCollapsedView, ALPHA, 1),
ObjectAnimator.ofFloat(mIconTextExpandedView, ALPHA, 0),
ObjectAnimator.ofFloat(mIconArrowView, TRANSLATION_X, 0));
- anim.setDuration(MENU_BACKGROUND_HIDE_DURATION);
- anim.setInterpolator(EMPHASIZED);
- anim.start();
+ mAnimator.setDuration(MENU_BACKGROUND_HIDE_DURATION);
+ mAnimator.setInterpolator(EMPHASIZED);
+ mAnimator.start();
}
}
@@ -425,6 +428,16 @@
mIconTextExpandedView.setAlpha(0);
mIconArrowView.setTranslationX(0);
((AnimatedVectorDrawable) mIconArrowView.getDrawable()).reset();
+ mAnimator = null;
+ }
+
+ private void cancelInProgressAnimations() {
+ // We null the `AnimatorSet` because it holds references to the `Animators` which aren't
+ // expecting to be mutable and will cause a crash if they are re-used.
+ if (mAnimator != null && mAnimator.isStarted()) {
+ mAnimator.cancel();
+ mAnimator = null;
+ }
}
@Override
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 66d651e..d10541a 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -542,6 +542,9 @@
private int mOverScrollShift = 0;
private long mScrollLastHapticTimestamp;
+ private int mKeyboardTaskFocusSnapAnimationDuration;
+ private int mKeyboardTaskFocusIndex = INVALID_PAGE;
+
/**
* TODO: Call reloadIdNeeded in onTaskStackChanged.
*/
@@ -1704,6 +1707,7 @@
// Removing views sets the currentPage to 0, so we save this and restore it after
// the new set of views are added
int previousCurrentPage = mCurrentPage;
+ int previousFocusedPage = indexOfChild(getFocusedChild());
removeAllViews();
// If we are entering Overview as a result of initiating a split from somewhere else
@@ -1833,6 +1837,8 @@
targetPage = indexOfChild(currentTaskView);
}
}
+ } else if (previousFocusedPage != INVALID_PAGE) {
+ targetPage = previousFocusedPage;
} else {
// Set the current page to the running task, but not if settling on new task.
if (hasAnyValidTaskIds(runningTaskId)) {
@@ -2914,7 +2920,7 @@
int focusedTaskShift = 0;
int focusedTaskWidthAndSpacing = 0;
int snappedTaskRowWidth = 0;
- int snappedPage = getNextPage();
+ int snappedPage = isKeyboardTaskFocusPending() ? mKeyboardTaskFocusIndex : getNextPage();
TaskView snappedTaskView = getTaskViewAt(snappedPage);
TaskView homeTaskView = getHomeTaskView();
TaskView nextFocusedTaskView = null;
@@ -5577,6 +5583,19 @@
}
/**
+ * Returns how many pixels the running task is offset on the currently laid out dominant axis
+ * specifically during a Keyboard task focus.
+ */
+ public int getScrollOffsetForKeyboardTaskFocus() {
+ if (!isKeyboardTaskFocusPending()) {
+ return getScrollOffset(getRunningTaskIndex());
+ }
+ return getPagedOrientationHandler().getPrimaryScroll(this)
+ - getScrollForPage(mKeyboardTaskFocusIndex)
+ + getScrollOffset(getRunningTaskIndex());
+ }
+
+ /**
* Sets whether or not we should clamp the scroll offset.
* This is used to avoid x-axis movement when swiping up transient taskbar.
* Should only be set at the beginning and end of the gesture, otherwise a jump may occur.
@@ -5609,14 +5628,14 @@
if (pageIndex == -1) {
return 0;
}
-
- int overScrollShift = getOverScrollShift();
- if (mAdjacentPageHorizontalOffset > 0) {
- // Don't dampen the scroll (due to overscroll) if the adjacent tasks are offscreen, so
- // that the page can move freely given there's no visual indication why it shouldn't.
- overScrollShift = (int) Utilities.mapRange(mAdjacentPageHorizontalOffset,
- overScrollShift, getUndampedOverScrollShift());
- }
+ // Don't dampen the scroll (due to overscroll) if the adjacent tasks are offscreen, so that
+ // the page can move freely given there's no visual indication why it shouldn't.
+ int overScrollShift = mAdjacentPageHorizontalOffset > 0
+ ? (int) Utilities.mapRange(
+ mAdjacentPageHorizontalOffset,
+ getOverScrollShift(),
+ getUndampedOverScrollShift())
+ : getOverScrollShift();
return getScrollForPage(pageIndex) - getPagedOrientationHandler().getPrimaryScroll(this)
+ overScrollShift + getOffsetFromScrollPosition(pageIndex);
}
@@ -6025,11 +6044,50 @@
dispatchScrollChanged();
}
+ /**
+ * Prepares this RecentsView to scroll properly for an upcoming child view focus request from
+ * keyboard quick switching
+ */
+ public void setKeyboardTaskFocusIndex(int taskIndex) {
+ mKeyboardTaskFocusIndex = taskIndex;
+ }
+
+ /** Returns whether this RecentsView will be scrolling to a child view for a focus request */
+ public boolean isKeyboardTaskFocusPending() {
+ return mKeyboardTaskFocusIndex != INVALID_PAGE;
+ }
+
+ private boolean isKeyboardTaskFocusPendingForChild(View child) {
+ return isKeyboardTaskFocusPending() && mKeyboardTaskFocusIndex == indexOfChild(child);
+ }
+
@Override
- protected boolean shouldHandleRequestChildFocus() {
- // If we are already scrolling to a task view, then the focus request has already been
- // handled
- return mScroller.isFinished();
+ protected int getSnapAnimationDuration() {
+ return isKeyboardTaskFocusPending()
+ ? mKeyboardTaskFocusSnapAnimationDuration : super.getSnapAnimationDuration();
+ }
+
+ @Override
+ protected void onVelocityValuesUpdated() {
+ super.onVelocityValuesUpdated();
+ mKeyboardTaskFocusSnapAnimationDuration =
+ getResources().getInteger(R.integer.config_keyboardTaskFocusSnapAnimationDuration);
+ }
+
+ @Override
+ protected boolean shouldHandleRequestChildFocus(View child) {
+ // If we are already scrolling to a task view and we aren't focusing to this child from
+ // keyboard quick switch, then the focus request has already been handled
+ return mScroller.isFinished() || isKeyboardTaskFocusPendingForChild(child);
+ }
+
+ @Override
+ public void requestChildFocus(View child, View focused) {
+ if (isKeyboardTaskFocusPendingForChild(child)) {
+ updateGridProperties();
+ updateScrollSynchronously();
+ }
+ super.requestChildFocus(child, focused);
}
private void dispatchScrollChanged() {
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index d779276..cf50835 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -24,6 +24,7 @@
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Outline;
import android.graphics.Rect;
@@ -69,6 +70,8 @@
private TextView mTaskName;
@Nullable
private AnimatorSet mOpenCloseAnimator;
+ @Nullable
+ private ValueAnimator mRevealAnimator;
@Nullable private Runnable mOnClosingStartCallback;
private TaskView mTaskView;
private TaskIdAttributeContainer mTaskContainer;
@@ -290,13 +293,18 @@
private void animateOpenOrClosed(boolean closing) {
if (mOpenCloseAnimator != null && mOpenCloseAnimator.isRunning()) {
- mOpenCloseAnimator.end();
+ mOpenCloseAnimator.cancel();
}
mOpenCloseAnimator = new AnimatorSet();
-
- final Animator revealAnimator = createOpenCloseOutlineProvider()
- .createRevealAnimator(this, closing);
- revealAnimator.setInterpolator(enableOverviewIconMenu() ? Interpolators.EMPHASIZED
+ // If we're opening, we just start from the beginning as a new `TaskMenuView` is created
+ // each time we do the open animation so there will never be a partial value here.
+ float revealAnimationStartProgress = 0f;
+ if (closing && mRevealAnimator != null) {
+ revealAnimationStartProgress = 1f - mRevealAnimator.getAnimatedFraction();
+ }
+ mRevealAnimator = createOpenCloseOutlineProvider()
+ .createRevealAnimator(this, closing, revealAnimationStartProgress);
+ mRevealAnimator.setInterpolator(enableOverviewIconMenu() ? Interpolators.EMPHASIZED
: Interpolators.DECELERATE);
if (enableOverviewIconMenu()) {
@@ -349,7 +357,7 @@
mOpenCloseAnimator.playTogether(translationXAnim, menuTranslationXAnim);
}
- mOpenCloseAnimator.playTogether(revealAnimator,
+ mOpenCloseAnimator.playTogether(mRevealAnimator,
ObjectAnimator.ofFloat(
mTaskContainer.getThumbnailView(), DIM_ALPHA,
closing ? 0 : TaskView.MAX_PAGE_SCRIM_ALPHA),
@@ -378,6 +386,7 @@
mIsOpen = false;
resetOverviewIconMenu();
mActivity.getDragLayer().removeView(this);
+ mRevealAnimator = null;
}
private void resetOverviewIconMenu() {
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 11e721e..b971f67 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -1137,9 +1137,8 @@
DeviceProfile dp = mActivity.getDeviceProfile();
if (enableOverviewIconMenu() && iconView instanceof IconAppChipView) {
((IconAppChipView) iconView).revealAnim(/* isRevealing= */ true);
- return TaskMenuView.showForTask(menuContainer, () -> {
- ((IconAppChipView) iconView).revealAnim(/* isRevealing= */ false);
- });
+ return TaskMenuView.showForTask(menuContainer,
+ () -> ((IconAppChipView) iconView).revealAnim(/* isRevealing= */ false));
} else if (dp.isTablet) {
int alignedOptionIndex = 0;
if (getRecentsView().isOnGridBottomRow(menuContainer.getTaskView()) && dp.isLandscape) {
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java b/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java
index 7191f70..36c591e 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java
@@ -22,6 +22,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.tapl.KeyboardQuickSwitch;
+import com.android.launcher3.taskbar.KeyboardQuickSwitchController;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import org.junit.Assume;
@@ -49,7 +50,7 @@
DISMISS(0),
LAUNCH_LAST_APP(0),
LAUNCH_SELECTED_APP(1),
- LAUNCH_OVERVIEW(5);
+ LAUNCH_OVERVIEW(KeyboardQuickSwitchController.MAX_TASKS - 1);
private final int mNumAdditionalRunningTasks;
@@ -196,7 +197,9 @@
if (!testSurface.mInitialFocusAtZero) {
kqs.moveFocusBackward();
}
- kqs.launchFocusedOverviewTask();
+ kqs.launchFocusedOverviewTask()
+ // Check that the correct task was focused
+ .launchFocusedTaskByEnterKey(CALCULATOR_APP_PACKAGE);
break;
default:
throw new IllegalStateException("Cannot run test case: " + testCase);
diff --git a/res/values-sw600dp/config.xml b/res/values-sw600dp/config.xml
index e718d9c..bddfcfc 100644
--- a/res/values-sw600dp/config.xml
+++ b/res/values-sw600dp/config.xml
@@ -18,6 +18,9 @@
<!-- The duration of the PagedView page snap animation -->
<integer name="config_pageSnapAnimationDuration">550</integer>
+ <!-- The duration of the PagedView page snap animation -->
+ <integer name="config_keyboardTaskFocusSnapAnimationDuration">400</integer>
+
<!-- The duration of the Widget picker opening and closing animation -->
<integer name="config_bottomSheetOpenDuration">500</integer>
<integer name="config_bottomSheetCloseDuration">500</integer>
diff --git a/res/values/config.xml b/res/values/config.xml
index 29c4e66..1b74238 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -43,6 +43,9 @@
<!-- The duration of the PagedView page snap animation -->
<integer name="config_pageSnapAnimationDuration">750</integer>
+ <!-- The duration of the PagedView page snap animation -->
+ <integer name="config_keyboardTaskFocusSnapAnimationDuration">750</integer>
+
<!-- View tag key used to store SpringAnimation data. -->
<item type="id" name="spring_animation_tag" />
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 3016559..3aa4a77 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -56,7 +56,7 @@
<!-- App Widget resize frame -->
<dimen name="widget_handle_margin">13dp</dimen>
<dimen name="resize_frame_background_padding">24dp</dimen>
- <dimen name="resize_frame_margin">22dp</dimen>
+ <dimen name="resize_frame_margin">23dp</dimen>
<dimen name="resize_frame_invalid_drag_across_two_panel_opacity_margin">24dp</dimen>
<!-- App widget reconfigure button -->
@@ -196,6 +196,8 @@
<dimen name="widget_list_header_view_vertical_padding">20dp</dimen>
<dimen name="widget_list_entry_spacing">2dp</dimen>
<dimen name="widget_list_horizontal_margin">16dp</dimen>
+ <!-- Margin applied to the recycler view with search bar & the list of widget apps below it. -->
+ <dimen name="widget_list_left_pane_horizontal_margin">0dp</dimen>
<dimen name="widget_list_horizontal_margin_two_pane">24dp</dimen>
<dimen name="widget_preview_shadow_blur">0.5dp</dimen>
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 496cb4e..798b238 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -411,6 +411,8 @@
private final SettingsCache.OnChangeListener mNaturalScrollingChangedListener =
enabled -> mIsNaturalScrollingEnabled = enabled;
+ private int mActivityStopCount; // Used only by tests
+
public static Launcher getLauncher(Context context) {
return fromContext(context);
}
@@ -1052,10 +1054,18 @@
mAppWidgetHolder.setActivityStarted(false);
NotificationListener.removeNotificationsChangedListener(getPopupDataProvider());
FloatingIconView.resetIconLoadResult();
+ ++mActivityStopCount;
AccessibilityManagerCompat.sendTestProtocolEventToTest(
this, LAUNCHER_ACTIVITY_STOPPED_MESSAGE);
}
+ /** Return activity stop count and reset it. Used only by tests. */
+ public int getAndResetActivityStopCount() {
+ final int activityStopCount = mActivityStopCount;
+ mActivityStopCount = 0;
+ return activityStopCount;
+ }
+
@Override
protected void onStart() {
TraceHelper.INSTANCE.beginSection(ON_START_EVT);
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 43dca5c..ca83245 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -637,6 +637,11 @@
mMinFlingVelocity = res.getDimensionPixelSize(R.dimen.min_fling_velocity);
mMinSnapVelocity = res.getDimensionPixelSize(R.dimen.min_page_snap_velocity);
mPageSnapAnimationDuration = res.getInteger(R.integer.config_pageSnapAnimationDuration);
+ onVelocityValuesUpdated();
+ }
+
+ protected void onVelocityValuesUpdated() {
+ // Overridden in RecentsView
}
@Override
@@ -1582,7 +1587,7 @@
@Override
public void requestChildFocus(View child, View focused) {
super.requestChildFocus(child, focused);
- if (!shouldHandleRequestChildFocus()) {
+ if (!shouldHandleRequestChildFocus(child)) {
return;
}
// In case the device is controlled by a controller, mCurrentPage isn't updated properly
@@ -1598,7 +1603,7 @@
}
}
- protected boolean shouldHandleRequestChildFocus() {
+ protected boolean shouldHandleRequestChildFocus(View child) {
return true;
}
@@ -1652,7 +1657,7 @@
}
protected void snapToDestination() {
- snapToPage(getDestinationPage(), mPageSnapAnimationDuration);
+ snapToPage(getDestinationPage(), getSnapAnimationDuration());
}
// We want the duration of the page snap animation to be influenced by the distance that
@@ -1676,7 +1681,7 @@
if (Math.abs(velocity) < mMinFlingVelocity) {
// If the velocity is low enough, then treat this more as an automatic page advance
// as opposed to an apparent physical response to flinging
- return snapToPage(whichPage, mPageSnapAnimationDuration);
+ return snapToPage(whichPage, getSnapAnimationDuration());
}
// Here we compute a "distance" that will be used in the computation of the overall
@@ -1698,12 +1703,16 @@
return snapToPage(whichPage, delta, duration);
}
+ protected int getSnapAnimationDuration() {
+ return mPageSnapAnimationDuration;
+ }
+
public boolean snapToPage(int whichPage) {
- return snapToPage(whichPage, mPageSnapAnimationDuration);
+ return snapToPage(whichPage, getSnapAnimationDuration());
}
public boolean snapToPageImmediately(int whichPage) {
- return snapToPage(whichPage, mPageSnapAnimationDuration, true);
+ return snapToPage(whichPage, getSnapAnimationDuration(), true);
}
public boolean snapToPage(int whichPage, int duration) {
diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java
index 557ec48..6422943 100644
--- a/src/com/android/launcher3/allapps/PrivateProfileManager.java
+++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java
@@ -22,6 +22,7 @@
import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_SYS_APPS_DIVIDER;
import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_NOTHING;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_PRIVATE_SPACE_INSTALL_APP;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
@@ -109,7 +110,7 @@
itemInfo.bitmap = bitmapInfo;
itemInfo.contentDescription = context.getResources().getString(
com.android.launcher3.R.string.ps_add_button_content_description);
- itemInfo.runtimeStatusFlags |= FLAG_PRIVATE_SPACE_INSTALL_APP;
+ itemInfo.runtimeStatusFlags |= FLAG_PRIVATE_SPACE_INSTALL_APP | FLAG_NOT_PINNABLE;
BaseAllAppsAdapter.AdapterItem item = new BaseAllAppsAdapter.AdapterItem(VIEW_TYPE_ICON);
item.itemInfo = itemInfo;
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index 4427a49..f9d047b 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -20,6 +20,7 @@
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.style.SuggestionSpan;
+import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnFocusChangeListener;
@@ -42,6 +43,7 @@
implements TextWatcher, OnEditorActionListener, ExtendedEditText.OnBackKeyListener,
OnFocusChangeListener {
+ private static final String TAG = "AllAppsSearchBarController";
protected ActivityContext mLauncher;
protected SearchCallback<AdapterItem> mCallback;
protected ExtendedEditText mInput;
@@ -122,6 +124,7 @@
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_GO) {
+ Log.i(TAG, "User tapped ime search button");
// selectFocusedView should return SearchTargetEvent that is passed onto onClick
return mLauncher.getAppsView().getMainAdapterProvider().launchHighlightedItem();
}
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 3ccde0a..b6e5977 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -16,6 +16,8 @@
package com.android.launcher3.dragndrop;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE;
+
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -29,8 +31,10 @@
import com.android.app.animation.Interpolators;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
+import com.android.launcher3.Flags;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.ActivityContext;
@@ -221,6 +225,12 @@
}
}
+ protected boolean isItemPinnable() {
+ return !Flags.privateSpaceRestrictItemDrag()
+ || !(mDragObject.dragInfo instanceof ItemInfoWithIcon itemInfoWithIcon)
+ || (itemInfoWithIcon.runtimeStatusFlags & FLAG_NOT_PINNABLE) == 0;
+ }
+
public Optional<InstanceId> getLogInstanceId() {
return Optional.ofNullable(mDragObject)
.map(dragObject -> dragObject.logInstanceId);
diff --git a/src/com/android/launcher3/dragndrop/LauncherDragController.java b/src/com/android/launcher3/dragndrop/LauncherDragController.java
index da6f446..f3708a2 100644
--- a/src/com/android/launcher3/dragndrop/LauncherDragController.java
+++ b/src/com/android/launcher3/dragndrop/LauncherDragController.java
@@ -149,9 +149,10 @@
handleMoveEvent(mLastTouch.x, mLastTouch.y);
- if (!mActivity.isTouchInProgress() && options.simulatedDndStartPoint == null) {
+ if (!isItemPinnable()
+ || (!mActivity.isTouchInProgress() && options.simulatedDndStartPoint == null)) {
// If it is an internal drag and the touch is already complete, cancel immediately
- MAIN_EXECUTOR.submit(this::cancelDrag);
+ MAIN_EXECUTOR.post(this::cancelDrag);
}
return dragView;
}
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 7ae70e0..2f3f029 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -800,6 +800,14 @@
return;
}
+ int size = getIconsInReadingOrder().size();
+ if (size <= 1) {
+ Log.d(TAG, "Couldn't animate folder closed because there's " + size + " icons");
+ closeComplete(false);
+ post(this::announceAccessibilityChanges);
+ return;
+ }
+
mContent.completePendingPageChanges();
mContent.snapToPageImmediately(mContent.getDestinationPage());
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 5e86bd6..96a8da9 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -33,6 +33,7 @@
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemFactory;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -102,6 +103,11 @@
Objects.requireNonNull(item.getIntent()))) {
continue;
}
+
+ if (item instanceof ItemInfoWithIcon
+ && ((ItemInfoWithIcon) item).isArchived()) {
+ continue;
+ }
}
if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
diff --git a/src/com/android/launcher3/model/ItemInstallQueue.java b/src/com/android/launcher3/model/ItemInstallQueue.java
index 9a3abd4..59f453a 100644
--- a/src/com/android/launcher3/model/ItemInstallQueue.java
+++ b/src/com/android/launcher3/model/ItemInstallQueue.java
@@ -18,10 +18,12 @@
import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
+import static com.android.launcher3.Flags.enableSupportForArchiving;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
import static com.android.launcher3.model.data.AppInfo.makeLaunchIntent;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.appwidget.AppWidgetManager;
@@ -276,6 +278,7 @@
return intent;
}
+ @SuppressWarnings("NewApi")
public Pair<ItemInfo, Object> getItemInfo(Context context) {
switch (itemType) {
case ITEM_TYPE_APPLICATION: {
@@ -297,6 +300,9 @@
} else {
lai = laiList.get(0);
si.intent = makeLaunchIntent(lai);
+ if (enableSupportForArchiving() && lai.getActivityInfo().isArchived) {
+ si.runtimeStatusFlags |= FLAG_ARCHIVED;
+ }
}
LauncherAppState.getInstance(context).getIconCache()
.getTitleAndIcon(si, () -> lai, usePackageIcon, false);
diff --git a/src/com/android/launcher3/pm/UserCache.java b/src/com/android/launcher3/pm/UserCache.java
index 8708d5a..032de31 100644
--- a/src/com/android/launcher3/pm/UserCache.java
+++ b/src/com/android/launcher3/pm/UserCache.java
@@ -28,9 +28,13 @@
import androidx.annotation.AnyThread;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.UserBadgeDrawable;
+import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.SimpleBroadcastReceiver;
@@ -166,4 +170,14 @@
public List<UserHandle> getUserProfiles() {
return List.copyOf(mUserToSerialMap.keySet());
}
+
+ /**
+ * Get a non-themed {@link UserBadgeDrawable} based on the provided {@link UserHandle}.
+ */
+ @Nullable
+ public static UserBadgeDrawable getBadgeDrawable(Context context, UserHandle userHandle) {
+ return (UserBadgeDrawable) BitmapInfo.LOW_RES_INFO.withFlags(UserCache.getInstance(context)
+ .getUserInfo(userHandle).applyBitmapInfoFlags(FlagOp.NO_OP))
+ .getBadgeDrawable(context, false /* isThemed */);
+ }
}
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index c9c5fd3..1c9db17 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -19,6 +19,7 @@
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS;
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.Utilities.squaredTouchSlop;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE;
import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -44,6 +45,7 @@
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
import com.android.launcher3.DropTarget.DragObject;
+import com.android.launcher3.Flags;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
@@ -53,6 +55,7 @@
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
@@ -204,17 +207,21 @@
.collect(Collectors.toList());
container = (PopupContainerWithArrow) launcher.getLayoutInflater().inflate(
R.layout.popup_container, launcher.getDragLayer(), false);
- container.configureForLauncher(launcher);
+ container.configureForLauncher(launcher, item);
container.populateAndShowRows(icon, deepShortcutCount, systemShortcuts);
launcher.refreshAndBindWidgetsForPackageUser(PackageUserKey.fromItemInfo(item));
container.requestFocus();
return container;
}
- private void configureForLauncher(Launcher launcher) {
+ private void configureForLauncher(Launcher launcher, ItemInfo itemInfo) {
addOnAttachStateChangeListener(new LauncherPopupLiveUpdateHandler(
launcher, (PopupContainerWithArrow<Launcher>) this));
- mPopupItemDragHandler = new LauncherPopupItemDragHandler(launcher, this);
+ if (!Flags.privateSpaceRestrictItemDrag()
+ || !(itemInfo instanceof ItemInfoWithIcon itemInfoWithIcon)
+ || (itemInfoWithIcon.runtimeStatusFlags & FLAG_NOT_PINNABLE) == 0) {
+ mPopupItemDragHandler = new LauncherPopupItemDragHandler(launcher, this);
+ }
mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(launcher);
launcher.getDragController().addDragListener(this);
}
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDragController.java b/src/com/android/launcher3/secondarydisplay/SecondaryDragController.java
index 8d1d96b..79b25a4 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDragController.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDragController.java
@@ -16,6 +16,8 @@
package com.android.launcher3.secondarydisplay;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
import android.content.res.Resources;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -129,6 +131,10 @@
dragView.show(mLastTouch.x, mLastTouch.y);
mDistanceSinceScroll = 0;
+ if (!isItemPinnable()) {
+ MAIN_EXECUTOR.post(this:: cancelDrag);
+ }
+
if (!mIsInPreDrag) {
callOnDragStart();
} else if (mOptions.preDragCondition != null) {
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 315b5e3..27a153b 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -150,6 +150,12 @@
}, this::getCurrentActivity);
}
+ case TestProtocol.REQUEST_CELL_LAYOUT_BOARDER_HEIGHT: {
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+ mDeviceProfile.cellLayoutBorderSpacePx.y);
+ return response;
+ }
+
case TestProtocol.REQUEST_SYSTEM_GESTURE_REGION: {
return getUIProperty(Bundle::putParcelable, activity -> {
WindowInsetsCompat insets = WindowInsetsCompat.toWindowInsetsCompat(
@@ -208,6 +214,16 @@
.forceAllowRotationForTesting(Boolean.parseBoolean(arg)));
return response;
+ case TestProtocol.REQUEST_GET_AND_RESET_ACTIVITY_STOP_COUNT: {
+ final Bundle bundle = getLauncherUIProperty(Bundle::putInt,
+ launcher -> launcher.getAndResetActivityStopCount());
+ if (bundle != null) return bundle;
+
+ // If Launcher activity wasn't created, 'it' was stopped 0 times.
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, 0);
+ return response;
+ }
+
case TestProtocol.REQUEST_WORKSPACE_CELL_LAYOUT_SIZE:
return getLauncherUIProperty(Bundle::putIntArray, launcher -> {
final Workspace<?> workspace = launcher.getWorkspace();
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 2b5aaf5..50d8886 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -16,6 +16,8 @@
package com.android.launcher3.util;
+import static com.android.launcher3.Flags.enableSupportForArchiving;
+
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
@@ -112,8 +114,7 @@
@NonNull final UserHandle user, final int flags) {
try {
ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, flags, user);
- return (info.flags & ApplicationInfo.FLAG_INSTALLED) == 0 || !info.enabled
- ? null : info;
+ return !isPackageInstalledOrArchived(info) || !info.enabled ? null : info;
} catch (PackageManager.NameNotFoundException e) {
return null;
}
@@ -253,4 +254,11 @@
}
return 100;
}
+
+ /** Returns true in case app is installed on the device or in archived state. */
+ @SuppressWarnings("NewApi")
+ private boolean isPackageInstalledOrArchived(ApplicationInfo info) {
+ return (info.flags & ApplicationInfo.FLAG_INSTALLED) != 0 || (
+ enableSupportForArchiving() && info.isArchived);
+ }
}
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index c5317e3..230a651 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -155,6 +155,19 @@
}
/**
+ * Handle user tapping on unsupported target when in split selection mode.
+ * See {@link #isSplitSelectionActive()}
+ *
+ * @return {@code true} if this method will handle the incorrect target selection,
+ * {@code false} if it could not be handled or if not possible to handle based on
+ * current split state
+ */
+ default boolean handleIncorrectSplitTargetSelection() {
+ // Overridden
+ return false;
+ }
+
+ /**
* The root view to support drag-and-drop and popup support.
*/
BaseDragLayer getDragLayer();
diff --git a/src/com/android/launcher3/views/FloatingSurfaceView.java b/src/com/android/launcher3/views/FloatingSurfaceView.java
index 76b6fde..c60e1a4 100644
--- a/src/com/android/launcher3/views/FloatingSurfaceView.java
+++ b/src/com/android/launcher3/views/FloatingSurfaceView.java
@@ -175,7 +175,6 @@
if (!mTmpPosition.equals(mIconPosition)) {
mIconPosition.set(mTmpPosition);
- sendIconInfo();
LayoutParams lp = (LayoutParams) mSurfaceView.getLayoutParams();
lp.width = Math.round(mIconPosition.width());
@@ -184,6 +183,9 @@
lp.topMargin = Math.round(mIconPosition.top);
}
}
+
+ sendIconInfo();
+
if (mIcon != null && iconChanged && !mIconBounds.isEmpty()) {
// Record the icon display
setCurrentIconVisible(true);
@@ -197,7 +199,7 @@
}
private void sendIconInfo() {
- if (mContract != null && !mIconPosition.isEmpty()) {
+ if (mContract != null) {
mContract.sendEndPosition(mIconPosition, mLauncher, mSurfaceView.getSurfaceControl());
}
}
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 9de7f62..145ad80 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -72,14 +72,21 @@
public BaseWidgetSheet(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- mContentHorizontalMargin = getResources().getDimensionPixelSize(
- R.dimen.widget_list_horizontal_margin);
+ mContentHorizontalMargin = getWidgetListHorizontalMargin();
mWidgetCellHorizontalPadding = getResources().getDimensionPixelSize(
R.dimen.widget_cell_horizontal_padding);
mNavBarScrimPaint = new Paint();
mNavBarScrimPaint.setColor(Themes.getNavBarScrimColor(mActivityContext));
}
+ /**
+ * Returns the margins to be applied to the left and right of the widget apps list.
+ */
+ protected int getWidgetListHorizontalMargin() {
+ return getResources().getDimensionPixelSize(
+ R.dimen.widget_list_horizontal_margin);
+ }
+
protected int getScrimColor(Context context) {
return context.getResources().getColor(R.color.widgets_picker_scrim);
}
@@ -142,8 +149,7 @@
@Override
public void setInsets(Rect insets) {
mInsets.set(insets);
- @Px int contentHorizontalMargin = getResources().getDimensionPixelSize(
- R.dimen.widget_list_horizontal_margin);
+ @Px int contentHorizontalMargin = getWidgetListHorizontalMargin();
if (contentHorizontalMargin != mContentHorizontalMargin) {
onContentHorizontalMarginChanged(contentHorizontalMargin);
mContentHorizontalMargin = contentHorizontalMargin;
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index e9a590b..17d9276 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -182,7 +182,8 @@
.stream()
.anyMatch(user -> mUserCache.getUserInfo(user).isWork());
mWorkWidgetsFilter = entry -> mHasWorkProfile
- && mUserCache.getUserInfo(entry.mPkgItem.user).isWork();
+ && mUserCache.getUserInfo(entry.mPkgItem.user).isWork()
+ && !mUserManagerState.isUserQuiet(entry.mPkgItem.user);
mAdapters.put(AdapterHolder.PRIMARY, new AdapterHolder(AdapterHolder.PRIMARY));
mAdapters.put(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK));
mAdapters.put(AdapterHolder.SEARCH, new AdapterHolder(AdapterHolder.SEARCH));
diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
index 6656237..26c04f5 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
@@ -303,6 +303,12 @@
}
@Override
+ protected int getWidgetListHorizontalMargin() {
+ return getResources().getDimensionPixelSize(
+ R.dimen.widget_list_left_pane_horizontal_margin);
+ }
+
+ @Override
protected boolean isTwoPane() {
return true;
}
diff --git a/tests/Android.bp b/tests/Android.bp
index 310e418..ed8609e 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -176,6 +176,7 @@
name: "launcher-testing-shared",
srcs: [
"multivalentTests/shared/com/android/launcher3/testing/shared/**/*.java",
+ "multivalentTests/shared/com/android/launcher3/testing/shared/**/*.kt"
],
resource_dirs: [],
manifest: "multivalentTests/shared/AndroidManifest.xml",
diff --git a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index 3e188e6..187daca 100644
--- a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -121,6 +121,7 @@
public static final String REQUEST_IS_TABLET = "is-tablet";
public static final String REQUEST_NUM_ALL_APPS_COLUMNS = "num-all-apps-columns";
public static final String REQUEST_IS_TWO_PANELS = "is-two-panel";
+ public static final String REQUEST_CELL_LAYOUT_BOARDER_HEIGHT = "cell-layout-boarder-height";
public static final String REQUEST_START_DRAG_THRESHOLD = "start-drag-threshold";
public static final String REQUEST_SHELL_DRAG_READY = "shell-drag-ready";
public static final String REQUEST_GET_ACTIVITIES_CREATED_COUNT =
@@ -177,6 +178,9 @@
public static final String REQUEST_UNSTASH_BUBBLE_BAR_IF_STASHED =
"unstash-bubble-bar-if-stashed";
+ public static final String REQUEST_GET_AND_RESET_ACTIVITY_STOP_COUNT =
+ "get-and-reset-activity-stops";
+
/** Logs {@link Log#d(String, String)} if {@link #sDebugTracing} is true. */
public static void testLogD(String tag, String message) {
if (!sDebugTracing) {
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java
index dbbdcf5..62f2259 100644
--- a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java
@@ -66,7 +66,7 @@
}
public CellLayoutBoard(int width, int height) {
- mWidget = new char[width + 1][height + 1];
+ mWidget = new char[width][height];
this.mWidth = width;
this.mHeight = height;
for (int x = 0; x < mWidget.length; x++) {
@@ -371,8 +371,8 @@
s.append("\n");
maxX = Math.min(maxX, mWidget.length);
maxY = Math.min(maxY, mWidget[0].length);
- for (int y = 0; y <= maxY; y++) {
- for (int x = 0; x <= maxX; x++) {
+ for (int y = 0; y < maxY; y++) {
+ for (int x = 0; x < maxX; x++) {
s.append(mWidget[x][y]);
}
s.append('\n');
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/RandomBoardGenerator.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/RandomBoardGenerator.kt
index 770024f..fcfb3db 100644
--- a/tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/RandomBoardGenerator.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/RandomBoardGenerator.kt
@@ -27,7 +27,7 @@
* usually less than 100.
* @return a randomly generated board filled with icons and widgets.
*/
- open fun generateBoard(width: Int, height: Int, remainingEmptySpaces: Int): CellLayoutBoard? {
+ open fun generateBoard(width: Int, height: Int, remainingEmptySpaces: Int): CellLayoutBoard {
val cellLayoutBoard = CellLayoutBoard(width, height)
return fillBoard(cellLayoutBoard, Rect(0, 0, width, height), remainingEmptySpaces)
}
@@ -39,8 +39,8 @@
): CellLayoutBoard {
var remainingEmptySpaces = remainingEmptySpacesArg
if (area.height() * area.width() <= 0) return board
- val width = getRandom(1, area.width() - 1)
- val height = getRandom(1, area.height() - 1)
+ val width = getRandom(1, area.width())
+ val height = getRandom(1, area.height())
val x = area.left + getRandom(0, area.width() - width)
val y = area.top + getRandom(0, area.height() - height)
if (remainingEmptySpaces > 0) {
diff --git a/tests/multivalentTests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/multivalentTests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index f0d2e20..dcf56f8 100644
--- a/tests/multivalentTests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -599,6 +599,9 @@
Wait.atMost("Launcher activity didn't stop",
() -> !launcherInstrumentation.isLauncherActivityStarted(),
DEFAULT_ACTIVITY_TIMEOUT, launcherInstrumentation);
+
+ // Reset activity stop count.
+ launcherInstrumentation.getAndResetActivityStopCount();
}
public static ActivityInfo resolveSystemAppInfo(String category) {
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/rule/TestIsolationRule.java b/tests/multivalentTests/src/com/android/launcher3/util/rule/TestIsolationRule.java
index 2b45902..e98dcf4 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/rule/TestIsolationRule.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/rule/TestIsolationRule.java
@@ -44,11 +44,15 @@
return new Statement() {
@Override
public void evaluate() throws Throwable {
- base.evaluate();
- // Make sure that Launcher workspace looks correct.
+ // Reset activity stop count.
+ mLauncher.getAndResetActivityStopCount();
+ base.evaluate();
+
+ // Make sure that Launcher workspace looks correct.
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).pressHome();
AbstractLauncherUiTest.checkDetectedLeaks(mLauncher, mRequireOneActiveActivity);
+ mLauncher.assertNoUnexpectedStops();
}
};
}
diff --git a/tests/multivalentTests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/multivalentTests/tapl/com/android/launcher3/tapl/BaseOverview.java
index 1bc489c..d337b91 100644
--- a/tests/multivalentTests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/multivalentTests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -19,9 +19,11 @@
import static android.view.KeyEvent.KEYCODE_ESCAPE;
import static com.android.launcher3.tapl.LauncherInstrumentation.TASKBAR_RES_ID;
+import static com.android.launcher3.tapl.OverviewTask.TASK_START_EVENT;
import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
import android.graphics.Rect;
+import android.view.KeyEvent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -47,6 +49,10 @@
"Key event: KeyEvent.*?action=ACTION_DOWN.*?keyCode=KEYCODE_ESCAPE.*?metaState=0");
private static final Pattern EVENT_ALT_ESC_UP = Pattern.compile(
"Key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_ESCAPE.*?metaState=0");
+ private static final Pattern EVENT_ENTER_DOWN = Pattern.compile(
+ "Key event: KeyEvent.*?action=ACTION_DOWN.*?keyCode=KEYCODE_ENTER");
+ private static final Pattern EVENT_ENTER_UP = Pattern.compile(
+ "Key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_ENTER");
private static final int FLINGS_FOR_DISMISS_LIMIT = 40;
@@ -414,6 +420,31 @@
}
}
+ /**
+ * Presses the enter key to launch the focused task
+ * <p>
+ * If no task is focused, this will fail.
+ */
+ public LaunchedAppState launchFocusedTaskByEnterKey(@NonNull String expectedPackageName) {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ENTER_DOWN);
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ENTER_UP);
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
+
+ mLauncher.executeAndWaitForLauncherStop(
+ () -> mLauncher.assertTrue(
+ "Failed to press enter",
+ mLauncher.getDevice().pressKeyCode(KeyEvent.KEYCODE_ENTER)),
+ "pressing enter");
+ mLauncher.assertAppLaunched(expectedPackageName);
+
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "pressed enter")) {
+ return new LaunchedAppState(mLauncher);
+ }
+ }
+ }
+
private void verifyActionsViewVisibility() {
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"want to assert overview actions view visibility")) {
diff --git a/tests/multivalentTests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/multivalentTests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index fef93b7..90ca990 100644
--- a/tests/multivalentTests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/multivalentTests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -207,6 +207,7 @@
private TrackpadGestureType mTrackpadGestureType = TrackpadGestureType.NONE;
private int mPointerCount = 0;
+ private final boolean mIsLauncherTest;
private static Pattern getKeyEventPattern(String action, String keyCode) {
return Pattern.compile("Key event: KeyEvent.*action=" + action + ".*keyCode=" + keyCode);
@@ -243,6 +244,7 @@
*/
@Deprecated
public LauncherInstrumentation(Instrumentation instrumentation, boolean isLauncherTest) {
+ mIsLauncherTest = isLauncherTest;
mInstrumentation = instrumentation;
mDevice = UiDevice.getInstance(instrumentation);
@@ -405,11 +407,21 @@
.getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
+ int getCellLayoutBoarderHeight() {
+ return getTestInfo(TestProtocol.REQUEST_CELL_LAYOUT_BOARDER_HEIGHT)
+ .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+ }
+
int getFocusedTaskHeightForTablet() {
return getTestInfo(TestProtocol.REQUEST_GET_FOCUSED_TASK_HEIGHT_FOR_TABLET).getInt(
TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
+ public int getAndResetActivityStopCount() {
+ return getTestInfo(TestProtocol.REQUEST_GET_AND_RESET_ACTIVITY_STOP_COUNT).getInt(
+ TestProtocol.TEST_INFO_RESPONSE_FIELD);
+ }
+
Rect getGridTaskRectForTablet() {
return ((Rect) getTestInfo(TestProtocol.REQUEST_GET_GRID_TASK_SIZE_RECT_FOR_TABLET)
.getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD));
@@ -623,10 +635,19 @@
public void onTestStart() {
mTestStartTime = System.currentTimeMillis();
+ assertNoUnexpectedStops();
}
public void onTestFinish() {
mTestStartTime = -1;
+ assertNoUnexpectedStops();
+ }
+
+ /** Verify that the activity stop count is zero. */
+ public void assertNoUnexpectedStops() {
+ if (mIsLauncherTest) {
+ assertEquals("Unexpected activity stops", 0, getAndResetActivityStopCount());
+ }
}
private String formatSystemHealthMessage(String message) {
@@ -1000,6 +1021,9 @@
event -> TestProtocol.LAUNCHER_ACTIVITY_STOPPED_MESSAGE
.equals(event.getClassName().toString()),
() -> "Launcher activity didn't stop", actionName);
+
+ // Reset activity stop count.
+ getAndResetActivityStopCount();
}
/**
@@ -2252,6 +2276,7 @@
}
public Closable eventsCheck() {
+ assertNoUnexpectedStops();
Assert.assertTrue("Nested event checking", mEventChecker == null);
disableSensorRotation();
final Integer initialPid = getPid();
@@ -2259,6 +2284,7 @@
if (eventChecker.start()) mEventChecker = eventChecker;
return () -> {
+ assertNoUnexpectedStops();
if (initialPid != null && initialPid.intValue() != getPid()) {
if (mOnLauncherCrashed != null) mOnLauncherCrashed.run();
checkForAnomaly();
diff --git a/tests/multivalentTests/tapl/com/android/launcher3/tapl/WidgetResizeFrame.java b/tests/multivalentTests/tapl/com/android/launcher3/tapl/WidgetResizeFrame.java
index d0573e0..3895302 100644
--- a/tests/multivalentTests/tapl/com/android/launcher3/tapl/WidgetResizeFrame.java
+++ b/tests/multivalentTests/tapl/com/android/launcher3/tapl/WidgetResizeFrame.java
@@ -57,7 +57,8 @@
Rect originalWidgetSize = widget.getVisibleBounds();
Point targetStart = bottomResizeHandle.getVisibleCenter();
Point targetDest = bottomResizeHandle.getVisibleCenter();
- targetDest.offset(0, originalWidgetSize.height());
+ targetDest.offset(0,
+ originalWidgetSize.height() + mLauncher.getCellLayoutBoarderHeight());
final long downTime = SystemClock.uptimeMillis();
mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetStart,
diff --git a/tests/src/com/android/launcher3/allapps/TaplAllAppsIconsWorkingTest.java b/tests/src/com/android/launcher3/allapps/TaplAllAppsIconsWorkingTest.java
index 27a2c75..ba74244 100644
--- a/tests/src/com/android/launcher3/allapps/TaplAllAppsIconsWorkingTest.java
+++ b/tests/src/com/android/launcher3/allapps/TaplAllAppsIconsWorkingTest.java
@@ -62,5 +62,6 @@
} finally {
allApps.unfreeze();
}
+ mLauncher.goHome();
}
}
diff --git a/tests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt b/tests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt
new file mode 100644
index 0000000..13dfd5e
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt
@@ -0,0 +1,179 @@
+/*
+ * 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.util.Log
+import android.view.View
+import androidx.core.view.get
+import androidx.test.core.app.ApplicationProvider
+import com.android.launcher3.CellLayout
+import com.android.launcher3.celllayout.board.CellLayoutBoard
+import com.android.launcher3.celllayout.board.IconPoint
+import com.android.launcher3.celllayout.board.PermutedBoardComparator
+import com.android.launcher3.celllayout.board.WidgetRect
+import com.android.launcher3.celllayout.testgenerator.RandomBoardGenerator
+import com.android.launcher3.util.ActivityContextWrapper
+import com.android.launcher3.views.DoubleShadowBubbleTextView
+import java.util.Random
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+
+private class HotseatReorderTestCase(
+ val startBoard: CellLayoutBoard,
+ val endBoard: CellLayoutBoard
+) {
+ override fun toString(): String {
+ return "$startBoard#endBoard:\n$endBoard"
+ }
+}
+
+class HotseatReorderUnitTest {
+
+ private val applicationContext: Context =
+ ActivityContextWrapper(ApplicationProvider.getApplicationContext())
+
+ @JvmField @Rule var cellLayoutBuilder = UnitTestCellLayoutBuilderRule()
+
+ /**
+ * This test generates random CellLayout configurations and then try to reorder it and makes
+ * sure the result is a valid board meaning it didn't remove any widget or icon.
+ */
+ @Test
+ fun generateValidTests() {
+ val generator = Random(Companion.SEED.toLong())
+ for (i in 0 until Companion.TOTAL_OF_CASES_GENERATED) {
+ // Using a new seed so that we can replicate the same test cases.
+ val seed = generator.nextInt()
+ Log.d(Companion.TAG, "Seed = $seed")
+
+ val testCase: HotseatReorderTestCase =
+ generateRandomTestCase(RandomBoardGenerator(Random(seed.toLong())))
+ Log.d(Companion.TAG, "testCase = $testCase")
+
+ Assert.assertTrue(
+ "invalid case $i",
+ PermutedBoardComparator().compare(testCase.startBoard, testCase.endBoard) == 0
+ )
+ }
+ }
+
+ private fun addViewInCellLayout(
+ cellLayout: CellLayout,
+ cellX: Int,
+ cellY: Int,
+ spanX: Int,
+ spanY: Int,
+ isWidget: Boolean
+ ) {
+ val cell =
+ if (isWidget) View(applicationContext)
+ else DoubleShadowBubbleTextView(applicationContext)
+ cell.layoutParams = CellLayoutLayoutParams(cellX, cellY, spanX, spanY)
+ cellLayout.addViewToCellLayout(
+ cell,
+ -1,
+ cell.id,
+ cell.layoutParams as CellLayoutLayoutParams,
+ true
+ )
+ }
+
+ private fun solve(board: CellLayoutBoard): CellLayout {
+ val cl = cellLayoutBuilder.createCellLayout(board.width, board.height, false)
+ // The views have to be sorted or the result can vary
+ board.icons
+ .map(IconPoint::getCoord)
+ .sortedWith(
+ Comparator.comparing { p: Any -> (p as Point).x }
+ .thenComparing { p: Any -> (p as Point).y }
+ )
+ .forEach { p ->
+ addViewInCellLayout(
+ cellLayout = cl,
+ cellX = p.x,
+ cellY = p.y,
+ spanX = 1,
+ spanY = 1,
+ isWidget = false
+ )
+ }
+ board.widgets
+ .sortedWith(
+ Comparator.comparing(WidgetRect::getCellX).thenComparing(WidgetRect::getCellY)
+ )
+ .forEach { widget ->
+ addViewInCellLayout(
+ cl,
+ widget.cellX,
+ widget.cellY,
+ widget.spanX,
+ widget.spanY,
+ isWidget = true
+ )
+ }
+ if (cl.makeSpaceForHotseatMigration(true)) {
+ commitTempPosition(cl)
+ }
+ return cl
+ }
+
+ private fun commitTempPosition(cellLayout: CellLayout) {
+ val count = cellLayout.shortcutsAndWidgets.childCount
+ for (i in 0 until count) {
+ val params = cellLayout.shortcutsAndWidgets[i].layoutParams as CellLayoutLayoutParams
+ params.cellX = params.tmpCellX
+ params.cellY = params.tmpCellY
+ }
+ }
+
+ private fun boardFromCellLayout(cellLayout: CellLayout): CellLayoutBoard {
+ val views = mutableListOf<View>()
+ for (i in 0 until cellLayout.shortcutsAndWidgets.childCount) {
+ views.add(cellLayout.shortcutsAndWidgets.getChildAt(i))
+ }
+ return CellLayoutTestUtils.viewsToBoard(views, cellLayout.countX, cellLayout.countY)
+ }
+
+ private fun generateRandomTestCase(
+ boardGenerator: RandomBoardGenerator
+ ): HotseatReorderTestCase {
+ val width: Int = boardGenerator.getRandom(3, Companion.MAX_BOARD_SIZE)
+ val height: Int = boardGenerator.getRandom(3, Companion.MAX_BOARD_SIZE)
+ val targetWidth: Int = boardGenerator.getRandom(1, width - 2)
+ val targetHeight: Int = boardGenerator.getRandom(1, height - 2)
+ val board: CellLayoutBoard =
+ boardGenerator.generateBoard(width, height, targetWidth * targetHeight)
+ val finishBoard: CellLayoutBoard = boardFromCellLayout(solve(board))
+ return HotseatReorderTestCase(board, finishBoard)
+ }
+
+ companion object {
+ private const val MAX_BOARD_SIZE = 13
+
+ /**
+ * There is nothing special about this numbers, the random seed is just to be able to
+ * reproduce the test cases and the height and width is a random number similar to what
+ * users expect on their devices
+ */
+ private const val SEED = -194162315
+ private const val TOTAL_OF_CASES_GENERATED = 300
+ private const val TAG = "HotseatReorderUnitTest"
+ }
+}
diff --git a/tests/src/com/android/launcher3/ui/widget/TaplAddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/TaplAddConfigWidgetTest.java
index d96287f..7aa26a1 100644
--- a/tests/src/com/android/launcher3/ui/widget/TaplAddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/TaplAddConfigWidgetTest.java
@@ -161,11 +161,12 @@
public int getWidgetId() throws InterruptedException {
Intent intent = blockingGetExtraIntent();
- assertNotNull(intent);
- assertEquals(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE, intent.getAction());
+ assertNotNull("Null EXTRA_INTENT", intent);
+ assertEquals("Intent action is not ACTION_APPWIDGET_CONFIGURE",
+ AppWidgetManager.ACTION_APPWIDGET_CONFIGURE, intent.getAction());
int widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
LauncherAppWidgetInfo.NO_ID);
- assertNotSame(widgetId, LauncherAppWidgetInfo.NO_ID);
+ assertNotSame("Widget id is NO_ID", widgetId, LauncherAppWidgetInfo.NO_ID);
return widgetId;
}
}
diff --git a/tests/src/com/android/launcher3/util/PackageManagerHelperTest.java b/tests/src/com/android/launcher3/util/PackageManagerHelperTest.java
new file mode 100644
index 0000000..d1da5f4
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/PackageManagerHelperTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+import static com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link PackageManagerHelper}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class PackageManagerHelperTest {
+ @Rule
+ public ExpectedException exception = ExpectedException.none();
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ private static final String TEST_PACKAGE = "com.android.test.package";
+ private static final int TEST_USER = 2;
+
+ private Context mContext;
+ private LauncherApps mLauncherApps;
+ private PackageManagerHelper mPackageManagerHelper;
+
+ @Before
+ public void setup() {
+ mContext = mock(Context.class);
+ mLauncherApps = mock(LauncherApps.class);
+ when(mContext.getSystemService(eq(LauncherApps.class))).thenReturn(mLauncherApps);
+ mPackageManagerHelper = new PackageManagerHelper(mContext);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_ENABLE_SUPPORT_FOR_ARCHIVING)
+ public void getApplicationInfo_archivedApp_appInfoIsNotNull()
+ throws PackageManager.NameNotFoundException {
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.isArchived = true;
+ when(mLauncherApps.getApplicationInfo(TEST_PACKAGE, 0 /* flags */,
+ UserHandle.of(TEST_USER)))
+ .thenReturn(applicationInfo);
+
+ assertThat(mPackageManagerHelper.getApplicationInfo(TEST_PACKAGE, UserHandle.of(TEST_USER),
+ 0 /* flags */))
+ .isNotNull();
+ }
+}