Merge "Only add accessibility actions in Overview if not in select mode" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index ee7e975..878aa6e 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -519,4 +519,11 @@
namespace: "launcher"
description: "Enable MSDL feedback for Launcher interactions"
bug: "377496684"
+}
+
+flag {
+ name: "taskbar_recents_layout_transition"
+ namespace: "launcher"
+ description: "Enable Taskbar LayoutTransition for Recent Apps"
+ bug: "343521765"
}
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index bd2c7cc..dc0f899 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -55,6 +55,7 @@
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.picker.WidgetsFullSheet;
import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider;
+import com.android.systemui.animation.back.FlingOnBackAnimationCallback;
import java.util.ArrayList;
import java.util.HashSet;
@@ -374,12 +375,12 @@
/**
* Animation callback for different predictive back animation states for the widget picker.
*/
- private class BackAnimationCallback implements OnBackAnimationCallback {
+ private class BackAnimationCallback extends FlingOnBackAnimationCallback {
@Nullable
OnBackAnimationCallback mActiveOnBackAnimationCallback;
@Override
- public void onBackStarted(@NonNull BackEvent backEvent) {
+ public void onBackStartedCompat(@NonNull BackEvent backEvent) {
if (mActiveOnBackAnimationCallback != null) {
mActiveOnBackAnimationCallback.onBackCancelled();
}
@@ -390,7 +391,7 @@
}
@Override
- public void onBackInvoked() {
+ public void onBackInvokedCompat() {
if (mActiveOnBackAnimationCallback == null) {
return;
}
@@ -399,7 +400,7 @@
}
@Override
- public void onBackProgressed(@NonNull BackEvent backEvent) {
+ public void onBackProgressedCompat(@NonNull BackEvent backEvent) {
if (mActiveOnBackAnimationCallback == null) {
return;
}
@@ -407,7 +408,7 @@
}
@Override
- public void onBackCancelled() {
+ public void onBackCancelledCompat() {
if (mActiveOnBackAnimationCallback == null) {
return;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 09dbeb6..b1cb2c6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -372,6 +372,11 @@
* mTaskbarInAppDisplayProgress.value);
mControllers.navbarButtonsViewController
.getOnTaskbarBackgroundNavButtonColorOverride().updateValue(progress);
+
+ if (isBubbleBarEnabled()) {
+ mControllers.bubbleControllers.ifPresent(
+ c -> c.bubbleStashController.setInAppDisplayOverrideProgress(progress));
+ }
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt
index 831faa1..d9589bb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt
@@ -176,12 +176,18 @@
bubbleBarTranslationYForTaskbar
}
- /** Translation Y to align the bubble bar with the hotseat. */
+ /** Translation Y to align the bubble bar with the taskbar. */
val bubbleBarTranslationYForTaskbar: Float
- /** Return translation Y to align the bubble bar with the taskbar. */
+ /** Return translation Y to align the bubble bar with the hotseat. */
val bubbleBarTranslationYForHotseat: Float
+ /**
+ * Show bubble bar is if it were in-app while launcher state is still on home. Set as a progress
+ * value between 0 and 1: 0 - use home layout, 1 - use in-app layout.
+ */
+ var inAppDisplayOverrideProgress: Float
+
/** Dumps the state of BubbleStashController. */
fun dump(pw: PrintWriter) {
pw.println("Bubble stash controller state:")
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt
index c117ad4..9a68335 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt
@@ -22,6 +22,8 @@
import android.graphics.Rect
import android.view.MotionEvent
import android.view.View
+import com.android.app.animation.Interpolators
+import com.android.launcher3.Utilities
import com.android.launcher3.anim.AnimatedFloat
import com.android.launcher3.taskbar.TaskbarInsetsController
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
@@ -97,6 +99,34 @@
return -hotseatVerticalCenter + bubbleBarHeight / 2
}
+ override val bubbleBarTranslationY: Float
+ get() =
+ if (inAppDisplayOverrideProgress > 0f && launcherState == BubbleLauncherState.HOME) {
+ Utilities.mapToRange(
+ inAppDisplayOverrideProgress,
+ /* fromMin = */ 0f,
+ /* fromMax = */ 1f,
+ bubbleBarTranslationYForHotseat,
+ bubbleBarTranslationYForTaskbar,
+ Interpolators.LINEAR,
+ )
+ } else {
+ super.bubbleBarTranslationY
+ }
+
+ override var inAppDisplayOverrideProgress: Float = 0f
+ set(value) {
+ if (field == value) return
+ field = value
+ if (launcherState == BubbleLauncherState.HOME) {
+ bubbleBarViewController.bubbleBarTranslationY.updateValue(bubbleBarTranslationY)
+ if (value == 0f || value == 1f) {
+ // Update insets only when we reach the end values
+ taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
+ }
+ }
+ }
+
override fun init(
taskbarInsetsController: TaskbarInsetsController,
bubbleBarViewController: BubbleBarViewController,
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
index fbeecaa..71303f8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
@@ -126,6 +126,9 @@
override val bubbleBarTranslationYForTaskbar: Float =
-taskbarHotseatDimensionsProvider.getTaskbarBottomSpace().toFloat()
+ /** Not supported in transient mode */
+ override var inAppDisplayOverrideProgress: Float = 0f
+
/** Check if we have handle view controller */
override val hasHandleView: Boolean
get() = bubbleStashedHandleViewController != null
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index fe68ebc..34d9a68 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -190,6 +190,7 @@
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.RecentsViewContainer;
import com.android.quickstep.views.TaskView;
+import com.android.systemui.animation.back.FlingOnBackAnimationCallback;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.unfold.RemoteUnfoldSharedComponent;
@@ -900,12 +901,12 @@
protected void registerBackDispatcher() {
getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
- new OnBackAnimationCallback() {
+ new FlingOnBackAnimationCallback() {
@Nullable OnBackAnimationCallback mActiveOnBackAnimationCallback;
@Override
- public void onBackStarted(@NonNull BackEvent backEvent) {
+ public void onBackStartedCompat(@NonNull BackEvent backEvent) {
if (mActiveOnBackAnimationCallback != null) {
mActiveOnBackAnimationCallback.onBackCancelled();
}
@@ -915,7 +916,7 @@
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Override
- public void onBackInvoked() {
+ public void onBackInvokedCompat() {
// Recreate mActiveOnBackAnimationCallback if necessary to avoid NPE
// because:
// 1. b/260636433: In 3-button-navigation mode, onBackStarted() is not
@@ -931,7 +932,7 @@
}
@Override
- public void onBackProgressed(@NonNull BackEvent backEvent) {
+ public void onBackProgressedCompat(@NonNull BackEvent backEvent) {
if (!FeatureFlags.IS_STUDIO_BUILD
&& mActiveOnBackAnimationCallback == null) {
return;
@@ -940,7 +941,7 @@
}
@Override
- public void onBackCancelled() {
+ public void onBackCancelledCompat() {
if (!FeatureFlags.IS_STUDIO_BUILD
&& mActiveOnBackAnimationCallback == null) {
return;
diff --git a/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt b/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt
index db02f55..f719bed 100644
--- a/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt
+++ b/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt
@@ -217,6 +217,11 @@
animation.addListener(
AnimatorListeners.forEndCallback(
Runnable {
+ // The workspace might stay at a transparent state when the animation is
+ // cancelled, and the alpha will not be recovered (this doesn't apply to scales
+ // somehow). Resetting the alpha for the workspace here.
+ workspace.alpha = 1.0F
+
workspace.setLayerType(View.LAYER_TYPE_NONE, null)
hotseat.setLayerType(View.LAYER_TYPE_NONE, null)
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 9a8041b..839c42e 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -3607,7 +3607,8 @@
* @param dismissingForSplitSelection task dismiss animation is used for entering split
* selection state from app icon
*/
- public void createTaskDismissAnimation(PendingAnimation anim, TaskView dismissedTaskView,
+ public void createTaskDismissAnimation(PendingAnimation anim,
+ @Nullable TaskView dismissedTaskView,
boolean animateTaskView, boolean shouldRemoveTask, long duration,
boolean dismissingForSplitSelection) {
if (mPendingAnimation != null) {
@@ -3622,7 +3623,8 @@
boolean showAsGrid = showAsGrid();
int taskCount = getTaskViewCount();
int dismissedIndex = indexOfChild(dismissedTaskView);
- int dismissedTaskViewId = dismissedTaskView.getTaskViewId();
+ int dismissedTaskViewId =
+ dismissedTaskView != null ? dismissedTaskView.getTaskViewId() : INVALID_TASK_ID;
// Grid specific properties.
boolean isFocusedTaskDismissed = false;
@@ -3637,10 +3639,18 @@
int scrollDiffPerPage = 0;
// Non-grid specific properties.
boolean needsCurveUpdates = false;
+ boolean areAllDesktopTasksDismissed = false;
if (showAsGrid) {
- dismissedTaskWidth = dismissedTaskView.getLayoutParams().width + mPageSpacing;
- isFocusedTaskDismissed = dismissedTaskViewId == mFocusedTaskViewId;
+ if (dismissedTaskView != null) {
+ dismissedTaskWidth = dismissedTaskView.getLayoutParams().width + mPageSpacing;
+ }
+ isFocusedTaskDismissed = dismissedTaskViewId != INVALID_TASK_ID
+ && dismissedTaskViewId == mFocusedTaskViewId;
+ if (dismissingForSplitSelection && getTaskViewAt(
+ mCurrentPage) instanceof DesktopTaskView) {
+ areAllDesktopTasksDismissed = true;
+ }
if (isFocusedTaskDismissed) {
if (isSplitSelectionActive()) {
isStagingFocusedTask = true;
@@ -3812,19 +3822,23 @@
SplitAnimationTimings splitTimings =
AnimUtils.getDeviceOverviewToSplitTimings(mContainer.getDeviceProfile().isTablet);
- int distanceFromDismissedTask = 0;
+ int distanceFromDismissedTask = 1;
+ int stagingTranslation = 0;
+ if (isStagingFocusedTask || areAllDesktopTasksDismissed) {
+ int nextSnappedPage = isStagingFocusedTask
+ ? indexOfChild(mUtils.getFirstSmallTaskView(getTaskViews()))
+ : mUtils.getDesktopTaskViewCount(getTaskViews());
+ stagingTranslation = getPagedOrientationHandler().getPrimaryScroll(this)
+ - getScrollForPage(nextSnappedPage);
+ }
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child == dismissedTaskView) {
- if (animateTaskView) {
- if (dismissingForSplitSelection) {
- createInitialSplitSelectAnimation(anim);
- } else {
- addDismissedTaskAnimations(dismissedTaskView, duration, anim);
- }
+ if (animateTaskView && !dismissingForSplitSelection) {
+ addDismissedTaskAnimations(dismissedTaskView, duration, anim);
}
} else if (!showAsGrid || (enableLargeDesktopWindowingTile()
- && dismissedTaskView.isLargeTile()
+ && dismissedTaskView != null && dismissedTaskView.isLargeTile()
&& nextFocusedTaskView == null && !dismissingForSplitSelection)) {
int offset = getOffsetToDismissedTask(scrollDiffPerPage, dismissedIndex, taskCount);
int scrollDiff = newScroll[i] - oldScroll[i] + offset;
@@ -3838,19 +3852,9 @@
needsCurveUpdates = true;
}
} else if (child instanceof TaskView taskView) {
- if (isFocusedTaskDismissed) {
- if (nextFocusedTaskView != null &&
- !isSameGridRow(taskView, nextFocusedTaskView)) {
- 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.
- distanceFromDismissedTask++;
int staggerColumn = isStagingFocusedTask
? (int) Math.ceil(distanceFromDismissedTask / 2f)
: distanceFromDismissedTask;
@@ -3877,16 +3881,15 @@
: dismissTranslationInterpolationEnd;
Interpolator dismissInterpolator = isStagingFocusedTask ? OVERSHOOT_0_75 : LINEAR;
+ float primaryTranslation = 0;
if (taskView == nextFocusedTaskView) {
// Enlarge the task to be focused next, and translate into focus position.
float scale = mTaskWidth / (float) mLastComputedGridTaskSize.width();
anim.setFloat(taskView, TaskView.DISMISS_SCALE, scale,
clampToProgress(LINEAR, animationStartProgress,
dismissTranslationInterpolationEnd));
- anim.setFloat(taskView, taskView.getPrimaryDismissTranslationProperty(),
- mIsRtl ? dismissedTaskWidth : -dismissedTaskWidth,
- clampToProgress(LINEAR, animationStartProgress,
- dismissTranslationInterpolationEnd));
+ primaryTranslation += mIsRtl ? dismissedTaskWidth : -dismissedTaskWidth;
+ animationEndProgress = dismissTranslationInterpolationEnd;
float secondaryTranslation = -mTaskGridVerticalDiff;
if (!nextFocusedTaskFromTop) {
secondaryTranslation -= mTopBottomRowHeightDiff;
@@ -3896,25 +3899,27 @@
dismissTranslationInterpolationEnd));
anim.add(taskView.getFocusTransitionScaleAndDimOutAnimator(),
clampToProgress(LINEAR, 0f, ANIMATION_DISMISS_PROGRESS_MIDPOINT));
- } else {
- float primaryTranslation =
+ } else if ((isFocusedTaskDismissed && nextFocusedTaskView != null && isSameGridRow(
+ taskView, nextFocusedTaskView))
+ || (!isFocusedTaskDismissed && i >= dismissedIndex && isSameGridRow(
+ taskView, dismissedTaskView))) {
+ primaryTranslation +=
nextFocusedTaskView != null ? nextFocusedTaskWidth : dismissedTaskWidth;
- if (isStagingFocusedTask) {
- // Moves less if focused task is not in scroll position.
- int focusedTaskScroll = getScrollForPage(dismissedIndex);
- int primaryScroll = getPagedOrientationHandler().getPrimaryScroll(this);
- int focusedTaskScrollDiff = primaryScroll - focusedTaskScroll;
- primaryTranslation +=
- mIsRtl ? focusedTaskScrollDiff : -focusedTaskScrollDiff;
- }
+ }
+ primaryTranslation += mIsRtl ? stagingTranslation : -stagingTranslation;
+ if (primaryTranslation != 0) {
anim.setFloat(taskView, taskView.getPrimaryDismissTranslationProperty(),
mIsRtl ? primaryTranslation : -primaryTranslation,
clampToProgress(dismissInterpolator, animationStartProgress,
animationEndProgress));
+ distanceFromDismissedTask++;
}
}
}
+ if (dismissingForSplitSelection) {
+ createInitialSplitSelectAnimation(anim);
+ }
if (needsCurveUpdates) {
anim.addOnFrameCallback(this::updateCurveProperties);
@@ -3923,7 +3928,7 @@
// Add a tiny bit of translation Z, so that it draws on top of other views. This is relevant
// (e.g.) when we dismiss a task by sliding it upward: if there is a row of icons above, we
// want the dragged task to stay above all other views.
- if (animateTaskView) {
+ if (animateTaskView && dismissedTaskView != null) {
dismissedTaskView.setTranslationZ(0.1f);
}
@@ -3935,7 +3940,8 @@
mPendingAnimation.addEndListener(new Consumer<>() {
@Override
public void accept(Boolean success) {
- if (mEnableDrawingLiveTile && dismissedTaskView.isRunningTask() && success) {
+ if (mEnableDrawingLiveTile && dismissedTaskView != null
+ && dismissedTaskView.isRunningTask() && success) {
finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
() -> onEnd(true));
} else {
@@ -3951,7 +3957,7 @@
if (success) {
mAnyTaskHasBeenDismissed = true;
- if (shouldRemoveTask) {
+ if (shouldRemoveTask && dismissedTaskView != null) {
if (dismissedTaskView.isRunningTask()) {
finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
() -> removeTaskInternal(dismissedTaskView));
@@ -5080,9 +5086,10 @@
if (enableLargeDesktopWindowingTile()) {
for (TaskView taskView : getTaskViews()) {
if (taskView instanceof DesktopTaskView) {
+ // Correcting the animation for split mode since we hide DW in split.
builder.addFloat(taskView.getSplitAlphaProperty(),
MULTI_PROPERTY_VALUE, 1f, 0f,
- deskTopFadeInterPolator);
+ clampToProgress(deskTopFadeInterPolator, 0f, 0.1f));
}
}
}
@@ -5140,7 +5147,15 @@
true /* dismissingForSplitSelection*/);
} else {
// Splitting from Home
- createInitialSplitSelectAnimation(builder);
+ TaskView currentPageTaskView = getTaskViewAt(mCurrentPage);
+ // When current page is a Desktop task it needs special handling to
+ // display correct animation in split mode
+ if (currentPageTaskView instanceof DesktopTaskView) {
+ createTaskDismissAnimation(builder, null, true, false, duration,
+ true /* dismissingForSplitSelection*/);
+ } else {
+ createInitialSplitSelectAnimation(builder);
+ }
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashControllerTest.kt
index 00b42bc..e3d41e7 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashControllerTest.kt
@@ -38,6 +38,7 @@
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -228,6 +229,90 @@
.isEqualTo(TASK_BAR_TRANSLATION_Y)
}
+ @Test
+ fun inAppDisplayOverrideProgress_onHome_updatesTranslationFromHomeToInApp() {
+ whenever(bubbleBarViewController.hasBubbles()).thenReturn(false)
+ persistentTaskBarStashController.launcherState = BubbleLauncherState.HOME
+
+ assertThat(persistentTaskBarStashController.bubbleBarTranslationY)
+ .isEqualTo(HOTSEAT_TRANSLATION_Y)
+
+ persistentTaskBarStashController.inAppDisplayOverrideProgress = 0.5f
+
+ val middleBetweenHotseatAndTaskbar = (HOTSEAT_TRANSLATION_Y + TASK_BAR_TRANSLATION_Y) / 2f
+ assertThat(persistentTaskBarStashController.bubbleBarTranslationY)
+ .isWithin(0.1f)
+ .of(middleBetweenHotseatAndTaskbar)
+
+ persistentTaskBarStashController.inAppDisplayOverrideProgress = 1f
+
+ assertThat(persistentTaskBarStashController.bubbleBarTranslationY)
+ .isEqualTo(TASK_BAR_TRANSLATION_Y)
+ }
+
+ @Test
+ fun inAppDisplayOverrideProgress_onHome_updatesInsetsWhenProgressReachesOne() {
+ whenever(bubbleBarViewController.hasBubbles()).thenReturn(false)
+ persistentTaskBarStashController.launcherState = BubbleLauncherState.HOME
+ // Reset invocations to track only changes from in-app display override
+ clearInvocations(taskbarInsetsController)
+
+ // Insets are not updated for values between 0 and 1
+ persistentTaskBarStashController.inAppDisplayOverrideProgress = 0.5f
+ verify(taskbarInsetsController, never()).onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
+
+ // Update insets when progress reaches 1
+ persistentTaskBarStashController.inAppDisplayOverrideProgress = 1f
+ verify(taskbarInsetsController).onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
+ }
+
+ @Test
+ fun inAppDisplayOverrideProgress_onHome_updatesInsetsWhenProgressReachesZero() {
+ whenever(bubbleBarViewController.hasBubbles()).thenReturn(false)
+ persistentTaskBarStashController.launcherState = BubbleLauncherState.HOME
+ persistentTaskBarStashController.inAppDisplayOverrideProgress = 1f
+ // Reset invocations to track only changes from in-app display override
+ clearInvocations(taskbarInsetsController)
+
+ // Insets are not updated for values between 0 and 1
+ persistentTaskBarStashController.inAppDisplayOverrideProgress = 0.5f
+ verify(taskbarInsetsController, never()).onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
+
+ // Update insets when progress reaches 0
+ persistentTaskBarStashController.inAppDisplayOverrideProgress = 0f
+ verify(taskbarInsetsController).onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
+ }
+
+ @Test
+ fun inAppDisplayProgressUpdate_inApp_noTranslationUpdate() {
+ whenever(bubbleBarViewController.hasBubbles()).thenReturn(false)
+ persistentTaskBarStashController.launcherState = BubbleLauncherState.IN_APP
+
+ assertThat(persistentTaskBarStashController.bubbleBarTranslationY)
+ .isEqualTo(TASK_BAR_TRANSLATION_Y)
+
+ persistentTaskBarStashController.inAppDisplayOverrideProgress = 0.5f
+
+ assertThat(persistentTaskBarStashController.bubbleBarTranslationY)
+ .isEqualTo(TASK_BAR_TRANSLATION_Y)
+ }
+
+ @Test
+ fun inAppDisplayOverrideProgress_inApp_noInsetsUpdate() {
+ whenever(bubbleBarViewController.hasBubbles()).thenReturn(false)
+ persistentTaskBarStashController.launcherState = BubbleLauncherState.IN_APP
+
+ // Reset invocations to track only changes from in-app display override
+ clearInvocations(taskbarInsetsController)
+
+ persistentTaskBarStashController.inAppDisplayOverrideProgress = 0.5f
+ persistentTaskBarStashController.inAppDisplayOverrideProgress = 1f
+ persistentTaskBarStashController.inAppDisplayOverrideProgress = 0f
+
+ // Never triggers an update to insets
+ verify(taskbarInsetsController, never()).onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
+ }
+
private fun advanceTimeBy(advanceMs: Long) {
// Advance animator for on-device tests
getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(advanceMs) }
diff --git a/quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
similarity index 73%
rename from quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java
rename to quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
index e981570..5b46dc8 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java
+++ b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
@@ -15,7 +15,9 @@
*/
package com.android.quickstep;
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.launcher3.util.TestUtil.resolveSystemAppInfo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -25,10 +27,13 @@
import android.app.usage.UsageStatsManager;
import android.content.Intent;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.util.BaseLauncherActivityTest;
import com.android.quickstep.views.DigitalWellBeingToast;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskContainer;
@@ -41,30 +46,31 @@
@LargeTest
@RunWith(AndroidJUnit4.class)
-public class TaplDigitalWellBeingToastTest extends AbstractQuickStepTest {
- private static final String CALCULATOR_PACKAGE =
- resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR);
+public class DigitalWellBeingToastTest extends BaseLauncherActivityTest<QuickstepLauncher> {
+
+ public final String calculatorPackage =
+ resolveSystemAppInfo(Intent.CATEGORY_APP_CALCULATOR).packageName;
@Test
- public void testToast() throws Exception {
- startAppFast(CALCULATOR_PACKAGE);
+ public void testToast() {
+ startAppFast(calculatorPackage);
final UsageStatsManager usageStatsManager =
- mTargetContext.getSystemService(UsageStatsManager.class);
+ targetContext().getSystemService(UsageStatsManager.class);
final int observerId = 0;
try {
- final String[] packages = new String[]{CALCULATOR_PACKAGE};
+ final String[] packages = new String[]{calculatorPackage};
// Set time limit for app.
runWithShellPermission(() ->
usageStatsManager.registerAppUsageLimitObserver(observerId, packages,
Duration.ofSeconds(600), Duration.ofSeconds(300),
- PendingIntent.getActivity(mTargetContext, -1, new Intent()
- .setPackage(mTargetContext.getPackageName()),
+ PendingIntent.getActivity(targetContext(), -1, new Intent()
+ .setPackage(targetContext().getPackageName()),
PendingIntent.FLAG_MUTABLE)));
- mLauncher.goHome();
+ loadLauncherSync();
final DigitalWellBeingToast toast = getToast();
waitForLauncherCondition("Toast is not visible", launcher -> toast.getHasLimit());
@@ -74,7 +80,7 @@
runWithShellPermission(
() -> usageStatsManager.unregisterAppUsageLimitObserver(observerId));
- mLauncher.goHome();
+ goToState(LauncherState.NORMAL);
assertFalse("Toast is visible", getToast().getHasLimit());
} finally {
runWithShellPermission(
@@ -83,12 +89,12 @@
}
private DigitalWellBeingToast getToast() {
- mLauncher.getWorkspace().switchToOverview();
+ goToState(LauncherState.OVERVIEW);
final TaskView task = getOnceNotNull("No latest task", launcher -> getLatestTask(launcher));
return getFromLauncher(launcher -> {
TaskContainer taskContainer = task.getTaskContainers().get(0);
- assertTrue("Latest task is not Calculator", CALCULATOR_PACKAGE.equals(
+ assertTrue("Latest task is not Calculator", calculatorPackage.equals(
taskContainer.getTask().getTopComponent().getPackageName()));
return taskContainer.getDigitalWellBeingToast();
});
@@ -105,6 +111,5 @@
} finally {
getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
}
-
}
}
diff --git a/res/drawable/bg_widgets_header_states_two_pane.xml b/res/drawable/bg_widgets_header_states_two_pane.xml
index 5f4b8c6..1ec41a9 100644
--- a/res/drawable/bg_widgets_header_states_two_pane.xml
+++ b/res/drawable/bg_widgets_header_states_two_pane.xml
@@ -14,18 +14,16 @@
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_expanded="true">
- <shape android:shape="rectangle">
- <solid android:color="?attr/widgetPickerHeaderBackgroundColor" />
- <corners android:radius="@dimen/widget_list_top_bottom_corner_radius" />
- </shape>
+ <item android:state_expanded="true" android:state_focused="false">
+ <ripple android:color="@color/accent_ripple_color">
+ <item android:drawable="@drawable/bg_widgets_header_two_pane_expanded_unfocused" />
+ </ripple>
</item>
-
- <item android:state_expanded="false">
- <shape android:shape="rectangle">
- <solid android:color="@android:color/transparent" />
- <corners android:radius="@dimen/widget_list_top_bottom_corner_radius" />
- </shape>
+ <item android:state_expanded="false" android:state_focused="false">
+ <ripple android:color="@color/accent_ripple_color">
+ <item android:drawable="@drawable/bg_widgets_header_two_pane_unexpanded_unfocused" />
+ </ripple>
</item>
+ <item android:drawable="@drawable/bg_widgets_header_two_pane_expanded_focused" android:state_expanded="true" android:state_focused="true" />
+ <item android:drawable="@drawable/bg_widgets_header_two_pane_unexpanded_focused" android:state_expanded="false" android:state_focused="true" />
</selector>
diff --git a/res/drawable/bg_widgets_header_two_pane.xml b/res/drawable/bg_widgets_header_two_pane.xml
index ca3feef..e237002 100644
--- a/res/drawable/bg_widgets_header_two_pane.xml
+++ b/res/drawable/bg_widgets_header_two_pane.xml
@@ -14,13 +14,10 @@
limitations under the License.
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:insetTop="@dimen/widget_list_entry_spacing" >
- <ripple
- android:color="@color/accent_ripple_color"
- android:paddingTop="@dimen/widget_list_header_view_vertical_padding"
- android:paddingBottom="@dimen/widget_list_header_view_vertical_padding" >
- <item android:id="@android:id/mask"
- android:drawable="@drawable/bg_widgets_header_states_two_pane" />
+ android:insetTop="@dimen/widget_list_entry_spacing">
+ <layer-list
+ android:paddingBottom="@dimen/widget_list_header_view_vertical_padding"
+ android:paddingTop="@dimen/widget_list_header_view_vertical_padding">
<item android:drawable="@drawable/bg_widgets_header_states_two_pane" />
- </ripple>
-</inset>
+ </layer-list>
+</inset>
\ No newline at end of file
diff --git a/res/drawable/bg_widgets_header_two_pane_expanded_focused.xml b/res/drawable/bg_widgets_header_two_pane_expanded_focused.xml
new file mode 100644
index 0000000..0ee3d14
--- /dev/null
+++ b/res/drawable/bg_widgets_header_two_pane_expanded_focused.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Draw the focus ring -->
+ <item>
+ <shape>
+ <corners android:radius="@dimen/widget_focus_ring_corner_radius" />
+ <stroke
+ android:width="@dimen/widget_header_focus_ring_width"
+ android:color="?attr/widgetPickerTabBackgroundSelected" />
+ </shape>
+ </item>
+
+ <!-- Draw the background with padding to make it spaced within the focus ring. -->
+ <item
+ android:bottom="@dimen/widget_header_background_border"
+ android:end="@dimen/widget_header_background_border"
+ android:start="@dimen/widget_header_background_border"
+ android:top="@dimen/widget_header_background_border">
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/widget_list_top_bottom_corner_radius" />
+ <solid android:color="?attr/widgetPickerHeaderBackgroundColor" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/res/drawable/bg_widgets_header_two_pane_expanded_unfocused.xml b/res/drawable/bg_widgets_header_two_pane_expanded_unfocused.xml
new file mode 100644
index 0000000..9028ebe
--- /dev/null
+++ b/res/drawable/bg_widgets_header_two_pane_expanded_unfocused.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+ android:paddingBottom="@dimen/widget_list_header_view_vertical_padding"
+ android:paddingTop="@dimen/widget_list_header_view_vertical_padding">
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/widget_list_top_bottom_corner_radius" />
+ <solid android:color="?attr/widgetPickerHeaderBackgroundColor" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/res/drawable/bg_widgets_header_two_pane_unexpanded_focused.xml b/res/drawable/bg_widgets_header_two_pane_unexpanded_focused.xml
new file mode 100644
index 0000000..12dc907
--- /dev/null
+++ b/res/drawable/bg_widgets_header_two_pane_unexpanded_focused.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Draw the focus ring and a transparent background -->
+ <item>
+ <shape>
+ <corners android:radius="@dimen/widget_focus_ring_corner_radius" />
+ <solid android:color="@android:color/transparent" />
+ <stroke
+ android:width="@dimen/widget_header_focus_ring_width"
+ android:color="?attr/widgetPickerTabBackgroundSelected" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/res/drawable/bg_widgets_header_two_pane_unexpanded_unfocused.xml b/res/drawable/bg_widgets_header_two_pane_unexpanded_unfocused.xml
new file mode 100644
index 0000000..ba26f9f
--- /dev/null
+++ b/res/drawable/bg_widgets_header_two_pane_unexpanded_unfocused.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/widget_list_top_bottom_corner_radius" />
+ <solid android:color="@android:color/transparent" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index d1dde3f..c69778a 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -228,6 +228,10 @@
<!-- Bottom margin for the search and recommended widgets container with work profile -->
<dimen name="search_and_recommended_widgets_container_small_bottom_margin">10dp</dimen>
+ <dimen name="widget_header_focus_ring_width">3dp</dimen>
+ <dimen name="widget_focus_ring_corner_radius">28dp</dimen>
+ <dimen name="widget_header_background_border">5dp</dimen>
+
<dimen name="widget_list_top_bottom_corner_radius">28dp</dimen>
<dimen name="widget_list_content_corner_radius">4dp</dimen>
<!-- Button that expands the widget apps list in the widget picker. -->
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 27602af..6468f74 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -186,18 +186,20 @@
*/
public void adjustForBubbleBar(boolean isBubbleBarVisible) {
DeviceProfile dp = mActivity.getDeviceProfile();
+ float adjustedBorderSpace = dp.getHotseatAdjustedBorderSpaceForBubbleBar(getContext());
+ boolean adjustmentRequired = Float.compare(adjustedBorderSpace, 0f) != 0;
ShortcutAndWidgetContainer icons = getShortcutsAndWidgets();
- AnimatorSet animatorSet = new AnimatorSet();
-
// update the translation provider for future layout passes of hotseat icons.
- if (isBubbleBarVisible) {
+ if (adjustmentRequired && isBubbleBarVisible) {
icons.setTranslationProvider(
cellX -> dp.getHotseatAdjustedTranslation(getContext(), cellX));
} else {
icons.setTranslationProvider(null);
}
+ if (!adjustmentRequired) return;
+ AnimatorSet animatorSet = new AnimatorSet();
for (int i = 0; i < icons.getChildCount(); i++) {
View child = icons.getChildAt(i);
float tx = isBubbleBarVisible ? dp.getHotseatAdjustedTranslation(getContext(), i) : 0;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index be2ba57..6145077 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -96,7 +96,6 @@
import static com.android.launcher3.popup.SystemShortcut.INSTALL;
import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
-import static com.android.launcher3.states.RotationHelper.REQUEST_FIXED_LANDSCAPE;
import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
import static com.android.launcher3.testing.shared.TestProtocol.LAUNCHER_ACTIVITY_STOPPED_MESSAGE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -782,12 +781,9 @@
if (!com.android.launcher3.Flags.oneGridSpecs()) {
return;
}
- if (Objects.requireNonNull(mDeviceProfile.inv).isFixedLandscapeMode) {
- // Set rotation fixed
- getRotationHelper().setStateHandlerRequest(REQUEST_FIXED_LANDSCAPE);
- } else {
- getRotationHelper().setStateHandlerRequest(REQUEST_NONE);
- }
+ getRotationHelper().setFixedLandscape(
+ Objects.requireNonNull(mDeviceProfile.inv).isFixedLandscapeMode
+ );
}
public void onAssistantVisibilityChanged(float visibility) {
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index bd604eb..cfb238b 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -43,7 +43,6 @@
import androidx.annotation.FloatRange;
import androidx.annotation.Nullable;
-import com.android.app.animation.Interpolators;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.Launcher;
@@ -277,10 +276,9 @@
return;
}
- float deceleratedProgress = Interpolators.BACK_GESTURE.getInterpolation(backProgress);
float scaleProgress = ScrollableLayoutManager.PREDICTIVE_BACK_MIN_SCALE
+ (1 - ScrollableLayoutManager.PREDICTIVE_BACK_MIN_SCALE)
- * (1 - deceleratedProgress);
+ * (1 - backProgress);
mAllAppScale.updateValue(scaleProgress);
}
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index 94c36c0..3000b25 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -16,6 +16,8 @@
package com.android.launcher3.graphics;
+import static android.content.res.Configuration.UI_MODE_NIGHT_NO;
+import static android.content.res.Configuration.UI_MODE_NIGHT_YES;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
@@ -25,6 +27,7 @@
import android.app.WallpaperColors;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
+import android.content.res.Configuration;
import android.database.Cursor;
import android.hardware.display.DisplayManager;
import android.os.Bundle;
@@ -84,6 +87,7 @@
private static final String KEY_COLORS = "wallpaper_colors";
private static final String KEY_COLOR_RESOURCE_IDS = "color_resource_ids";
private static final String KEY_COLOR_VALUES = "color_values";
+ private static final String KEY_DARK_MODE = "use_dark_mode";
private Context mContext;
private final IBinder mHostToken;
@@ -95,6 +99,7 @@
private final Display mDisplay;
private final WallpaperColors mWallpaperColors;
private SparseIntArray mPreviewColorOverride;
+ @Nullable private Boolean mDarkMode;
private final RunnableList mLifeCycleTracker;
private final SurfaceControlViewHost mSurfaceControlViewHost;
@@ -235,6 +240,8 @@
}
private void updateColorOverrides(Bundle bundle) {
+ mDarkMode =
+ bundle.containsKey(KEY_DARK_MODE) ? bundle.getBoolean(KEY_DARK_MODE) : null;
int[] ids = bundle.getIntArray(KEY_COLOR_RESOURCE_IDS);
int[] colors = bundle.getIntArray(KEY_COLOR_VALUES);
if (ids != null && colors != null) {
@@ -253,6 +260,18 @@
*/
private Context getPreviewContext() {
Context context = mContext.createDisplayContext(mDisplay);
+ if (mDarkMode != null) {
+ Configuration configuration = new Configuration(
+ context.getResources().getConfiguration());
+ if (mDarkMode) {
+ configuration.uiMode &= ~UI_MODE_NIGHT_NO;
+ configuration.uiMode |= UI_MODE_NIGHT_YES;
+ } else {
+ configuration.uiMode &= ~UI_MODE_NIGHT_YES;
+ configuration.uiMode |= UI_MODE_NIGHT_NO;
+ }
+ context = context.createConfigurationContext(configuration);
+ }
if (Flags.newCustomizationPickerUi()) {
if (mPreviewColorOverride != null) {
LocalColorExtractor.newInstance(context)
diff --git a/src/com/android/launcher3/model/GridSizeMigrationDBController.java b/src/com/android/launcher3/model/GridSizeMigrationDBController.java
index 2d6be7e..617cac7 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationDBController.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationDBController.java
@@ -17,7 +17,6 @@
package com.android.launcher3.model;
import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
-import static com.android.launcher3.LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.LauncherSettings.Favorites.TMP_TABLE;
import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
@@ -118,13 +117,14 @@
@NonNull DeviceGridState srcDeviceState,
@NonNull DeviceGridState destDeviceState,
@NonNull DatabaseHelper target,
- @NonNull SQLiteDatabase source) {
+ @NonNull SQLiteDatabase source,
+ boolean isDestNewDb) {
if (!needsToMigrate(srcDeviceState, destDeviceState)) {
return true;
}
- if (LauncherPrefs.get(context).get(IS_FIRST_LOAD_AFTER_RESTORE)
+ if (isDestNewDb
&& srcDeviceState.getColumns().equals(destDeviceState.getColumns())
&& srcDeviceState.getRows() < destDeviceState.getRows()) {
// Only use this strategy when comparing the previous grid to the new grid and the
diff --git a/src/com/android/launcher3/model/GridSizeMigrationLogic.kt b/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
index 07316ef..c856d4b 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
+++ b/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
@@ -46,6 +46,7 @@
destDeviceState: DeviceGridState,
target: DatabaseHelper,
source: SQLiteDatabase,
+ isDestNewDb: Boolean,
) {
if (!GridSizeMigrationDBController.needsToMigrate(srcDeviceState, destDeviceState)) {
return
@@ -57,7 +58,7 @@
// This is a special case where if the grid is the same amount of columns but a larger
// amount of rows we simply copy over the source grid to the destination grid, rather
// than undergoing the general grid migration.
- if (shouldMigrateToStrictlyTallerGrid(isFirstLoad, srcDeviceState, destDeviceState)) {
+ if (shouldMigrateToStrictlyTallerGrid(isDestNewDb, srcDeviceState, destDeviceState)) {
GridSizeMigrationDBController.copyCurrentGridToNewGrid(
context,
destDeviceState,
@@ -335,11 +336,11 @@
/** Only migrate the grid in this manner if the target grid is taller and not wider. */
private fun shouldMigrateToStrictlyTallerGrid(
- isFirstLoad: Boolean,
+ isDestNewDb: Boolean,
srcDeviceState: DeviceGridState,
destDeviceState: DeviceGridState,
): Boolean {
- return isFirstLoad &&
+ return isDestNewDb &&
srcDeviceState.columns == destDeviceState.columns &&
srcDeviceState.rows < destDeviceState.rows
}
diff --git a/src/com/android/launcher3/model/ModelDbController.java b/src/com/android/launcher3/model/ModelDbController.java
index 094798b..6ff8547 100644
--- a/src/com/android/launcher3/model/ModelDbController.java
+++ b/src/com/android/launcher3/model/ModelDbController.java
@@ -89,6 +89,7 @@
import java.io.File;
import java.io.InputStream;
import java.io.StringReader;
+import java.util.List;
/**
* Utility class which maintains an instance of Launcher database and provides utility methods
@@ -368,6 +369,13 @@
InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
DatabaseHelper oldHelper = mOpenHelper;
+
+ // We save the existing db's before creating the destination db helper so we know what logic
+ // to run in grid migration based on if that grid already existed before migration or not.
+ List<String> existingDBs = LauncherFiles.GRID_DB_FILES.stream()
+ .filter(dbName -> mContext.getDatabasePath(dbName).exists())
+ .toList();
+
mOpenHelper = (mContext instanceof SandboxContext) ? oldHelper
: createDatabaseHelper(true, new DeviceGridState(idp).getDbFile());
try {
@@ -376,9 +384,11 @@
// This is the state we want to migrate to that is given by the idp
DeviceGridState destDeviceState = new DeviceGridState(idp);
+ boolean isDestNewDb = !existingDBs.contains(destDeviceState.getDbFile());
+
GridSizeMigrationLogic gridSizeMigrationLogic = new GridSizeMigrationLogic();
gridSizeMigrationLogic.migrateGrid(mContext, srcDeviceState, destDeviceState,
- mOpenHelper, oldHelper.getWritableDatabase());
+ mOpenHelper, oldHelper.getWritableDatabase(), isDestNewDb);
} catch (Exception e) {
resetLauncherDb(restoreEventLogger);
throw new Exception("Failed to migrate grid", e);
@@ -441,6 +451,13 @@
return false;
}
DatabaseHelper oldHelper = mOpenHelper;
+
+ // We save the existing db's before creating the destination db helper so we know what logic
+ // to run in grid migration based on if that grid already existed before migration or not.
+ List<String> existingDBs = LauncherFiles.GRID_DB_FILES.stream()
+ .filter(dbName -> mContext.getDatabasePath(dbName).exists())
+ .toList();
+
mOpenHelper = (mContext instanceof SandboxContext) ? oldHelper
: createDatabaseHelper(true /* forMigration */, targetDbName);
try {
@@ -448,8 +465,11 @@
DeviceGridState srcDeviceState = new DeviceGridState(mContext);
// This is the state we want to migrate to that is given by the idp
DeviceGridState destDeviceState = new DeviceGridState(idp);
+
+ boolean isDestNewDb = !existingDBs.contains(destDeviceState.getDbFile());
+
return GridSizeMigrationDBController.migrateGridIfNeeded(mContext, srcDeviceState,
- destDeviceState, mOpenHelper, oldHelper.getWritableDatabase());
+ destDeviceState, mOpenHelper, oldHelper.getWritableDatabase(), isDestNewDb);
} catch (Exception e) {
FileLog.e(TAG, "Failed to migrate grid", e);
return false;
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index 76f8dd3..5851f62 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -294,6 +294,9 @@
case NOTIFICATION_DOTS_PREFERENCE_KEY:
return BuildConfig.NOTIFICATION_DOTS_ENABLED;
case ALLOW_ROTATION_PREFERENCE_KEY:
+ if (Flags.oneGridSpecs()) {
+ return false;
+ }
if (info.isTablet(info.realBounds)) {
// Launcher supports rotation by default. No need to show this setting.
return false;
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index 1f397d1..7d7ccd3 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -63,7 +63,8 @@
public static final int REQUEST_NONE = 0;
public static final int REQUEST_ROTATE = 1;
public static final int REQUEST_LOCK = 2;
- public static final int REQUEST_FIXED_LANDSCAPE = 3;
+
+ private boolean mIsFixedLandscape = false;
@NonNull
private final BaseActivity mActivity;
@@ -165,6 +166,18 @@
notifyChange();
}
+ public boolean isFixedLandscape() {
+ return mIsFixedLandscape;
+ }
+
+ /**
+ * If fixedLandscape is true then the Launcher become landscape until set false..
+ */
+ public void setFixedLandscape(boolean fixedLandscape) {
+ mIsFixedLandscape = fixedLandscape;
+ notifyChange();
+ }
+
// Used by tests only.
public void forceAllowRotationForTesting(boolean allowRotation) {
if (mDestroyed) return;
@@ -197,9 +210,7 @@
}
final int activityFlags;
- if (mStateHandlerRequest == REQUEST_FIXED_LANDSCAPE) {
- activityFlags = SCREEN_ORIENTATION_USER_LANDSCAPE;
- } else if (mStateHandlerRequest != REQUEST_NONE) {
+ if (mStateHandlerRequest != REQUEST_NONE) {
activityFlags = mStateHandlerRequest == REQUEST_LOCK ?
SCREEN_ORIENTATION_LOCKED : SCREEN_ORIENTATION_UNSPECIFIED;
} else if (mCurrentTransitionRequest != REQUEST_NONE) {
@@ -207,6 +218,8 @@
SCREEN_ORIENTATION_LOCKED : SCREEN_ORIENTATION_UNSPECIFIED;
} else if (mCurrentStateRequest == REQUEST_LOCK) {
activityFlags = SCREEN_ORIENTATION_LOCKED;
+ } else if (mIsFixedLandscape) {
+ activityFlags = SCREEN_ORIENTATION_USER_LANDSCAPE;
} else if (mIgnoreAutoRotateSettings || mCurrentStateRequest == REQUEST_ROTATE
|| mHomeRotationEnabled || mForceAllowRotationForTesting) {
activityFlags = SCREEN_ORIENTATION_UNSPECIFIED;
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index 85aad89..65d02d0 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -298,8 +298,7 @@
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public void onBackProgressed(BackEvent backEvent) {
final float progress = backEvent.getProgress();
- float deceleratedProgress = Interpolators.BACK_GESTURE.getInterpolation(progress);
- mSwipeToDismissProgress.updateValue(deceleratedProgress);
+ mSwipeToDismissProgress.updateValue(progress);
}
/**
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java b/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java
index ce682f1..eb25acf 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java
@@ -29,7 +29,10 @@
import android.app.blob.BlobHandle;
import android.app.blob.BlobStoreManager;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Point;
import android.os.AsyncTask;
@@ -231,6 +234,15 @@
.adoptShellPermissionIdentity(Manifest.permission.WRITE_SECURE_SETTINGS);
}
+ /**
+ * Returns the activity info corresponding to the system app for the provided category
+ */
+ public static ActivityInfo resolveSystemAppInfo(String category) {
+ return getInstrumentation().getTargetContext().getPackageManager().resolveActivity(
+ new Intent(Intent.ACTION_MAIN).addCategory(category),
+ PackageManager.MATCH_SYSTEM_ONLY).activityInfo;
+ }
+
/** Interface to indicate a runnable which can throw any exception. */
public interface UncheckedRunnable {
/** Method to run the task */
diff --git a/tests/src/com/android/launcher3/LauncherIntentTest.java b/tests/src/com/android/launcher3/LauncherIntentTest.java
index 3e16713..a3d9614 100644
--- a/tests/src/com/android/launcher3/LauncherIntentTest.java
+++ b/tests/src/com/android/launcher3/LauncherIntentTest.java
@@ -27,17 +27,23 @@
import com.android.launcher3.allapps.ActivityAllAppsContainerView;
import com.android.launcher3.allapps.SearchRecyclerView;
-import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.util.BaseLauncherActivityTest;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@LargeTest
@RunWith(AndroidJUnit4.class)
-public class LauncherIntentTest extends AbstractLauncherUiTest<Launcher> {
+public class LauncherIntentTest extends BaseLauncherActivityTest<Launcher> {
public final Intent allAppsIntent = new Intent(Intent.ACTION_ALL_APPS);
+ @Before
+ public void setUp() {
+ loadLauncherSync();
+ }
+
@Test
public void testAllAppsIntent() {
// Try executing ALL_APPS intent
@@ -45,7 +51,6 @@
// A-Z view with Main adapter should be loaded
assertOnMainAdapterAToZView();
-
// Try Moving to search view now
moveToSearchView();
// Try executing ALL_APPS intent
@@ -63,12 +68,14 @@
// Search view should be in focus
waitForLauncherCondition("Search view is not in focus.",
launcher -> launcher.getAppsView().getSearchView().hasFocus());
- mLauncher.pressAndHoldKeyCode(KeyEvent.KEYCODE_C, 0);
+
+ injectKeyEvent(KeyEvent.KEYCODE_C, true);
// Upon key press, search recycler view should be loaded
waitForLauncherCondition("Search view not active.",
launcher -> launcher.getAppsView().getActiveRecyclerView()
instanceof SearchRecyclerView);
- mLauncher.unpressKeyCode(KeyEvent.KEYCODE_C, 0);
+
+ injectKeyEvent(KeyEvent.KEYCODE_C, false);
}
// Checks if main adapter view is selected, search bar is out of focus and scroller is at start.
diff --git a/tests/src/com/android/launcher3/allapps/KeyboardFocusTest.java b/tests/src/com/android/launcher3/allapps/KeyboardFocusTest.java
new file mode 100644
index 0000000..44df5b8
--- /dev/null
+++ b/tests/src/com/android/launcher3/allapps/KeyboardFocusTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 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.allapps;
+
+import android.view.KeyEvent;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.util.BaseLauncherActivityTest;
+import com.android.launcher3.views.ActivityContext;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KeyboardFocusTest extends BaseLauncherActivityTest<Launcher> {
+
+ @Test
+ public void testAllAppsFocusApp() {
+ loadLauncherSync();
+ goToState(LauncherState.ALL_APPS);
+ freezeAllApps();
+
+ injectKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, true);
+ injectKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, false);
+ waitForLauncherCondition("No focused child", launcher ->
+ launcher.getAppsView().getActiveRecyclerView().getApps().getFocusedChild()
+ != null);
+ }
+
+ @Test
+ public void testAllAppsExitSearchAndFocusApp() {
+ loadLauncherSync();
+ goToState(LauncherState.ALL_APPS);
+ freezeAllApps();
+
+ executeOnLauncher(launcher -> launcher.getAppsView().getSearchView().requestFocus());
+ waitForLauncherCondition("Search view does not have focus.",
+ launcher -> launcher.getAppsView().getSearchView().hasFocus());
+
+ injectKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, true);
+ injectKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, false);
+ waitForLauncherCondition("No focused child", launcher ->
+ launcher.getAppsView().getActiveRecyclerView().getApps().getFocusedChild()
+ != null);
+ }
+
+ @Test
+ public void testAllAppsExitSearchAndFocusSearchResults() {
+ loadLauncherSync();
+ goToState(LauncherState.ALL_APPS);
+ freezeAllApps();
+
+ executeOnLauncher(launcher -> launcher.getAppsView().getSearchView().requestFocus());
+ waitForLauncherCondition("Search view does not have focus.",
+ launcher -> launcher.getAppsView().getSearchView().hasFocus());
+
+ injectKeyEvent(KeyEvent.KEYCODE_C, true);
+ waitForLauncherCondition("Search view not active.",
+ launcher -> launcher.getAppsView().getActiveRecyclerView()
+ instanceof SearchRecyclerView);
+ injectKeyEvent(KeyEvent.KEYCODE_C, false);
+
+ executeOnLauncher(launcher -> launcher.getAppsView().getSearchUiManager().getEditText()
+ .hideKeyboard(/* clearFocus= */ false));
+ waitForLauncherCondition("Keyboard still visible.",
+ ActivityContext::isSoftwareKeyboardHidden);
+
+ injectKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, true);
+ injectKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, false);
+ waitForLauncherCondition("No focused child", launcher ->
+ launcher.getAppsView().getActiveRecyclerView().getApps().getFocusedChild()
+ != null);
+ }
+}
diff --git a/tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java b/tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java
deleted file mode 100644
index 4e627a9..0000000
--- a/tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2023 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.allapps;
-
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.view.KeyEvent;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.tapl.HomeAllApps;
-import com.android.launcher3.ui.AbstractLauncherUiTest;
-import com.android.launcher3.util.rule.TestStabilityRule;
-import com.android.launcher3.views.ActivityContext;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class TaplKeyboardFocusTest extends AbstractLauncherUiTest<Launcher> {
-
- @Test
- public void testAllAppsFocusApp() {
- final HomeAllApps allApps = mLauncher.goHome().switchToAllApps();
- assertTrue("Launcher internal state is not All Apps",
- isInState(() -> LauncherState.ALL_APPS));
- allApps.freeze();
- try {
- mLauncher.pressAndHoldKeyCode(KeyEvent.KEYCODE_DPAD_DOWN, 0);
- executeOnLauncher(launcher -> assertNotNull("No focused child.",
- launcher.getAppsView().getActiveRecyclerView().getApps().getFocusedChild()));
- } finally {
- allApps.unfreeze();
- }
- }
-
- @Test
- public void testAllAppsExitSearchAndFocusApp() {
- final HomeAllApps allApps = mLauncher.goHome().switchToAllApps();
- assertTrue("Launcher internal state is not All Apps",
- isInState(() -> LauncherState.ALL_APPS));
- allApps.freeze();
- try {
- executeOnLauncher(launcher -> launcher.getAppsView().getSearchView().requestFocus());
- waitForLauncherCondition("Search view does not have focus.",
- launcher -> launcher.getAppsView().getSearchView().hasFocus());
-
- mLauncher.pressAndHoldKeyCode(KeyEvent.KEYCODE_DPAD_DOWN, 0);
- executeOnLauncher(launcher -> assertNotNull("No focused child.",
- launcher.getAppsView().getActiveRecyclerView().getApps().getFocusedChild()));
- } finally {
- allApps.unfreeze();
- }
- }
-
- @Test
- public void testAllAppsExitSearchAndFocusSearchResults() {
- final HomeAllApps allApps = mLauncher.goHome().switchToAllApps();
- assertTrue("Launcher internal state is not All Apps",
- isInState(() -> LauncherState.ALL_APPS));
- allApps.freeze();
- try {
- executeOnLauncher(launcher -> launcher.getAppsView().getSearchView().requestFocus());
- waitForLauncherCondition("Search view does not have focus.",
- launcher -> launcher.getAppsView().getSearchView().hasFocus());
-
- mLauncher.pressAndHoldKeyCode(KeyEvent.KEYCODE_C, 0);
- waitForLauncherCondition("Search view not active.",
- launcher -> launcher.getAppsView().getActiveRecyclerView()
- instanceof SearchRecyclerView);
- mLauncher.unpressKeyCode(KeyEvent.KEYCODE_C, 0);
-
- executeOnLauncher(launcher -> launcher.getAppsView().getSearchUiManager().getEditText()
- .hideKeyboard(/* clearFocus= */ false));
- waitForLauncherCondition("Keyboard still visible.",
- ActivityContext::isSoftwareKeyboardHidden);
-
- mLauncher.pressAndHoldKeyCode(KeyEvent.KEYCODE_DPAD_DOWN, 0);
- mLauncher.unpressKeyCode(KeyEvent.KEYCODE_DPAD_DOWN, 0);
- waitForLauncherCondition("No focused child", launcher ->
- launcher.getAppsView().getActiveRecyclerView().getApps().getFocusedChild()
- != null);
- } finally {
- allApps.unfreeze();
- }
- }
-}
diff --git a/tests/src/com/android/launcher3/compat/TaplPromiseIconUiTest.java b/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
similarity index 73%
rename from tests/src/com/android/launcher3/compat/TaplPromiseIconUiTest.java
rename to tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
index 1500538..34b292c 100644
--- a/tests/src/com/android/launcher3/compat/TaplPromiseIconUiTest.java
+++ b/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
@@ -1,18 +1,19 @@
/*
* Copyright (C) 2019 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
+ * 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
+ * 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.
+ * 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.compat;
import static com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING;
@@ -27,32 +28,31 @@
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.text.TextUtils;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.tapl.AllApps;
-import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.util.BaseLauncherActivityTest;
import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
import com.android.launcher3.util.TestUtil;
-import com.android.launcher3.util.rule.ViewCaptureRule;
import org.junit.After;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.IOException;
+import java.util.Arrays;
import java.util.UUID;
-
/**
* Test to verify promise icon flow.
*/
@LargeTest
@RunWith(AndroidJUnit4.class)
-public class TaplPromiseIconUiTest extends AbstractLauncherUiTest<Launcher> {
+public class PromiseIconUiTest extends BaseLauncherActivityTest<Launcher> {
@Rule
public final CheckFlagsRule mCheckFlagsRule =
@@ -64,19 +64,17 @@
private int mSessionId = -1;
- @Override
+ @Before
public void setUp() throws Exception {
- super.setUp();
- mDevice.pressHome();
- waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null);
- waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
+ loadLauncherSync();
+ goToState(LauncherState.NORMAL);
mSessionId = -1;
}
@After
public void tearDown() throws IOException {
if (mSessionId > -1) {
- mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId);
+ targetContext().getPackageManager().getPackageInstaller().abandonSession(mSessionId);
}
TestUtil.uninstallDummyApp();
}
@@ -90,7 +88,7 @@
params.setAppLabel(label);
params.setAppIcon(icon);
params.setInstallReason(PackageManager.INSTALL_REASON_USER);
- return mTargetContext.getPackageManager().getPackageInstaller().createSession(params);
+ return targetContext().getPackageManager().getPackageInstaller().createSession(params);
}
@Test
@@ -108,7 +106,7 @@
launcher.getWorkspace().getFirstMatch(findPromiseApp) != null);
// Remove session
- mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId);
+ targetContext().getPackageManager().getPackageInstaller().abandonSession(mSessionId);
mSessionId = -1;
// Verify promise icon is removed
@@ -117,7 +115,6 @@
}
@Test
- @ViewCaptureRule.MayProduceNoFrames
public void testPromiseIcon_notAddedFromIneligibleSession() throws Throwable {
final String appLabel = "Test Promise App " + UUID.randomUUID().toString();
final ItemOperator findPromiseApp = (info, view) ->
@@ -138,7 +135,8 @@
@RequiresFlagsEnabled(FLAG_ENABLE_SUPPORT_FOR_ARCHIVING)
public void testPromiseIcon_addedArchivedApp() throws Throwable {
installDummyAppAndWaitForUIUpdate();
- assertThat(mDevice.executeShellCommand(String.format("pm archive %s", DUMMY_PACKAGE)))
+ assertThat(executeShellCommand(
+ String.format("pm archive %s", DUMMY_PACKAGE)))
.isEqualTo("Success\n");
// Create and add test session
@@ -148,28 +146,19 @@
// Verify promise icon is added to all apps view. The icon may not be added to the
// workspace even if there might be no icon present for archived app. But icon will
// always be in all apps view. In case an icon is not added, an exception would be thrown.
- final AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
+ goToState(LauncherState.ALL_APPS);
// Wait for the promise icon to be added.
waitForLauncherCondition(
DUMMY_PACKAGE + " app was not found on all apps after being archived",
- launcher -> {
- try {
- allApps.getAppIcon(DUMMY_LABEL);
- } catch (Throwable t) {
- return false;
- }
- return true;
- });
-
- // Remove session
- mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId);
- mSessionId = -1;
+ launcher -> Arrays.stream(launcher.getAppsView().getAppsStore().getApps())
+ .filter(info -> DUMMY_LABEL.equals(info.title.toString()))
+ .findAny()
+ .isPresent());
}
private void installDummyAppAndWaitForUIUpdate() throws IOException {
TestUtil.installDummyApp();
- mLauncher.waitForModelQueueCleared();
- mLauncher.waitForLauncherInitialized();
+ loadLauncherSync();
}
}
diff --git a/tests/src/com/android/launcher3/model/GridMigrationTest.kt b/tests/src/com/android/launcher3/model/GridMigrationTest.kt
index 666ec16..379e98d 100644
--- a/tests/src/com/android/launcher3/model/GridMigrationTest.kt
+++ b/tests/src/com/android/launcher3/model/GridMigrationTest.kt
@@ -101,6 +101,7 @@
dst.gridState,
dst.dbHelper,
src.dbHelper.readableDatabase,
+ false,
)
} else {
GridSizeMigrationDBController.migrateGridIfNeeded(
@@ -109,6 +110,7 @@
dst.gridState,
dst.dbHelper,
src.dbHelper.readableDatabase,
+ false,
)
}
}
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 1fbdceb..a273648 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -21,6 +21,7 @@
import static com.android.launcher3.testing.shared.TestProtocol.ICON_MISSING;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.TestUtil.resolveSystemAppInfo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -31,7 +32,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Point;
@@ -627,13 +627,6 @@
launcherInstrumentation);
}
- public static ActivityInfo resolveSystemAppInfo(String category) {
- return getInstrumentation().getContext().getPackageManager().resolveActivity(
- new Intent(Intent.ACTION_MAIN).addCategory(category),
- PackageManager.MATCH_SYSTEM_ONLY).
- activityInfo;
- }
-
public static String resolveSystemApp(String category) {
return resolveSystemAppInfo(category).packageName;
diff --git a/tests/src/com/android/launcher3/util/BaseLauncherActivityTest.kt b/tests/src/com/android/launcher3/util/BaseLauncherActivityTest.kt
new file mode 100644
index 0000000..bacce40
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/BaseLauncherActivityTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util
+
+import android.content.Context
+import android.content.Intent
+import android.os.SystemClock
+import android.view.InputDevice
+import android.view.KeyCharacterMap
+import android.view.KeyEvent
+import android.view.MotionEvent
+import androidx.lifecycle.Lifecycle.State.RESUMED
+import androidx.test.core.app.ActivityScenario
+import androidx.test.core.app.ActivityScenario.ActivityAction
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.Launcher
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherState
+import com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST
+import com.android.launcher3.tapl.TestHelpers
+import com.android.launcher3.util.ModelTestExtensions.loadModelSync
+import com.android.launcher3.util.Wait.atMost
+import java.util.function.Function
+import java.util.function.Supplier
+import org.junit.After
+
+/**
+ * Base class for tests which use Launcher activity with some utility methods.
+ *
+ * This should instead be a rule, but is kept as a base class for easier migration from TAPL
+ */
+open class BaseLauncherActivityTest<LAUNCHER_TYPE : Launcher> {
+
+ private var currentScenario: ActivityScenario<LAUNCHER_TYPE>? = null
+
+ val scenario: ActivityScenario<LAUNCHER_TYPE>
+ get() =
+ currentScenario
+ ?: ActivityScenario.launch<LAUNCHER_TYPE>(
+ TestHelpers.getHomeIntentInPackage(targetContext()),
+ null,
+ )
+ .also { currentScenario = it }
+
+ @After
+ fun closeCurrentActivity() {
+ currentScenario?.close()
+ currentScenario = null
+ }
+
+ protected fun loadLauncherSync() {
+ LauncherAppState.getInstance(targetContext()).model.loadModelSync()
+ scenario.moveToState(RESUMED)
+ }
+
+ protected fun targetContext(): Context = getInstrumentation().targetContext
+
+ protected fun goToState(state: LauncherState) = executeOnLauncher {
+ it.stateManager.goToState(state, 0)
+ }
+
+ protected fun executeOnLauncher(f: ActivityAction<LAUNCHER_TYPE>) = scenario.onActivity(f)
+
+ protected fun <T> getFromLauncher(f: Function<in LAUNCHER_TYPE, out T?>): T? {
+ var result: T? = null
+ executeOnLauncher { result = f.apply(it) }
+ return result
+ }
+
+ protected fun isInState(state: Supplier<LauncherState>): Boolean =
+ getFromLauncher { it.stateManager.state == state.get() }!!
+
+ protected fun waitForState(message: String, state: Supplier<LauncherState>) =
+ waitForLauncherCondition(message) { it.stateManager.currentStableState === state.get() }
+
+ protected fun waitForLauncherCondition(
+ message: String,
+ condition: Function<LAUNCHER_TYPE, Boolean>,
+ ) = atMost(message, { getFromLauncher(condition)!! })
+
+ protected fun <T> getOnceNotNull(message: String, f: Function<LAUNCHER_TYPE, T?>): T? {
+ var output: T? = null
+ atMost(
+ message,
+ {
+ val fromLauncher = getFromLauncher<T>(f)
+ output = fromLauncher
+ fromLauncher != null
+ },
+ )
+ return output
+ }
+
+ protected fun getAllAppsScroll(launcher: LAUNCHER_TYPE) =
+ launcher.appsView.activeRecyclerView.computeVerticalScrollOffset()
+
+ @JvmOverloads
+ protected fun injectKeyEvent(keyCode: Int, actionDown: Boolean, metaState: Int = 0) {
+ val eventTime = SystemClock.uptimeMillis()
+ val event =
+ KeyEvent.obtain(
+ eventTime,
+ eventTime,
+ if (actionDown) KeyEvent.ACTION_DOWN else MotionEvent.ACTION_UP,
+ keyCode,
+ /* repeat= */ 0,
+ metaState,
+ KeyCharacterMap.VIRTUAL_KEYBOARD,
+ /* scancode= */ 0,
+ /* flags= */ 0,
+ InputDevice.SOURCE_KEYBOARD,
+ /* characters =*/ null,
+ )
+ executeOnLauncher { it.dispatchKeyEvent(event) }
+ event.recycle()
+ }
+
+ fun startAppFast(packageName: String) {
+ val intent = targetContext().packageManager.getLaunchIntentForPackage(packageName)!!
+ intent.addCategory(Intent.CATEGORY_LAUNCHER)
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ targetContext().startActivity(intent)
+ UiDevice.getInstance(getInstrumentation()).waitForIdle()
+ }
+
+ fun freezeAllApps() = executeOnLauncher {
+ it.appsView.appsStore.enableDeferUpdates(DEFER_UPDATES_TEST)
+ }
+
+ fun executeShellCommand(cmd: String) =
+ UiDevice.getInstance(getInstrumentation()).executeShellCommand(cmd)
+}