Merge "Bubble bar flyout polish" into main
diff --git a/quickstep/res/values-en-rGB/strings.xml b/quickstep/res/values-en-rGB/strings.xml
index a74a17b..04b04dd 100644
--- a/quickstep/res/values-en-rGB/strings.xml
+++ b/quickstep/res/values-en-rGB/strings.xml
@@ -139,8 +139,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Always show Taskbar"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Change navigation mode"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Taskbar divider"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Taskbar overflow"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Move to top/left"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Move to bottom/right"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{more app}other{more apps}}"</string>
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 065646a..43b1ae0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -24,6 +24,7 @@
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_OVERVIEW;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_FOR_BUBBLES;
+import static com.android.launcher3.taskbar.TaskbarStashController.UNLOCK_TRANSITION_MEMOIZATION_MS;
import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME;
import static com.android.launcher3.taskbar.bubbles.BubbleBarView.FADE_IN_ANIM_ALPHA_DURATION_MS;
import static com.android.launcher3.taskbar.bubbles.BubbleBarView.FADE_OUT_ANIM_POSITION_DURATION_MS;
@@ -167,7 +168,12 @@
private boolean mSkipNextRecentsAnimEnd;
// Time when FLAG_TASKBAR_HIDDEN was last cleared, SystemClock.elapsedRealtime (milliseconds).
- private long mLastUnlockTimeMs = 0;
+ private long mLastRemoveTaskbarHiddenTimeMs = 0;
+ /**
+ * Time when FLAG_DEVICE_LOCKED was last cleared, plus
+ * {@link TaskbarStashController#UNLOCK_TRANSITION_MEMOIZATION_MS}
+ */
+ private long mLastUnlockTransitionTimeout;
private @Nullable TaskBarRecentsAnimationListener mTaskBarRecentsAnimationListener;
@@ -535,7 +541,7 @@
if (hasAnyFlag(changedFlags, FLAG_TASKBAR_HIDDEN) && !hasAnyFlag(FLAG_TASKBAR_HIDDEN)) {
// Take note of the current time, as the taskbar is made visible again.
- mLastUnlockTimeMs = SystemClock.elapsedRealtime();
+ mLastRemoveTaskbarHiddenTimeMs = SystemClock.elapsedRealtime();
}
boolean isHidden = hasAnyFlag(FLAG_TASKBAR_HIDDEN);
@@ -561,7 +567,8 @@
// with a fingerprint reader. This should only be done when the device was woken
// up via fingerprint reader, however since this information is currently not
// available, opting to always delay the fade-in a bit.
- long durationSinceLastUnlockMs = SystemClock.elapsedRealtime() - mLastUnlockTimeMs;
+ long durationSinceLastUnlockMs = SystemClock.elapsedRealtime()
+ - mLastRemoveTaskbarHiddenTimeMs;
taskbarVisibility.setStartDelay(
Math.max(0, TASKBAR_SHOW_DELAY_MS - durationSinceLastUnlockMs));
}
@@ -631,6 +638,15 @@
boolean isUnlockTransition =
hasAnyFlag(changedFlags, FLAG_DEVICE_LOCKED) && !hasAnyFlag(FLAG_DEVICE_LOCKED);
if (isUnlockTransition) {
+ // the launcher might not be resumed at the time the device is considered
+ // unlocked (when the keyguard goes away), but possibly shortly afterwards.
+ // To play the unlock transition at the time the unstash animation actually happens,
+ // this memoizes the state transition for UNLOCK_TRANSITION_MEMOIZATION_MS.
+ mLastUnlockTransitionTimeout =
+ SystemClock.elapsedRealtime() + UNLOCK_TRANSITION_MEMOIZATION_MS;
+ }
+ boolean isInUnlockTimeout = SystemClock.elapsedRealtime() < mLastUnlockTransitionTimeout;
+ if (isUnlockTransition || isInUnlockTimeout) {
// When transitioning to unlocked, ensure the hotseat is fully visible from the
// beginning. The hotseat itself is animated by LauncherUnlockAnimationController.
mIconAlignment.cancelAnimation();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 6222e53..8991965 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -191,7 +191,7 @@
// Duration for which an unlock event is considered "current", as other events are received
// asynchronously.
- private static final long UNLOCK_TRANSITION_MEMOIZATION_MS = 200;
+ public static final long UNLOCK_TRANSITION_MEMOIZATION_MS = 200;
/**
* The default stash animation, morphing the taskbar into the navbar.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index b495e7d..e522035 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -329,7 +329,8 @@
*/
public void setRecentsButtonDisabled(boolean isDisabled) {
// TODO: check TaskbarStashController#supportsStashing(), to stash instead of setting alpha.
- mTaskbarIconAlpha.get(ALPHA_INDEX_RECENTS_DISABLED).setValue(isDisabled ? 0 : 1);
+ mTaskbarIconAlpha.get(ALPHA_INDEX_RECENTS_DISABLED).animateToValue(isDisabled ? 0 : 1)
+ .start();
}
/**
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 235ec7b..111069f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -155,6 +155,9 @@
0,
timings.getGridSlideSecondaryInterpolator());
+ mRecentsView.handleDesktopTaskInSplitSelectState(builder,
+ timings.getDesktopTaskFadeInterpolator());
+
if (!animate) {
AnimatorSet as = builder.buildAnim();
as.start();
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 9ad6b303..dc7ed24 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -1149,12 +1149,18 @@
if (endTarget != NEW_TASK) {
InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
+ } else {
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
}
if (endTarget != HOME) {
InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);
+ } else {
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);
}
if (endTarget != RECENTS) {
InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS);
+ } else {
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS);
}
switch (endTarget) {
diff --git a/quickstep/src/com/android/quickstep/FocusState.kt b/quickstep/src/com/android/quickstep/FocusState.kt
new file mode 100644
index 0000000..ba3991f
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/FocusState.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep
+
+import android.os.RemoteException
+import android.util.Log
+import android.view.Display.DEFAULT_DISPLAY
+import com.android.launcher3.util.Executors
+import com.android.wm.shell.shared.IFocusTransitionListener.Stub
+import com.android.wm.shell.shared.IShellTransitions
+
+/** Class to track focus state of displays and windows */
+class FocusState {
+
+ var focusedDisplayId = DEFAULT_DISPLAY
+ private set
+
+ private var listeners = mutableSetOf<FocusChangeListener>()
+
+ fun addListener(l: FocusChangeListener) = listeners.add(l)
+
+ fun removeListener(l: FocusChangeListener) = listeners.remove(l)
+
+ fun init(transitions: IShellTransitions?) {
+ try {
+ transitions?.setFocusTransitionListener(
+ object : Stub() {
+ override fun onFocusedDisplayChanged(displayId: Int) {
+ Executors.MAIN_EXECUTOR.execute {
+ listeners.forEach { it.onFocusedDisplayChanged(displayId) }
+ }
+ }
+ }
+ )
+ } catch (e: RemoteException) {
+ Log.w(TAG, "Failed call setFocusTransitionListener", e)
+ }
+ }
+
+ interface FocusChangeListener {
+ fun onFocusedDisplayChanged(displayId: Int)
+ }
+
+ override fun toString() = "{FocusState focusedDisplayId=$focusedDisplayId}"
+
+ companion object {
+ private const val TAG = "FocusState"
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 34435d5..a55cf18 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -161,6 +161,7 @@
private IRemoteAnimationRunner mBackToLauncherRunner;
private IDragAndDrop mDragAndDrop;
private final HomeVisibilityState mHomeVisibilityState = new HomeVisibilityState();
+ private final FocusState mFocusState = new FocusState();
// Used to dedupe calls to SystemUI
private int mLastShelfHeight;
@@ -300,6 +301,7 @@
registerSplitScreenListener(mSplitScreenListener);
registerSplitSelectListener(mSplitSelectListener);
mHomeVisibilityState.init(mShellTransitions);
+ mFocusState.init(mShellTransitions);
setStartingWindowListener(mStartingWindowListener);
setLauncherUnlockAnimationController(
mLauncherActivityClass, mLauncherUnlockAnimationController);
@@ -1144,6 +1146,10 @@
return mHomeVisibilityState;
}
+ public FocusState getFocusState() {
+ return mFocusState;
+ }
+
/**
* Returns a surface which can be used to attach overlays to home task or null if
* the task doesn't exist or sysui is not connected
@@ -1592,6 +1598,7 @@
pw.println("\tmOneHanded=" + mOneHanded);
pw.println("\tmShellTransitions=" + mShellTransitions);
pw.println("\tmHomeVisibilityState=" + mHomeVisibilityState);
+ pw.println("\tmFocusState=" + mFocusState);
pw.println("\tmStartingWindow=" + mStartingWindow);
pw.println("\tmStartingWindowListener=" + mStartingWindowListener);
pw.println("\tmSysuiUnlockAnimationController=" + mSysuiUnlockAnimationController);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
index 5ad55ae..49bff8d 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
@@ -228,10 +228,13 @@
}
float velocityYPxPerS = mVelocityTracker.getYVelocity();
+ float dY = Math.abs(mLastPos.y - mDownPos.y);
if (mCanPlayTaskbarBgAlphaAnimation
&& mMotionMoveCount >= NUM_MOTION_MOVE_THRESHOLD // Arbitrary value
&& velocityYPxPerS != 0 // Ignore these
- && velocityYPxPerS >= mTaskbarSlowVelocityYThreshold) {
+ && velocityYPxPerS >= mTaskbarSlowVelocityYThreshold
+ && dY != 0
+ && dY > mTouchSlop) {
mTaskbarActivityContext.playTaskbarBackgroundAlphaAnimation();
mCanPlayTaskbarBgAlphaAnimation = false;
}
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index 5028da4..9510a05 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -52,7 +52,6 @@
import androidx.annotation.StringRes;
import androidx.annotation.StyleRes;
import androidx.appcompat.app.AlertDialog;
-import androidx.appcompat.content.res.AppCompatResources;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
@@ -610,8 +609,8 @@
private void updateDrawables() {
if (mContext != null) {
- mTutorialFragment.getRootView().setBackground(AppCompatResources.getDrawable(
- mContext, getMockWallpaperResId()));
+ mTutorialFragment.getRootView()
+ .setBackground(mContext.getDrawable(getMockWallpaperResId()));
mTutorialFragment.updateFeedbackAnimation();
mFakeLauncherView.setBackgroundColor(getFakeLauncherColor());
updateFakeViewLayout(mFakeHotseatView, getMockHotseatResId());
@@ -619,9 +618,7 @@
mFakeTaskView.animate().alpha(1).setListener(
AnimatorListeners.forSuccessCallback(() -> mFakeTaskView.animate().cancel()));
mFakePreviousTaskView.setFakeTaskViewFillColor(getMockPreviousAppTaskThumbnailColor());
- mFakeIconView.setBackground(AppCompatResources.getDrawable(
- mContext, getMockAppIconResId()));
-
+ mFakeIconView.setBackground(mContext.getDrawable(getMockAppIconResId()));
mExitingAppView.setBackgroundColor(getExitingAppColor());
mFakeTaskView.setBackgroundColor(getFakeTaskViewColor());
updateHotseatChildViewColor(mHotseatIconView);
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialStepIndicator.java b/quickstep/src/com/android/quickstep/interaction/TutorialStepIndicator.java
index ae0e725..f1fc179 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialStepIndicator.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialStepIndicator.java
@@ -22,8 +22,6 @@
import android.widget.ImageView;
import android.widget.LinearLayout;
-import androidx.appcompat.content.res.AppCompatResources;
-
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.icons.GraphicsUtils;
@@ -91,9 +89,8 @@
int inactiveStepIndicatorColor = GraphicsUtils.getAttrColor(
getContext(), android.R.attr.textColorSecondaryInverse);
for (int i = 0; i < mTotalSteps; i++) {
- Drawable pageIndicatorPillDrawable = AppCompatResources.getDrawable(
- getContext(), R.drawable.tutorial_step_indicator_pill);
-
+ Drawable pageIndicatorPillDrawable =
+ getContext().getDrawable(R.drawable.tutorial_step_indicator_pill);
if (i >= getChildCount()) {
ImageView pageIndicatorPill = new ImageView(getContext());
pageIndicatorPill.setImageDrawable(pageIndicatorPillDrawable);
diff --git a/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
index aa0fd88..c3b072d 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
+++ b/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
@@ -73,9 +73,21 @@
* Returns the first TaskView that should be displayed as a large tile.
*
* @param taskViews List of [TaskView]s
+ * @param splitSelectActive current split state
*/
- fun getFirstLargeTaskView(taskViews: Iterable<TaskView>): TaskView? =
- taskViews.firstOrNull { it.isLargeTile }
+ fun getFirstLargeTaskView(
+ taskViews: MutableIterable<TaskView>,
+ splitSelectActive: Boolean,
+ ): TaskView? =
+ taskViews.firstOrNull { it.isLargeTile && !(splitSelectActive && it is DesktopTaskView) }
+
+ /**
+ * Returns the first TaskView that is not large
+ *
+ * @param taskViews List of [TaskView]s
+ */
+ fun getFirstSmallTaskView(taskViews: MutableIterable<TaskView>): TaskView? =
+ taskViews.firstOrNull { !it.isLargeTile }
/** Returns the last TaskView that should be displayed as a large tile. */
fun getLastLargeTaskView(taskViews: Iterable<TaskView>): TaskView? =
@@ -101,6 +113,12 @@
it.isVisibleInCarousel(runningTaskView, nonRunningTaskCarouselHidden)
}
+ /** Returns if any small tasks are fully visible */
+ fun isAnySmallTaskFullyVisible(
+ taskViews: Iterable<TaskView>,
+ isTaskViewFullyVisible: (TaskView) -> Boolean,
+ ): Boolean = taskViews.any { !it.isLargeTile && isTaskViewFullyVisible(it) }
+
/** Returns the current list of [TaskView] children. */
fun getTaskViews(taskViewCount: Int, requireTaskViewAt: (Int) -> TaskView): Iterable<TaskView> =
(0 until taskViewCount).map(requireTaskViewAt)
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 3449cf2..f708f4b 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -238,7 +238,7 @@
taskViewHeight,
)
val snapshotViewSize =
- if (isPrimaryTaskSplitting) primarySnapshotViewSize else secondarySnapshotViewSize
+ if (isPrimaryTaskSplitting) secondarySnapshotViewSize else primarySnapshotViewSize
if (deviceProfile.isLeftRightSplit) {
// Center view first so scaling happens uniformly, alternatively we can move pivotX to 0
val centerThumbnailTranslationX: Float = (taskViewWidth - snapshotViewSize.x) / 2f
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java b/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java
index b618546..90569b4 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java
@@ -105,6 +105,10 @@
default Interpolator getGridSlidePrimaryInterpolator() { return LINEAR; }
default Interpolator getGridSlideSecondaryInterpolator() { return LINEAR; }
+ default Interpolator getDesktopTaskFadeInterpolator() {
+ return LINEAR;
+ }
+
// Defaults for HomeToSplit
default float getScrimFadeInStartOffset() { return 0; }
default float getScrimFadeInEndOffset() { return 0; }
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 60c6ade..8f579e2 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -743,6 +743,7 @@
*/
public void resetState() {
mSplitSelectDataHolder.resetState();
+ mContainer.<RecentsView>getOverviewPanel().resetDesktopTaskFromSplitSelectState();
dispatchOnSplitSelectionExit();
mRecentsAnimationRunning = false;
mLaunchingTaskView = null;
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index c7777d8..f5be103 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -305,6 +305,14 @@
}
/**
+ * Override the pivot used to apply scale changes.
+ */
+ public void setPivotOverride(PointF pivotOverride) {
+ mPivotOverride = pivotOverride;
+ getFullScreenScale();
+ }
+
+ /**
* Adds animation for all the components corresponding to transition from an app to overview.
*/
public void addAppToOverviewAnim(PendingAnimation pa, Interpolator interpolator) {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index a075c58..82f08e5 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -1706,10 +1706,11 @@
return;
}
TaskView taskView = getTaskViewAt(mNextPage);
- // Snap to fully visible focused task and clear all button.
boolean shouldSnapToLargeTask = taskView != null && taskView.isLargeTile()
- && isTaskViewFullyVisible(taskView);
+ && !mUtils.isAnySmallTaskFullyVisible(getTaskViews(),
+ this::isTaskViewFullyVisible);
boolean shouldSnapToClearAll = mNextPage == indexOfChild(mClearAllButton);
+ // Snap to large tile when grid tasks aren't fully visible or the clear all button.
if (!shouldSnapToLargeTask && !shouldSnapToClearAll) {
return;
}
@@ -3140,7 +3141,7 @@
// Horizontal grid translation for each task
float[] gridTranslations = new float[taskCount];
- int focusedTaskIndex = Integer.MAX_VALUE;
+ int lastLargeTaskIndex = Integer.MAX_VALUE;
Set<Integer> largeTasksIndices = new HashSet<>();
int focusedTaskShift = 0;
int largeTaskWidthAndSpacing = 0;
@@ -3163,8 +3164,12 @@
boolean isLargeTile = taskView.isLargeTile();
if (isLargeTile) {
- topRowWidth += taskWidthAndSpacing;
- bottomRowWidth += taskWidthAndSpacing;
+ // DesktopTaskView`s are hidden during split select state, so we shouldn't count
+ // them when calculating row width.
+ if (!(taskView instanceof DesktopTaskView && isSplitSelectionActive())) {
+ topRowWidth += taskWidthAndSpacing;
+ bottomRowWidth += taskWidthAndSpacing;
+ }
gridTranslations[i] += focusedTaskShift;
gridTranslations[i] += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
@@ -3172,9 +3177,7 @@
taskView.setGridTranslationY((mLastComputedTaskSize.height() + taskTopMargin
- taskView.getLayoutParams().height) / 2f);
- if (taskView.getTaskViewId() == mFocusedTaskViewId) {
- focusedTaskIndex = i;
- }
+ lastLargeTaskIndex = i;
largeTasksIndices.add(i);
largeTaskWidthAndSpacing = taskWidthAndSpacing;
@@ -3183,8 +3186,8 @@
snappedTaskRowWidth = taskWidthAndSpacing;
}
} else {
- if (i > focusedTaskIndex) {
- // For tasks after the focused task, shift by focused task's width and spacing.
+ if (i > lastLargeTaskIndex) {
+ // For tasks after the last large task, shift by large task's width and spacing.
gridTranslations[i] +=
mIsRtl ? largeTaskWidthAndSpacing : -largeTaskWidthAndSpacing;
} else {
@@ -3609,10 +3612,10 @@
float dismissedTaskWidth = 0;
float nextFocusedTaskWidth = 0;
- // Non-grid specific properties.
int[] oldScroll = new int[count];
int[] newScroll = new int[count];
int scrollDiffPerPage = 0;
+ // Non-grid specific properties.
boolean needsCurveUpdates = false;
if (showAsGrid) {
@@ -3642,13 +3645,13 @@
}
}
}
- } else {
- getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC);
- getPageScrolls(newScroll, false,
- v -> v.getVisibility() != GONE && v != dismissedTaskView);
- if (count > 1) {
- scrollDiffPerPage = Math.abs(oldScroll[1] - oldScroll[0]);
- }
+ }
+
+ getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC);
+ getPageScrolls(newScroll, false,
+ v -> v.getVisibility() != GONE && v != dismissedTaskView);
+ if (count > 1) {
+ scrollDiffPerPage = Math.abs(oldScroll[1] - oldScroll[0]);
}
float dismissTranslationInterpolationEnd = 1;
@@ -3800,7 +3803,9 @@
addDismissedTaskAnimations(dismissedTaskView, duration, anim);
}
}
- } else if (!showAsGrid) {
+ } else if (!showAsGrid || (enableLargeDesktopWindowingTile()
+ && dismissedTaskView.isLargeTile()
+ && nextFocusedTaskView == null)) {
int offset = getOffsetToDismissedTask(scrollDiffPerPage, dismissedIndex, taskCount);
int scrollDiff = newScroll[i] - oldScroll[i] + offset;
if (scrollDiff != 0) {
@@ -3812,18 +3817,16 @@
splitTimings);
needsCurveUpdates = true;
}
- } else if (child instanceof TaskView) {
- TaskView taskView = (TaskView) child;
+ } else if (child instanceof TaskView taskView) {
if (isFocusedTaskDismissed) {
if (nextFocusedTaskView != null &&
!isSameGridRow(taskView, nextFocusedTaskView)) {
continue;
}
- } else {
- if (i < dismissedIndex || !isSameGridRow(taskView, dismissedTaskView)) {
- continue;
- }
+ } else if (i < dismissedIndex || !isSameGridRow(taskView, dismissedTaskView)) {
+ continue;
}
+
// Animate task with index >= dismissed index and in the same row as the
// dismissed index or next focused index. Offset successive task dismissal
// durations for a staggered effect.
@@ -4134,15 +4137,18 @@
* - Dragging an adjacent page on the left side (right side for RTL)
*/
private int getOffsetToDismissedTask(int scrollDiffPerPage, int dismissedIndex, int taskCount) {
+ // When mCurrentPage is ClearAllButton, use the last TaskView instead to calculate
+ // offset.
+ int currentPage = mCurrentPage == taskCount ? taskCount - 1 : mCurrentPage;
int offset = mIsRtl ? scrollDiffPerPage : 0;
- if (mCurrentPage == dismissedIndex) {
+ if (currentPage == dismissedIndex) {
int lastPage = taskCount - 1;
- if (mCurrentPage == lastPage) {
+ if (currentPage == lastPage) {
offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage;
}
} else {
// Dismissing an adjacent page.
- int negativeAdjacent = mCurrentPage - 1; // (Right in RTL, left in LTR)
+ int negativeAdjacent = currentPage - 1; // (Right in RTL, left in LTR)
if (dismissedIndex == negativeAdjacent) {
offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage;
}
@@ -5024,6 +5030,35 @@
}
/**
+ * Animate DesktopTaskView(s) to hide in split select
+ */
+ public void handleDesktopTaskInSplitSelectState(PendingAnimation builder,
+ Interpolator deskTopFadeInterPolator) {
+ if (enableLargeDesktopWindowingTile()) {
+ for (TaskView taskView : getTaskViews()) {
+ if (taskView instanceof DesktopTaskView) {
+ builder.addFloat(taskView.getSplitAlphaProperty(),
+ MULTI_PROPERTY_VALUE, 1f, 0f,
+ deskTopFadeInterPolator);
+ }
+ }
+ }
+ }
+
+ /**
+ * While exiting from split mode, show all existing DesktopTaskViews.
+ */
+ public void resetDesktopTaskFromSplitSelectState() {
+ if (enableLargeDesktopWindowingTile()) {
+ for (TaskView taskView : getTaskViews()) {
+ if (taskView instanceof DesktopTaskView) {
+ taskView.setSplitAlpha(1f);
+ }
+ }
+ }
+ }
+
+ /**
* Modifies a PendingAnimation with the animations for entering split staging
*/
public void createSplitSelectInitAnimation(PendingAnimation builder, int duration) {
@@ -5387,6 +5422,13 @@
mTempPointF);
setPivotX(mTempPointF.x);
setPivotY(mTempPointF.y);
+ runActionOnRemoteHandles(
+ remoteTargetHandle -> {
+ remoteTargetHandle.getTaskViewSimulator().setPivotOverride(
+ mTempPointF);
+ remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(
+ false);
+ });
}
});
} else if (!showAsGrid) {
@@ -5857,11 +5899,12 @@
if (mShowAsGridLastOnLayout) {
// For grid Overview, it always start if a large tile (focused task or desktop task) if
// they exist, otherwise it start with the first task.
- TaskView firstLargeTaskView = mUtils.getFirstLargeTaskView(getTaskViews());
+ TaskView firstLargeTaskView = mUtils.getFirstLargeTaskView(getTaskViews(),
+ isSplitSelectionActive());
if (firstLargeTaskView != null) {
firstView = firstLargeTaskView;
} else {
- firstView = getTaskViewAt(0);
+ firstView = mUtils.getFirstSmallTaskView(getTaskViews());
}
} else {
firstView = mUtils.getFirstTaskViewInCarousel(
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index f513a82..cc64dba 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -402,6 +402,15 @@
}
get() = taskViewAlpha.get(ALPHA_INDEX_ATTACH).value
+ var splitAlpha
+ set(value) {
+ splitAlphaProperty.value = value
+ }
+ get() = splitAlphaProperty.value
+
+ val splitAlphaProperty: MultiPropertyFactory<View>.MultiProperty
+ get() = taskViewAlpha.get(ALPHA_INDEX_SPLIT)
+
protected var shouldShowScreenshot = false
get() = !isRunningTask || field
private set
@@ -606,6 +615,7 @@
override fun onRecycle() {
resetPersistentViewTransforms()
attachAlpha = 1f
+ splitAlpha = 1f
// Clear any references to the thumbnail (it will be re-read either from the cache or the
// system on next bind)
if (!enableRefactorTaskThumbnail()) {
@@ -1687,8 +1697,9 @@
private const val ALPHA_INDEX_STABLE = 0
private const val ALPHA_INDEX_ATTACH = 1
+ private const val ALPHA_INDEX_SPLIT = 2
- private const val NUM_ALPHA_CHANNELS = 2
+ private const val NUM_ALPHA_CHANNELS = 3
/** The maximum amount that a task view can be scrimmed, dimmed or tinted. */
const val MAX_PAGE_SCRIM_ALPHA = 0.4f
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt
index d4a3b3a..412bdeb 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt
@@ -29,6 +29,7 @@
import com.android.launcher3.anim.AnimatedFloat
import com.android.launcher3.taskbar.StashedHandleView
import com.android.launcher3.taskbar.TaskbarInsetsController
+import com.android.launcher3.taskbar.TaskbarStashController
import com.android.launcher3.taskbar.bubbles.BubbleBarView
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController
@@ -36,6 +37,7 @@
import com.android.launcher3.util.MultiValueAlpha
import com.android.wm.shell.shared.animation.PhysicsAnimator
import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
+import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
@@ -59,7 +61,7 @@
const val BUBBLE_BAR_WIDTH = 200
const val BUBBLE_BAR_HEIGHT = 100
const val HOTSEAT_TRANSLATION_Y = -45f
- const val TASK_BAR_TRANSLATION_Y = -TASKBAR_BOTTOM_SPACE
+ const val TASK_BAR_TRANSLATION_Y = -TASKBAR_BOTTOM_SPACE.toFloat()
const val HANDLE_VIEW_WIDTH = 150
const val HANDLE_VIEW_HEIGHT = 4
const val BUBBLE_BAR_STASHED_TRANSLATION_Y = -4.5f
@@ -196,6 +198,93 @@
}
@Test
+ fun updateStashedAndExpandedState_unstash_bubbleBarShown_stashedHandleHidden() {
+ // Given bubble bar has bubbles and is stashed
+ mTransientBubbleStashController.isStashed = true
+ whenever(bubbleBarViewController.isHiddenForNoBubbles).thenReturn(false)
+
+ val bubbleInitialTranslation = bubbleView.translationY
+
+ // When unstash
+ getInstrumentation().runOnMainSync {
+ mTransientBubbleStashController.updateStashedAndExpandedState(
+ stash = false,
+ expand = false,
+ )
+ }
+
+ // Wait until animations ends
+ advanceTimeBy(BubbleStashController.BAR_STASH_DURATION)
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
+
+ // Then check BubbleBarController is notified
+ verify(bubbleBarViewController).onStashStateChanging()
+ // Bubble bar is unstashed
+ assertThat(mTransientBubbleStashController.isStashed).isFalse()
+ assertThat(bubbleBarView.translationY).isEqualTo(TASK_BAR_TRANSLATION_Y)
+ assertThat(bubbleBarView.alpha).isEqualTo(1f)
+ assertThat(bubbleBarView.scaleX).isEqualTo(1f)
+ assertThat(bubbleBarView.scaleY).isEqualTo(1f)
+ assertThat(bubbleBarView.background.alpha).isEqualTo(255)
+ // Handle view is hidden
+ assertThat(stashedHandleView.translationY).isEqualTo(0)
+ assertThat(stashedHandleView.alpha).isEqualTo(0)
+ // Bubble view is reset
+ assertThat(bubbleView.translationY).isEqualTo(bubbleInitialTranslation)
+ assertThat(bubbleView.alpha).isEqualTo(1f)
+ }
+
+ @Test
+ fun updateStashedAndExpandedState_stash_animatesAlphaForBubblesAndBackgroundSeparately() {
+ // Given bubble bar has bubbles and is unstashed
+ mTransientBubbleStashController.isStashed = false
+ whenever(bubbleBarViewController.isHiddenForNoBubbles).thenReturn(false)
+
+ // When stash
+ getInstrumentation().runOnMainSync {
+ mTransientBubbleStashController.updateStashedAndExpandedState(
+ stash = true,
+ expand = false,
+ )
+ }
+
+ // Stop after alpha starts
+ advanceTimeBy(TaskbarStashController.TASKBAR_STASH_ALPHA_START_DELAY + 10)
+
+ // Bubble bar alpha is set to 1
+ assertThat(bubbleBarView.alpha).isEqualTo(1f)
+ // We animate alpha for background and children separately
+ assertThat(bubbleView.alpha).isIn(Range.open(0f, 1f))
+ assertThat(bubbleBarView.background.alpha).isIn(Range.open(0, 255))
+ assertThat(bubbleBarView.background.alpha).isNotEqualTo((bubbleView.alpha * 255f).toInt())
+ }
+
+ @Test
+ fun updateStashedAndExpandedState_unstash_animatesAlphaForBubblesAndBackgroundSeparately() {
+ // Given bubble bar has bubbles and is stashed
+ mTransientBubbleStashController.isStashed = true
+ whenever(bubbleBarViewController.isHiddenForNoBubbles).thenReturn(false)
+
+ // When unstash
+ getInstrumentation().runOnMainSync {
+ mTransientBubbleStashController.updateStashedAndExpandedState(
+ stash = false,
+ expand = false,
+ )
+ }
+
+ // Stop after alpha starts
+ advanceTimeBy(TaskbarStashController.TASKBAR_STASH_ALPHA_START_DELAY + 10)
+
+ // Bubble bar alpha is set to 1
+ assertThat(bubbleBarView.alpha).isEqualTo(1f)
+ // We animate alpha for background and children separately
+ assertThat(bubbleView.alpha).isIn(Range.open(0f, 1f))
+ assertThat(bubbleBarView.background.alpha).isIn(Range.open(0, 255))
+ assertThat(bubbleBarView.background.alpha).isNotEqualTo((bubbleView.alpha * 255f).toInt())
+ }
+
+ @Test
fun isSysuiLockedSwitchedToFalseForOverview_unlockAnimationIsShown() {
// Given screen is locked and bubble bar has bubbles
getInstrumentation().runOnMainSync {
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
index 9d0b2ab..cb70694 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
@@ -36,6 +36,7 @@
import com.android.quickstep.RecentsModel
import com.android.quickstep.SystemUiProxy
import com.android.quickstep.util.SplitSelectStateController.SplitFromDesktopController
+import com.android.quickstep.views.RecentsView
import com.android.quickstep.views.RecentsViewContainer
import com.android.systemui.shared.recents.model.Task
import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50
@@ -50,6 +51,7 @@
import org.mockito.Mockito.`when`
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import java.util.function.Consumer
@@ -66,6 +68,7 @@
private val recentsModel: RecentsModel = mock()
private val pendingIntent: PendingIntent = mock()
private val splitFromDesktopController: SplitFromDesktopController = mock()
+ private val recentsView: RecentsView<*, *> = mock()
private lateinit var splitSelectStateController: SplitSelectStateController
@@ -73,6 +76,7 @@
private val nonPrimaryUserHandle = UserHandle(ActivityManager.RunningTaskInfo().userId + 10)
private var taskIdCounter = 0
+
private fun getUniqueId(): Int {
return ++taskIdCounter
}
@@ -90,7 +94,7 @@
statsLogManager,
systemUiProxy,
recentsModel,
- null /*activityBackCallback*/
+ null, /*activityBackCallback*/
)
}
@@ -100,12 +104,12 @@
val groupTask1 =
generateGroupTask(
ComponentName("pomegranate", "juice"),
- ComponentName("pumpkin", "pie")
+ ComponentName("pumpkin", "pie"),
)
val groupTask2 =
generateGroupTask(
ComponentName("hotdog", "juice"),
- ComponentName("personal", "computer")
+ ComponentName("personal", "computer"),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask1)
@@ -122,7 +126,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(nonMatchingComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -141,12 +145,12 @@
val groupTask1 =
generateGroupTask(
ComponentName(matchingPackage, matchingClass),
- ComponentName("pomegranate", "juice")
+ ComponentName("pomegranate", "juice"),
)
val groupTask2 =
generateGroupTask(
ComponentName("pumpkin", "pie"),
- ComponentName("personal", "computer")
+ ComponentName("personal", "computer"),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask1)
@@ -159,12 +163,12 @@
assertEquals(
"ComponentName package mismatched",
it[0].key.baseIntent.component?.packageName,
- matchingPackage
+ matchingPackage,
)
assertEquals(
"ComponentName class mismatched",
it[0].key.baseIntent.component?.className,
- matchingClass
+ matchingClass,
)
assertEquals(it[0], groupTask1.task1)
}
@@ -175,7 +179,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -194,12 +198,12 @@
val groupTask1 =
generateGroupTask(
ComponentName(matchingPackage, matchingClass),
- ComponentName("pomegranate", "juice")
+ ComponentName("pomegranate", "juice"),
)
val groupTask2 =
generateGroupTask(
ComponentName("pumpkin", "pie"),
- ComponentName("personal", "computer")
+ ComponentName("personal", "computer"),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask1)
@@ -216,7 +220,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(nonPrimaryUserComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -237,12 +241,12 @@
ComponentName(matchingPackage, matchingClass),
nonPrimaryUserHandle,
ComponentName("pomegranate", "juice"),
- nonPrimaryUserHandle
+ nonPrimaryUserHandle,
)
val groupTask2 =
generateGroupTask(
ComponentName("pumpkin", "pie"),
- ComponentName("personal", "computer")
+ ComponentName("personal", "computer"),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask1)
@@ -255,12 +259,12 @@
assertEquals(
"ComponentName package mismatched",
it[0].key.baseIntent.component?.packageName,
- matchingPackage
+ matchingPackage,
)
assertEquals(
"ComponentName class mismatched",
it[0].key.baseIntent.component?.className,
- matchingClass
+ matchingClass,
)
assertEquals("userId mismatched", it[0].key.userId, nonPrimaryUserHandle.identifier)
assertEquals(it[0], groupTask1.task1)
@@ -272,7 +276,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(nonPrimaryUserComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -291,12 +295,12 @@
val groupTask1 =
generateGroupTask(
ComponentName(matchingPackage, matchingClass),
- ComponentName("pumpkin", "pie")
+ ComponentName("pumpkin", "pie"),
)
val groupTask2 =
generateGroupTask(
ComponentName("pomegranate", "juice"),
- ComponentName(matchingPackage, matchingClass)
+ ComponentName(matchingPackage, matchingClass),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask2)
@@ -309,12 +313,12 @@
assertEquals(
"ComponentName package mismatched",
it[0].key.baseIntent.component?.packageName,
- matchingPackage
+ matchingPackage,
)
assertEquals(
"ComponentName class mismatched",
it[0].key.baseIntent.component?.className,
- matchingClass
+ matchingClass,
)
assertEquals(it[0], groupTask1.task1)
}
@@ -325,7 +329,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -348,7 +352,7 @@
val groupTask2 =
generateGroupTask(
ComponentName("pomegranate", "juice"),
- ComponentName(matchingPackage, matchingClass)
+ ComponentName(matchingPackage, matchingClass),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask2)
@@ -363,12 +367,12 @@
assertEquals(
"ComponentName package mismatched",
it[1].key.baseIntent.component?.packageName,
- matchingPackage
+ matchingPackage,
)
assertEquals(
"ComponentName class mismatched",
it[1].key.baseIntent.component?.className,
- matchingClass
+ matchingClass,
)
assertEquals(it[1], groupTask2.task2)
}
@@ -379,7 +383,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(nonMatchingComponent, matchingComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -401,7 +405,7 @@
val groupTask2 =
generateGroupTask(
ComponentName("pomegranate", "juice"),
- ComponentName(matchingPackage, matchingClass)
+ ComponentName(matchingPackage, matchingClass),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask2)
@@ -415,12 +419,12 @@
assertEquals(
"ComponentName package mismatched",
it[0].key.baseIntent.component?.packageName,
- matchingPackage
+ matchingPackage,
)
assertEquals(
"ComponentName class mismatched",
it[0].key.baseIntent.component?.className,
- matchingClass
+ matchingClass,
)
assertEquals(it[0], groupTask2.task2)
assertNull("No tasks should have matched", it[1] /*task*/)
@@ -432,7 +436,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent, matchingComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -452,12 +456,12 @@
val groupTask1 =
generateGroupTask(
ComponentName(matchingPackage, matchingClass),
- ComponentName("pumpkin", "pie")
+ ComponentName("pumpkin", "pie"),
)
val groupTask2 =
generateGroupTask(
ComponentName("pomegranate", "juice"),
- ComponentName(matchingPackage, matchingClass)
+ ComponentName(matchingPackage, matchingClass),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask2)
@@ -471,23 +475,23 @@
assertEquals(
"ComponentName package mismatched",
it[0].key.baseIntent.component?.packageName,
- matchingPackage
+ matchingPackage,
)
assertEquals(
"ComponentName class mismatched",
it[0].key.baseIntent.component?.className,
- matchingClass
+ matchingClass,
)
assertEquals(it[0], groupTask1.task1)
assertEquals(
"ComponentName package mismatched",
it[1].key.baseIntent.component?.packageName,
- matchingPackage
+ matchingPackage,
)
assertEquals(
"ComponentName class mismatched",
it[1].key.baseIntent.component?.className,
- matchingClass
+ matchingClass,
)
assertEquals(it[1], groupTask2.task2)
}
@@ -498,7 +502,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent, matchingComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -524,12 +528,12 @@
val groupTask2 =
generateGroupTask(
ComponentName(matchingPackage2, matchingClass2),
- ComponentName(matchingPackage, matchingClass)
+ ComponentName(matchingPackage, matchingClass),
)
val groupTask3 =
generateGroupTask(
ComponentName("hotdog", "pie"),
- ComponentName(matchingPackage, matchingClass)
+ ComponentName(matchingPackage, matchingClass),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask3)
@@ -550,7 +554,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent2, matchingComponent),
true /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -567,7 +571,7 @@
-1 /*stagePosition*/,
ItemInfo(),
null /*splitEvent*/,
- 10 /*alreadyRunningTask*/
+ 10, /*alreadyRunningTask*/
)
assertTrue(splitSelectStateController.isSplitSelectActive)
}
@@ -579,21 +583,23 @@
-1 /*stagePosition*/,
ItemInfo(),
null /*splitEvent*/,
- -1 /*alreadyRunningTask*/
+ -1, /*alreadyRunningTask*/
)
assertTrue(splitSelectStateController.isSplitSelectActive)
}
@Test
fun resetAfterInitial() {
+ whenever(context.getOverviewPanel<RecentsView<*, *>>()).thenReturn(recentsView)
splitSelectStateController.setInitialTaskSelect(
Intent() /*intent*/,
-1 /*stagePosition*/,
ItemInfo(),
null /*splitEvent*/,
- -1
+ -1,
)
splitSelectStateController.resetState()
+ verify(recentsView, times(1)).resetDesktopTaskFromSplitSelectState()
assertFalse(splitSelectStateController.isSplitSelectActive)
}
@@ -622,7 +628,7 @@
// Generate GroupTask with default userId.
private fun generateGroupTask(
task1ComponentName: ComponentName,
- task2ComponentName: ComponentName
+ task2ComponentName: ComponentName,
): GroupTask {
val task1 = Task()
var taskInfo = ActivityManager.RunningTaskInfo()
@@ -642,7 +648,7 @@
return GroupTask(
task1,
task2,
- SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_2_50_50)
+ SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_2_50_50),
)
}
@@ -651,7 +657,7 @@
task1ComponentName: ComponentName,
userHandle1: UserHandle,
task2ComponentName: ComponentName,
- userHandle2: UserHandle
+ userHandle2: UserHandle,
): GroupTask {
val task1 = Task()
var taskInfo = ActivityManager.RunningTaskInfo()
@@ -674,7 +680,7 @@
return GroupTask(
task1,
task2,
- SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_2_50_50)
+ SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_2_50_50),
)
}
}
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index aee49be..f7b04a3 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"App info for %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Usage settings for %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"New window"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Save app pair"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"This app pair isn\'t supported on this device"</string>
diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
index 0a50e8b..9f6b40b 100644
--- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
+++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
@@ -18,6 +18,7 @@
import android.content.Context;
+import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.util.DaggerSingletonTracker;
import dagger.BindsInstance;
@@ -32,6 +33,8 @@
*/
public interface LauncherBaseAppComponent {
DaggerSingletonTracker getDaggerSingletonTracker();
+ InstallSessionHelper getInstallSessionHelper();
+
/** Builder for LauncherBaseAppComponent. */
interface Builder {
@BindsInstance Builder appContext(@ApplicationContext Context context);
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index 124907f..f36f595 100644
--- a/src/com/android/launcher3/pm/InstallSessionHelper.java
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -31,12 +31,17 @@
import com.android.launcher3.Flags;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.SessionCommitReceiver;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.dagger.LauncherBaseAppComponent;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.ItemInstallQueue;
import com.android.launcher3.util.ApplicationInfoWrapper;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
+import com.android.launcher3.util.ExecutorUtil;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
-import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.SafeCloseable;
@@ -47,10 +52,13 @@
import java.util.List;
import java.util.Objects;
+import javax.inject.Inject;
+
/**
* Utility class to tracking install sessions
*/
@SuppressWarnings("NewApi")
+@LauncherAppSingleton
public class InstallSessionHelper implements SafeCloseable {
@NonNull
@@ -64,8 +72,8 @@
private static final boolean DEBUG = false;
@NonNull
- public static final MainThreadInitializedObject<InstallSessionHelper> INSTANCE =
- new MainThreadInitializedObject<>(InstallSessionHelper::new);
+ public static final DaggerSingletonObject<InstallSessionHelper> INSTANCE =
+ new DaggerSingletonObject<>(LauncherBaseAppComponent::getInstallSessionHelper);
@Nullable
private final LauncherApps mLauncherApps;
@@ -82,10 +90,13 @@
@Nullable
private IntSet mPromiseIconIds;
- public InstallSessionHelper(@NonNull final Context context) {
+ @Inject
+ public InstallSessionHelper(@NonNull @ApplicationContext final Context context,
+ DaggerSingletonTracker tracker) {
mInstaller = context.getPackageManager().getPackageInstaller();
mAppContext = context.getApplicationContext();
mLauncherApps = context.getSystemService(LauncherApps.class);
+ ExecutorUtil.executeSyncOnMainOrFail(() -> tracker.addCloseable(this));
}
@Override
diff --git a/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt b/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt
index ca2ef42..a991981 100644
--- a/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt
+++ b/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt
@@ -28,6 +28,7 @@
import androidx.test.filters.SmallTest
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.LauncherPrefs.Companion.PROMISE_ICON_IDS
+import com.android.launcher3.util.DaggerSingletonTracker
import com.android.launcher3.util.Executors.MODEL_EXECUTOR
import com.android.launcher3.util.IntArray
import com.android.launcher3.util.LauncherModelHelper
@@ -53,6 +54,7 @@
private val expectedAppPackage = "expectedAppPackage"
private val expectedInstallerPackage = "expectedInstallerPackage"
private val mockPackageInstaller: PackageInstaller = mock()
+ private val mTracker: DaggerSingletonTracker = mock()
private lateinit var installSessionHelper: InstallSessionHelper
private lateinit var launcherApps: LauncherApps
@@ -62,7 +64,7 @@
whenever(packageManager.packageInstaller).thenReturn(mockPackageInstaller)
whenever(sandboxContext.packageName).thenReturn(expectedInstallerPackage)
launcherApps = sandboxContext.spyService(LauncherApps::class.java)
- installSessionHelper = InstallSessionHelper(sandboxContext)
+ installSessionHelper = InstallSessionHelper(sandboxContext, mTracker)
}
@Test
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 68004bb..cee88ac 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -26,6 +26,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -103,6 +104,8 @@
public static final long DEFAULT_UI_TIMEOUT = TestUtil.DEFAULT_UI_TIMEOUT;
private static final String TAG = "AbstractLauncherUiTest";
+ private static final long BYTES_PER_MEGABYTE = 1 << 20;
+
private static boolean sDumpWasGenerated = false;
private static boolean sActivityLeakReported = false;
private static boolean sSeenKeyguard = false;
@@ -124,6 +127,10 @@
protected String mTargetPackage;
private int mLauncherPid;
+ private final ActivityManager.MemoryInfo mMemoryInfo = new ActivityManager.MemoryInfo();
+ private final ActivityManager mActivityManager;
+ private long mMemoryBefore;
+
/** Detects activity leaks and throws an exception if a leak is found. */
public static void checkDetectedLeaks(LauncherInstrumentation launcher) {
checkDetectedLeaks(launcher, false);
@@ -192,6 +199,8 @@
}
protected AbstractLauncherUiTest() {
+ mActivityManager = InstrumentationRegistry.getContext()
+ .getSystemService(ActivityManager.class);
mLauncher.enableCheckEventsForSuccessfulGestures();
mLauncher.setAnomalyChecker(AbstractLauncherUiTest::verifyKeyguardInvisible);
try {
@@ -311,6 +320,26 @@
initialize(this);
}
+ private long getAvailableMemory() {
+ mActivityManager.getMemoryInfo(mMemoryInfo);
+
+ return Math.divideExact(mMemoryInfo.availMem, BYTES_PER_MEGABYTE);
+ }
+
+ @Before
+ public void saveMemoryBefore() {
+ mMemoryBefore = getAvailableMemory();
+ }
+
+ @After
+ public void logMemoryAfter() {
+ long memoryAfter = getAvailableMemory();
+
+ Log.d(TAG, "Available memory: before=" + mMemoryBefore
+ + "MB, after=" + memoryAfter
+ + "MB, delta=" + (memoryAfter - mMemoryBefore) + "MB");
+ }
+
/** Method that should be called when a test starts. */
public static void onTestStart() {
waitForSetupWizardDismissal();