Merge "Separate getTaskViewAt usage"
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index 9d70cfa..e1d89a1 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -44,7 +44,6 @@
import com.android.systemui.shared.system.BlurUtils;
import com.android.systemui.shared.system.WallpaperManagerCompat;
-import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.function.Consumer;
@@ -156,6 +155,10 @@
// Workaround for animating the depth when multiwindow mode changes.
private boolean mIgnoreStateChangesDuringMultiWindowAnimation = false;
+ // Hints that there is potentially content behind Launcher and that we shouldn't optimize by
+ // marking the launcher surface as opaque. Only used in certain Launcher states.
+ private boolean mHasContentBehindLauncher;
+
private View.OnAttachStateChangeListener mOnAttachListener;
public DepthController(Launcher l) {
@@ -199,6 +202,10 @@
mLauncher.getScrimView().addOpaquenessListener(mOpaquenessListener);
}
+ public void setHasContentBehindLauncher(boolean hasContentBehindLauncher) {
+ mHasContentBehindLauncher = hasContentBehindLauncher;
+ }
+
/**
* Sets if the underlying activity is started or not
*/
@@ -311,13 +318,14 @@
}
if (supportsBlur) {
- boolean opaque = mLauncher.getScrimView().isFullyOpaque();
+ boolean hasOpaqueBg = mLauncher.getScrimView().isFullyOpaque();
+ boolean isSurfaceOpaque = !mHasContentBehindLauncher && hasOpaqueBg;
- mCurrentBlur = !mCrossWindowBlursEnabled || mBlurDisabledForAppLaunch
+ mCurrentBlur = !mCrossWindowBlursEnabled || mBlurDisabledForAppLaunch || hasOpaqueBg
? 0 : (int) (depth * mMaxBlurRadius);
SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()
.setBackgroundBlurRadius(mSurface, mCurrentBlur)
- .setOpaque(mSurface, opaque);
+ .setOpaque(mSurface, isSurfaceOpaque);
// Set early wake-up flags when we know we're executing an expensive operation, this way
// SurfaceFlinger will adjust its internal offsets to avoid jank.
diff --git a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
index 24a88a4..90c035f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
@@ -46,16 +46,13 @@
}
};
- // Initialized in init.
- TaskbarControllers mControllers;
-
public FallbackTaskbarUIController(RecentsActivity recentsActivity) {
mRecentsActivity = recentsActivity;
}
@Override
protected void init(TaskbarControllers taskbarControllers) {
- mControllers = taskbarControllers;
+ super.init(taskbarControllers);
mRecentsActivity.setTaskbarUIController(this);
mRecentsActivity.getStateManager().addStateListener(mStateListener);
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 1c0c773..7d23439 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -25,7 +25,6 @@
import android.util.Log;
import android.view.MotionEvent;
import android.view.TaskTransitionSpec;
-import android.view.View;
import android.view.WindowManagerGlobal;
import androidx.annotation.NonNull;
@@ -62,7 +61,6 @@
this::onStashedInAppChanged;
// Initialized in init.
- private TaskbarControllers mControllers;
private AnimatedFloat mTaskbarOverrideBackgroundAlpha;
private TaskbarKeyguardController mKeyguardController;
private final TaskbarLauncherStateController
@@ -83,7 +81,7 @@
@Override
protected void init(TaskbarControllers taskbarControllers) {
- mControllers = taskbarControllers;
+ super.init(taskbarControllers);
mTaskbarLauncherStateController.init(mControllers, mLauncher);
mTaskbarOverrideBackgroundAlpha = mControllers.taskbarDragLayerController
@@ -168,10 +166,6 @@
return mControllers.taskbarDragController.isDragging();
}
- public View getRootView() {
- return mControllers.taskbarActivityContext.getDragLayer();
- }
-
@Override
protected void onStashedInAppChanged() {
onStashedInAppChanged(mLauncher.getDeviceProfile());
@@ -251,4 +245,12 @@
mLauncher.logAppLaunch(mControllers.taskbarActivityContext.getStatsLogManager(), item,
instanceId);
}
+
+ @Override
+ public void setSystemGestureInProgress(boolean inProgress) {
+ super.setSystemGestureInProgress(inProgress);
+ // Launcher's ScrimView will draw the background throughout the gesture. But once the
+ // gesture ends, start drawing taskbar's background again since launcher might stop drawing.
+ forceHideBackground(inProgress);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 0565f7e..f6e0426 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -21,7 +21,6 @@
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
-import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_IME;
import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_KEYGUARD;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
@@ -140,11 +139,6 @@
mNavButtonsView.getLayoutParams().height = mContext.getDeviceProfile().taskbarSize;
mNavButtonTranslationYMultiplier.value = 1;
- mPropertyHolders.add(new StatePropertyHolder(
- mControllers.taskbarViewController.getTaskbarIconAlpha()
- .getProperty(ALPHA_INDEX_IME),
- flags -> (flags & FLAG_IME_VISIBLE) == 0, MultiValueAlpha.VALUE, 1, 0));
-
boolean isThreeButtonNav = mContext.isThreeButtonNav();
// IME switcher
View imeSwitcherButton = addButton(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH,
@@ -196,7 +190,7 @@
}
// Animate taskbar background when any of these flags are enabled
- int flagsToShowBg = FLAG_IME_VISIBLE | FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE
+ int flagsToShowBg = FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE
| FLAG_NOTIFICATION_SHADE_EXPANDED;
mPropertyHolders.add(new StatePropertyHolder(
mControllers.taskbarDragLayerController.getNavbarBackgroundAlpha(),
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index 2c80f06..22ca63f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -16,6 +16,8 @@
package com.android.launcher3.taskbar;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Outline;
@@ -23,8 +25,6 @@
import android.view.View;
import android.view.ViewOutlineProvider;
-import androidx.annotation.Nullable;
-
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.RevealOutlineAnimation;
@@ -66,7 +66,10 @@
private final Rect mStashedHandleBounds = new Rect();
private float mStashedHandleRadius;
- private boolean mIsAtStashedRevealBounds = true;
+ // When the reveal animation is cancelled, we can assume it's about to create a new animation,
+ // which should start off at the same point the cancelled one left off.
+ private float mStartProgressForNextRevealAnim;
+ private boolean mWasLastRevealAnimReversed;
public StashedHandleViewController(TaskbarActivityContext activity,
StashedHandleView stashedHandleView) {
@@ -148,15 +151,27 @@
* shape and size. When stashed, the shape is a thin rounded pill. When unstashed, the shape
* morphs into the size of where the taskbar icons will be.
*/
- public @Nullable Animator createRevealAnimToIsStashed(boolean isStashed) {
- if (mIsAtStashedRevealBounds == isStashed) {
- return null;
- }
- mIsAtStashedRevealBounds = isStashed;
+ public Animator createRevealAnimToIsStashed(boolean isStashed) {
final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider(
mStashedHandleRadius, mStashedHandleRadius,
mControllers.taskbarViewController.getIconLayoutBounds(), mStashedHandleBounds);
- return handleRevealProvider.createRevealAnimator(mStashedHandleView, !isStashed);
+
+ boolean isReversed = !isStashed;
+ boolean changingDirection = mWasLastRevealAnimReversed != isReversed;
+ mWasLastRevealAnimReversed = isReversed;
+ if (changingDirection) {
+ mStartProgressForNextRevealAnim = 1f - mStartProgressForNextRevealAnim;
+ }
+
+ ValueAnimator revealAnim = handleRevealProvider.createRevealAnimator(mStashedHandleView,
+ isReversed, mStartProgressForNextRevealAnim);
+ revealAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mStartProgressForNextRevealAnim = ((ValueAnimator) animation).getAnimatedFraction();
+ }
+ });
+ return revealAnim;
}
public void onIsStashed(boolean isStashed) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 508ca4e..bc68173 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -20,7 +20,6 @@
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
-import static com.android.launcher3.testing.TestProtocol.TASKBAR_WINDOW_CRASH;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
@@ -207,7 +206,6 @@
updateSysuiStateFlags(sharedState.sysuiStateFlags, true /* fromInit */);
mWindowManager.addView(mDragLayer, mWindowLayoutParams);
- Log.d(TASKBAR_WINDOW_CRASH, "Adding taskbar window");
}
public boolean isThreeButtonNav() {
@@ -354,7 +352,6 @@
setUIController(TaskbarUIController.DEFAULT);
mControllers.onDestroy();
mWindowManager.removeViewImmediate(mDragLayer);
- Log.d(TASKBAR_WINDOW_CRASH, "Removing taskbar window");
}
public void updateSysuiStateFlags(int systemUiStateFlags, boolean fromInit) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index 248c40d..806b390 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -44,6 +44,7 @@
private final AnimatedFloat mKeyguardBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
private final AnimatedFloat mNotificationShadeBgTaskbar = new AnimatedFloat(
this::updateBackgroundAlpha);
+ private final AnimatedFloat mImeBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
// Used to hide our background color when someone else (e.g. ScrimView) is handling it.
private final AnimatedFloat mBgOverride = new AnimatedFloat(this::updateBackgroundAlpha);
@@ -74,6 +75,7 @@
mBgTaskbar.value = 1;
mKeyguardBgTaskbar.value = 1;
mNotificationShadeBgTaskbar.value = 1;
+ mImeBgTaskbar.value = 1;
mBgOverride.value = 1;
updateBackgroundAlpha();
}
@@ -108,6 +110,10 @@
return mNotificationShadeBgTaskbar;
}
+ public AnimatedFloat getImeBgTaskbar() {
+ return mImeBgTaskbar;
+ }
+
public AnimatedFloat getOverrideBackgroundAlpha() {
return mBgOverride;
}
@@ -119,7 +125,7 @@
private void updateBackgroundAlpha() {
final float bgNavbar = mBgNavbar.value;
final float bgTaskbar = mBgTaskbar.value * mKeyguardBgTaskbar.value
- * mNotificationShadeBgTaskbar.value;
+ * mNotificationShadeBgTaskbar.value * mImeBgTaskbar.value;
mLastSetBackgroundAlpha = mBgOverride.value * Math.max(bgNavbar, bgTaskbar);
mTaskbarDragLayer.setTaskbarBackgroundAlpha(mLastSetBackgroundAlpha);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 8a0e39f..8a86157 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -19,7 +19,6 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
-import static com.android.launcher3.testing.TestProtocol.TASKBAR_WINDOW_CRASH;
import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
import static com.android.launcher3.util.DisplayController.CHANGE_SUPPORTED_BOUNDS;
@@ -210,8 +209,6 @@
}
private void recreateTaskbar() {
- Log.d(TASKBAR_WINDOW_CRASH, "Recreating taskbar: mTaskbarActivityContext="
- + mTaskbarActivityContext);
destroyExistingTaskbar();
DeviceProfile dp =
@@ -234,7 +231,6 @@
mTaskbarActivityContext.setUIController(
createTaskbarUIControllerForActivity(mActivity));
}
- Log.d(TASKBAR_WINDOW_CRASH, "Finished recreating taskbar");
}
public void onSystemUiFlagsChanged(int systemUiStateFlags) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 60842c8..8965dc4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -19,6 +19,7 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_HIDE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_SHOW;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import android.animation.Animator;
@@ -48,11 +49,13 @@
public static final int FLAG_STASHED_IN_APP_PINNED = 1 << 2; // app pinning
public static final int FLAG_STASHED_IN_APP_EMPTY = 1 << 3; // no hotseat icons
public static final int FLAG_STASHED_IN_APP_SETUP = 1 << 4; // setup wizard and AllSetActivity
- public static final int FLAG_IN_STASHED_LAUNCHER_STATE = 1 << 5;
+ public static final int FLAG_STASHED_IN_APP_IME = 1 << 5; // IME is visible
+ public static final int FLAG_IN_STASHED_LAUNCHER_STATE = 1 << 6;
// If we're in an app and any of these flags are enabled, taskbar should be stashed.
public static final int FLAGS_STASHED_IN_APP = FLAG_STASHED_IN_APP_MANUAL
- | FLAG_STASHED_IN_APP_PINNED | FLAG_STASHED_IN_APP_EMPTY | FLAG_STASHED_IN_APP_SETUP;
+ | FLAG_STASHED_IN_APP_PINNED | FLAG_STASHED_IN_APP_EMPTY | FLAG_STASHED_IN_APP_SETUP
+ | FLAG_STASHED_IN_APP_IME;
/**
* How long to stash/unstash when manually invoked via long press.
@@ -60,6 +63,11 @@
public static final long TASKBAR_STASH_DURATION = 300;
/**
+ * How long to stash/unstash when keyboard is appearing/disappearing.
+ */
+ private static final long TASKBAR_STASH_DURATION_FOR_IME = 80;
+
+ /**
* The scale TaskbarView animates to when being stashed.
*/
private static final float STASHED_TASKBAR_SCALE = 0.5f;
@@ -100,6 +108,7 @@
private TaskbarControllers mControllers;
// Taskbar background properties.
private AnimatedFloat mTaskbarBackgroundOffset;
+ private AnimatedFloat mTaskbarImeBgAlpha;
// TaskbarView icon properties.
private AlphaProperty mIconAlphaForStash;
private AnimatedFloat mIconScaleForStash;
@@ -113,6 +122,8 @@
private int mState;
private @Nullable AnimatorSet mAnimator;
+ private boolean mIsSystemGestureInProgress;
+ private boolean mIsImeShowing;
// Evaluate whether the handle should be stashed
private final StatePropertyHolder mStatePropertyHolder = new StatePropertyHolder(
@@ -137,6 +148,7 @@
TaskbarDragLayerController dragLayerController = controllers.taskbarDragLayerController;
mTaskbarBackgroundOffset = dragLayerController.getTaskbarBackgroundOffset();
+ mTaskbarImeBgAlpha = dragLayerController.getImeBgTaskbar();
TaskbarViewController taskbarViewController = controllers.taskbarViewController;
mIconAlphaForStash = taskbarViewController.getTaskbarIconAlpha().getProperty(
@@ -271,17 +283,21 @@
* Create a stash animation and save to {@link #mAnimator}.
* @param isStashed whether it's a stash animation or an unstash animation
* @param duration duration of the animation
+ * @param startDelay how many milliseconds to delay the animation after starting it.
*/
- private void createAnimToIsStashed(boolean isStashed, long duration) {
+ private void createAnimToIsStashed(boolean isStashed, long duration, long startDelay) {
if (mAnimator != null) {
mAnimator.cancel();
}
mAnimator = new AnimatorSet();
if (!supportsVisualStashing()) {
- // Just hide/show the icons instead of stashing into a handle.
+ // Just hide/show the icons and background instead of stashing into a handle.
mAnimator.play(mIconAlphaForStash.animateToValue(isStashed ? 0 : 1)
.setDuration(duration));
+ mAnimator.play(mTaskbarImeBgAlpha.animateToValue(
+ hasAnyFlag(FLAG_STASHED_IN_APP_IME) ? 0 : 1).setDuration(duration));
+ mAnimator.setStartDelay(startDelay);
mAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -332,11 +348,8 @@
);
}
- Animator stashedHandleRevealAnim = mControllers.stashedHandleViewController
- .createRevealAnimToIsStashed(isStashed);
- if (stashedHandleRevealAnim != null) {
- fullLengthAnimatorSet.play(stashedHandleRevealAnim);
- }
+ fullLengthAnimatorSet.play(mControllers.stashedHandleViewController
+ .createRevealAnimToIsStashed(isStashed));
// Return the stashed handle to its default scale in case it was changed as part of the
// feedforward hint. Note that the reveal animation above also visually scales it.
fullLengthAnimatorSet.play(mTaskbarStashedHandleHintScale.animateToValue(1f));
@@ -348,6 +361,7 @@
mAnimator.playTogether(fullLengthAnimatorSet, firstHalfAnimatorSet,
secondHalfAnimatorSet);
+ mAnimator.setStartDelay(startDelay);
mAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
@@ -406,6 +420,10 @@
mStatePropertyHolder.setState(mState, duration, true);
}
+ public void applyState(long duration, long startDelay) {
+ mStatePropertyHolder.setState(mState, duration, startDelay, true);
+ }
+
public Animator applyStateWithoutStart() {
return applyStateWithoutStart(TASKBAR_STASH_DURATION);
}
@@ -414,11 +432,50 @@
return mStatePropertyHolder.setState(mState, duration, false);
}
+ /**
+ * Should be called when a system gesture starts and settles, so we can defer updating
+ * FLAG_STASHED_IN_APP_IME until after the gesture transition completes.
+ */
+ public void setSystemGestureInProgress(boolean inProgress) {
+ mIsSystemGestureInProgress = inProgress;
+ // Only update FLAG_STASHED_IN_APP_IME when system gesture is not in progress.
+ if (!mIsSystemGestureInProgress) {
+ updateStateForFlag(FLAG_STASHED_IN_APP_IME, mIsImeShowing);
+ applyState(TASKBAR_STASH_DURATION_FOR_IME, getTaskbarStashStartDelayForIme());
+ }
+ }
+
+ /**
+ * When hiding the IME, delay the unstash animation to align with the end of the transition.
+ */
+ private long getTaskbarStashStartDelayForIme() {
+ if (mIsImeShowing) {
+ // Only delay when IME is exiting, not entering.
+ return 0;
+ }
+ // This duration is based on input_method_extract_exit.xml.
+ long imeExitDuration = mControllers.taskbarActivityContext.getResources()
+ .getInteger(android.R.integer.config_shortAnimTime);
+ return imeExitDuration - TASKBAR_STASH_DURATION_FOR_IME;
+ }
+
/** Called when some system ui state has changed. (See SYSUI_STATE_... in QuickstepContract) */
public void updateStateForSysuiFlags(int systemUiStateFlags, boolean skipAnim) {
+ long animDuration = TASKBAR_STASH_DURATION;
+ long startDelay = 0;
+
updateStateForFlag(FLAG_STASHED_IN_APP_PINNED,
hasAnyFlag(systemUiStateFlags, SYSUI_STATE_SCREEN_PINNING));
- applyState(skipAnim ? 0 : TASKBAR_STASH_DURATION);
+
+ // Only update FLAG_STASHED_IN_APP_IME when system gesture is not in progress.
+ mIsImeShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SHOWING);
+ if (!mIsSystemGestureInProgress) {
+ updateStateForFlag(FLAG_STASHED_IN_APP_IME, mIsImeShowing);
+ animDuration = TASKBAR_STASH_DURATION_FOR_IME;
+ startDelay = getTaskbarStashStartDelayForIme();
+ }
+
+ applyState(skipAnim ? 0 : animDuration, skipAnim ? 0 : startDelay);
}
/**
@@ -474,16 +531,34 @@
mStashCondition = stashCondition;
}
+ /**
+ * @see #setState(int, long, long, boolean) with a default startDelay = 0.
+ */
public Animator setState(int flags, long duration, boolean start) {
+ return setState(flags, duration, 0 /* startDelay */, start);
+ }
+
+ /**
+ * Applies the latest state, potentially calling onStateChangeApplied() and creating a new
+ * animation (stored in mAnimator) which is started if {@param start} is true.
+ * @param flags The latest flags to apply (see the top of this file).
+ * @param duration The length of the animation.
+ * @param startDelay How long to delay the animation after calling start().
+ * @param start Whether to start mAnimator immediately.
+ * @return mAnimator if mIsStashed changed, else null.
+ */
+ public Animator setState(int flags, long duration, long startDelay, boolean start) {
+ int changedFlags = mPrevFlags ^ flags;
if (mPrevFlags != flags) {
- int changedFlags = mPrevFlags ^ flags;
onStateChangeApplied(changedFlags);
mPrevFlags = flags;
}
boolean isStashed = mStashCondition.test(flags);
if (mIsStashed != isStashed) {
mIsStashed = isStashed;
- createAnimToIsStashed(mIsStashed, duration);
+
+ // This sets mAnimator.
+ createAnimToIsStashed(mIsStashed, duration, startDelay);
if (start) {
mAnimator.start();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index d8360e0..f713dca 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -16,6 +16,7 @@
package com.android.launcher3.taskbar;
import android.graphics.Rect;
+import android.view.View;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -29,7 +30,12 @@
public static final TaskbarUIController DEFAULT = new TaskbarUIController();
- protected void init(TaskbarControllers taskbarControllers) { }
+ // Initialized in init.
+ protected TaskbarControllers mControllers;
+
+ protected void init(TaskbarControllers taskbarControllers) {
+ mControllers = taskbarControllers;
+ }
protected void onDestroy() { }
@@ -46,4 +52,16 @@
}
public void onTaskbarIconLaunched(WorkspaceItemInfo item) { }
+
+ public View getRootView() {
+ return mControllers.taskbarActivityContext.getDragLayer();
+ }
+
+ /**
+ * Called when swiping from the bottom nav region in fully gestural mode.
+ * @param inProgress True if the animation started, false if we just settled on an end target.
+ */
+ public void setSystemGestureInProgress(boolean inProgress) {
+ mControllers.taskbarStashController.setSystemGestureInProgress(inProgress);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 96eac51..a4d899c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -46,12 +46,11 @@
private static final Runnable NO_OP = () -> { };
public static final int ALPHA_INDEX_HOME = 0;
- public static final int ALPHA_INDEX_IME = 1;
- public static final int ALPHA_INDEX_KEYGUARD = 2;
- public static final int ALPHA_INDEX_STASH = 3;
- public static final int ALPHA_INDEX_RECENTS_DISABLED = 4;
- public static final int ALPHA_INDEX_NOTIFICATION_EXPANDED = 5;
- private static final int NUM_ALPHA_CHANNELS = 6;
+ public static final int ALPHA_INDEX_KEYGUARD = 1;
+ public static final int ALPHA_INDEX_STASH = 2;
+ public static final int ALPHA_INDEX_RECENTS_DISABLED = 3;
+ public static final int ALPHA_INDEX_NOTIFICATION_EXPANDED = 4;
+ private static final int NUM_ALPHA_CHANNELS = 5;
private final TaskbarActivityContext mActivity;
private final TaskbarView mTaskbarView;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 2fa8b07..d74b6c5 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -16,7 +16,6 @@
package com.android.launcher3.uioverrides;
-import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
import static com.android.launcher3.anim.Interpolators.INSTANT;
@@ -73,8 +72,6 @@
getTaskModalnessProperty().set(mRecentsView, state.getOverviewModalness());
RECENTS_GRID_PROGRESS.set(mRecentsView,
state.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f);
-
- applySplitScrollOffset(state);
}
@Override
@@ -120,16 +117,6 @@
boolean showAsGrid = toState.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile());
setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS, showAsGrid ? 1f : 0f,
showAsGrid ? INSTANT : FINAL_FRAME);
-
- applySplitScrollOffset(toState);
- }
-
- private void applySplitScrollOffset(@NonNull final LauncherState state) {
- if (state == OVERVIEW_SPLIT_SELECT) {
- mRecentsView.applySplitPrimaryScrollOffset();
- } else {
- mRecentsView.resetSplitPrimaryScrollOffset();
- }
}
abstract FloatProperty getTaskModalnessProperty();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 72195a0..9050ddc 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -68,11 +68,9 @@
import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarTouchController;
-import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.util.PendingRequestArgs;
-import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.util.UiThreadHelper;
import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
@@ -93,8 +91,6 @@
public class QuickstepLauncher extends BaseQuickstepLauncher {
- public static final String HOME_IS_READY = "com.android.launcher3.HOME_IS_READY";
-
public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
/**
* Reusable command for applying the shelf height on the background thread.
@@ -236,12 +232,6 @@
}
@Override
- public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) {
- super.onInitialBindComplete(boundPages, pendingTasks);
- sendBroadcast(new Intent(HOME_IS_READY).setPackage("com.android.settings"));
- }
-
- @Override
public void bindExtraContainerItems(FixedContainerItems item) {
if (item.containerId == Favorites.CONTAINER_PREDICTION) {
mAllAppsPredictions = item;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 1f744e1..b21d677 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -63,6 +63,9 @@
}
setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, new StateAnimationConfig(), state);
mRecentsView.setFullscreenProgress(state.getOverviewFullscreenProgress());
+ // In Overview, we may be layering app surfaces behind Launcher, so we need to notify
+ // DepthController to prevent optimizations which might occlude the layers behind
+ mLauncher.getDepthController().setHasContentBehindLauncher(state.overviewUi);
}
@Override
@@ -78,13 +81,19 @@
builder.addListener(
AnimatorListeners.forSuccessCallback(mRecentsView::resetTaskVisuals));
}
+ // In Overview, we may be layering app surfaces behind Launcher, so we need to notify
+ // DepthController to prevent optimizations which might occlude the layers behind
+ builder.addListener(AnimatorListeners.forSuccessCallback(() ->
+ mLauncher.getDepthController().setHasContentBehindLauncher(toState.overviewUi)));
// Create or dismiss split screen select animations
LauncherState currentState = mLauncher.getStateManager().getState();
if (isSplitSelectionState(toState) && !isSplitSelectionState(currentState)) {
builder.add(mRecentsView.createSplitSelectInitAnimation().buildAnim());
+ mRecentsView.applySplitPrimaryScrollOffset();
} else if (!isSplitSelectionState(toState) && isSplitSelectionState(currentState)) {
builder.add(mRecentsView.cancelSplitSelect(true).buildAnim());
+ mRecentsView.resetSplitPrimaryScrollOffset();
}
setAlphas(builder, config, toState);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index d396018..a4eff87 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -21,13 +21,11 @@
import android.content.Context;
import android.graphics.Rect;
import android.os.SystemProperties;
-import android.view.View;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
-import com.android.launcher3.Workspace;
import com.android.launcher3.util.Themes;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.util.LayoutUtils;
@@ -66,10 +64,7 @@
@Override
public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
RecentsView recentsView = launcher.getOverviewPanel();
- Workspace workspace = launcher.getWorkspace();
- View workspacePage = workspace.getPageAt(workspace.getCurrentPage());
- float workspacePageWidth = workspacePage != null && workspacePage.getWidth() != 0
- ? workspacePage.getWidth() : launcher.getDeviceProfile().availableWidthPx;
+ float workspacePageWidth = launcher.getDeviceProfile().getWorkspaceWidth();
recentsView.getTaskSize(sTempRect);
float scale = (float) sTempRect.width() / workspacePageWidth;
float parallaxFactor = 0.5f;
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 45b2081..c9cbba1 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -121,8 +121,8 @@
import com.android.systemui.shared.system.TaskStackChangeListeners;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.function.Consumer;
/**
@@ -687,14 +687,17 @@
}
private void onAnimatorPlaybackControllerCreated(AnimatorControllerWithResistance anim) {
+ boolean isFirstCreation = mLauncherTransitionController == null;
mLauncherTransitionController = anim;
- mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, () -> {
- // Wait until the gesture is started (touch slop was passed) to start in sync with
- // mWindowTransitionController. This ensures we don't hide the taskbar background when
- // long pressing to stash it, for instance.
- mLauncherTransitionController.getNormalController().dispatchOnStart();
- updateLauncherTransitionProgress();
- });
+ if (isFirstCreation) {
+ mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, () -> {
+ // Wait until the gesture is started (touch slop was passed) to start in sync with
+ // mWindowTransitionController. This ensures we don't hide the taskbar background
+ // when long pressing to stash it, for instance.
+ mLauncherTransitionController.getNormalController().dispatchOnStart();
+ updateLauncherTransitionProgress();
+ });
+ }
}
public Intent getLaunchIntent() {
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index e15aa92..a566765 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -30,6 +30,7 @@
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.content.Context;
@@ -398,6 +399,11 @@
* (This is a hack to ensure Taskbar draws its background first to avoid flickering.)
*/
public @Nullable View onSettledOnEndTarget(GestureState.GestureEndTarget endTarget) {
+ TaskbarUIController taskbarUIController = getTaskbarController();
+ if (taskbarUIController != null) {
+ taskbarUIController.setSystemGestureInProgress(false);
+ return taskbarUIController.getRootView();
+ }
return null;
}
@@ -533,6 +539,16 @@
pa.addFloat(recentsView, RECENTS_SCALE_PROPERTY,
recentsView.getMaxScaleForFullScreen(), 1, LINEAR);
pa.addFloat(recentsView, FULLSCREEN_PROGRESS, 1, 0, LINEAR);
+
+ pa.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ TaskbarUIController taskbarUIController = getTaskbarController();
+ if (taskbarUIController != null) {
+ taskbarUIController.setSystemGestureInProgress(true);
+ }
+ }
+ });
}
}
}
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index aa9435b..719c2ae 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -25,12 +25,10 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.content.Context;
import android.graphics.Rect;
import android.view.MotionEvent;
-import android.view.View;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
@@ -132,19 +130,6 @@
pa.addFloat(getDepthController(),
new ClampedDepthProperty(fromDepthRatio, toDepthRatio),
fromDepthRatio, toDepthRatio, LINEAR);
-
- pa.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- LauncherTaskbarUIController taskbarUIController =
- activity.getTaskbarUIController();
- if (taskbarUIController != null) {
- // Launcher's ScrimView will draw the background throughout the gesture.
- taskbarUIController.forceHideBackground(true);
- }
- }
- });
-
}
};
@@ -366,16 +351,4 @@
return NORMAL;
}
}
-
- @Override
- public View onSettledOnEndTarget(@Nullable GestureEndTarget endTarget) {
- View superRet = super.onSettledOnEndTarget(endTarget);
- LauncherTaskbarUIController taskbarUIController = getTaskbarController();
- if (taskbarUIController != null) {
- // Start drawing taskbar's background again since launcher might stop drawing.
- taskbarUIController.forceHideBackground(false);
- return taskbarUIController.getRootView();
- }
- return superRet;
- }
}
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index c5f4a53..097850f 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -36,6 +36,7 @@
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.StagedSplitBounds;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Consumer;
@@ -219,6 +220,26 @@
return newTasks;
}
+ public void dump(String prefix, PrintWriter writer) {
+ writer.println(prefix + "RecentTasksList:");
+ writer.println(prefix + " mChangeId=" + mChangeId);
+ writer.println(prefix + " mResultsUi=[id=" + mResultsUi.mRequestId + ", tasks=");
+ for (GroupTask task : mResultsUi) {
+ writer.println(prefix + " t1=" + task.task1.key.id
+ + " t2=" + (task.hasMultipleTasks() ? task.task2.key.id : "-1"));
+ }
+ writer.println(prefix + " ]");
+ int currentUserId = Process.myUserHandle().getIdentifier();
+ ArrayList<GroupedRecentTaskInfo> rawTasks =
+ mSysUiProxy.getRecentTasks(Integer.MAX_VALUE, currentUserId);
+ writer.println(prefix + " rawTasks=[");
+ for (GroupedRecentTaskInfo task : rawTasks) {
+ writer.println(prefix + " t1=" + task.mTaskInfo1.taskId
+ + " t2=" + (task.mTaskInfo2 != null ? task.mTaskInfo2.taskId : "-1"));
+ }
+ writer.println(prefix + " ]");
+ }
+
private static class TaskLoadResult extends ArrayList<GroupTask> {
final int mRequestId;
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index e539a8c..5d77a6e 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -43,6 +43,7 @@
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
@@ -220,6 +221,11 @@
mThumbnailChangeListeners.remove(listener);
}
+ public void dump(String prefix, PrintWriter writer) {
+ writer.println(prefix + "RecentsModel:");
+ mTaskList.dump(" ", writer);
+ }
+
/**
* Listener for receiving various task properties changes
*/
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 68b7558..c8abd14 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -83,14 +83,16 @@
MAIN_EXECUTOR.execute(() -> clearProxy());
};
- // Save the listeners passed into the proxy since when set/register these listeners,
- // setProxy may not have been called, eg. OverviewProxyService is not connected yet.
- private IPipAnimationListener mPendingPipAnimationListener;
- private ISplitScreenListener mPendingSplitScreenListener;
- private IStartingWindowListener mPendingStartingWindowListener;
- private ISmartspaceCallback mPendingSmartspaceCallback;
- private IRecentTasksListener mPendingRecentTasksListener;
- private final ArrayList<RemoteTransitionCompat> mPendingRemoteTransitions = new ArrayList<>();
+ // Save the listeners passed into the proxy since OverviewProxyService may not have been bound
+ // yet, and we'll need to set/register these listeners with SysUI when they do. Note that it is
+ // up to the caller to clear the listeners to prevent leaks as these can be held indefinitely
+ // in case SysUI needs to rebind.
+ private IPipAnimationListener mPipAnimationListener;
+ private ISplitScreenListener mSplitScreenListener;
+ private IStartingWindowListener mStartingWindowListener;
+ private ISmartspaceCallback mSmartspaceCallback;
+ private IRecentTasksListener mRecentTasksListener;
+ private final ArrayList<RemoteTransitionCompat> mRemoteTransitions = new ArrayList<>();
// Used to dedupe calls to SystemUI
private int mLastShelfHeight;
@@ -167,29 +169,23 @@
mRecentTasks = recentTasks;
linkToDeath();
// re-attach the listeners once missing due to setProxy has not been initialized yet.
- if (mPendingPipAnimationListener != null && mPip != null) {
- setPinnedStackAnimationListener(mPendingPipAnimationListener);
- mPendingPipAnimationListener = null;
+ if (mPipAnimationListener != null && mPip != null) {
+ setPinnedStackAnimationListener(mPipAnimationListener);
}
- if (mPendingSplitScreenListener != null && mSplitScreen != null) {
- registerSplitScreenListener(mPendingSplitScreenListener);
- mPendingSplitScreenListener = null;
+ if (mSplitScreenListener != null && mSplitScreen != null) {
+ registerSplitScreenListener(mSplitScreenListener);
}
- if (mPendingStartingWindowListener != null && mStartingWindow != null) {
- setStartingWindowListener(mPendingStartingWindowListener);
- mPendingStartingWindowListener = null;
+ if (mStartingWindowListener != null && mStartingWindow != null) {
+ setStartingWindowListener(mStartingWindowListener);
}
- if (mPendingSmartspaceCallback != null && mSmartspaceTransitionController != null) {
- setSmartspaceCallback(mPendingSmartspaceCallback);
- mPendingSmartspaceCallback = null;
+ if (mSmartspaceCallback != null && mSmartspaceTransitionController != null) {
+ setSmartspaceCallback(mSmartspaceCallback);
}
- for (int i = mPendingRemoteTransitions.size() - 1; i >= 0; --i) {
- registerRemoteTransition(mPendingRemoteTransitions.get(i));
+ for (int i = mRemoteTransitions.size() - 1; i >= 0; --i) {
+ registerRemoteTransition(mRemoteTransitions.get(i));
}
- mPendingRemoteTransitions.clear();
- if (mPendingRecentTasksListener != null && mRecentTasks != null) {
- registerRecentTasksListener(mPendingRecentTasksListener);
- mPendingRecentTasksListener = null;
+ if (mRecentTasksListener != null && mRecentTasks != null) {
+ registerRecentTasksListener(mRecentTasksListener);
}
if (mPendingSetNavButtonAlpha != null) {
@@ -513,9 +509,8 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed call setPinnedStackAnimationListener", e);
}
- } else {
- mPendingPipAnimationListener = listener;
}
+ mPipAnimationListener = listener;
}
public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
@@ -553,9 +548,8 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed call registerSplitScreenListener");
}
- } else {
- mPendingSplitScreenListener = listener;
}
+ mSplitScreenListener = listener;
}
public void unregisterSplitScreenListener(ISplitScreenListener listener) {
@@ -566,7 +560,7 @@
Log.w(TAG, "Failed call unregisterSplitScreenListener");
}
}
- mPendingSplitScreenListener = null;
+ mSplitScreenListener = null;
}
/** Start multiple tasks in split-screen simultaneously. */
@@ -687,9 +681,8 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed call registerRemoteTransition");
}
- } else {
- mPendingRemoteTransitions.add(remoteTransition);
}
+ mRemoteTransitions.add(remoteTransition);
}
public void unregisterRemoteTransition(RemoteTransitionCompat remoteTransition) {
@@ -700,7 +693,7 @@
Log.w(TAG, "Failed call registerRemoteTransition");
}
}
- mPendingRemoteTransitions.remove(remoteTransition);
+ mRemoteTransitions.remove(remoteTransition);
}
//
@@ -717,9 +710,8 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed call setStartingWindowListener", e);
}
- } else {
- mPendingStartingWindowListener = listener;
}
+ mStartingWindowListener = listener;
}
//
@@ -733,9 +725,8 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed call setStartingWindowListener", e);
}
- } else {
- mPendingSmartspaceCallback = callback;
}
+ mSmartspaceCallback = callback;
}
//
@@ -749,9 +740,8 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed call registerRecentTasksListener", e);
}
- } else {
- mPendingRecentTasksListener = listener;
}
+ mRecentTasksListener = listener;
}
public void unregisterRecentTasksListener(IRecentTasksListener listener) {
@@ -762,7 +752,7 @@
Log.w(TAG, "Failed call unregisterRecentTasksListener");
}
}
- mPendingRecentTasksListener = null;
+ mRecentTasksListener = null;
}
public ArrayList<GroupedRecentTaskInfo> getRecentTasks(int numTasks, int userId) {
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 8c4ba97..cbdbdb5 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -72,7 +72,13 @@
@Override
public SystemShortcut getShortcut(BaseDraggingActivity activity,
TaskIdAttributeContainer taskContainer) {
- return new AppInfo(activity, taskContainer.getItemInfo());
+ TaskView taskView = taskContainer.getTaskView();
+ AppInfo.SplitAccessibilityInfo accessibilityInfo =
+ new AppInfo.SplitAccessibilityInfo(taskView.containsMultipleTasks(),
+ TaskUtils.getTitle(taskView.getContext(), taskContainer.getTask()),
+ taskContainer.getA11yNodeId()
+ );
+ return new AppInfo(activity, taskContainer.getItemInfo(), accessibilityInfo);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index e1894ae..d1f39d2 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -22,7 +22,6 @@
import static com.android.launcher3.config.FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.testing.TestProtocol.TASKBAR_WINDOW_CRASH;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.quickstep.GestureState.DEFAULT_STATE;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
@@ -353,7 +352,6 @@
@Override
public void onCreate() {
super.onCreate();
- Log.d(TASKBAR_WINDOW_CRASH, "TIS created");
// Initialize anything here that is needed in direct boot mode.
// Everything else should be initialized in onUserUnlocked() below.
mMainChoreographer = Choreographer.getInstance();
@@ -507,7 +505,6 @@
@Override
public void onDestroy() {
Log.d(TAG, "Touch service destroyed: user=" + getUserId());
- Log.d(TASKBAR_WINDOW_CRASH, "TIS destroyed");
sIsInitialized = false;
if (mDeviceState.isUserUnlocked()) {
mInputConsumer.unregisterInputConsumer();
@@ -951,6 +948,7 @@
pw.println(" resumed=" + resumed);
pw.println(" mConsumer=" + mConsumer.getName());
ActiveGestureLog.INSTANCE.dump("", pw);
+ RecentsModel.INSTANCE.get(this).dump("", pw);
pw.println("ProtoTrace:");
pw.println(" file=" + ProtoTracer.INSTANCE.get(this).getTraceFile());
}
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 0ae415d..fcff858 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -16,7 +16,6 @@
package com.android.quickstep.fallback;
import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
-import static com.android.quickstep.ViewUtils.postFrameDrawn;
import static com.android.quickstep.fallback.RecentsState.DEFAULT;
import static com.android.quickstep.fallback.RecentsState.HOME;
import static com.android.quickstep.fallback.RecentsState.MODAL_TASK;
@@ -225,8 +224,8 @@
setFreezeViewVisibility(false);
if (isOverlayEnabled) {
- postFrameDrawn(this, () -> runActionOnRemoteHandles(remoteTargetHandle ->
- remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true)));
+ runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true));
}
}
diff --git a/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
index c3b166f..adea1a4 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
@@ -136,7 +136,7 @@
}
/** Returns the maximum corner radius of {@param drawable}. */
- private static float getMaxRadius(Drawable drawable) {
+ private static float getMaxRadius(@Nullable Drawable drawable) {
if (!(drawable instanceof GradientDrawable)) return 0;
float[] cornerRadii = ((GradientDrawable) drawable).getCornerRadii();
float cornerRadius = ((GradientDrawable) drawable).getCornerRadius();
diff --git a/quickstep/src/com/android/quickstep/views/IconView.java b/quickstep/src/com/android/quickstep/views/IconView.java
index ccb1a99..5895c05 100644
--- a/quickstep/src/com/android/quickstep/views/IconView.java
+++ b/quickstep/src/com/android/quickstep/views/IconView.java
@@ -87,6 +87,14 @@
return mDrawable;
}
+ public int getDrawableWidth() {
+ return mDrawableWidth;
+ }
+
+ public int getDrawableHeight() {
+ return mDrawableHeight;
+ }
+
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 3cba392..a2e9e57 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -21,7 +21,6 @@
import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
import static com.android.launcher3.LauncherState.SPRING_LOADED;
-import static com.android.quickstep.ViewUtils.postFrameDrawn;
import android.annotation.TargetApi;
import android.content.Context;
@@ -110,8 +109,8 @@
setFreezeViewVisibility(false);
if (isOverlayEnabled) {
- postFrameDrawn(this, () -> runActionOnRemoteHandles(remoteTargetHandle ->
- remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true)));
+ runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true));
}
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index aba0944..e270cb7 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -2614,10 +2614,8 @@
clampToProgress(FINAL_FRAME, 0, 0.5f));
});
}
- boolean isTaskInBottomGridRow = showAsGrid() && !mTopRowIdSet.contains(
- taskView.getTaskViewId()) && taskView.getTaskViewId() != mFocusedTaskViewId;
anim.setFloat(taskView, VIEW_ALPHA, 0,
- clampToProgress(isTaskInBottomGridRow ? ACCEL : FINAL_FRAME, 0, 0.5f));
+ clampToProgress(isOnGridBottomRow(taskView) ? ACCEL : FINAL_FRAME, 0, 0.5f));
FloatProperty<TaskView> secondaryViewTranslate =
taskView.getSecondaryDissmissTranslationProperty();
int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView);
@@ -3122,14 +3120,10 @@
}
} else {
// Update focus task and its size.
- if (finalIsFocusedTaskDismissed) {
- if (finalNextFocusedTaskView != null) {
- mFocusedTaskViewId = finalNextFocusedTaskView.getTaskViewId();
- mTopRowIdSet.remove(mFocusedTaskViewId);
- finalNextFocusedTaskView.animateIconScaleAndDimIntoView();
- } else {
- mFocusedTaskViewId = -1;
- }
+ if (finalIsFocusedTaskDismissed && finalNextFocusedTaskView != null) {
+ mFocusedTaskViewId = finalNextFocusedTaskView.getTaskViewId();
+ mTopRowIdSet.remove(mFocusedTaskViewId);
+ finalNextFocusedTaskView.animateIconScaleAndDimIntoView();
}
updateTaskSize(/*isTaskDismissal=*/ true);
updateChildTaskOrientations();
@@ -4031,6 +4025,7 @@
// * Focused Task
updateGridProperties();
resetFromSplitSelectionState();
+ updateScrollSynchronously();
}
});
@@ -4052,7 +4047,6 @@
resetTaskVisuals();
mSplitHiddenTaskViewIndex = -1;
if (mSplitHiddenTaskView != null) {
- mSplitHiddenTaskView.setTranslationY(0);
mSplitHiddenTaskView.setVisibility(VISIBLE);
mSplitHiddenTaskView = null;
}
@@ -4513,9 +4507,8 @@
}
private int getFirstViewIndex() {
- return mShowAsGridLastOnLayout && mFocusedTaskViewId != -1
- ? indexOfChild(getFocusedTaskView())
- : 0;
+ TaskView focusedTaskView = mShowAsGridLastOnLayout ? getFocusedTaskView() : null;
+ return focusedTaskView != null ? indexOfChild(focusedTaskView) : 0;
}
private int getLastViewIndex() {
@@ -4691,6 +4684,15 @@
return position != -1 ? position : bottomRowIdArray.indexOf(taskView.getTaskViewId());
}
+ /**
+ * @return true if the task in on the top of the grid
+ */
+ public boolean isOnGridBottomRow(TaskView taskView) {
+ return showAsGrid()
+ && !mTopRowIdSet.contains(taskView.getTaskViewId())
+ && taskView.getTaskViewId() != mFocusedTaskViewId;
+ }
+
public Consumer<MotionEvent> getEventDispatcher(float navbarRotation) {
float degreesRotated;
if (navbarRotation == 0) {
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
index cd1691b..06a5793 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
@@ -45,7 +45,10 @@
companion object {
const val TAG = "TaskMenuViewWithArrow"
- fun showForTask(taskContainer: TaskIdAttributeContainer): Boolean {
+ fun showForTask(
+ taskContainer: TaskIdAttributeContainer,
+ alignSecondRow: Boolean = false
+ ): Boolean {
val activity = BaseDraggingActivity
.fromContext<BaseDraggingActivity>(taskContainer.taskView.context)
val taskMenuViewWithArrow = activity.layoutInflater
@@ -55,7 +58,7 @@
false
) as TaskMenuViewWithArrow<*>
- return taskMenuViewWithArrow.populateAndShowForTask(taskContainer)
+ return taskMenuViewWithArrow.populateAndShowForTask(taskContainer, alignSecondRow)
}
}
@@ -78,6 +81,9 @@
CLOSE_FADE_DURATION = CLOSE_CHILD_FADE_DURATION
}
+ private var alignSecondRow: Boolean = false
+ private val extraSpaceForSecondRowAlignment: Int
+ get() = if (alignSecondRow) optionMeasuredHeight else 0
private val menuWidth = context.resources.getDimensionPixelSize(R.dimen.task_menu_width_grid)
private lateinit var taskView: TaskView
@@ -91,6 +97,10 @@
else
0
+ private var iconView: IconView? = null
+ private var scrim: View? = null
+ private val scrimAlpha = 0.8f
+
override fun isOfType(type: Int): Boolean = type and TYPE_TASK_MENU != 0
override fun getTargetObjectLocation(outPos: Rect?) {
@@ -112,18 +122,35 @@
optionLayout = findViewById(KtR.id.menu_option_layout)
}
- private fun populateAndShowForTask(taskContainer: TaskIdAttributeContainer): Boolean {
+ private fun populateAndShowForTask(
+ taskContainer: TaskIdAttributeContainer,
+ alignSecondRow: Boolean
+ ): Boolean {
if (isAttachedToWindow) {
return false
}
taskView = taskContainer.taskView
this.taskContainer = taskContainer
+ this.alignSecondRow = alignSecondRow
if (!populateMenu()) return false
+ addScrim()
show()
return true
}
+ private fun addScrim() {
+ scrim = View(context).apply {
+ layoutParams = FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ FrameLayout.LayoutParams.MATCH_PARENT
+ )
+ setBackgroundColor(Themes.getAttrColor(context, R.attr.overviewScrimColor))
+ alpha = 0f
+ }
+ popupContainer.addView(scrim)
+ }
+
/** @return true if successfully able to populate task view menu, false otherwise
*/
private fun populateMenu(): Boolean {
@@ -180,18 +207,50 @@
}
override fun onCreateOpenAnimation(anim: AnimatorSet) {
- anim.play(
- ObjectAnimator.ofFloat(
- taskContainer.thumbnailView, TaskThumbnailView.DIM_ALPHA,
- TaskView.MAX_PAGE_SCRIM_ALPHA
+ scrim?.let {
+ anim.play(
+ ObjectAnimator.ofFloat(it, View.ALPHA, 0f, scrimAlpha)
+ .setDuration(OPEN_DURATION.toLong())
)
- )
+ }
}
override fun onCreateCloseAnimation(anim: AnimatorSet) {
- anim.play(
- ObjectAnimator.ofFloat(taskContainer.thumbnailView, TaskThumbnailView.DIM_ALPHA, 0f)
- )
+ scrim?.let {
+ anim.play(
+ ObjectAnimator.ofFloat(it, View.ALPHA, scrimAlpha, 0f)
+ .setDuration(CLOSE_DURATION.toLong())
+ )
+ }
+ }
+
+ override fun closeComplete() {
+ super.closeComplete()
+ popupContainer.removeView(scrim)
+ popupContainer.removeView(iconView)
+ }
+
+ /**
+ * Copy the iconView from taskView to dragLayer so it can stay on top of the scrim.
+ * It needs to be called after [getTargetObjectLocation] because [mTempRect] needs to be
+ * populated.
+ */
+ private fun copyIconToDragLayer(insets: Rect) {
+ iconView = IconView(context).apply {
+ layoutParams = FrameLayout.LayoutParams(
+ taskContainer.iconView.width,
+ taskContainer.iconView.height
+ )
+ x = mTempRect.left.toFloat() - insets.left
+ y = mTempRect.top.toFloat() - insets.top
+ drawable = taskContainer.iconView.drawable
+ setDrawableSize(
+ taskContainer.iconView.drawableWidth,
+ taskContainer.iconView.drawableHeight
+ )
+ }
+
+ popupContainer.addView(iconView)
}
/**
@@ -217,7 +276,10 @@
val dragLayer: InsettableFrameLayout = popupContainer
val insets = dragLayer.insets
- // Put to the right of the icon if there is space, which means left aligned with the menu
+ copyIconToDragLayer(insets)
+
+ // Put this menu to the right of the icon if there is space,
+ // which means the arrow is left aligned with the menu
val rightAlignedMenuStartX = mTempRect.left - widthWithArrow
val leftAlignedMenuStartX = mTempRect.right + extraHorizontalSpace
mIsLeftAligned = if (mIsRtl) {
@@ -229,18 +291,17 @@
var menuStartX = if (mIsLeftAligned) leftAlignedMenuStartX else rightAlignedMenuStartX
- // Offset y so that the arrow and first row are center-aligned with the original icon.
+ // Offset y so that the arrow and row are center-aligned with the original icon.
val iconHeight = mTempRect.height()
- val optionHeight = optionMeasuredHeight
- val yOffset = (optionHeight - iconHeight) / 2
- var menuStartY = mTempRect.top - yOffset
+ val yOffset = (optionMeasuredHeight - iconHeight) / 2
+ var menuStartY = mTempRect.top - yOffset - extraSpaceForSecondRowAlignment
// Insets are added later, so subtract them now.
menuStartX -= insets.left
menuStartY -= insets.top
- setX(menuStartX.toFloat())
- setY(menuStartY.toFloat())
+ x = menuStartX.toFloat()
+ y = menuStartY.toFloat()
val lp = layoutParams as FrameLayout.LayoutParams
val arrowLp = mArrow.layoutParams as FrameLayout.LayoutParams
@@ -251,7 +312,8 @@
override fun addArrow() {
popupContainer.addView(mArrow)
mArrow.x = getArrowX()
- mArrow.y = y + (optionMeasuredHeight / 2) - (mArrowHeight / 2)
+ mArrow.y = y + (optionMeasuredHeight / 2) - (mArrowHeight / 2) +
+ extraSpaceForSecondRowAlignment
updateArrowColor()
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 1ff2a88..e8077cf 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -30,6 +30,7 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -38,6 +39,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
+import android.annotation.IdRes;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
@@ -840,7 +842,9 @@
TaskIdAttributeContainer menuContainer =
mTaskIdAttributeContainer[iconView == mIconView ? 0 : 1];
if (mActivity.getDeviceProfile().overviewShowAsGrid) {
- return TaskMenuViewWithArrow.Companion.showForTask(menuContainer);
+ boolean alignSecondRow = getRecentsView().isOnGridBottomRow(menuContainer.getTaskView())
+ && mActivity.getDeviceProfile().isLandscape;
+ return TaskMenuViewWithArrow.Companion.showForTask(menuContainer, alignSecondRow);
} else {
return TaskMenuView.showForTask(menuContainer);
}
@@ -1302,10 +1306,14 @@
getContext().getText(R.string.accessibility_close)));
final Context context = getContext();
- // TODO(b/200609838) Determine which task to run A11y action on when in split screen
- for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
- mActivity.getDeviceProfile(), mTaskIdAttributeContainer[0])) {
- info.addAction(s.createAccessibilityAction(context));
+ for (TaskIdAttributeContainer taskContainer : mTaskIdAttributeContainer) {
+ if (taskContainer == null) {
+ continue;
+ }
+ for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
+ mActivity.getDeviceProfile(), taskContainer)) {
+ info.addAction(s.createAccessibilityAction(context));
+ }
}
if (mDigitalWellBeingToast.hasLimit()) {
@@ -1336,12 +1344,16 @@
return true;
}
- // TODO(b/200609838) Determine which task to run A11y action on when in split screen
- for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
- mActivity.getDeviceProfile(), mTaskIdAttributeContainer[0])) {
- if (s.hasHandlerForAction(action)) {
- s.onClick(this);
- return true;
+ for (TaskIdAttributeContainer taskContainer : mTaskIdAttributeContainer) {
+ if (taskContainer == null) {
+ continue;
+ }
+ for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
+ mActivity.getDeviceProfile(), taskContainer)) {
+ if (s.hasHandlerForAction(action)) {
+ s.onClick(this);
+ return true;
+ }
}
}
@@ -1558,7 +1570,6 @@
mScale = previewWidth / (previewWidth + currentInsetsLeft + currentInsetsRight);
}
}
-
}
public class TaskIdAttributeContainer {
@@ -1567,6 +1578,8 @@
private final IconView mIconView;
/** Defaults to STAGE_POSITION_UNDEFINED if in not a split screen task view */
private @SplitConfigurationOptions.StagePosition int mStagePosition;
+ @IdRes
+ private final int mA11yNodeId;
public TaskIdAttributeContainer(Task task, TaskThumbnailView thumbnailView,
IconView iconView, int stagePosition) {
@@ -1574,6 +1587,8 @@
this.mThumbnailView = thumbnailView;
this.mIconView = iconView;
this.mStagePosition = stagePosition;
+ this.mA11yNodeId = (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) ?
+ R.id.split_bottomRight_appInfo : R.id.split_topLeft_appInfo;
}
public TaskThumbnailView getThumbnailView() {
@@ -1603,5 +1618,9 @@
void setStagePosition(@SplitConfigurationOptions.StagePosition int stagePosition) {
this.mStagePosition = stagePosition;
}
+
+ public int getA11yNodeId() {
+ return mA11yNodeId;
+ }
}
}
diff --git a/res/values/id.xml b/res/values/id.xml
index ebc4075..508caff 100644
--- a/res/values/id.xml
+++ b/res/values/id.xml
@@ -21,6 +21,10 @@
<item type="id" name="view_type_widgets_list" />
<item type="id" name="view_type_widgets_header" />
<item type="id" name="view_type_widgets_search_header" />
+ <!-- Used for A11y actions in staged split to identify each task uniquely -->
+ <item type="id" name="split_topLeft_appInfo" />
+ <item type="id" name="split_bottomRight_appInfo" />
+
<!-- Do not change, must be kept in sync with sysui navbar button IDs for tests! -->
<item type="id" name="home" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5f53d4e..868b5f3 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -40,9 +40,10 @@
<!-- Options for recent tasks -->
<!-- Title for an option to enter split screen mode for a given app -->
<string name="recent_task_option_split_screen">Split screen</string>
- <string translatable="false" name="split_screen_position_top">Split top</string>
- <string translatable="false" name="split_screen_position_left">Split left</string>
- <string translatable="false" name="split_screen_position_right">Split right</string>
+ <string name="split_screen_position_top">Split top</string>
+ <string name="split_screen_position_left">Split left</string>
+ <string name="split_screen_position_right">Split right</string>
+ <string name="split_app_info_accessibility">App info for %1$s</string>
<!-- Widgets -->
<!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] -->
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 5e67bf0..9fad2eb 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -814,15 +814,23 @@
Point padding = getTotalWorkspacePadding();
int numColumns = isTwoPanels ? inv.numColumns * 2 : inv.numColumns;
- int cellLayoutTotalPadding =
- isTwoPanels ? 4 * cellLayoutPaddingLeftRightPx : 2 * cellLayoutPaddingLeftRightPx;
- int screenWidthPx = availableWidthPx - padding.x - cellLayoutTotalPadding;
+ int screenWidthPx = getWorkspaceWidth(padding);
result.x = calculateCellWidth(screenWidthPx, cellLayoutBorderSpacePx.x, numColumns);
result.y = calculateCellHeight(availableHeightPx - padding.y
- cellLayoutBottomPaddingPx, cellLayoutBorderSpacePx.y, inv.numRows);
return result;
}
+ public int getWorkspaceWidth() {
+ return getWorkspaceWidth(getTotalWorkspacePadding());
+ }
+
+ public int getWorkspaceWidth(Point workspacePadding) {
+ int cellLayoutTotalPadding =
+ isTwoPanels ? 4 * cellLayoutPaddingLeftRightPx : 2 * cellLayoutPaddingLeftRightPx;
+ return availableWidthPx - workspacePadding.x - cellLayoutTotalPadding;
+ }
+
public Point getTotalWorkspacePadding() {
updateWorkspacePadding();
return new Point(workspacePadding.left + workspacePadding.right,
diff --git a/src/com/android/launcher3/anim/RevealOutlineAnimation.java b/src/com/android/launcher3/anim/RevealOutlineAnimation.java
index f99dabc..f5a746f 100644
--- a/src/com/android/launcher3/anim/RevealOutlineAnimation.java
+++ b/src/com/android/launcher3/anim/RevealOutlineAnimation.java
@@ -26,9 +26,27 @@
/** Sets the progress, from 0 to 1, of the reveal animation. */
abstract void setProgress(float progress);
+ /**
+ * @see #createRevealAnimator(View, boolean, float) where startProgress is set to 0.
+ */
public ValueAnimator createRevealAnimator(final View revealView, boolean isReversed) {
- ValueAnimator va =
- isReversed ? ValueAnimator.ofFloat(1f, 0f) : ValueAnimator.ofFloat(0f, 1f);
+ return createRevealAnimator(revealView, isReversed, 0f /* startProgress */);
+ }
+
+ /**
+ * Animates the given View's ViewOutline according to {@link #setProgress(float)}.
+ * @param revealView The View whose outline we are animating.
+ * @param isReversed Whether we are hiding rather than revealing the View.
+ * @param startProgress The progress at which to start the newly created animation. Useful if
+ * the previous reveal animation was cancelled and we want to create a new animation where it
+ * left off. Note that if isReversed=true, we start at 1 - startProgress (and go to 0).
+ * @return The Animator, which the caller must start.
+ */
+ public ValueAnimator createRevealAnimator(final View revealView, boolean isReversed,
+ float startProgress) {
+ ValueAnimator va = isReversed
+ ? ValueAnimator.ofFloat(1f - startProgress, 0f)
+ : ValueAnimator.ofFloat(startProgress, 1f);
final float elevation = revealView.getElevation();
va.addListener(new AnimatorListenerAdapter() {
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 5ebbc67..08d3779 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -41,8 +41,8 @@
implements View.OnClickListener {
private final int mIconResId;
- private final int mLabelResId;
- private final int mAccessibilityActionId;
+ protected final int mLabelResId;
+ protected int mAccessibilityActionId;
protected final T mTarget;
protected final ItemInfo mItemInfo;
@@ -142,11 +142,43 @@
public static class AppInfo<T extends Context & ActivityContext> extends SystemShortcut<T> {
+ @Nullable
+ private SplitAccessibilityInfo mSplitA11yInfo;
+
public AppInfo(T target, ItemInfo itemInfo) {
super(R.drawable.ic_info_no_shadow, R.string.app_info_drop_target_label, target,
itemInfo);
}
+ /**
+ * Constructor used by overview for staged split to provide custom A11y information.
+ *
+ * Future improvements considerations:
+ * Have the logic in {@link #createAccessibilityAction(Context)} be moved to super
+ * call in {@link SystemShortcut#createAccessibilityAction(Context)} by having
+ * SystemShortcut be aware of TaskContainers and staged split.
+ * That way it could directly create the correct node info for any shortcut that supports
+ * split, but then we'll need custom resIDs for each pair of shortcuts.
+ */
+ public AppInfo(T target, ItemInfo itemInfo, SplitAccessibilityInfo accessibilityInfo) {
+ this(target, itemInfo);
+ mSplitA11yInfo = accessibilityInfo;
+ mAccessibilityActionId = accessibilityInfo.nodeId;
+ }
+
+ @Override
+ public AccessibilityNodeInfo.AccessibilityAction createAccessibilityAction(
+ Context context) {
+ if (mSplitA11yInfo != null && mSplitA11yInfo.containsMultipleTasks) {
+ String accessibilityLabel = context.getString(R.string.split_app_info_accessibility,
+ mSplitA11yInfo.taskTitle);
+ return new AccessibilityNodeInfo.AccessibilityAction(mAccessibilityActionId,
+ accessibilityLabel);
+ } else {
+ return super.createAccessibilityAction(context);
+ }
+ }
+
@Override
public void onClick(View view) {
dismissTaskMenuView(mTarget);
@@ -156,6 +188,19 @@
mTarget.getStatsLogManager().logger().withItemInfo(mItemInfo)
.log(LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP);
}
+
+ public static class SplitAccessibilityInfo {
+ public final boolean containsMultipleTasks;
+ public final CharSequence taskTitle;
+ public final int nodeId;
+
+ public SplitAccessibilityInfo(boolean containsMultipleTasks,
+ CharSequence taskTitle, int nodeId) {
+ this.containsMultipleTasks = containsMultipleTasks;
+ this.taskTitle = taskTitle;
+ this.nodeId = nodeId;
+ }
+ }
}
public static final Factory<BaseDraggingActivity> INSTALL = (activity, itemInfo) -> {
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 62b8a48..673b011 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -122,7 +122,6 @@
public static final String REQUEST_MOCK_SENSOR_ROTATION = "mock-sensor-rotation";
public static final String PERMANENT_DIAG_TAG = "TaplTarget";
- public static final String TASKBAR_WINDOW_CRASH = "b/201305599";
public static final String TASK_VIEW_ID_CRASH = "b/195430732";
public static final String NO_DROP_TARGET = "b/195031154";
public static final String NULL_INT_SET = "b/200572078";
diff --git a/tests/Android.bp b/tests/Android.bp
index 3670c37..c329ece 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -23,7 +23,7 @@
// Source code used for test
filegroup {
name: "launcher-tests-src",
- srcs: ["src/**/*.java"],
+ srcs: ["src/**/*.java", "src/**/*.kt"],
}
// Source code used for oop test helpers
diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
deleted file mode 100644
index 8a4590a..0000000
--- a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
+++ /dev/null
@@ -1,201 +0,0 @@
-package com.android.launcher3.model;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Rect;
-import android.util.Pair;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.model.BgDataModel.Callbacks;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.util.ContentWriter;
-import com.android.launcher3.util.Executors;
-import com.android.launcher3.util.GridOccupancy;
-import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.IntSparseArrayMap;
-import com.android.launcher3.util.LauncherModelHelper;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Tests for {@link AddWorkspaceItemsTask}
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class AddWorkspaceItemsTaskTest {
-
- private final ComponentName mComponent1 = new ComponentName("a", "b");
- private final ComponentName mComponent2 = new ComponentName("b", "b");
-
- private Context mTargetContext;
- private InvariantDeviceProfile mIdp;
- private LauncherAppState mAppState;
- private LauncherModelHelper mModelHelper;
-
- private IntArray mExistingScreens;
- private IntArray mNewScreens;
- private IntSparseArrayMap<GridOccupancy> mScreenOccupancy;
-
- @Before
- public void setup() {
- mModelHelper = new LauncherModelHelper();
- mTargetContext = mModelHelper.sandboxContext;
- mIdp = InvariantDeviceProfile.INSTANCE.get(mTargetContext);
- mIdp.numColumns = mIdp.numRows = 5;
- mAppState = LauncherAppState.getInstance(mTargetContext);
-
- mExistingScreens = new IntArray();
- mScreenOccupancy = new IntSparseArrayMap<>();
- mNewScreens = new IntArray();
- }
-
- @After
- public void tearDown() {
- mModelHelper.destroy();
- }
-
- private AddWorkspaceItemsTask newTask(ItemInfo... items) {
- List<Pair<ItemInfo, Object>> list = new ArrayList<>();
- for (ItemInfo item : items) {
- list.add(Pair.create(item, null));
- }
- return new AddWorkspaceItemsTask(list);
- }
-
- @Test
- public void testFindSpaceForItem_prefers_second() throws Exception {
- // First screen has only one hole of size 1
- int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
-
- // Second screen has 2 holes of sizes 3x2 and 2x3
- setupWorkspaceWithHoles(nextId, 2, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
-
- int[] spaceFound = newTask().findSpaceForItem(
- mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 1, 1);
- assertEquals(1, spaceFound[0]);
- assertTrue(mScreenOccupancy.get(spaceFound[0])
- .isRegionVacant(spaceFound[1], spaceFound[2], 1, 1));
-
- // Find a larger space
- spaceFound = newTask().findSpaceForItem(
- mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 2, 3);
- assertEquals(2, spaceFound[0]);
- assertTrue(mScreenOccupancy.get(spaceFound[0])
- .isRegionVacant(spaceFound[1], spaceFound[2], 2, 3));
- }
-
- @Test
- public void testFindSpaceForItem_adds_new_screen() throws Exception {
- // First screen has 2 holes of sizes 3x2 and 2x3
- setupWorkspaceWithHoles(1, 1, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
-
- IntArray oldScreens = mExistingScreens.clone();
- int[] spaceFound = newTask().findSpaceForItem(
- mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 3, 3);
- assertFalse(oldScreens.contains(spaceFound[0]));
- assertTrue(mNewScreens.contains(spaceFound[0]));
- }
-
- @Test
- public void testAddItem_existing_item_ignored() throws Exception {
- WorkspaceItemInfo info = new WorkspaceItemInfo();
- info.intent = new Intent().setComponent(mComponent1);
-
- // Setup a screen with a hole
- setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
-
- // Nothing was added
- assertTrue(mModelHelper.executeTaskForTest(newTask(info)).isEmpty());
- }
-
- @Test
- public void testAddItem_some_items_added() throws Exception {
- Callbacks callbacks = mock(Callbacks.class);
- Executors.MAIN_EXECUTOR.submit(() -> mModelHelper.getModel().addCallbacks(callbacks)).get();
-
- WorkspaceItemInfo info = new WorkspaceItemInfo();
- info.intent = new Intent().setComponent(mComponent1);
-
- WorkspaceItemInfo info2 = new WorkspaceItemInfo();
- info2.intent = new Intent().setComponent(mComponent2);
-
- // Setup a screen with a hole
- setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
-
- mModelHelper.executeTaskForTest(newTask(info, info2)).get(0).run();
- ArgumentCaptor<ArrayList> notAnimated = ArgumentCaptor.forClass(ArrayList.class);
- ArgumentCaptor<ArrayList> animated = ArgumentCaptor.forClass(ArrayList.class);
-
- // only info2 should be added because info was already added to the workspace
- // in setupWorkspaceWithHoles()
- verify(callbacks).bindAppsAdded(any(IntArray.class), notAnimated.capture(),
- animated.capture());
- assertTrue(notAnimated.getValue().isEmpty());
-
- assertEquals(1, animated.getValue().size());
- assertTrue(animated.getValue().contains(info2));
- }
-
- private int setupWorkspaceWithHoles(int startId, int screenId, Rect... holes) throws Exception {
- return mModelHelper.executeSimpleTask(
- model -> writeWorkspaceWithHoles(model, startId, screenId, holes));
- }
-
- private int writeWorkspaceWithHoles(
- BgDataModel bgDataModel, int startId, int screenId, Rect... holes) {
- GridOccupancy occupancy = new GridOccupancy(mIdp.numColumns, mIdp.numRows);
- occupancy.markCells(0, 0, mIdp.numColumns, mIdp.numRows, true);
- for (Rect r : holes) {
- occupancy.markCells(r, false);
- }
-
- mExistingScreens.add(screenId);
- mScreenOccupancy.append(screenId, occupancy);
-
- for (int x = 0; x < mIdp.numColumns; x++) {
- for (int y = 0; y < mIdp.numRows; y++) {
- if (!occupancy.cells[x][y]) {
- continue;
- }
-
- WorkspaceItemInfo info = new WorkspaceItemInfo();
- info.intent = new Intent().setComponent(mComponent1);
- info.id = startId++;
- info.screenId = screenId;
- info.cellX = x;
- info.cellY = y;
- info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
- bgDataModel.addItem(mTargetContext, info, false);
-
- ContentWriter writer = new ContentWriter(mTargetContext);
- info.writeToValues(writer);
- writer.put(Favorites._ID, info.id);
- mTargetContext.getContentResolver().insert(Favorites.CONTENT_URI,
- writer.getValues(mTargetContext));
- }
- }
- return startId;
- }
-}
diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
new file mode 100644
index 0000000..e315658
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.Rect
+import android.util.Pair
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherSettings
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.util.*
+import com.android.launcher3.util.IntArray
+import org.junit.After
+import org.junit.Assert.*
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.*
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito.verify
+import kotlin.collections.ArrayList
+
+/**
+ * Tests for [AddWorkspaceItemsTask]
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AddWorkspaceItemsTaskTest {
+
+ @Captor
+ private lateinit var animatedItemArgumentCaptor: ArgumentCaptor<ArrayList<ItemInfo>>
+
+ @Captor
+ private lateinit var notAnimatedItemArgumentCaptor: ArgumentCaptor<ArrayList<ItemInfo>>
+
+ @Mock
+ private lateinit var dataModelCallbacks: BgDataModel.Callbacks
+
+ private lateinit var mTargetContext: Context
+ private lateinit var mIdp: InvariantDeviceProfile
+ private lateinit var mAppState: LauncherAppState
+ private lateinit var mModelHelper: LauncherModelHelper
+ private lateinit var mExistingScreens: IntArray
+ private lateinit var mNewScreens: IntArray
+ private lateinit var mScreenOccupancy: IntSparseArrayMap<GridOccupancy>
+
+ private val emptyScreenHoles = listOf(Rect(0, 0, 5, 5))
+ private val fullScreenHoles = emptyList<Rect>()
+
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ mModelHelper = LauncherModelHelper()
+ mTargetContext = mModelHelper.sandboxContext
+ mIdp = InvariantDeviceProfile.INSTANCE[mTargetContext]
+ mIdp.numRows = 5
+ mIdp.numColumns = mIdp.numRows
+ mAppState = LauncherAppState.getInstance(mTargetContext)
+ mExistingScreens = IntArray()
+ mScreenOccupancy = IntSparseArrayMap()
+ mNewScreens = IntArray()
+ Executors.MAIN_EXECUTOR.submit { mModelHelper.model.addCallbacks(dataModelCallbacks) }.get()
+ }
+
+ @After
+ fun tearDown() {
+ mModelHelper.destroy()
+ }
+
+ @Test
+ fun justEnoughSpaceOnFirstScreen_whenFindSpaceForItem_thenReturnFirstScreenId() {
+ setupWorkspacesWithHoles(
+ screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 hole
+ // 2 holes of sizes 3x2 and 2x3
+ screen2 = listOf(Rect(2, 0, 5, 2), Rect(0, 2, 2, 5)),
+ )
+
+ val spaceFound = newTask().findSpaceForItem(
+ mAppState, mModelHelper.bgDataModel, mExistingScreens, mNewScreens, 1, 1)
+ assertEquals(1, spaceFound[0])
+ assertTrue(mScreenOccupancy[spaceFound[0]]
+ .isRegionVacant(spaceFound[1], spaceFound[2], 1, 1))
+ }
+
+ @Test
+ fun notEnoughSpaceOnFirstScreen_whenFindSpaceForItem_thenReturnSecondScreenId() {
+ setupWorkspacesWithHoles(
+ screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 hole
+ // 2 holes of sizes 3x2 and 2x3
+ screen2 = listOf(Rect(2, 0, 5, 2), Rect(0, 2, 2, 5)),
+ )
+
+ // Find a larger space
+ val spaceFound = newTask().findSpaceForItem(
+ mAppState, mModelHelper.bgDataModel, mExistingScreens, mNewScreens, 2, 3)
+ assertEquals(2, spaceFound[0])
+ assertTrue(mScreenOccupancy[spaceFound[0]]
+ .isRegionVacant(spaceFound[1], spaceFound[2], 2, 3))
+ }
+
+ @Test
+ fun notEnoughSpaceOnExistingScreens_whenFindSpaceForItem_thenReturnNewScreenId() {
+ setupWorkspacesWithHoles(
+ // 2 holes of sizes 3x2 and 2x3
+ screen1 = listOf(Rect(2, 0, 5, 2), Rect(0, 2, 2, 5)),
+ // 2 holes of sizes 1x2 and 2x2
+ screen2 = listOf(Rect(1, 0, 2, 2), Rect(3, 2, 5, 4)),
+ )
+
+ val oldScreens = mExistingScreens.clone()
+ val spaceFound = newTask().findSpaceForItem(
+ mAppState, mModelHelper.bgDataModel, mExistingScreens, mNewScreens, 3, 3)
+ assertFalse(oldScreens.contains(spaceFound[0]))
+ assertTrue(mNewScreens.contains(spaceFound[0]))
+ }
+
+ @Test
+ fun enoughSpaceOnFirstScreen_whenTaskRuns_thenAddItemToFirstScreen() {
+ val workspaceHoles = createWorkspaceHoles(
+ screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 space
+ screen2 = listOf(Rect(2, 0, 5, 2)), // 3x2 space
+ )
+ val addedItems = testAddItems(workspaceHoles, getNewItem())
+ assertEquals(1, addedItems.size)
+ assertEquals(1, addedItems.first().itemInfo.screenId)
+ }
+
+ @Test
+ fun firstPageIsFull_whenTaskRuns_thenAddItemToSecondScreen() {
+ val workspaceHoles = createWorkspaceHoles(
+ screen1 = fullScreenHoles,
+ )
+ val addedItems = testAddItems(workspaceHoles, getNewItem())
+ assertEquals(1, addedItems.size)
+ assertEquals(2, addedItems.first().itemInfo.screenId)
+ }
+
+ @Test
+ fun firstScreenIsEmptyButSecondIsNotEmpty_whenTaskRuns_thenAddItemToSecondScreen() {
+ val workspaceHoles = createWorkspaceHoles(
+ screen1 = emptyScreenHoles,
+ screen2 = listOf(Rect(2, 0, 5, 2)), // 3x2 space
+ )
+ val addedItems = testAddItems(workspaceHoles, getNewItem())
+ assertEquals(1, addedItems.size)
+ assertEquals(2, addedItems.first().itemInfo.screenId)
+ }
+
+ @Test
+ fun twoEmptyMiddleScreens_whenTaskRuns_thenAddItemToThirdScreen() {
+ val workspaceHoles = createWorkspaceHoles(
+ screen1 = emptyScreenHoles,
+ screen2 = emptyScreenHoles,
+ screen3 = listOf(Rect(1, 1, 4, 4)), // 3x3 space
+ )
+ val addedItems = testAddItems(workspaceHoles, getNewItem())
+ assertEquals(1, addedItems.size)
+ assertEquals(3, addedItems.first().itemInfo.screenId)
+ }
+
+ @Test
+ fun allPagesAreFull_whenTaskRuns_thenAddItemToNewScreen() {
+ val workspaceHoles = createWorkspaceHoles(
+ screen1 = fullScreenHoles,
+ screen2 = fullScreenHoles,
+ )
+ val addedItems = testAddItems(workspaceHoles, getNewItem())
+ assertEquals(1, addedItems.size)
+ assertEquals(3, addedItems.first().itemInfo.screenId)
+ }
+
+ @Test
+ fun firstTwoPagesAreFull_and_ThirdPageIsEmpty_whenTaskRuns_thenAddItemToThirdPage() {
+ val workspaceHoles = createWorkspaceHoles(
+ screen1 = fullScreenHoles,
+ screen2 = fullScreenHoles,
+ screen3 = emptyScreenHoles
+ )
+ val addedItems = testAddItems(workspaceHoles, getNewItem())
+ assertEquals(1, addedItems.size)
+ assertEquals(3, addedItems.first().itemInfo.screenId)
+ }
+
+ @Test
+ fun itemIsAlreadyAdded_whenTaskRun_thenIgnoreItem() {
+ val task = newTask(getExistingItem())
+ setupWorkspacesWithHoles(
+ screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 hole
+ )
+
+ // Nothing was added
+ assertTrue(mModelHelper.executeTaskForTest(task).isEmpty())
+ }
+
+ @Test
+ fun newAndExistingItems_whenTaskRun_thenAddOnlyTheNewOne() {
+ val newItem = getNewItem()
+ val workspaceHoles = createWorkspaceHoles(
+ screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 hole
+ )
+ val addedItems = testAddItems(workspaceHoles, getExistingItem(), newItem)
+ assertEquals(1, addedItems.size)
+ val addedItem = addedItems.first()
+ assert(addedItem.isAnimated)
+ val addedItemInfo = addedItem.itemInfo
+ assertEquals(1, addedItemInfo.screenId)
+ assertEquals(newItem, addedItemInfo)
+ }
+
+ private fun testAddItems(
+ workspaceHoles: List<List<Rect>>,
+ vararg itemsToAdd: WorkspaceItemInfo
+ ): List<AddedItem> {
+ setupWorkspaces(workspaceHoles)
+ mModelHelper.executeTaskForTest(newTask(*itemsToAdd))[0].run()
+
+ verify(dataModelCallbacks).bindAppsAdded(any(),
+ notAnimatedItemArgumentCaptor.capture(), animatedItemArgumentCaptor.capture())
+
+ val addedItems = mutableListOf<AddedItem>()
+ addedItems.addAll(animatedItemArgumentCaptor.value.map { AddedItem(it, true) })
+ addedItems.addAll(notAnimatedItemArgumentCaptor.value.map { AddedItem(it, false) })
+ return addedItems
+ }
+
+ private fun setupWorkspaces(workspaceHoles: List<List<Rect>>) {
+ var nextItemId = 1
+ var screenId = 1
+ workspaceHoles.forEach { holes ->
+ nextItemId = setupWorkspace(nextItemId, screenId++, *holes.toTypedArray())
+ }
+ }
+
+ private fun setupWorkspace(startId: Int, screenId: Int, vararg holes: Rect): Int {
+ return mModelHelper.executeSimpleTask { dataModel ->
+ writeWorkspaceWithHoles(dataModel, startId, screenId, *holes)
+ }
+ }
+
+ private fun writeWorkspaceWithHoles(
+ bgDataModel: BgDataModel,
+ itemStartId: Int,
+ screenId: Int,
+ vararg holes: Rect,
+ ): Int {
+ var itemId = itemStartId
+ val occupancy = GridOccupancy(mIdp.numColumns, mIdp.numRows)
+ occupancy.markCells(0, 0, mIdp.numColumns, mIdp.numRows, true)
+ holes.forEach { holeRect ->
+ occupancy.markCells(holeRect, false)
+ }
+ mExistingScreens.add(screenId)
+ mScreenOccupancy.append(screenId, occupancy)
+ for (x in 0 until mIdp.numColumns) {
+ for (y in 0 until mIdp.numRows) {
+ if (!occupancy.cells[x][y]) {
+ continue
+ }
+ val info = getExistingItem()
+ info.id = itemId++
+ info.screenId = screenId
+ info.cellX = x
+ info.cellY = y
+ info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP
+ bgDataModel.addItem(mTargetContext, info, false)
+ val writer = ContentWriter(mTargetContext)
+ info.writeToValues(writer)
+ writer.put(LauncherSettings.Favorites._ID, info.id)
+ mTargetContext.contentResolver.insert(LauncherSettings.Favorites.CONTENT_URI,
+ writer.getValues(mTargetContext))
+ }
+ }
+ return itemId
+ }
+
+ private fun setupWorkspacesWithHoles(
+ screen1: List<Rect>? = null,
+ screen2: List<Rect>? = null,
+ screen3: List<Rect>? = null,
+ ) = createWorkspaceHoles(screen1, screen2, screen3)
+ .let(this::setupWorkspaces)
+
+ private fun createWorkspaceHoles(
+ screen1: List<Rect>? = null,
+ screen2: List<Rect>? = null,
+ screen3: List<Rect>? = null,
+ ): List<List<Rect>> = listOfNotNull(screen1, screen2, screen3)
+
+ private fun newTask(vararg items: ItemInfo): AddWorkspaceItemsTask =
+ items.map { Pair.create(it, Any()) }
+ .toMutableList()
+ .let(::AddWorkspaceItemsTask)
+
+ private fun getExistingItem() = WorkspaceItemInfo()
+ .apply { intent = Intent().setComponent(ComponentName("a", "b")) }
+
+ private fun getNewItem() = WorkspaceItemInfo()
+ .apply { intent = Intent().setComponent(ComponentName("b", "b")) }
+}
+
+private data class AddedItem(
+ val itemInfo: ItemInfo,
+ val isAnimated: Boolean
+)
\ No newline at end of file