Merge "Enable predictive back in TAPL tests" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 462d947..4de1c96 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -90,6 +90,9 @@
namespace: "launcher"
description: "Enables full width two pane widget picker for tablets in landscape and portrait"
bug: "315055849"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
@@ -175,9 +178,6 @@
namespace: "launcher"
description: "When adding app widget through config activity, directly add it to workspace to reduce flicker"
bug: "284236964"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
}
flag {
@@ -188,21 +188,30 @@
}
flag {
- name: "grid_migration_fix"
+ name: "enable_grid_migration_fix"
namespace: "launcher"
description: "Keep items in place when migrating to a bigger grid"
bug: "325286145"
+ is_fixed_read_only: true
metadata {
purpose: PURPOSE_BUGFIX
}
}
flag {
- name: "narrow_grid_restore"
+ name: "enable_narrow_grid_restore"
namespace: "launcher"
description: "Using only the most recent workspace when restoring to avoid confusion."
+ is_fixed_read_only: true
bug: "325285743"
metadata {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "enable_scaling_reveal_home_animation"
+ namespace: "launcher"
+ description: "Enables the Home gesture animation"
+ bug: "308801666"
+}
diff --git a/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml b/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml
index 69e1574..38df756 100644
--- a/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml
+++ b/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml
@@ -36,19 +36,19 @@
app:layout_constraintEnd_toEndOf="parent">
<include
- layout="@layout/keyboard_quick_switch_thumbnail"
- android:id="@+id/thumbnail1"
+ layout="@layout/keyboard_quick_switch_taskview_thumbnail"
+ android:id="@+id/thumbnail_1"
android:layout_width="0dp"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/thumbnail2"/>
+ app:layout_constraintEnd_toStartOf="@id/thumbnail_2"/>
<include
- layout="@layout/keyboard_quick_switch_thumbnail"
- android:id="@+id/thumbnail2"
+ layout="@layout/keyboard_quick_switch_taskview_thumbnail"
+ android:id="@+id/thumbnail_2"
android:layout_width="0dp"
android:layout_height="match_parent"
android:visibility="gone"
@@ -56,31 +56,33 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toEndOf="@id/thumbnail1"
+ app:layout_constraintStart_toEndOf="@id/thumbnail_1"
app:layout_constraintEnd_toEndOf="parent"/>
<ImageView
- android:id="@+id/icon1"
+ android:id="@+id/icon_1"
android:layout_width="@dimen/keyboard_quick_switch_taskview_icon_size"
android:layout_height="@dimen/keyboard_quick_switch_taskview_icon_size"
- android:layout_marginTop="@dimen/keyboard_quick_switch_taskview_icon_margin"
- android:layout_marginStart="@dimen/keyboard_quick_switch_taskview_icon_margin"
android:importantForAccessibility="no"
+ android:scaleType="centerCrop"
- app:layout_constraintTop_toTopOf="@id/thumbnail1"
- app:layout_constraintStart_toStartOf="@id/thumbnail1"/>
+ app:layout_constraintTop_toTopOf="@id/thumbnail_1"
+ app:layout_constraintBottom_toBottomOf="@id/thumbnail_1"
+ app:layout_constraintStart_toStartOf="@id/thumbnail_1"
+ app:layout_constraintEnd_toEndOf="@id/thumbnail_1"/>
<ImageView
- android:id="@+id/icon2"
+ android:id="@+id/icon_2"
android:layout_width="@dimen/keyboard_quick_switch_taskview_icon_size"
android:layout_height="@dimen/keyboard_quick_switch_taskview_icon_size"
- android:layout_marginTop="@dimen/keyboard_quick_switch_taskview_icon_margin"
- android:layout_marginStart="@dimen/keyboard_quick_switch_taskview_icon_margin"
android:importantForAccessibility="no"
android:visibility="gone"
+ android:scaleType="centerCrop"
- app:layout_constraintTop_toTopOf="@id/thumbnail2"
- app:layout_constraintStart_toStartOf="@id/thumbnail2"/>
+ app:layout_constraintTop_toTopOf="@id/thumbnail_2"
+ app:layout_constraintBottom_toBottomOf="@id/thumbnail_2"
+ app:layout_constraintStart_toStartOf="@id/thumbnail_2"
+ app:layout_constraintEnd_toEndOf="@id/thumbnail_2"/>
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/quickstep/res/layout/keyboard_quick_switch_taskview.xml b/quickstep/res/layout/keyboard_quick_switch_taskview.xml
index 6ed3c6e..c0ace9a 100644
--- a/quickstep/res/layout/keyboard_quick_switch_taskview.xml
+++ b/quickstep/res/layout/keyboard_quick_switch_taskview.xml
@@ -36,51 +36,53 @@
app:layout_constraintEnd_toEndOf="parent">
<include
- layout="@layout/keyboard_quick_switch_thumbnail"
- android:id="@+id/thumbnail1"
+ layout="@layout/keyboard_quick_switch_taskview_thumbnail"
+ android:id="@+id/thumbnail_1"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/thumbnail2"
+ app:layout_constraintBottom_toTopOf="@id/thumbnail_2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<include
- layout="@layout/keyboard_quick_switch_thumbnail"
- android:id="@+id/thumbnail2"
+ layout="@layout/keyboard_quick_switch_taskview_thumbnail"
+ android:id="@+id/thumbnail_2"
android:layout_width="match_parent"
android:layout_height="0dp"
android:visibility="gone"
android:layout_marginTop="@dimen/keyboard_quick_switch_split_view_spacing"
- app:layout_constraintTop_toBottomOf="@id/thumbnail1"
+ app:layout_constraintTop_toBottomOf="@id/thumbnail_1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<ImageView
- android:id="@+id/icon1"
+ android:id="@+id/icon_1"
android:layout_width="@dimen/keyboard_quick_switch_taskview_icon_size"
android:layout_height="@dimen/keyboard_quick_switch_taskview_icon_size"
- android:layout_marginTop="@dimen/keyboard_quick_switch_taskview_icon_margin"
- android:layout_marginStart="@dimen/keyboard_quick_switch_taskview_icon_margin"
android:importantForAccessibility="no"
+ android:scaleType="centerCrop"
- app:layout_constraintTop_toTopOf="@id/thumbnail1"
- app:layout_constraintStart_toStartOf="@id/thumbnail1"/>
+ app:layout_constraintTop_toTopOf="@id/thumbnail_1"
+ app:layout_constraintBottom_toBottomOf="@id/thumbnail_1"
+ app:layout_constraintStart_toStartOf="@id/thumbnail_1"
+ app:layout_constraintEnd_toEndOf="@id/thumbnail_1"/>
<ImageView
- android:id="@+id/icon2"
+ android:id="@+id/icon_2"
android:layout_width="@dimen/keyboard_quick_switch_taskview_icon_size"
android:layout_height="@dimen/keyboard_quick_switch_taskview_icon_size"
- android:layout_marginTop="@dimen/keyboard_quick_switch_taskview_icon_margin"
- android:layout_marginStart="@dimen/keyboard_quick_switch_taskview_icon_margin"
android:importantForAccessibility="no"
android:visibility="gone"
+ android:scaleType="centerCrop"
- app:layout_constraintTop_toTopOf="@id/thumbnail2"
- app:layout_constraintStart_toStartOf="@id/thumbnail2"/>
+ app:layout_constraintTop_toTopOf="@id/thumbnail_2"
+ app:layout_constraintBottom_toBottomOf="@id/thumbnail_2"
+ app:layout_constraintStart_toStartOf="@id/thumbnail_2"
+ app:layout_constraintEnd_toEndOf="@id/thumbnail_2"/>
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/quickstep/res/layout/keyboard_quick_switch_thumbnail.xml b/quickstep/res/layout/keyboard_quick_switch_taskview_thumbnail.xml
similarity index 100%
rename from quickstep/res/layout/keyboard_quick_switch_thumbnail.xml
rename to quickstep/res/layout/keyboard_quick_switch_taskview_thumbnail.xml
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 0e18fb4..fcc2eff 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -441,8 +441,7 @@
<dimen name="keyboard_quick_switch_border_width">4dp</dimen>
<dimen name="keyboard_quick_switch_taskview_width">104dp</dimen>
<dimen name="keyboard_quick_switch_taskview_height">134dp</dimen>
- <dimen name="keyboard_quick_switch_taskview_icon_size">28dp</dimen>
- <dimen name="keyboard_quick_switch_taskview_icon_margin">4dp</dimen>
+ <dimen name="keyboard_quick_switch_taskview_icon_size">52dp</dimen>
<dimen name="keyboard_quick_switch_recents_icon_size">20dp</dimen>
<dimen name="keyboard_quick_switch_margin_top">56dp</dimen>
<dimen name="keyboard_quick_switch_margin_ends">16dp</dimen>
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index 350c752..16fb6d2 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -315,5 +315,6 @@
<style name="WidgetPickerActivityTheme" parent="@android:style/Theme.Translucent.NoTitleBar">
<item name="widgetsTheme">@style/WidgetContainerTheme</item>
<item name="android:windowBackground">@android:color/transparent</item>
+ <item name="pageIndicatorDotColor">@color/page_indicator_dot_color_light</item>
</style>
</resources>
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 569e95a..be532b4 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -43,6 +43,7 @@
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR;
import static com.android.launcher3.LauncherState.ALL_APPS;
@@ -215,7 +216,8 @@
public static final int TASKBAR_TO_APP_DURATION = 600;
// TODO(b/236145847): Tune TASKBAR_TO_HOME_DURATION to 383 after conflict with unlock animation
// is solved.
- public static final int TASKBAR_TO_HOME_DURATION = 300;
+ private static final int TASKBAR_TO_HOME_DURATION_FAST = 300;
+ private static final int TASKBAR_TO_HOME_DURATION_SLOW = 1000;
protected static final int CONTENT_SCALE_DURATION = 350;
protected static final int CONTENT_SCRIM_DURATION = 350;
@@ -1704,6 +1706,14 @@
return new Pair(rectFSpringAnim, anim);
}
+ public static int getTaskbarToHomeDuration() {
+ if (enableScalingRevealHomeAnimation()) {
+ return TASKBAR_TO_HOME_DURATION_SLOW;
+ } else {
+ return TASKBAR_TO_HOME_DURATION_FAST;
+ }
+ }
+
/**
* Remote animation runner for animation from the app to Launcher, including recents.
*/
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index e1443d0..b36fd66 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -138,9 +138,9 @@
int totalHeight = iconHeight + iconPadding + textHeight + mVerticalPadding * 2;
// Prediction row height will be 4dp bigger than the regular apps in A-Z list when two line
// is not enabled. Otherwise, the extra height will increase by just the textHeight.
- int extraHeight = (FeatureFlags.enableTwolineAllapps() && (!Flags.enableTwolineToggle()
- || (Flags.enableTwolineToggle() && LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(
- getContext())))) ? textHeight : mTopRowExtraHeight;
+ int extraHeight = (Flags.enableTwolineToggle() &&
+ LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(getContext()))
+ ? textHeight : mTopRowExtraHeight;
totalHeight += extraHeight;
return getVisibility() == GONE ? 0 : totalHeight + getPaddingTop() + getPaddingBottom();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/BlurredBitmapDrawable.kt b/quickstep/src/com/android/launcher3/taskbar/BlurredBitmapDrawable.kt
new file mode 100644
index 0000000..8aee1aa
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/BlurredBitmapDrawable.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.taskbar
+
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.PixelFormat
+import android.graphics.RenderEffect
+import android.graphics.RenderNode
+import android.graphics.Shader
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.DrawableWrapper
+
+/* BitmapDrawable that can blur the given bitmap. */
+class BlurredBitmapDrawable(bitmap: Bitmap?, radiusX: Float, radiusY: Float) :
+ DrawableWrapper(BitmapDrawable(bitmap)) {
+ private val mBlurRenderNode: RenderNode = RenderNode("BlurredConstraintLayoutBlurNode")
+
+ constructor(bitmap: Bitmap?, radius: Float) : this(bitmap, radius, radius)
+
+ init {
+ mBlurRenderNode.setRenderEffect(
+ RenderEffect.createBlurEffect(radiusX, radiusY, Shader.TileMode.CLAMP)
+ )
+ }
+
+ override fun draw(canvas: Canvas) {
+ if (!canvas.isHardwareAccelerated) {
+ super.draw(canvas)
+ return
+ }
+ mBlurRenderNode.setPosition(bounds)
+ if (!mBlurRenderNode.hasDisplayList()) {
+ // Record render node if its display list is not recorded or discarded
+ // (which happens when it's no longer drawn by anything).
+ val recordingCanvas = mBlurRenderNode.beginRecording()
+ super.draw(recordingCanvas)
+ mBlurRenderNode.endRecording()
+ }
+ canvas.drawRenderNode(mBlurRenderNode)
+ }
+
+ override fun getOpacity(): Int {
+ return PixelFormat.OPAQUE
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index f15d12b..8566e20 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -193,10 +193,14 @@
}
void closeQuickSwitchView() {
+ closeQuickSwitchView(true);
+ }
+
+ void closeQuickSwitchView(boolean animate) {
if (mQuickSwitchViewController == null) {
return;
}
- mQuickSwitchViewController.closeQuickSwitchView(true);
+ mQuickSwitchViewController.closeQuickSwitchView(animate);
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
index a9d50b9..5b407f0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
@@ -23,6 +23,7 @@
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
@@ -47,6 +48,8 @@
*/
public class KeyboardQuickSwitchTaskView extends ConstraintLayout {
+ private static final float THUMBNAIL_BLUR_RADIUS = 1f;
+
@ColorInt private final int mBorderColor;
@Nullable private BorderAnimator mBorderAnimator;
@@ -89,10 +92,10 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mThumbnailView1 = findViewById(R.id.thumbnail1);
- mThumbnailView2 = findViewById(R.id.thumbnail2);
- mIcon1 = findViewById(R.id.icon1);
- mIcon2 = findViewById(R.id.icon2);
+ mThumbnailView1 = findViewById(R.id.thumbnail_1);
+ mThumbnailView2 = findViewById(R.id.thumbnail_2);
+ mIcon1 = findViewById(R.id.icon_1);
+ mIcon2 = findViewById(R.id.icon_2);
mContent = findViewById(R.id.content);
Resources resources = mContext.getResources();
@@ -167,10 +170,7 @@
@Nullable ImageView thumbnailView,
@Nullable Task task,
@Nullable ThumbnailUpdateFunction updateFunction) {
- if (thumbnailView == null) {
- return;
- }
- if (task == null) {
+ if (thumbnailView == null || task == null) {
return;
}
if (updateFunction == null) {
@@ -182,19 +182,30 @@
}
private void applyThumbnail(
- @NonNull ImageView thumbnailView, ThumbnailData thumbnailData) {
+ @NonNull ImageView thumbnailView,
+ ThumbnailData thumbnailData) {
Bitmap bm = thumbnailData == null ? null : thumbnailData.thumbnail;
- thumbnailView.setVisibility(VISIBLE);
- thumbnailView.setImageBitmap(bm);
+ if (thumbnailView.getVisibility() != VISIBLE) {
+ thumbnailView.setVisibility(VISIBLE);
+ }
+ thumbnailView.setImageDrawable(new BlurredBitmapDrawable(bm, THUMBNAIL_BLUR_RADIUS));
}
private void applyIcon(@Nullable ImageView iconView, @Nullable Task task) {
- if (iconView == null || task == null) {
+ if (iconView == null || task == null || task.icon == null) {
return;
}
- iconView.setVisibility(VISIBLE);
- iconView.setImageDrawable(task.icon);
+ Drawable.ConstantState constantState = task.icon.getConstantState();
+ if (constantState == null) {
+ return;
+ }
+ if (iconView.getVisibility() != VISIBLE) {
+ iconView.setVisibility(VISIBLE);
+ }
+ // Use the bitmap directly since the drawable's scale can change
+ iconView.setImageDrawable(
+ constantState.newDrawable(getResources(), getContext().getTheme()));
}
protected interface ThumbnailUpdateFunction {
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index c830aa8..5134f3b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -238,8 +238,7 @@
}
void launchTaskAt(int index) {
- mCurrentFocusIndex = Utilities.boundToRange(
- index, 0, mKeyboardQuickSwitchView.getChildCount() - 1);
+ mCurrentFocusIndex = index;
mControllers.taskbarActivityContext.launchKeyboardFocusedTask();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 1e861d2..36ce049 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -191,7 +191,7 @@
? TRANSIENT_TASKBAR_TRANSITION_DURATION
: (!isVisible
? QuickstepTransitionManager.TASKBAR_TO_APP_DURATION
- : QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION));
+ : QuickstepTransitionManager.getTaskbarToHomeDuration()));
}
@Nullable
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 27a895c..5f69a9c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -944,10 +944,14 @@
}
if (landscapePhoneButtonNav) {
mWindowLayoutParams.width = size;
- mWindowLayoutParams.paramsForRotation[getDisplay().getRotation()].width = size;
+ for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
+ mWindowLayoutParams.paramsForRotation[rot].width = size;
+ }
} else {
mWindowLayoutParams.height = size;
- mWindowLayoutParams.paramsForRotation[getDisplay().getRotation()].height = size;
+ for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
+ mWindowLayoutParams.paramsForRotation[rot].height = size;
+ }
}
mControllers.taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
notifyUpdateLayoutParams();
@@ -1322,8 +1326,12 @@
* Called when we want to unstash taskbar when user performs swipes up gesture.
*/
public void onSwipeToUnstashTaskbar() {
- VibratorWrapper.INSTANCE.get(this).vibrateForTaskbarUnstash();
+ boolean wasStashed = mControllers.taskbarStashController.isStashed();
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(/* stash= */ false);
+ boolean isStashed = mControllers.taskbarStashController.isStashed();
+ if (isStashed != wasStashed) {
+ VibratorWrapper.INSTANCE.get(this).vibrateForTaskbarUnstash();
+ }
mControllers.taskbarEduTooltipController.hide();
}
@@ -1553,4 +1561,9 @@
public float getStashedTaskbarScale() {
return mControllers.stashedHandleViewController.getStashedHandleHintScale().value;
}
+
+ /** Closes the KeyboardQuickSwitchView without an animation if open. */
+ public void closeKeyboardQuickSwitchView() {
+ mControllers.keyboardQuickSwitchController.closeQuickSwitchView(false);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 8d83716..a14e3fd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -193,7 +193,7 @@
updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, true);
if (!mShouldDelayLauncherStateAnim) {
if (toState == LauncherState.NORMAL) {
- applyState(QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION);
+ applyState(QuickstepTransitionManager.getTaskbarToHomeDuration());
} else {
applyState();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 629c951..3d58464 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -900,12 +900,12 @@
}
// Only update the following flags when system gesture is not in progress.
+ updateStateForFlag(FLAG_STASHED_IN_TASKBAR_ALL_APPS, false);
setStashedImeState();
}
private void setStashedImeState() {
boolean shouldStashForIme = shouldStashForIme();
- updateStateForFlag(FLAG_STASHED_IN_TASKBAR_ALL_APPS, false);
if (hasAnyFlag(FLAG_STASHED_IN_APP_IME) != shouldStashForIme) {
updateStateForFlag(FLAG_STASHED_IN_APP_IME, shouldStashForIme);
applyState(TASKBAR_STASH_DURATION_FOR_IME, getTaskbarStashStartDelayForIme());
diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
index dcc3b05..873dea8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -21,6 +21,7 @@
import android.app.Person;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
@@ -158,6 +159,28 @@
}
/**
+ * Returns an intent which can be used to open Private Space Settings.
+ */
+ public static Intent getPrivateSpaceSettingsIntent(Context context) {
+ if (android.os.Flags.allowPrivateProfile() && Flags.enablePrivateSpace()) {
+ LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
+ IntentSender intentSender = launcherApps.getPrivateSpaceSettingsIntent();
+ if (intentSender == null) {
+ return null;
+ }
+ StartActivityParams params = new StartActivityParams((PendingIntent) null, 0);
+ params.intentSender = intentSender;
+ ActivityOptions options = ActivityOptions.makeBasic()
+ .setPendingIntentBackgroundActivityStartMode(ActivityOptions
+ .MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ params.options = options.toBundle();
+ params.requireActivityResult = false;
+ return ProxyActivityStarter.getLaunchIntent(context, params);
+ }
+ return null;
+ }
+
+ /**
* Checks if an activity is flagged as non-resizeable.
*/
public static boolean isNonResizeableActivity(LauncherActivityInfo lai) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
index e3ff281..23e2622 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
@@ -26,10 +26,10 @@
import android.util.SparseArray;
import android.widget.RemoteViews;
+import androidx.annotation.AnyThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
-import androidx.annotation.WorkerThread;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.WidgetsModel;
@@ -265,6 +265,14 @@
}
}
+ /**
+ * Clears all the internal widget views excluding the update listeners
+ */
+ @Override
+ public void clearWidgetViews() {
+ mViews.clear();
+ }
+
private static class QuickstepWidgetHolderListener
implements AppWidgetHost.AppWidgetHostListener {
@@ -288,21 +296,21 @@
}
@Override
- @WorkerThread
+ @AnyThread
public void onUpdateProviderInfo(@Nullable AppWidgetProviderInfo info) {
mRemoteViews = null;
executeOnMainExecutor(KEY_PROVIDER_UPDATE, info);
}
@Override
- @WorkerThread
+ @AnyThread
public void updateAppWidget(@Nullable RemoteViews views) {
mRemoteViews = views;
executeOnMainExecutor(KEY_VIEWS_UPDATE, mRemoteViews);
}
@Override
- @WorkerThread
+ @AnyThread
public void onViewDataChanged(int viewId) {
executeOnMainExecutor(KEY_VIEW_DATA_CHANGED, viewId);
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index e6a115a..6c1d4b1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -133,7 +133,7 @@
// Create transition animations to split select
RecentsPagedOrientationHandler orientationHandler =
((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler();
- Pair<FloatProperty, FloatProperty> taskViewsFloat =
+ Pair<FloatProperty<RecentsView>, FloatProperty<RecentsView>> taskViewsFloat =
orientationHandler.getSplitSelectTaskOffset(
TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
mLauncher.getDeviceProfile());
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index 577eba6..b6002e8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -16,6 +16,7 @@
package com.android.launcher3.uioverrides.states;
import static com.android.app.animation.Interpolators.DECELERATE_2;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS;
import android.content.Context;
@@ -28,6 +29,7 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
+import com.android.quickstep.util.BaseDepthController;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
/**
@@ -105,8 +107,14 @@
return context.getDeviceProfile().bottomSheetDepth;
} else {
// The scrim fades in at approximately 50% of the swipe gesture.
- // This means that the depth should be greater than 1, in order to fully zoom out.
- return 2f;
+ if (enableScalingRevealHomeAnimation()) {
+ // This means that the depth should be twice of what we want, in order to fully zoom
+ // out during the visible portion of the animation.
+ return BaseDepthController.DEPTH_60_PERCENT;
+ } else {
+ // This means that the depth should be greater than 1, in order to fully zoom out.
+ return 2f;
+ }
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index d11a08b..6a25c21 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.uioverrides.states;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
@@ -26,6 +27,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.quickstep.util.BaseDepthController;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
@@ -90,13 +92,14 @@
@Override
protected float getDepthUnchecked(Context context) {
- if (isDesktopModeSupported()) {
- if (Launcher.getLauncher(context).areFreeformTasksVisible()) {
- // Don't blur the background while freeform tasks are visible
- return 0;
- }
+ if (isDesktopModeSupported() && Launcher.getLauncher(context).areFreeformTasksVisible()) {
+ // Don't blur the background while freeform tasks are visible
+ return BaseDepthController.DEPTH_0_PERCENT;
+ } else if (enableScalingRevealHomeAnimation()) {
+ return BaseDepthController.DEPTH_70_PERCENT;
+ } else {
+ return 1f;
}
- return 1;
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 7650235..8c2efc2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -16,6 +16,7 @@
package com.android.launcher3.uioverrides.states;
import static com.android.app.animation.Interpolators.DECELERATE_2;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
import static com.android.wm.shell.Flags.enableSplitContextual;
@@ -29,6 +30,7 @@
import com.android.launcher3.R;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.Themes;
+import com.android.quickstep.util.BaseDepthController;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -186,8 +188,14 @@
@Override
protected float getDepthUnchecked(Context context) {
- //TODO revert when b/178661709 is fixed
- return SystemProperties.getBoolean("ro.launcher.depth.overview", true) ? 1 : 0;
+ // TODO(178661709): revert to always scaled
+ if (enableScalingRevealHomeAnimation()) {
+ return SystemProperties.getBoolean("ro.launcher.depth.overview", true)
+ ? BaseDepthController.DEPTH_70_PERCENT
+ : BaseDepthController.DEPTH_0_PERCENT;
+ } else {
+ return SystemProperties.getBoolean("ro.launcher.depth.overview", true) ? 1 : 0;
+ }
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 72218bf..b401868 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -37,7 +37,6 @@
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
-import static com.android.launcher3.QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION;
import static com.android.launcher3.WorkspaceStateTransitionAnimation.getWorkspaceSpringScaleAnimator;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
@@ -59,6 +58,7 @@
import com.android.launcher3.CellLayout;
import com.android.launcher3.Hotseat;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.QuickstepTransitionManager;
import com.android.launcher3.Workspace;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.AllAppsSwipeController;
@@ -108,7 +108,8 @@
// We sync the scrim fade with the taskbar animation duration to avoid any flickers for
// taskbar icons disappearing before hotseat icons show up.
- float scrimUpperBoundFromSplit = TASKBAR_TO_HOME_DURATION / (float) config.duration;
+ float scrimUpperBoundFromSplit =
+ QuickstepTransitionManager.getTaskbarToHomeDuration() / (float) config.duration;
config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, clampToProgress(LINEAR, 0, 0.25f));
config.setInterpolator(ANIM_SCRIM_FADE,
fromState == OVERVIEW_SPLIT_SELECT
@@ -135,9 +136,9 @@
config.duration = Math.max(config.duration, scrollDuration);
// Sync scroll so that it ends before or at the same time as the taskbar animation.
- if (DisplayController.isTransientTaskbar(mActivity)
- && mActivity.getDeviceProfile().isTaskbarPresent) {
- config.duration = Math.min(config.duration, TASKBAR_TO_HOME_DURATION);
+ if (mActivity.getDeviceProfile().isTaskbarPresent) {
+ config.duration = Math.min(
+ config.duration, QuickstepTransitionManager.getTaskbarToHomeDuration());
}
overview.snapToPage(DEFAULT_PAGE, Math.toIntExact(config.duration));
} else {
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index a9d8afc..f678bea 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -17,6 +17,7 @@
import static com.android.app.animation.Interpolators.EXAGGERATED_EASE;
import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.Utilities.mapBoundToRange;
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
@@ -46,6 +47,7 @@
import com.android.launcher3.views.FloatingView;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.quickstep.util.RectFSpringAnim;
+import com.android.quickstep.util.ScalingWorkspaceRevealAnim;
import com.android.quickstep.util.StaggeredWorkspaceAnim;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.views.FloatingWidgetView;
@@ -296,9 +298,15 @@
@Override
public void playAtomicAnimation(float velocity) {
- new StaggeredWorkspaceAnim(mActivity, velocity, true /* animateOverviewScrim */,
- getViewIgnoredInWorkspaceRevealAnimation())
- .start();
+ if (enableScalingRevealHomeAnimation()) {
+ if (mActivity != null) {
+ new ScalingWorkspaceRevealAnim(mActivity).start();
+ }
+ } else {
+ new StaggeredWorkspaceAnim(mActivity, velocity, true /* animateOverviewScrim */,
+ getViewIgnoredInWorkspaceRevealAnimation())
+ .start();
+ }
}
}
}
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 65b5397..56c9a00 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -248,7 +248,15 @@
case TYPE_SHOW:
// already visible
return true;
+ case TYPE_KEYBOARD_INPUT: {
+ if (visibleRecentsView.isHandlingTouch()) {
+ return true;
+ }
+ }
case TYPE_HIDE: {
+ if (visibleRecentsView.isHandlingTouch()) {
+ return true;
+ }
mKeyboardTaskFocusIndex = INVALID_PAGE;
int currentPage = visibleRecentsView.getNextPage();
TaskView tv = (currentPage >= 0
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 36bdad4..62ce341 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -328,11 +328,15 @@
// No "save app pair" menu item if:
// - app pairs feature is not enabled
+ // - we are in 3p launcher
// - the task in question is a single task
// - at least one app in app pair is unpinnable
// - the Overview Actions Button should be visible
- if (!FeatureFlags.enableAppPairs() || !taskView.containsMultipleTasks()
- || hasUnpinnableApp || shouldShowActionsButtonInstead) {
+ if (!FeatureFlags.enableAppPairs()
+ || !recentsView.supportsAppPairs()
+ || !taskView.containsMultipleTasks()
+ || hasUnpinnableApp
+ || shouldShowActionsButtonInstead) {
return null;
}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 415f73f..719c4f7 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -40,7 +40,6 @@
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_DOWN;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_MOVE;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_UP;
-import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.RECENTS_ANIMATION_START_PENDING;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
@@ -734,15 +733,17 @@
// an ACTION_HOVER_ENTER will fire as well.
boolean isHoverActionWithoutConsumer = enableCursorHoverStates()
&& isHoverActionWithoutConsumer(event);
- if (mTaskAnimationManager.isRecentsAnimationStartPending()
- && (action == ACTION_DOWN || isHoverActionWithoutConsumer)) {
- ActiveGestureLog.INSTANCE.addLog(
- new CompoundString("TIS.onInputEvent: ")
- .append("Cannot process input event: a recents animation has been ")
- .append("requested, but hasn't started."),
- RECENTS_ANIMATION_START_PENDING);
- return;
- }
+
+ // TODO(b/285636175): Uncomment this once WM can properly guarantee all animation callbacks
+// if (mTaskAnimationManager.isRecentsAnimationStartPending()
+// && (action == ACTION_DOWN || isHoverActionWithoutConsumer)) {
+// ActiveGestureLog.INSTANCE.addLog(
+// new CompoundString("TIS.onInputEvent: ")
+// .append("Cannot process input event: a recents animation has been ")
+// .append("requested, but hasn't started."),
+// RECENTS_ANIMATION_START_PENDING);
+// return;
+// }
SafeCloseable traceToken = TraceHelper.INSTANCE.allowIpcs("TIS.onInputEvent");
@@ -753,6 +754,10 @@
boolean isOneHandedModeActive = mDeviceState.isOneHandedModeActive();
boolean isInSwipeUpTouchRegion = mRotationTouchHelper.isInSwipeUpTouchRegion(event);
+ TaskbarActivityContext tac = mTaskbarManager.getCurrentActivityContext();
+ if (isInSwipeUpTouchRegion && tac != null) {
+ tac.closeKeyboardQuickSwitchView();
+ }
if ((!isOneHandedModeActive && isInSwipeUpTouchRegion)
|| isHoverActionWithoutConsumer) {
reasonString.append(!isOneHandedModeActive && isInSwipeUpTouchRegion
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java b/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
index 8a87f63..69de3b0 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
@@ -45,8 +45,7 @@
NavBarPosition navBarPosition = new NavBarPosition(sysUINavigationMode,
DisplayController.INSTANCE.get(mActivity).getInfo());
mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(mActivity,
- true /* disableHorizontalSwipe */, navBarPosition,
- null /* onInterceptTouch */, this);
+ true /* disableHorizontalSwipe */, navBarPosition, this);
} else {
mTriggerSwipeUpTracker = null;
}
@@ -78,7 +77,4 @@
public void onSwipeUp(boolean wasFling, PointF finalVelocity) {
mActivity.<FallbackRecentsView>getOverviewPanel().startHome();
}
-
- @Override
- public void onSwipeUpCancelled() {}
}
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 3e731e5..69eaf6a 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -49,6 +49,7 @@
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.quickstep.RecentsActivity;
import com.android.quickstep.views.ClearAllButton;
+import com.android.quickstep.views.RecentsView;
/**
* State controller for fallback recents activity
@@ -125,7 +126,7 @@
setter.add(pa.buildAnim());
}
- Pair<FloatProperty, FloatProperty> taskViewsFloat =
+ Pair<FloatProperty<RecentsView>, FloatProperty<RecentsView>> taskViewsFloat =
mRecentsView.getPagedOrientationHandler().getSplitSelectTaskOffset(
TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
mActivity.getDeviceProfile());
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 0ee50a4..32d8be9 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -302,4 +302,10 @@
protected boolean canLaunchFullscreenTask() {
return !mActivity.isInState(OVERVIEW_SPLIT_SELECT);
}
+
+ /** Returns if app pairs are supported in this launcher. */
+ @Override
+ public boolean supportsAppPairs() {
+ return false;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index 41730bb..42e8694 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -51,7 +51,7 @@
mGestureState = gestureState;
mInputMonitor = inputMonitor;
mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(context, disableHorizontalSwipe,
- deviceState.getNavBarPosition(), this::onInterceptTouch, this);
+ deviceState.getNavBarPosition(), this);
}
@Override
@@ -69,7 +69,8 @@
mTriggerSwipeUpTracker.onMotionEvent(ev);
}
- private void onInterceptTouch() {
+ @Override
+ public void onSwipeUpTouchIntercepted() {
if (mInputMonitor != null) {
TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
mInputMonitor.pilferPointers();
@@ -93,7 +94,4 @@
.build())
.log(LAUNCHER_HOME_GESTURE);
}
-
- @Override
- public void onSwipeUpCancelled() {}
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
index 4806ac1..871d075 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
@@ -54,7 +54,7 @@
mContext = context;
mInputMonitor = inputMonitor;
mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(context, true,
- deviceState.getNavBarPosition(), this::onInterceptTouch, this);
+ deviceState.getNavBarPosition(), this);
}
@Override
@@ -72,7 +72,8 @@
mTriggerSwipeUpTracker.onMotionEvent(ev);
}
- private void onInterceptTouch() {
+ @Override
+ public void onSwipeUpTouchIntercepted() {
if (mInputMonitor != null) {
TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
mInputMonitor.pilferPointers();
@@ -88,9 +89,4 @@
Log.e(TAG, "Exception calling closeSystemDialogs " + e.getMessage());
}
}
-
- @Override
- public void onSwipeUpCancelled() {
-
- }
}
diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
index c4a2216..c00f508 100644
--- a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
@@ -65,7 +65,7 @@
mSwipeUpTouchTracker =
new TriggerSwipeUpTouchTracker(context, true /*disableHorizontalSwipe*/,
new NavBarPosition(NavigationMode.NO_BUTTON, displayInfo),
- null /*onInterceptTouch*/, this);
+ this);
mMotionPauseDetector = new MotionPauseDetector(context);
final Resources resources = context.getResources();
diff --git a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.java
deleted file mode 100644
index 8648b56..0000000
--- a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.java
+++ /dev/null
@@ -1,698 +0,0 @@
-/*
- * 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.orientation;
-
-import static android.view.Gravity.BOTTOM;
-import static android.view.Gravity.CENTER_VERTICAL;
-import static android.view.Gravity.END;
-import static android.view.Gravity.LEFT;
-import static android.view.Gravity.START;
-import static android.view.Gravity.TOP;
-import static android.view.View.LAYOUT_DIRECTION_RTL;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-
-import static com.android.launcher3.Flags.enableOverviewIconMenu;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
-import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
-
-import android.content.res.Resources;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.ShapeDrawable;
-import android.util.FloatProperty;
-import android.util.Pair;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.Surface;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.touch.SingleAxisSwipeDetector;
-import com.android.launcher3.util.SplitConfigurationOptions;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
-import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
-import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.views.IconAppChipView;
-
-import java.util.Collections;
-import java.util.List;
-
-public class LandscapePagedViewHandler implements RecentsPagedOrientationHandler {
-
- @Override
- public <T> T getPrimaryValue(T x, T y) {
- return y;
- }
-
- @Override
- public <T> T getSecondaryValue(T x, T y) {
- return x;
- }
-
- @Override
- public int getPrimaryValue(int x, int y) {
- return y;
- }
-
- @Override
- public int getSecondaryValue(int x, int y) {
- return x;
- }
-
- @Override
- public float getPrimaryValue(float x, float y) {
- return y;
- }
-
- @Override
- public float getSecondaryValue(float x, float y) {
- return x;
- }
-
- @Override
- public boolean isLayoutNaturalToLauncher() {
- return false;
- }
-
- @Override
- public void adjustFloatingIconStartVelocity(PointF velocity) {
- float oldX = velocity.x;
- float oldY = velocity.y;
- velocity.set(-oldY, oldX);
- }
-
- @Override
- public void fixBoundsForHomeAnimStartRect(RectF outStartRect, DeviceProfile deviceProfile) {
- // We don't need to check the "top" value here because the startRect is in the orientation
- // of the app, not of the fixed portrait launcher.
- if (outStartRect.left > deviceProfile.heightPx) {
- outStartRect.offsetTo(0, outStartRect.top);
- } else if (outStartRect.left < -deviceProfile.heightPx) {
- outStartRect.offsetTo(0, outStartRect.top);
- }
- }
-
- @Override
- public <T> void setPrimary(T target, Int2DAction<T> action, int param) {
- action.call(target, 0, param);
- }
-
- @Override
- public <T> void setPrimary(T target, Float2DAction<T> action, float param) {
- action.call(target, 0, param);
- }
-
- @Override
- public <T> void setSecondary(T target, Float2DAction<T> action, float param) {
- action.call(target, param, 0);
- }
-
- @Override
- public <T> void set(T target, Int2DAction<T> action, int primaryParam,
- int secondaryParam) {
- action.call(target, secondaryParam, primaryParam);
- }
-
- @Override
- public float getPrimaryDirection(MotionEvent event, int pointerIndex) {
- return event.getY(pointerIndex);
- }
-
- @Override
- public float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId) {
- return velocityTracker.getYVelocity(pointerId);
- }
-
- @Override
- public int getMeasuredSize(View view) {
- return view.getMeasuredHeight();
- }
-
- @Override
- public int getPrimarySize(View view) {
- return view.getHeight();
- }
-
- @Override
- public float getPrimarySize(RectF rect) {
- return rect.height();
- }
-
- @Override
- public float getStart(RectF rect) {
- return rect.top;
- }
-
- @Override
- public float getEnd(RectF rect) {
- return rect.bottom;
- }
-
- @Override
- public int getClearAllSidePadding(View view, boolean isRtl) {
- return (isRtl ? view.getPaddingBottom() : - view.getPaddingTop()) / 2;
- }
-
- @Override
- public int getSecondaryDimension(View view) {
- return view.getWidth();
- }
-
- @Override
- public FloatProperty<View> getPrimaryViewTranslate() {
- return VIEW_TRANSLATE_Y;
- }
-
- @Override
- public FloatProperty<View> getSecondaryViewTranslate() {
- return VIEW_TRANSLATE_X;
- }
-
- @Override
- public int getPrimaryScroll(View view) {
- return view.getScrollY();
- }
-
- @Override
- public float getPrimaryScale(View view) {
- return view.getScaleY();
- }
-
- @Override
- public void setMaxScroll(AccessibilityEvent event, int maxScroll) {
- event.setMaxScrollY(maxScroll);
- }
-
- @Override
- public boolean getRecentsRtlSetting(Resources resources) {
- return !Utilities.isRtl(resources);
- }
-
- @Override
- public float getDegreesRotated() {
- return 90;
- }
-
- @Override
- public int getRotation() {
- return Surface.ROTATION_90;
- }
-
- @Override
- public void setPrimaryScale(View view, float scale) {
- view.setScaleY(scale);
- }
-
- @Override
- public void setSecondaryScale(View view, float scale) {
- view.setScaleX(scale);
- }
-
- @Override
- public int getChildStart(View view) {
- return view.getTop();
- }
-
- @Override
- public int getCenterForPage(View view, Rect insets) {
- return (view.getPaddingLeft() + view.getMeasuredWidth() + insets.left
- - insets.right - view.getPaddingRight()) / 2;
- }
-
- @Override
- public int getScrollOffsetStart(View view, Rect insets) {
- return insets.top + view.getPaddingTop();
- }
-
- @Override
- public int getScrollOffsetEnd(View view, Rect insets) {
- return view.getHeight() - view.getPaddingBottom() - insets.bottom;
- }
-
- public int getSecondaryTranslationDirectionFactor() {
- return 1;
- }
-
- @Override
- public int getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile) {
- if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
- return -1;
- } else {
- return 1;
- }
- }
-
- @Override
- public float getTaskMenuX(float x, View thumbnailView,
- DeviceProfile deviceProfile, float taskInsetMargin, View taskViewIcon) {
- return thumbnailView.getMeasuredWidth() + x - taskInsetMargin;
- }
-
- @Override
- public float getTaskMenuY(float y, View thumbnailView, int stagePosition,
- View taskMenuView, float taskInsetMargin, View taskViewIcon) {
- BaseDragLayer.LayoutParams lp = (BaseDragLayer.LayoutParams) taskMenuView.getLayoutParams();
- int taskMenuWidth = lp.width;
- if (stagePosition == STAGE_POSITION_UNDEFINED) {
- return y + taskInsetMargin
- + (thumbnailView.getMeasuredHeight() - taskMenuWidth) / 2f;
- } else {
- return y + taskInsetMargin;
- }
- }
-
- @Override
- public int getTaskMenuWidth(View thumbnailView, DeviceProfile deviceProfile,
- @StagePosition int stagePosition) {
- if (enableOverviewIconMenu()) {
- return thumbnailView.getResources().getDimensionPixelSize(
- R.dimen.task_thumbnail_icon_menu_expanded_width);
- }
- if (stagePosition == SplitConfigurationOptions.STAGE_POSITION_UNDEFINED) {
- return thumbnailView.getMeasuredWidth();
- } else {
- return thumbnailView.getMeasuredHeight();
- }
- }
-
- @Override
- public void setTaskOptionsMenuLayoutOrientation(DeviceProfile deviceProfile,
- LinearLayout taskMenuLayout, int dividerSpacing,
- ShapeDrawable dividerDrawable) {
- taskMenuLayout.setOrientation(LinearLayout.VERTICAL);
- dividerDrawable.setIntrinsicHeight(dividerSpacing);
- taskMenuLayout.setDividerDrawable(dividerDrawable);
- }
-
- @Override
- public void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp,
- LinearLayout viewGroup, DeviceProfile deviceProfile) {
- // Phone fake landscape
- viewGroup.setOrientation(LinearLayout.HORIZONTAL);
- lp.width = MATCH_PARENT;
- lp.height = WRAP_CONTENT;
- }
-
- @Override
- public Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth,
- int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile,
- View[] thumbnailViews, int desiredTaskId, View banner) {
- boolean isRtl = banner.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
- float translationX = 0;
- float translationY = 0;
- FrameLayout.LayoutParams bannerParams = (FrameLayout.LayoutParams) banner.getLayoutParams();
- banner.setPivotX(0);
- banner.setPivotY(0);
- banner.setRotation(getDegreesRotated());
- translationX = banner.getHeight();
- FrameLayout.LayoutParams snapshotParams =
- (FrameLayout.LayoutParams) thumbnailViews[0]
- .getLayoutParams();
- bannerParams.gravity = TOP | (isRtl ? END : START);
- if (splitBounds == null) {
- // Single, fullscreen case
- bannerParams.width = taskViewHeight - snapshotParams.topMargin;
- return new Pair<>(translationX, Integer.valueOf(snapshotParams.topMargin).floatValue());
- }
-
- // Set correct width
- if (desiredTaskId == splitBounds.leftTopTaskId) {
- bannerParams.width = thumbnailViews[0].getMeasuredHeight();
- } else {
- bannerParams.width = thumbnailViews[1].getMeasuredHeight();
- }
-
- // Set translations
- if (desiredTaskId == splitBounds.rightBottomTaskId) {
- float topLeftTaskPlusDividerPercent = splitBounds.appsStackedVertically
- ? (splitBounds.topTaskPercent + splitBounds.dividerHeightPercent)
- : (splitBounds.leftTaskPercent + splitBounds.dividerWidthPercent);
- translationY = snapshotParams.topMargin
- + ((taskViewHeight - snapshotParams.topMargin) * topLeftTaskPlusDividerPercent);
- }
- if (desiredTaskId == splitBounds.leftTopTaskId) {
- translationY = snapshotParams.topMargin;
- }
- return new Pair<>(translationX, translationY);
- }
-
- /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
-
- @Override
- public SingleAxisSwipeDetector.Direction getUpDownSwipeDirection() {
- return HORIZONTAL;
- }
-
- @Override
- public int getUpDirection(boolean isRtl) {
- return isRtl ? SingleAxisSwipeDetector.DIRECTION_NEGATIVE
- : SingleAxisSwipeDetector.DIRECTION_POSITIVE;
- }
-
- @Override
- public boolean isGoingUp(float displacement, boolean isRtl) {
- return isRtl ? displacement < 0 : displacement > 0;
- }
-
- @Override
- public int getTaskDragDisplacementFactor(boolean isRtl) {
- return isRtl ? 1 : -1;
- }
-
- /* -------------------- */
-
- @Override
- public ChildBounds getChildBounds(View child, int childStart, int pageCenter,
- boolean layoutChild) {
- final int childHeight = child.getMeasuredHeight();
- final int childBottom = childStart + childHeight;
- final int childWidth = child.getMeasuredWidth();
- final int childLeft = pageCenter - childWidth/ 2;
- if (layoutChild) {
- child.layout(childLeft, childStart, childLeft + childWidth, childBottom);
- }
- return new ChildBounds(childHeight, childWidth, childBottom, childLeft);
- }
-
- @SuppressWarnings("SuspiciousNameCombination")
- @Override
- public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
- return rect.left;
- }
-
- @Override
- public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
- // Add "left" side of phone which is actually the top
- return Collections.singletonList(new SplitPositionOption(
- R.drawable.ic_split_horizontal, R.string.recent_task_option_split_screen,
- STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
- }
-
- @Override
- public void getInitialSplitPlaceholderBounds(int placeholderHeight, int placeholderInset,
- DeviceProfile dp, @StagePosition int stagePosition, Rect out) {
- // In fake land/seascape, the placeholder always needs to go to the "top" of the device,
- // which is the same bounds as 0 rotation.
- int width = dp.widthPx;
- int insetSizeAdjustment = getPlaceholderSizeAdjustment(dp);
- out.set(0, 0, width, placeholderHeight + insetSizeAdjustment);
- out.inset(placeholderInset, 0);
-
- // Adjust the top to account for content off screen. This will help to animate the view in
- // with rounded corners.
- int screenWidth = dp.widthPx;
- int screenHeight = dp.heightPx;
- int totalHeight = (int) (1.0f * screenHeight / 2 * (screenWidth - 2 * placeholderInset)
- / screenWidth);
- out.top -= (totalHeight - placeholderHeight);
- }
-
- @Override
- public void updateSplitIconParams(View out, float onScreenRectCenterX,
- float onScreenRectCenterY, float fullscreenScaleX, float fullscreenScaleY,
- int drawableWidth, int drawableHeight, DeviceProfile dp,
- @StagePosition int stagePosition) {
- float insetAdjustment = getPlaceholderSizeAdjustment(dp) / 2f;
- out.setX(onScreenRectCenterX / fullscreenScaleX
- - 1.0f * drawableWidth / 2);
- out.setY((onScreenRectCenterY + insetAdjustment) / fullscreenScaleY
- - 1.0f * drawableHeight / 2);
- }
-
- /**
- * The split placeholder comes with a default inset to buffer the icon from the top of the
- * screen. But if the device already has a large inset (from cutouts etc), use that instead.
- */
- private int getPlaceholderSizeAdjustment(DeviceProfile dp) {
- return Math.max(dp.getInsets().top - dp.splitPlaceholderInset, 0);
- }
-
- @Override
- public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
- int splitInstructionsWidth) {
- out.setPivotX(0);
- out.setPivotY(splitInstructionsHeight);
- out.setRotation(getDegreesRotated());
- int distanceToEdge = out.getResources().getDimensionPixelSize(
- R.dimen.split_instructions_bottom_margin_phone_landscape);
- // Adjust for any insets on the left edge
- int insetCorrectionX = dp.getInsets().left;
- // Center the view in case of unbalanced insets on top or bottom of screen
- int insetCorrectionY = (dp.getInsets().bottom - dp.getInsets().top) / 2;
- out.setTranslationX(distanceToEdge - insetCorrectionX);
- out.setTranslationY(((-splitInstructionsHeight - splitInstructionsWidth) / 2f)
- + insetCorrectionY);
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
- // Setting gravity to LEFT instead of the lint-recommended START because we always want this
- // view to be screen-left when phone is in landscape, regardless of the RtL setting.
- lp.gravity = LEFT | CENTER_VERTICAL;
- out.setLayoutParams(lp);
- }
-
- @Override
- public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
- @StagePosition int stagePosition, Rect out1, Rect out2) {
- // In fake land/seascape, the window bounds are always top and bottom half
- int screenHeight = dp.heightPx;
- int screenWidth = dp.widthPx;
- out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize);
- out2.set(0, screenHeight / 2 + splitDividerSize, screenWidth, screenHeight);
- }
-
- @Override
- public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect,
- SplitBounds splitInfo, int desiredStagePosition) {
- float topLeftTaskPercent = splitInfo.appsStackedVertically
- ? splitInfo.topTaskPercent
- : splitInfo.leftTaskPercent;
- float dividerBarPercent = splitInfo.appsStackedVertically
- ? splitInfo.dividerHeightPercent
- : splitInfo.dividerWidthPercent;
-
- if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
- outRect.bottom = outRect.top + (int) (outRect.height() * topLeftTaskPercent);
- } else {
- outRect.top += (int) (outRect.height() * (topLeftTaskPercent + dividerBarPercent));
- }
- }
-
- @Override
- public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
- int parentWidth, int parentHeight, SplitBounds splitBoundsConfig, DeviceProfile dp,
- boolean isRtl) {
- FrameLayout.LayoutParams primaryParams =
- (FrameLayout.LayoutParams) primarySnapshot.getLayoutParams();
- FrameLayout.LayoutParams secondaryParams =
- (FrameLayout.LayoutParams) secondarySnapshot.getLayoutParams();
-
- // Swap the margins that are set in TaskView#setRecentsOrientedState()
- secondaryParams.topMargin = dp.overviewTaskThumbnailTopMarginPx;
- primaryParams.topMargin = 0;
-
- // Measure and layout the thumbnails bottom up, since the primary is on the visual left
- // (portrait bottom) and secondary is on the right (portrait top)
- int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
- int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
- int dividerBar = Math.round(totalThumbnailHeight * (splitBoundsConfig.appsStackedVertically
- ? splitBoundsConfig.dividerHeightPercent
- : splitBoundsConfig.dividerWidthPercent));
-
- Pair<Point, Point> taskViewSizes =
- getGroupedTaskViewSizes(dp, splitBoundsConfig, parentWidth, parentHeight);
-
- int translationY = taskViewSizes.first.y + spaceAboveSnapshot + dividerBar;
- primarySnapshot.setTranslationY(spaceAboveSnapshot);
- secondarySnapshot.setTranslationY(translationY - spaceAboveSnapshot);
-
- primarySnapshot.measure(
- View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.x, View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.y, View.MeasureSpec.EXACTLY)
- );
- secondarySnapshot.measure(
- View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.x, View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.y, View.MeasureSpec.EXACTLY)
- );
- }
-
- @Override
- public Pair<Point, Point> getGroupedTaskViewSizes(
- DeviceProfile dp,
- SplitBounds splitBoundsConfig,
- int parentWidth,
- int parentHeight) {
- int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
- int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
- int dividerBar = Math.round(totalThumbnailHeight * (splitBoundsConfig.appsStackedVertically
- ? splitBoundsConfig.dividerHeightPercent
- : splitBoundsConfig.dividerWidthPercent));
- float taskPercent = splitBoundsConfig.appsStackedVertically
- ? splitBoundsConfig.topTaskPercent
- : splitBoundsConfig.leftTaskPercent;
-
- Point firstTaskViewSize = new Point(
- parentWidth,
- (int) (totalThumbnailHeight * taskPercent)
- );
- Point secondTaskViewSize = new Point(
- parentWidth,
- totalThumbnailHeight - firstTaskViewSize.y - dividerBar
- );
-
- return new Pair<>(firstTaskViewSize, secondTaskViewSize);
- }
-
- @Override
- public void setTaskIconParams(FrameLayout.LayoutParams iconParams, int taskIconMargin,
- int taskIconHeight, int thumbnailTopMargin, boolean isRtl) {
- iconParams.gravity = (isRtl ? START : END) | CENTER_VERTICAL;
- iconParams.rightMargin = -taskIconHeight - taskIconMargin / 2;
- iconParams.leftMargin = 0;
- iconParams.topMargin = thumbnailTopMargin / 2;
- iconParams.bottomMargin = 0;
- }
-
- @Override
- public void setIconAppChipChildrenParams(FrameLayout.LayoutParams iconParams,
- int chipChildMarginStart) {
- iconParams.gravity = Gravity.START | Gravity.CENTER_VERTICAL;
- iconParams.setMarginStart(chipChildMarginStart);
- iconParams.topMargin = 0;
- }
-
- @Override
- public void setIconAppChipMenuParams(IconAppChipView iconAppChipView,
- FrameLayout.LayoutParams iconMenuParams, int iconMenuMargin, int thumbnailTopMargin) {
- boolean isRtl = iconAppChipView.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
- iconMenuParams.gravity = (isRtl ? START : END) | (isRtl ? BOTTOM : TOP);
- iconMenuParams.setMarginStart(isRtl ? iconMenuMargin : 0);
- iconMenuParams.topMargin = iconMenuMargin;
- iconMenuParams.bottomMargin = isRtl ? iconMenuMargin : 0;
- iconMenuParams.setMarginEnd(iconMenuMargin);
-
- iconAppChipView.setPivotX(isRtl ? iconMenuParams.width - (iconMenuParams.height / 2f)
- : iconMenuParams.width / 2f);
- iconAppChipView.setPivotY(
- isRtl ? (iconMenuParams.height / 2f) : iconMenuParams.width / 2f);
- iconAppChipView.setSplitTranslationY(0);
- iconAppChipView.setRotation(getDegreesRotated());
- }
-
- @Override
- public void setSplitIconParams(View primaryIconView, View secondaryIconView,
- int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
- int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
- DeviceProfile deviceProfile, SplitBounds splitConfig) {
- FrameLayout.LayoutParams primaryIconParams =
- (FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
- FrameLayout.LayoutParams secondaryIconParams = enableOverviewIconMenu()
- ? (FrameLayout.LayoutParams) secondaryIconView.getLayoutParams()
- : new FrameLayout.LayoutParams(primaryIconParams);
-
- // We calculate the "midpoint" of the thumbnail area, and place the icons there.
- // This is the place where the thumbnail area splits by default, in a near-50/50 split.
- // It is usually not exactly 50/50, due to insets/screen cutouts.
- int fullscreenInsetThickness = deviceProfile.getInsets().top
- - deviceProfile.getInsets().bottom;
- int fullscreenMidpointFromBottom = ((deviceProfile.heightPx - fullscreenInsetThickness)
- / 2);
- float midpointFromBottomPct = (float) fullscreenMidpointFromBottom / deviceProfile.heightPx;
- float insetPct = (float) fullscreenInsetThickness / deviceProfile.heightPx;
- int spaceAboveSnapshots = deviceProfile.overviewTaskThumbnailTopMarginPx;
- int overviewThumbnailAreaThickness = groupedTaskViewHeight - spaceAboveSnapshots;
- int bottomToMidpointOffset = (int) (overviewThumbnailAreaThickness * midpointFromBottomPct);
- int insetOffset = (int) (overviewThumbnailAreaThickness * insetPct);
-
- if (enableOverviewIconMenu()) {
- primaryIconParams.gravity = isRtl ? BOTTOM | START : TOP | END;
- secondaryIconParams.gravity = isRtl ? BOTTOM | START : TOP | END;
- } else {
- primaryIconParams.gravity = BOTTOM | (isRtl ? START : END);
- secondaryIconParams.gravity = BOTTOM | (isRtl ? START : END);
- }
- primaryIconView.setTranslationX(0);
- secondaryIconView.setTranslationX(0);
- if (enableOverviewIconMenu()) {
- IconAppChipView primaryAppChipView = (IconAppChipView) primaryIconView;
- IconAppChipView secondaryAppChipView = (IconAppChipView) secondaryIconView;
- if (primaryIconView.getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
- secondaryAppChipView.setSplitTranslationY(-primarySnapshotHeight);
- primaryAppChipView.setSplitTranslationY(0);
- } else {
- int secondarySnapshotHeight = groupedTaskViewHeight - primarySnapshotHeight;
- primaryAppChipView.setSplitTranslationY(secondarySnapshotHeight);
- }
- } else if (splitConfig.initiatedFromSeascape) {
- // if the split was initiated from seascape,
- // the task on the right (secondary) is slightly larger
- primaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset);
- secondaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset
- + taskIconHeight);
- } else {
- // if not,
- // the task on the left (primary) is slightly larger
- primaryIconView.setTranslationY(-bottomToMidpointOffset);
- secondaryIconView.setTranslationY(-bottomToMidpointOffset + taskIconHeight);
- }
-
- primaryIconView.setLayoutParams(primaryIconParams);
- secondaryIconView.setLayoutParams(secondaryIconParams);
- }
-
- @Override
- public int getDefaultSplitPosition(DeviceProfile deviceProfile) {
- throw new IllegalStateException("Default position not available in fake landscape");
- }
-
- @Override
- public Pair<FloatProperty, FloatProperty> getSplitSelectTaskOffset(FloatProperty primary,
- FloatProperty secondary, DeviceProfile deviceProfile) {
- return new Pair<>(primary, secondary);
- }
-
- @Override
- public float getFloatingTaskOffscreenTranslationTarget(View floatingTask, RectF onScreenRect,
- @StagePosition int stagePosition, DeviceProfile dp) {
- float currentTranslationY = floatingTask.getTranslationY();
- return currentTranslationY - onScreenRect.height();
- }
-
- @Override
- public void setFloatingTaskPrimaryTranslation(View floatingTask, float translation,
- DeviceProfile dp) {
- floatingTask.setTranslationY(translation);
- }
-
- @Override
- public Float getFloatingTaskPrimaryTranslation(View floatingTask, DeviceProfile dp) {
- return floatingTask.getTranslationY();
- }
-}
diff --git a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
new file mode 100644
index 0000000..39fb158
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
@@ -0,0 +1,659 @@
+/*
+ * 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.orientation
+
+import android.content.res.Resources
+import android.graphics.Point
+import android.graphics.PointF
+import android.graphics.Rect
+import android.graphics.RectF
+import android.graphics.drawable.ShapeDrawable
+import android.util.FloatProperty
+import android.util.Pair
+import android.view.Gravity
+import android.view.MotionEvent
+import android.view.Surface
+import android.view.VelocityTracker
+import android.view.View
+import android.view.View.MeasureSpec
+import android.view.ViewGroup
+import android.view.accessibility.AccessibilityEvent
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import androidx.core.util.component1
+import androidx.core.util.component2
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.Flags
+import com.android.launcher3.LauncherAnimUtils
+import com.android.launcher3.R
+import com.android.launcher3.Utilities
+import com.android.launcher3.touch.PagedOrientationHandler.ChildBounds
+import com.android.launcher3.touch.PagedOrientationHandler.Float2DAction
+import com.android.launcher3.touch.PagedOrientationHandler.Int2DAction
+import com.android.launcher3.touch.SingleAxisSwipeDetector
+import com.android.launcher3.util.SplitConfigurationOptions.*
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption
+import com.android.launcher3.util.SplitConfigurationOptions.StagePosition
+import com.android.launcher3.views.BaseDragLayer
+import com.android.quickstep.views.IconAppChipView
+import kotlin.math.max
+
+open class LandscapePagedViewHandler : RecentsPagedOrientationHandler {
+ override fun <T> getPrimaryValue(x: T, y: T): T = y
+
+ override fun <T> getSecondaryValue(x: T, y: T): T = x
+
+ override fun getPrimaryValue(x: Int, y: Int): Int = y
+
+ override fun getSecondaryValue(x: Int, y: Int): Int = x
+
+ override fun getPrimaryValue(x: Float, y: Float): Float = y
+
+ override fun getSecondaryValue(x: Float, y: Float): Float = x
+
+ override val isLayoutNaturalToLauncher: Boolean = false
+
+ override fun adjustFloatingIconStartVelocity(velocity: PointF) {
+ val oldX = velocity.x
+ val oldY = velocity.y
+ velocity.set(-oldY, oldX)
+ }
+
+ override fun fixBoundsForHomeAnimStartRect(outStartRect: RectF, deviceProfile: DeviceProfile) {
+ // We don't need to check the "top" value here because the startRect is in the orientation
+ // of the app, not of the fixed portrait launcher.
+ if (outStartRect.left > deviceProfile.heightPx) {
+ outStartRect.offsetTo(0f, outStartRect.top)
+ } else if (outStartRect.left < -deviceProfile.heightPx) {
+ outStartRect.offsetTo(0f, outStartRect.top)
+ }
+ }
+
+ override fun <T> setPrimary(target: T, action: Int2DAction<T>, param: Int) =
+ action.call(target, 0, param)
+
+ override fun <T> setPrimary(target: T, action: Float2DAction<T>, param: Float) =
+ action.call(target, 0f, param)
+
+ override fun <T> setSecondary(target: T, action: Float2DAction<T>, param: Float) =
+ action.call(target, param, 0f)
+
+ override fun <T> set(
+ target: T,
+ action: Int2DAction<T>,
+ primaryParam: Int,
+ secondaryParam: Int
+ ) = action.call(target, secondaryParam, primaryParam)
+
+ override fun getPrimaryDirection(event: MotionEvent, pointerIndex: Int): Float =
+ event.getY(pointerIndex)
+
+ override fun getPrimaryVelocity(velocityTracker: VelocityTracker, pointerId: Int): Float =
+ velocityTracker.getYVelocity(pointerId)
+
+ override fun getMeasuredSize(view: View): Int = view.measuredHeight
+
+ override fun getPrimarySize(view: View): Int = view.height
+
+ override fun getPrimarySize(rect: RectF): Float = rect.height()
+
+ override fun getStart(rect: RectF): Float = rect.top
+
+ override fun getEnd(rect: RectF): Float = rect.bottom
+
+ override fun getClearAllSidePadding(view: View, isRtl: Boolean): Int =
+ if (isRtl) view.paddingBottom / 2 else -view.paddingTop / 2
+
+ override fun getSecondaryDimension(view: View): Int = view.width
+
+ override val primaryViewTranslate: FloatProperty<View> = LauncherAnimUtils.VIEW_TRANSLATE_Y
+
+ override val secondaryViewTranslate: FloatProperty<View> = LauncherAnimUtils.VIEW_TRANSLATE_X
+
+ override fun getPrimaryScroll(view: View): Int = view.scrollY
+
+ override fun getPrimaryScale(view: View): Float = view.scaleY
+
+ override fun setMaxScroll(event: AccessibilityEvent, maxScroll: Int) {
+ event.maxScrollY = maxScroll
+ }
+
+ override fun getRecentsRtlSetting(resources: Resources): Boolean = !Utilities.isRtl(resources)
+
+ override val degreesRotated: Float = 90f
+
+ override val rotation: Int = Surface.ROTATION_90
+
+ override fun setPrimaryScale(view: View, scale: Float) {
+ view.scaleY = scale
+ }
+
+ override fun setSecondaryScale(view: View, scale: Float) {
+ view.scaleX = scale
+ }
+
+ override fun getChildStart(view: View): Int = view.top
+
+ override fun getCenterForPage(view: View, insets: Rect): Int =
+ (view.paddingLeft + view.measuredWidth + insets.left - insets.right - view.paddingRight) / 2
+
+ override fun getScrollOffsetStart(view: View, insets: Rect): Int = insets.top + view.paddingTop
+
+ override fun getScrollOffsetEnd(view: View, insets: Rect): Int =
+ view.height - view.paddingBottom - insets.bottom
+
+ override val secondaryTranslationDirectionFactor: Int = 1
+
+ override fun getSplitTranslationDirectionFactor(
+ stagePosition: Int,
+ deviceProfile: DeviceProfile
+ ): Int = if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) -1 else 1
+
+ override fun getTaskMenuX(
+ x: Float,
+ thumbnailView: View,
+ deviceProfile: DeviceProfile,
+ taskInsetMargin: Float,
+ taskViewIcon: View
+ ): Float = thumbnailView.measuredWidth + x - taskInsetMargin
+
+ override fun getTaskMenuY(
+ y: Float,
+ thumbnailView: View,
+ stagePosition: Int,
+ taskMenuView: View,
+ taskInsetMargin: Float,
+ taskViewIcon: View
+ ): Float {
+ val layoutParams = taskMenuView.layoutParams as BaseDragLayer.LayoutParams
+ var taskMenuY = y + taskInsetMargin
+
+ if (stagePosition == STAGE_POSITION_UNDEFINED) {
+ taskMenuY += (thumbnailView.measuredHeight - layoutParams.width) / 2f
+ }
+
+ return taskMenuY
+ }
+
+ override fun getTaskMenuWidth(
+ thumbnailView: View,
+ deviceProfile: DeviceProfile,
+ @StagePosition stagePosition: Int
+ ): Int =
+ when {
+ Flags.enableOverviewIconMenu() ->
+ thumbnailView.resources.getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_expanded_width
+ )
+ stagePosition == STAGE_POSITION_UNDEFINED -> thumbnailView.measuredWidth
+ else -> thumbnailView.measuredHeight
+ }
+
+ override fun getTaskMenuHeight(
+ taskInsetMargin: Float,
+ deviceProfile: DeviceProfile,
+ taskMenuX: Float,
+ taskMenuY: Float
+ ): Int = (taskMenuX - taskInsetMargin).toInt()
+
+ override fun setTaskOptionsMenuLayoutOrientation(
+ deviceProfile: DeviceProfile,
+ taskMenuLayout: LinearLayout,
+ dividerSpacing: Int,
+ dividerDrawable: ShapeDrawable
+ ) {
+ taskMenuLayout.orientation = LinearLayout.VERTICAL
+ dividerDrawable.intrinsicHeight = dividerSpacing
+ taskMenuLayout.dividerDrawable = dividerDrawable
+ }
+
+ override fun setLayoutParamsForTaskMenuOptionItem(
+ lp: LinearLayout.LayoutParams,
+ viewGroup: LinearLayout,
+ deviceProfile: DeviceProfile
+ ) {
+ // Phone fake landscape
+ viewGroup.orientation = LinearLayout.HORIZONTAL
+ lp.width = ViewGroup.LayoutParams.MATCH_PARENT
+ lp.height = ViewGroup.LayoutParams.WRAP_CONTENT
+ }
+
+ override fun getDwbLayoutTranslations(
+ taskViewWidth: Int,
+ taskViewHeight: Int,
+ splitBounds: SplitBounds?,
+ deviceProfile: DeviceProfile,
+ thumbnailViews: Array<View>,
+ desiredTaskId: Int,
+ banner: View
+ ): Pair<Float, Float> {
+ val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
+ val isRtl = banner.layoutDirection == View.LAYOUT_DIRECTION_RTL
+ val translationX = banner.height.toFloat()
+
+ val bannerParams = banner.layoutParams as FrameLayout.LayoutParams
+ bannerParams.gravity = Gravity.TOP or if (isRtl) Gravity.END else Gravity.START
+ banner.pivotX = 0f
+ banner.pivotY = 0f
+ banner.rotation = degreesRotated
+
+ if (splitBounds == null) {
+ // Single, fullscreen case
+ bannerParams.width = taskViewHeight - snapshotParams.topMargin
+ return Pair(translationX, snapshotParams.topMargin.toFloat())
+ }
+
+ // Set correct width and translations
+ val translationY: Float
+ if (desiredTaskId == splitBounds.leftTopTaskId) {
+ bannerParams.width = thumbnailViews[0].measuredHeight
+ translationY = snapshotParams.topMargin.toFloat()
+ } else {
+ bannerParams.width = thumbnailViews[1].measuredHeight
+ val topLeftTaskPlusDividerPercent =
+ if (splitBounds.appsStackedVertically) {
+ splitBounds.topTaskPercent + splitBounds.dividerHeightPercent
+ } else {
+ splitBounds.leftTaskPercent + splitBounds.dividerWidthPercent
+ }
+ translationY =
+ snapshotParams.topMargin +
+ (taskViewHeight - snapshotParams.topMargin) * topLeftTaskPlusDividerPercent
+ }
+
+ return Pair(translationX, translationY)
+ }
+
+ /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
+ override val upDownSwipeDirection: SingleAxisSwipeDetector.Direction =
+ SingleAxisSwipeDetector.HORIZONTAL
+
+ override fun getUpDirection(isRtl: Boolean): Int =
+ if (isRtl) SingleAxisSwipeDetector.DIRECTION_NEGATIVE
+ else SingleAxisSwipeDetector.DIRECTION_POSITIVE
+
+ override fun isGoingUp(displacement: Float, isRtl: Boolean): Boolean =
+ if (isRtl) displacement < 0 else displacement > 0
+
+ override fun getTaskDragDisplacementFactor(isRtl: Boolean): Int = if (isRtl) 1 else -1
+ /* -------------------- */
+
+ override fun getChildBounds(
+ child: View,
+ childStart: Int,
+ pageCenter: Int,
+ layoutChild: Boolean
+ ): ChildBounds {
+ val childHeight = child.measuredHeight
+ val childWidth = child.measuredWidth
+ val childBottom = childStart + childHeight
+ val childLeft = pageCenter - childWidth / 2
+ if (layoutChild) {
+ child.layout(childLeft, childStart, childLeft + childWidth, childBottom)
+ }
+ return ChildBounds(childHeight, childWidth, childBottom, childLeft)
+ }
+
+ override fun getDistanceToBottomOfRect(dp: DeviceProfile, rect: Rect): Int = rect.left
+
+ override fun getSplitPositionOptions(dp: DeviceProfile): List<SplitPositionOption> =
+ // Add "left" side of phone which is actually the top
+ listOf(
+ SplitPositionOption(
+ R.drawable.ic_split_horizontal,
+ R.string.recent_task_option_split_screen,
+ STAGE_POSITION_TOP_OR_LEFT,
+ STAGE_TYPE_MAIN
+ )
+ )
+
+ override fun getInitialSplitPlaceholderBounds(
+ placeholderHeight: Int,
+ placeholderInset: Int,
+ dp: DeviceProfile,
+ @StagePosition stagePosition: Int,
+ out: Rect
+ ) {
+ // In fake land/seascape, the placeholder always needs to go to the "top" of the device,
+ // which is the same bounds as 0 rotation.
+ val width = dp.widthPx
+ val insetSizeAdjustment = getPlaceholderSizeAdjustment(dp)
+ out.set(0, 0, width, placeholderHeight + insetSizeAdjustment)
+ out.inset(placeholderInset, 0)
+
+ // Adjust the top to account for content off screen. This will help to animate the view in
+ // with rounded corners.
+ val screenWidth = dp.widthPx
+ val screenHeight = dp.heightPx
+ val totalHeight =
+ (1.0f * screenHeight / 2 * (screenWidth - 2 * placeholderInset) / screenWidth).toInt()
+ out.top -= totalHeight - placeholderHeight
+ }
+
+ override fun updateSplitIconParams(
+ out: View,
+ onScreenRectCenterX: Float,
+ onScreenRectCenterY: Float,
+ fullscreenScaleX: Float,
+ fullscreenScaleY: Float,
+ drawableWidth: Int,
+ drawableHeight: Int,
+ dp: DeviceProfile,
+ @StagePosition stagePosition: Int
+ ) {
+ val insetAdjustment = getPlaceholderSizeAdjustment(dp) / 2f
+ out.x = (onScreenRectCenterX / fullscreenScaleX - 1.0f * drawableWidth / 2)
+ out.y =
+ ((onScreenRectCenterY + insetAdjustment) / fullscreenScaleY - 1.0f * drawableHeight / 2)
+ }
+
+ /**
+ * The split placeholder comes with a default inset to buffer the icon from the top of the
+ * screen. But if the device already has a large inset (from cutouts etc), use that instead.
+ */
+ private fun getPlaceholderSizeAdjustment(dp: DeviceProfile?): Int =
+ max((dp!!.insets.top - dp.splitPlaceholderInset).toDouble(), 0.0).toInt()
+
+ override fun setSplitInstructionsParams(
+ out: View,
+ dp: DeviceProfile,
+ splitInstructionsHeight: Int,
+ splitInstructionsWidth: Int
+ ) {
+ out.pivotX = 0f
+ out.pivotY = splitInstructionsHeight.toFloat()
+ out.rotation = degreesRotated
+ val distanceToEdge =
+ out.resources.getDimensionPixelSize(
+ R.dimen.split_instructions_bottom_margin_phone_landscape
+ )
+ // Adjust for any insets on the left edge
+ val insetCorrectionX = dp.insets.left
+ // Center the view in case of unbalanced insets on top or bottom of screen
+ val insetCorrectionY = (dp.insets.bottom - dp.insets.top) / 2
+ out.translationX = (distanceToEdge - insetCorrectionX).toFloat()
+ out.translationY =
+ (-splitInstructionsHeight - splitInstructionsWidth) / 2f + insetCorrectionY
+ // Setting gravity to LEFT instead of the lint-recommended START because we always want this
+ // view to be screen-left when phone is in landscape, regardless of the RtL setting.
+ val lp = out.layoutParams as FrameLayout.LayoutParams
+ lp.gravity = Gravity.LEFT or Gravity.CENTER_VERTICAL
+ out.layoutParams = lp
+ }
+
+ override fun getFinalSplitPlaceholderBounds(
+ splitDividerSize: Int,
+ dp: DeviceProfile,
+ @StagePosition stagePosition: Int,
+ out1: Rect,
+ out2: Rect
+ ) {
+ // In fake land/seascape, the window bounds are always top and bottom half
+ val screenHeight = dp.heightPx
+ val screenWidth = dp.widthPx
+ out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize)
+ out2.set(0, screenHeight / 2 + splitDividerSize, screenWidth, screenHeight)
+ }
+
+ override fun setSplitTaskSwipeRect(
+ dp: DeviceProfile,
+ outRect: Rect,
+ splitInfo: SplitBounds,
+ desiredStagePosition: Int
+ ) {
+ val topLeftTaskPercent: Float
+ val dividerBarPercent: Float
+ if (splitInfo.appsStackedVertically) {
+ topLeftTaskPercent = splitInfo.topTaskPercent
+ dividerBarPercent = splitInfo.dividerHeightPercent
+ } else {
+ topLeftTaskPercent = splitInfo.leftTaskPercent
+ dividerBarPercent = splitInfo.dividerWidthPercent
+ }
+
+ if (desiredStagePosition == STAGE_POSITION_TOP_OR_LEFT) {
+ outRect.bottom = outRect.top + (outRect.height() * topLeftTaskPercent).toInt()
+ } else {
+ outRect.top += (outRect.height() * (topLeftTaskPercent + dividerBarPercent)).toInt()
+ }
+ }
+
+ override fun measureGroupedTaskViewThumbnailBounds(
+ primarySnapshot: View,
+ secondarySnapshot: View,
+ parentWidth: Int,
+ parentHeight: Int,
+ splitBoundsConfig: SplitBounds,
+ dp: DeviceProfile,
+ isRtl: Boolean
+ ) {
+ val primaryParams = primarySnapshot.layoutParams as FrameLayout.LayoutParams
+ val secondaryParams = secondarySnapshot.layoutParams as FrameLayout.LayoutParams
+
+ // Swap the margins that are set in TaskView#setRecentsOrientedState()
+ secondaryParams.topMargin = dp.overviewTaskThumbnailTopMarginPx
+ primaryParams.topMargin = 0
+
+ // Measure and layout the thumbnails bottom up, since the primary is on the visual left
+ // (portrait bottom) and secondary is on the right (portrait top)
+ val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
+ val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
+ val dividerBar =
+ Math.round(
+ totalThumbnailHeight *
+ if (splitBoundsConfig.appsStackedVertically)
+ splitBoundsConfig.dividerHeightPercent
+ else splitBoundsConfig.dividerWidthPercent
+ )
+ val (taskViewFirst, taskViewSecond) =
+ getGroupedTaskViewSizes(dp, splitBoundsConfig, parentWidth, parentHeight)
+
+ primarySnapshot.translationY = spaceAboveSnapshot.toFloat()
+ primarySnapshot.measure(
+ MeasureSpec.makeMeasureSpec(taskViewFirst.x, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(taskViewFirst.y, MeasureSpec.EXACTLY)
+ )
+ val translationY = taskViewFirst.y + spaceAboveSnapshot + dividerBar
+ secondarySnapshot.translationY = (translationY - spaceAboveSnapshot).toFloat()
+ secondarySnapshot.measure(
+ MeasureSpec.makeMeasureSpec(taskViewSecond.x, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(taskViewSecond.y, MeasureSpec.EXACTLY)
+ )
+ }
+
+ override fun getGroupedTaskViewSizes(
+ dp: DeviceProfile,
+ splitBoundsConfig: SplitBounds,
+ parentWidth: Int,
+ parentHeight: Int
+ ): Pair<Point, Point> {
+ val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
+ val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
+ val dividerBar =
+ Math.round(
+ totalThumbnailHeight *
+ if (splitBoundsConfig.appsStackedVertically)
+ splitBoundsConfig.dividerHeightPercent
+ else splitBoundsConfig.dividerWidthPercent
+ )
+ val taskPercent =
+ if (splitBoundsConfig.appsStackedVertically) {
+ splitBoundsConfig.topTaskPercent
+ } else {
+ splitBoundsConfig.leftTaskPercent
+ }
+ val firstTaskViewSize = Point(parentWidth, (totalThumbnailHeight * taskPercent).toInt())
+ val secondTaskViewSize =
+ Point(parentWidth, totalThumbnailHeight - firstTaskViewSize.y - dividerBar)
+ return Pair(firstTaskViewSize, secondTaskViewSize)
+ }
+
+ override fun setTaskIconParams(
+ iconParams: FrameLayout.LayoutParams,
+ taskIconMargin: Int,
+ taskIconHeight: Int,
+ thumbnailTopMargin: Int,
+ isRtl: Boolean
+ ) {
+ iconParams.gravity =
+ if (isRtl) {
+ Gravity.START or Gravity.CENTER_VERTICAL
+ } else {
+ Gravity.END or Gravity.CENTER_VERTICAL
+ }
+ iconParams.rightMargin = -taskIconHeight - taskIconMargin / 2
+ iconParams.leftMargin = 0
+ iconParams.topMargin = thumbnailTopMargin / 2
+ iconParams.bottomMargin = 0
+ }
+
+ override fun setIconAppChipChildrenParams(
+ iconParams: FrameLayout.LayoutParams,
+ chipChildMarginStart: Int
+ ) {
+ iconParams.gravity = Gravity.START or Gravity.CENTER_VERTICAL
+ iconParams.marginStart = chipChildMarginStart
+ iconParams.topMargin = 0
+ }
+
+ override fun setIconAppChipMenuParams(
+ iconAppChipView: IconAppChipView,
+ iconMenuParams: FrameLayout.LayoutParams,
+ iconMenuMargin: Int,
+ thumbnailTopMargin: Int
+ ) {
+ val isRtl = iconAppChipView.layoutDirection == View.LAYOUT_DIRECTION_RTL
+
+ if (isRtl) {
+ iconMenuParams.gravity = Gravity.START or Gravity.BOTTOM
+ iconMenuParams.marginStart = iconMenuMargin
+ iconMenuParams.bottomMargin = iconMenuMargin
+ iconAppChipView.pivotX = iconMenuParams.width - iconMenuParams.height / 2f
+ iconAppChipView.pivotY = iconMenuParams.height / 2f
+ } else {
+ iconMenuParams.gravity = Gravity.END or Gravity.TOP
+ iconMenuParams.marginStart = 0
+ iconMenuParams.bottomMargin = 0
+ iconAppChipView.pivotX = iconMenuParams.width / 2f
+ iconAppChipView.pivotY = iconMenuParams.width / 2f
+ }
+
+ iconMenuParams.topMargin = iconMenuMargin
+ iconMenuParams.marginEnd = iconMenuMargin
+ iconAppChipView.setSplitTranslationY(0f)
+ iconAppChipView.setRotation(degreesRotated)
+ }
+
+ override fun setSplitIconParams(
+ primaryIconView: View,
+ secondaryIconView: View,
+ taskIconHeight: Int,
+ primarySnapshotWidth: Int,
+ primarySnapshotHeight: Int,
+ groupedTaskViewHeight: Int,
+ groupedTaskViewWidth: Int,
+ isRtl: Boolean,
+ deviceProfile: DeviceProfile,
+ splitConfig: SplitBounds
+ ) {
+ val primaryIconParams = primaryIconView.layoutParams as FrameLayout.LayoutParams
+ val secondaryIconParams =
+ if (Flags.enableOverviewIconMenu())
+ secondaryIconView.layoutParams as FrameLayout.LayoutParams
+ else FrameLayout.LayoutParams(primaryIconParams)
+
+ // We calculate the "midpoint" of the thumbnail area, and place the icons there.
+ // This is the place where the thumbnail area splits by default, in a near-50/50 split.
+ // It is usually not exactly 50/50, due to insets/screen cutouts.
+ val fullscreenInsetThickness = (deviceProfile.insets.top - deviceProfile.insets.bottom)
+ val fullscreenMidpointFromBottom = ((deviceProfile.heightPx - fullscreenInsetThickness) / 2)
+ val midpointFromBottomPct = fullscreenMidpointFromBottom.toFloat() / deviceProfile.heightPx
+ val insetPct = fullscreenInsetThickness.toFloat() / deviceProfile.heightPx
+ val spaceAboveSnapshots = deviceProfile.overviewTaskThumbnailTopMarginPx
+ val overviewThumbnailAreaThickness = groupedTaskViewHeight - spaceAboveSnapshots
+ val bottomToMidpointOffset =
+ (overviewThumbnailAreaThickness * midpointFromBottomPct).toInt()
+ val insetOffset = (overviewThumbnailAreaThickness * insetPct).toInt()
+ if (Flags.enableOverviewIconMenu()) {
+ val gravity = if (isRtl) Gravity.BOTTOM or Gravity.START else Gravity.TOP or Gravity.END
+ primaryIconParams.gravity = gravity
+ secondaryIconParams.gravity = gravity
+ } else {
+ primaryIconParams.gravity = Gravity.BOTTOM or if (isRtl) Gravity.START else Gravity.END
+ secondaryIconParams.gravity =
+ Gravity.BOTTOM or if (isRtl) Gravity.START else Gravity.END
+ }
+ primaryIconView.translationX = 0f
+ secondaryIconView.translationX = 0f
+ when {
+ Flags.enableOverviewIconMenu() -> {
+ val primaryAppChipView = primaryIconView as IconAppChipView
+ val secondaryAppChipView = secondaryIconView as IconAppChipView
+ if (primaryIconView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+ secondaryAppChipView.setSplitTranslationY(-primarySnapshotHeight.toFloat())
+ primaryAppChipView.setSplitTranslationY(0f)
+ } else {
+ val secondarySnapshotHeight = groupedTaskViewHeight - primarySnapshotHeight
+ primaryAppChipView.setSplitTranslationY(secondarySnapshotHeight.toFloat())
+ }
+ }
+ splitConfig.initiatedFromSeascape -> {
+ // if the split was initiated from seascape,
+ // the task on the right (secondary) is slightly larger
+ primaryIconView.translationY = (-bottomToMidpointOffset - insetOffset).toFloat()
+ secondaryIconView.translationY =
+ (-bottomToMidpointOffset - insetOffset + taskIconHeight).toFloat()
+ }
+ else -> {
+ // if not,
+ // the task on the left (primary) is slightly larger
+ primaryIconView.translationY = -bottomToMidpointOffset.toFloat()
+ secondaryIconView.translationY =
+ (-bottomToMidpointOffset + taskIconHeight).toFloat()
+ }
+ }
+ primaryIconView.layoutParams = primaryIconParams
+ secondaryIconView.layoutParams = secondaryIconParams
+ }
+
+ override fun getDefaultSplitPosition(deviceProfile: DeviceProfile): Int {
+ throw IllegalStateException("Default position not available in fake landscape")
+ }
+
+ override fun <T> getSplitSelectTaskOffset(
+ primary: FloatProperty<T>,
+ secondary: FloatProperty<T>,
+ deviceProfile: DeviceProfile
+ ): Pair<FloatProperty<T>, FloatProperty<T>> = Pair(primary, secondary)
+
+ override fun getFloatingTaskOffscreenTranslationTarget(
+ floatingTask: View,
+ onScreenRect: RectF,
+ @StagePosition stagePosition: Int,
+ dp: DeviceProfile
+ ): Float = floatingTask.translationY - onScreenRect.height()
+
+ override fun setFloatingTaskPrimaryTranslation(
+ floatingTask: View,
+ translation: Float,
+ dp: DeviceProfile
+ ) {
+ floatingTask.translationY = translation
+ }
+
+ override fun getFloatingTaskPrimaryTranslation(floatingTask: View, dp: DeviceProfile): Float =
+ floatingTask.translationY
+}
diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
index 60e6a25..62dfd82 100644
--- a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
+++ b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
@@ -210,6 +210,12 @@
}
@Override
+ public int getTaskMenuHeight(float taskInsetMargin, DeviceProfile deviceProfile,
+ float taskMenuX, float taskMenuY) {
+ return (int) (deviceProfile.availableHeightPx - taskInsetMargin - taskMenuY);
+ }
+
+ @Override
public void setTaskOptionsMenuLayoutOrientation(DeviceProfile deviceProfile,
LinearLayout taskMenuLayout, int dividerSpacing,
ShapeDrawable dividerDrawable) {
@@ -789,7 +795,7 @@
}
@Override
- public Float getFloatingTaskPrimaryTranslation(View floatingTask, DeviceProfile dp) {
+ public float getFloatingTaskPrimaryTranslation(View floatingTask, DeviceProfile dp) {
return dp.isLeftRightSplit
? floatingTask.getTranslationX()
: floatingTask.getTranslationY();
diff --git a/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.java b/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.java
deleted file mode 100644
index 01c1225..0000000
--- a/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * 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.orientation;
-
-
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.ShapeDrawable;
-import android.util.FloatProperty;
-import android.util.Pair;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.touch.SingleAxisSwipeDetector;
-import com.android.launcher3.util.SplitConfigurationOptions;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
-import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
-import com.android.quickstep.views.IconAppChipView;
-
-import java.util.List;
-
-/**
- * Abstraction layer to separate horizontal and vertical specific implementations
- * for {@link com.android.quickstep.views.RecentsView}. Majority of these implementations are
- * (should be) as simple as choosing the correct X and Y analogous methods.
- */
-public interface RecentsPagedOrientationHandler extends PagedOrientationHandler {
-
- RecentsPagedOrientationHandler PORTRAIT = new PortraitPagedViewHandler();
- RecentsPagedOrientationHandler LANDSCAPE = new LandscapePagedViewHandler();
- RecentsPagedOrientationHandler SEASCAPE = new SeascapePagedViewHandler();
-
- <T> void setSecondary(T target, Float2DAction<T> action, float param);
- <T> void set(T target, Int2DAction<T> action, int primaryParam, int secondaryParam);
- int getPrimarySize(View view);
- float getPrimarySize(RectF rect);
- int getSecondaryTranslationDirectionFactor();
- float getDegreesRotated();
- int getRotation();
- boolean isLayoutNaturalToLauncher();
-
- <T> T getPrimaryValue(T x, T y);
- <T> T getSecondaryValue(T x, T y);
- void setPrimaryScale(View view, float scale);
- void setSecondaryScale(View view, float scale);
- float getStart(RectF rect);
- float getEnd(RectF rect);
- int getClearAllSidePadding(View view, boolean isRtl);
- int getSecondaryDimension(View view);
- FloatProperty<View> getPrimaryViewTranslate();
- FloatProperty<View> getSecondaryViewTranslate();
- int getSplitTranslationDirectionFactor(@StagePosition int stagePosition,
- DeviceProfile deviceProfile);
- Pair<FloatProperty, FloatProperty> getSplitSelectTaskOffset(FloatProperty primary,
- FloatProperty secondary, DeviceProfile deviceProfile);
- int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect);
- List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp);
- /**
- * @param placeholderHeight height of placeholder view in portrait, width in landscape
- */
- void getInitialSplitPlaceholderBounds(int placeholderHeight, int placeholderInset,
- DeviceProfile dp, @StagePosition int stagePosition, Rect out);
-
- /**
- * Centers an icon in the split staging area, accounting for insets.
- * @param out The icon that needs to be centered.
- * @param onScreenRectCenterX The x-center of the on-screen staging area (most of the Rect is
- * offscreen).
- * @param onScreenRectCenterY The y-center of the on-screen staging area (most of the Rect is
- * offscreen).
- * @param fullscreenScaleX A x-scaling factor used to convert coordinates back into pixels.
- * @param fullscreenScaleY A y-scaling factor used to convert coordinates back into pixels.
- * @param drawableWidth The icon's drawable (final) width.
- * @param drawableHeight The icon's drawable (final) height.
- * @param dp The device profile, used to report rotation and hardware insets.
- * @param stagePosition 0 if the staging area is pinned to top/left, 1 for bottom/right.
- */
- void updateSplitIconParams(View out, float onScreenRectCenterX,
- float onScreenRectCenterY, float fullscreenScaleX, float fullscreenScaleY,
- int drawableWidth, int drawableHeight, DeviceProfile dp,
- @StagePosition int stagePosition);
-
- /**
- * Sets positioning and rotation for a SplitInstructionsView.
- * @param out The SplitInstructionsView that needs to be positioned.
- * @param dp The device profile, used to report rotation and device type.
- * @param splitInstructionsHeight The SplitInstructionView's height.
- * @param splitInstructionsWidth The SplitInstructionView's width.
- */
- void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
- int splitInstructionsWidth);
-
- /**
- * @param splitDividerSize height of split screen drag handle in portrait, width in landscape
- * @param stagePosition the split position option (top/left, bottom/right) of the first
- * task selected for entering split
- * @param out1 the bounds for where the first selected app will be
- * @param out2 the bounds for where the second selected app will be, complimentary to
- * {@param out1} based on {@param initialSplitOption}
- */
- void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
- @StagePosition int stagePosition, Rect out1, Rect out2);
-
- int getDefaultSplitPosition(DeviceProfile deviceProfile);
-
- /**
- * @param outRect This is expected to be the rect that has the dimensions for a non-split,
- * fullscreen task in overview. This will directly be modified.
- * @param desiredStagePosition Which stage position (topLeft/rightBottom) we want to resize
- * outRect for
- */
- void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect, SplitBounds splitInfo,
- @SplitConfigurationOptions.StagePosition int desiredStagePosition);
-
- void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
- int parentWidth, int parentHeight,
- SplitBounds splitBoundsConfig, DeviceProfile dp, boolean isRtl);
-
- /**
- * Creates two Points representing the dimensions of the two tasks in a GroupedTaskView
- *
- * @return first -> primary task snapshot, second -> secondary task snapshot.
- * x -> width, y -> height
- */
- Pair<Point, Point> getGroupedTaskViewSizes(DeviceProfile dp, SplitBounds splitBoundsConfig,
- int parentWidth, int parentHeight);
-
- // Overview TaskMenuView methods
- /** Sets layout params on a task's app icon. Only use this when app chip is disabled. */
- void setTaskIconParams(FrameLayout.LayoutParams iconParams,
- int taskIconMargin, int taskIconHeight, int thumbnailTopMargin, boolean isRtl);
-
- /**
- * Sets layout params on the children of an app chip. Only use this when app chip is enabled.
- */
- void setIconAppChipChildrenParams(
- FrameLayout.LayoutParams iconParams, int chipChildMarginStart);
-
- void setIconAppChipMenuParams(IconAppChipView iconAppChipView,
- FrameLayout.LayoutParams iconMenuParams,
- int iconMenuMargin, int thumbnailTopMargin);
- void setSplitIconParams(View primaryIconView, View secondaryIconView,
- int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
- int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
- DeviceProfile deviceProfile, SplitBounds splitConfig);
-
- /*
- * The following two methods try to center the TaskMenuView in landscape by finding the center
- * of the thumbnail view and then subtracting half of the taskMenu width. In this case, the
- * taskMenu width is the same size as the thumbnail width (what got set below in
- * getTaskMenuWidth()), so we directly use that in the calculations.
- */
- float getTaskMenuX(float x, View thumbnailView, DeviceProfile deviceProfile,
- float taskInsetMargin, View taskViewIcon);
- float getTaskMenuY(float y, View thumbnailView, int stagePosition,
- View taskMenuView, float taskInsetMargin, View taskViewIcon);
- int getTaskMenuWidth(View thumbnailView, DeviceProfile deviceProfile,
- @StagePosition int stagePosition);
- /**
- * Sets linear layout orientation for {@link com.android.launcher3.popup.SystemShortcut} items
- * inside task menu view.
- */
- void setTaskOptionsMenuLayoutOrientation(DeviceProfile deviceProfile,
- LinearLayout taskMenuLayout, int dividerSpacing,
- ShapeDrawable dividerDrawable);
- /**
- * Sets layout param attributes for {@link com.android.launcher3.popup.SystemShortcut} child
- * views inside task menu view.
- */
- void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp,
- LinearLayout viewGroup, DeviceProfile deviceProfile);
-
- /**
- * Calculates the position where a Digital Wellbeing Banner should be placed on its parent
- * TaskView.
- * @return A Pair of Floats representing the proper x and y translations.
- */
- Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth,
- int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile,
- View[] thumbnailViews, int desiredTaskId, View banner);
-
- // The following are only used by TaskViewTouchHandler.
- /** @return Either VERTICAL or HORIZONTAL. */
- SingleAxisSwipeDetector.Direction getUpDownSwipeDirection();
- /** @return Given {@link #getUpDownSwipeDirection()}, whether POSITIVE or NEGATIVE is up. */
- int getUpDirection(boolean isRtl);
- /** @return Whether the displacement is going towards the top of the screen. */
- boolean isGoingUp(float displacement, boolean isRtl);
- /** @return Either 1 or -1, a factor to multiply by so the animation goes the correct way. */
- int getTaskDragDisplacementFactor(boolean isRtl);
-
- /**
- * Maps the velocity from the coordinate plane of the foreground app to that
- * of Launcher's (which now will always be portrait)
- */
- void adjustFloatingIconStartVelocity(PointF velocity);
-
- /**
- * Ensures that outStartRect left bound is within the DeviceProfile's visual boundaries
- * @param outStartRect The start rect that will directly be modified
- */
- void fixBoundsForHomeAnimStartRect(RectF outStartRect, DeviceProfile deviceProfile);
-
- /**
- * Determine the target translation for animating the FloatingTaskView out. This value could
- * either be an x-coordinate or a y-coordinate, depending on which way the FloatingTaskView was
- * docked.
- *
- * @param floatingTask The FloatingTaskView.
- * @param onScreenRect The current on-screen dimensions of the FloatingTaskView.
- * @param stagePosition STAGE_POSITION_TOP_OR_LEFT or STAGE_POSITION_BOTTOM_OR_RIGHT.
- * @param dp The device profile.
- * @return A float. When an animation translates the FloatingTaskView to this position, it will
- * appear to tuck away off the edge of the screen.
- */
- float getFloatingTaskOffscreenTranslationTarget(View floatingTask, RectF onScreenRect,
- @StagePosition int stagePosition, DeviceProfile dp);
-
- /**
- * Sets the translation of a FloatingTaskView along its "slide-in/slide-out" axis (could be
- * either x or y), depending on how the view is oriented.
- *
- * @param floatingTask The FloatingTaskView to be translated.
- * @param translation The target translation value.
- * @param dp The current device profile.
- */
- void setFloatingTaskPrimaryTranslation(View floatingTask, float translation, DeviceProfile dp);
-
- /**
- * Gets the translation of a FloatingTaskView along its "slide-in/slide-out" axis (could be
- * either x or y), depending on how the view is oriented.
- *
- * @param floatingTask The FloatingTaskView in question.
- * @param dp The current device profile.
- * @return The current translation value.
- */
- Float getFloatingTaskPrimaryTranslation(View floatingTask, DeviceProfile dp);
-}
diff --git a/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt b/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
new file mode 100644
index 0000000..6c82890
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
@@ -0,0 +1,379 @@
+/*
+ * 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.orientation
+
+import android.graphics.Point
+import android.graphics.PointF
+import android.graphics.Rect
+import android.graphics.RectF
+import android.graphics.drawable.ShapeDrawable
+import android.util.FloatProperty
+import android.util.Pair
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.touch.PagedOrientationHandler
+import com.android.launcher3.touch.PagedOrientationHandler.Float2DAction
+import com.android.launcher3.touch.PagedOrientationHandler.Int2DAction
+import com.android.launcher3.touch.SingleAxisSwipeDetector
+import com.android.launcher3.util.SplitConfigurationOptions
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption
+import com.android.launcher3.util.SplitConfigurationOptions.StagePosition
+import com.android.quickstep.views.IconAppChipView
+
+/**
+ * Abstraction layer to separate horizontal and vertical specific implementations for
+ * [com.android.quickstep.views.RecentsView]. Majority of these implementations are (should be) as
+ * simple as choosing the correct X and Y analogous methods.
+ */
+interface RecentsPagedOrientationHandler : PagedOrientationHandler {
+ fun <T> setSecondary(target: T, action: Float2DAction<T>, param: Float)
+
+ operator fun <T> set(target: T, action: Int2DAction<T>, primaryParam: Int, secondaryParam: Int)
+
+ fun getPrimarySize(view: View): Int
+
+ fun getPrimarySize(rect: RectF): Float
+
+ val secondaryTranslationDirectionFactor: Int
+
+ val degreesRotated: Float
+
+ val rotation: Int
+
+ val isLayoutNaturalToLauncher: Boolean
+
+ fun <T> getPrimaryValue(x: T, y: T): T
+
+ fun <T> getSecondaryValue(x: T, y: T): T
+
+ fun setPrimaryScale(view: View, scale: Float)
+
+ fun setSecondaryScale(view: View, scale: Float)
+
+ fun getStart(rect: RectF): Float
+
+ fun getEnd(rect: RectF): Float
+
+ fun getClearAllSidePadding(view: View, isRtl: Boolean): Int
+
+ fun getSecondaryDimension(view: View): Int
+
+ val primaryViewTranslate: FloatProperty<View>
+ val secondaryViewTranslate: FloatProperty<View>
+
+ fun getSplitTranslationDirectionFactor(
+ @StagePosition stagePosition: Int,
+ deviceProfile: DeviceProfile
+ ): Int
+
+ fun <T> getSplitSelectTaskOffset(
+ primary: FloatProperty<T>,
+ secondary: FloatProperty<T>,
+ deviceProfile: DeviceProfile
+ ): Pair<FloatProperty<T>, FloatProperty<T>>
+
+ fun getDistanceToBottomOfRect(dp: DeviceProfile, rect: Rect): Int
+
+ fun getSplitPositionOptions(dp: DeviceProfile): List<SplitPositionOption>
+
+ /** @param placeholderHeight height of placeholder view in portrait, width in landscape */
+ fun getInitialSplitPlaceholderBounds(
+ placeholderHeight: Int,
+ placeholderInset: Int,
+ dp: DeviceProfile,
+ @StagePosition stagePosition: Int,
+ out: Rect
+ )
+
+ /**
+ * Centers an icon in the split staging area, accounting for insets.
+ *
+ * @param out The icon that needs to be centered.
+ * @param onScreenRectCenterX The x-center of the on-screen staging area (most of the Rect is
+ * offscreen).
+ * @param onScreenRectCenterY The y-center of the on-screen staging area (most of the Rect is
+ * offscreen).
+ * @param fullscreenScaleX A x-scaling factor used to convert coordinates back into pixels.
+ * @param fullscreenScaleY A y-scaling factor used to convert coordinates back into pixels.
+ * @param drawableWidth The icon's drawable (final) width.
+ * @param drawableHeight The icon's drawable (final) height.
+ * @param dp The device profile, used to report rotation and hardware insets.
+ * @param stagePosition 0 if the staging area is pinned to top/left, 1 for bottom/right.
+ */
+ fun updateSplitIconParams(
+ out: View,
+ onScreenRectCenterX: Float,
+ onScreenRectCenterY: Float,
+ fullscreenScaleX: Float,
+ fullscreenScaleY: Float,
+ drawableWidth: Int,
+ drawableHeight: Int,
+ dp: DeviceProfile,
+ @StagePosition stagePosition: Int
+ )
+
+ /**
+ * Sets positioning and rotation for a SplitInstructionsView.
+ *
+ * @param out The SplitInstructionsView that needs to be positioned.
+ * @param dp The device profile, used to report rotation and device type.
+ * @param splitInstructionsHeight The SplitInstructionView's height.
+ * @param splitInstructionsWidth The SplitInstructionView's width.
+ */
+ fun setSplitInstructionsParams(
+ out: View,
+ dp: DeviceProfile,
+ splitInstructionsHeight: Int,
+ splitInstructionsWidth: Int
+ )
+
+ /**
+ * @param splitDividerSize height of split screen drag handle in portrait, width in landscape
+ * @param stagePosition the split position option (top/left, bottom/right) of the first task
+ * selected for entering split
+ * @param out1 the bounds for where the first selected app will be
+ * @param out2 the bounds for where the second selected app will be, complimentary to {@param
+ * out1} based on {@param initialSplitOption}
+ */
+ fun getFinalSplitPlaceholderBounds(
+ splitDividerSize: Int,
+ dp: DeviceProfile,
+ @StagePosition stagePosition: Int,
+ out1: Rect,
+ out2: Rect
+ )
+
+ fun getDefaultSplitPosition(deviceProfile: DeviceProfile): Int
+
+ /**
+ * @param outRect This is expected to be the rect that has the dimensions for a non-split,
+ * fullscreen task in overview. This will directly be modified.
+ * @param desiredStagePosition Which stage position (topLeft/rightBottom) we want to resize
+ * outRect for
+ */
+ fun setSplitTaskSwipeRect(
+ dp: DeviceProfile,
+ outRect: Rect,
+ splitInfo: SplitConfigurationOptions.SplitBounds,
+ @StagePosition desiredStagePosition: Int
+ )
+
+ fun measureGroupedTaskViewThumbnailBounds(
+ primarySnapshot: View,
+ secondarySnapshot: View,
+ parentWidth: Int,
+ parentHeight: Int,
+ splitBoundsConfig: SplitConfigurationOptions.SplitBounds,
+ dp: DeviceProfile,
+ isRtl: Boolean
+ )
+
+ /**
+ * Creates two Points representing the dimensions of the two tasks in a GroupedTaskView
+ *
+ * @return first -> primary task snapshot, second -> secondary task snapshot. x -> width, y ->
+ * height
+ */
+ fun getGroupedTaskViewSizes(
+ dp: DeviceProfile,
+ splitBoundsConfig: SplitConfigurationOptions.SplitBounds,
+ parentWidth: Int,
+ parentHeight: Int
+ ): Pair<Point, Point>
+ // Overview TaskMenuView methods
+ /** Sets layout params on a task's app icon. Only use this when app chip is disabled. */
+ fun setTaskIconParams(
+ iconParams: FrameLayout.LayoutParams,
+ taskIconMargin: Int,
+ taskIconHeight: Int,
+ thumbnailTopMargin: Int,
+ isRtl: Boolean
+ )
+
+ /**
+ * Sets layout params on the children of an app chip. Only use this when app chip is enabled.
+ */
+ fun setIconAppChipChildrenParams(
+ iconParams: FrameLayout.LayoutParams,
+ chipChildMarginStart: Int
+ )
+
+ fun setIconAppChipMenuParams(
+ iconAppChipView: IconAppChipView,
+ iconMenuParams: FrameLayout.LayoutParams,
+ iconMenuMargin: Int,
+ thumbnailTopMargin: Int
+ )
+
+ fun setSplitIconParams(
+ primaryIconView: View,
+ secondaryIconView: View,
+ taskIconHeight: Int,
+ primarySnapshotWidth: Int,
+ primarySnapshotHeight: Int,
+ groupedTaskViewHeight: Int,
+ groupedTaskViewWidth: Int,
+ isRtl: Boolean,
+ deviceProfile: DeviceProfile,
+ splitConfig: SplitConfigurationOptions.SplitBounds
+ )
+
+ /*
+ * The following two methods try to center the TaskMenuView in landscape by finding the center
+ * of the thumbnail view and then subtracting half of the taskMenu width. In this case, the
+ * taskMenu width is the same size as the thumbnail width (what got set below in
+ * getTaskMenuWidth()), so we directly use that in the calculations.
+ */
+ fun getTaskMenuX(
+ x: Float,
+ thumbnailView: View,
+ deviceProfile: DeviceProfile,
+ taskInsetMargin: Float,
+ taskViewIcon: View
+ ): Float
+
+ fun getTaskMenuY(
+ y: Float,
+ thumbnailView: View,
+ stagePosition: Int,
+ taskMenuView: View,
+ taskInsetMargin: Float,
+ taskViewIcon: View
+ ): Float
+
+ fun getTaskMenuWidth(
+ thumbnailView: View,
+ deviceProfile: DeviceProfile,
+ @StagePosition stagePosition: Int
+ ): Int
+
+ fun getTaskMenuHeight(
+ taskInsetMargin: Float,
+ deviceProfile: DeviceProfile,
+ taskMenuX: Float,
+ taskMenuY: Float
+ ): Int
+
+ /**
+ * Sets linear layout orientation for [com.android.launcher3.popup.SystemShortcut] items inside
+ * task menu view.
+ */
+ fun setTaskOptionsMenuLayoutOrientation(
+ deviceProfile: DeviceProfile,
+ taskMenuLayout: LinearLayout,
+ dividerSpacing: Int,
+ dividerDrawable: ShapeDrawable
+ )
+
+ /**
+ * Sets layout param attributes for [com.android.launcher3.popup.SystemShortcut] child views
+ * inside task menu view.
+ */
+ fun setLayoutParamsForTaskMenuOptionItem(
+ lp: LinearLayout.LayoutParams,
+ viewGroup: LinearLayout,
+ deviceProfile: DeviceProfile
+ )
+
+ /**
+ * Calculates the position where a Digital Wellbeing Banner should be placed on its parent
+ * TaskView.
+ *
+ * @return A Pair of Floats representing the proper x and y translations.
+ */
+ fun getDwbLayoutTranslations(
+ taskViewWidth: Int,
+ taskViewHeight: Int,
+ splitBounds: SplitConfigurationOptions.SplitBounds?,
+ deviceProfile: DeviceProfile,
+ thumbnailViews: Array<View>,
+ desiredTaskId: Int,
+ banner: View
+ ): Pair<Float, Float>
+ // The following are only used by TaskViewTouchHandler.
+
+ /** @return Either VERTICAL or HORIZONTAL. */
+ val upDownSwipeDirection: SingleAxisSwipeDetector.Direction
+
+ /** @return Given [.getUpDownSwipeDirection], whether POSITIVE or NEGATIVE is up. */
+ fun getUpDirection(isRtl: Boolean): Int
+
+ /** @return Whether the displacement is going towards the top of the screen. */
+ fun isGoingUp(displacement: Float, isRtl: Boolean): Boolean
+
+ /** @return Either 1 or -1, a factor to multiply by so the animation goes the correct way. */
+ fun getTaskDragDisplacementFactor(isRtl: Boolean): Int
+
+ /**
+ * Maps the velocity from the coordinate plane of the foreground app to that of Launcher's
+ * (which now will always be portrait)
+ */
+ fun adjustFloatingIconStartVelocity(velocity: PointF)
+
+ /**
+ * Ensures that outStartRect left bound is within the DeviceProfile's visual boundaries
+ *
+ * @param outStartRect The start rect that will directly be modified
+ */
+ fun fixBoundsForHomeAnimStartRect(outStartRect: RectF, deviceProfile: DeviceProfile)
+
+ /**
+ * Determine the target translation for animating the FloatingTaskView out. This value could
+ * either be an x-coordinate or a y-coordinate, depending on which way the FloatingTaskView was
+ * docked.
+ *
+ * @param floatingTask The FloatingTaskView.
+ * @param onScreenRect The current on-screen dimensions of the FloatingTaskView.
+ * @param stagePosition STAGE_POSITION_TOP_OR_LEFT or STAGE_POSITION_BOTTOM_OR_RIGHT.
+ * @param dp The device profile.
+ * @return A float. When an animation translates the FloatingTaskView to this position, it will
+ * appear to tuck away off the edge of the screen.
+ */
+ fun getFloatingTaskOffscreenTranslationTarget(
+ floatingTask: View,
+ onScreenRect: RectF,
+ @StagePosition stagePosition: Int,
+ dp: DeviceProfile
+ ): Float
+
+ /**
+ * Sets the translation of a FloatingTaskView along its "slide-in/slide-out" axis (could be
+ * either x or y), depending on how the view is oriented.
+ *
+ * @param floatingTask The FloatingTaskView to be translated.
+ * @param translation The target translation value.
+ * @param dp The current device profile.
+ */
+ fun setFloatingTaskPrimaryTranslation(floatingTask: View, translation: Float, dp: DeviceProfile)
+
+ /**
+ * Gets the translation of a FloatingTaskView along its "slide-in/slide-out" axis (could be
+ * either x or y), depending on how the view is oriented.
+ *
+ * @param floatingTask The FloatingTaskView in question.
+ * @param dp The current device profile.
+ * @return The current translation value.
+ */
+ fun getFloatingTaskPrimaryTranslation(floatingTask: View, dp: DeviceProfile): Float
+
+ companion object {
+ @JvmField val PORTRAIT: RecentsPagedOrientationHandler = PortraitPagedViewHandler()
+ @JvmField val LANDSCAPE: RecentsPagedOrientationHandler = LandscapePagedViewHandler()
+ @JvmField val SEASCAPE: RecentsPagedOrientationHandler = SeascapePagedViewHandler()
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.java
deleted file mode 100644
index a964639..0000000
--- a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.java
+++ /dev/null
@@ -1,412 +0,0 @@
-/*
- * 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.orientation;
-
-import static android.view.Gravity.BOTTOM;
-import static android.view.Gravity.CENTER_VERTICAL;
-import static android.view.Gravity.END;
-import static android.view.Gravity.RIGHT;
-import static android.view.Gravity.START;
-import static android.view.Gravity.TOP;
-import static android.view.View.LAYOUT_DIRECTION_RTL;
-
-import static com.android.launcher3.Flags.enableOverviewIconMenu;
-import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
-
-import android.content.res.Resources;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.util.Pair;
-import android.view.Gravity;
-import android.view.Surface;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.touch.SingleAxisSwipeDetector;
-import com.android.launcher3.util.SplitConfigurationOptions;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
-import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.views.IconAppChipView;
-
-import java.util.Collections;
-import java.util.List;
-
-public class SeascapePagedViewHandler extends LandscapePagedViewHandler {
-
- @Override
- public int getSecondaryTranslationDirectionFactor() {
- return -1;
- }
-
- @Override
- public int getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile) {
- if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
- return -1;
- } else {
- return 1;
- }
- }
-
- @Override
- public boolean getRecentsRtlSetting(Resources resources) {
- return Utilities.isRtl(resources);
- }
-
- @Override
- public float getDegreesRotated() {
- return 270;
- }
-
- @Override
- public int getRotation() {
- return Surface.ROTATION_270;
- }
-
- @Override
- public void adjustFloatingIconStartVelocity(PointF velocity) {
- float oldX = velocity.x;
- float oldY = velocity.y;
- velocity.set(oldY, -oldX);
- }
-
- @Override
- public float getTaskMenuX(float x, View thumbnailView,
- DeviceProfile deviceProfile, float taskInsetMargin, View taskViewIcon) {
- return x + taskInsetMargin;
- }
-
- @Override
- public float getTaskMenuY(float y, View thumbnailView, int stagePosition,
- View taskMenuView, float taskInsetMargin, View taskViewIcon) {
- if (enableOverviewIconMenu()) {
- return y;
- }
- BaseDragLayer.LayoutParams lp = (BaseDragLayer.LayoutParams) taskMenuView.getLayoutParams();
- int taskMenuWidth = lp.width;
- if (stagePosition == STAGE_POSITION_UNDEFINED) {
- return y + taskInsetMargin
- + (thumbnailView.getMeasuredHeight() + taskMenuWidth) / 2f;
- } else {
- return y + taskMenuWidth + taskInsetMargin;
- }
- }
-
- @Override
- public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect, SplitBounds splitInfo,
- int desiredStagePosition) {
- float topLeftTaskPercent = splitInfo.appsStackedVertically
- ? splitInfo.topTaskPercent
- : splitInfo.leftTaskPercent;
- float dividerBarPercent = splitInfo.appsStackedVertically
- ? splitInfo.dividerHeightPercent
- : splitInfo.dividerWidthPercent;
-
- // In seascape, the primary thumbnail is counterintuitively placed at the physical bottom of
- // the screen. This is to preserve consistency when the user rotates: From the user's POV,
- // the primary should always be on the left.
- if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
- outRect.top += (int) (outRect.height() * ((1 - topLeftTaskPercent)));
- } else {
- outRect.bottom -= (int) (outRect.height() * (topLeftTaskPercent + dividerBarPercent));
- }
- }
-
- @Override
- public Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth,
- int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile,
- View[] thumbnailViews, int desiredTaskId, View banner) {
- boolean isRtl = banner.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
- float translationX = 0;
- float translationY = 0;
- FrameLayout.LayoutParams bannerParams = (FrameLayout.LayoutParams) banner.getLayoutParams();
- banner.setPivotX(0);
- banner.setPivotY(0);
- banner.setRotation(getDegreesRotated());
- translationX = taskViewWidth - banner.getHeight();
- FrameLayout.LayoutParams snapshotParams =
- (FrameLayout.LayoutParams) thumbnailViews[0]
- .getLayoutParams();
- bannerParams.gravity = BOTTOM | (isRtl ? END : START);
-
- if (splitBounds == null) {
- // Single, fullscreen case
- bannerParams.width = taskViewHeight - snapshotParams.topMargin;
- translationY = banner.getHeight();
- return new Pair<>(translationX, translationY);
- }
-
- // Set correct width
- if (desiredTaskId == splitBounds.leftTopTaskId) {
- bannerParams.width = thumbnailViews[0].getMeasuredHeight();
- } else {
- bannerParams.width = thumbnailViews[1].getMeasuredHeight();
- }
-
- // Set translations
- if (desiredTaskId == splitBounds.rightBottomTaskId) {
- translationY = banner.getHeight();
- }
- if (desiredTaskId == splitBounds.leftTopTaskId) {
- float bottomRightTaskPlusDividerPercent = splitBounds.appsStackedVertically
- ? (1f - splitBounds.topTaskPercent)
- : (1f - splitBounds.leftTaskPercent);
- translationY = banner.getHeight()
- - ((taskViewHeight - snapshotParams.topMargin)
- * bottomRightTaskPlusDividerPercent);
- }
- return new Pair<>(translationX, translationY);
- }
-
- @Override
- public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
- return dp.widthPx - rect.right;
- }
-
- @Override
- public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
- // Add "right" option which is actually the top
- return Collections.singletonList(new SplitPositionOption(
- R.drawable.ic_split_horizontal, R.string.recent_task_option_split_screen,
- STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_MAIN));
- }
-
- @Override
- public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
- int splitInstructionsWidth) {
- out.setPivotX(0);
- out.setPivotY(splitInstructionsHeight);
- out.setRotation(getDegreesRotated());
- int distanceToEdge = out.getResources().getDimensionPixelSize(
- R.dimen.split_instructions_bottom_margin_phone_landscape);
- // Adjust for any insets on the right edge
- int insetCorrectionX = dp.getInsets().right;
- // Center the view in case of unbalanced insets on top or bottom of screen
- int insetCorrectionY = (dp.getInsets().bottom - dp.getInsets().top) / 2;
- out.setTranslationX(splitInstructionsWidth - distanceToEdge + insetCorrectionX);
- out.setTranslationY(((-splitInstructionsHeight + splitInstructionsWidth) / 2f)
- + insetCorrectionY);
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
- // Setting gravity to RIGHT instead of the lint-recommended END because we always want this
- // view to be screen-right when phone is in seascape, regardless of the RtL setting.
- lp.gravity = RIGHT | CENTER_VERTICAL;
- out.setLayoutParams(lp);
- }
-
- @Override
- public void setTaskIconParams(FrameLayout.LayoutParams iconParams,
- int taskIconMargin, int taskIconHeight, int thumbnailTopMargin, boolean isRtl) {
- iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL;
- iconParams.setMargins(-taskIconHeight - taskIconMargin / 2, thumbnailTopMargin / 2, 0, 0);
- }
-
- @Override
- public void setIconAppChipChildrenParams(FrameLayout.LayoutParams iconParams,
- int chipChildMarginStart) {
- iconParams.setMargins(0, 0, 0, 0);
- iconParams.setMarginStart(chipChildMarginStart);
- iconParams.gravity = Gravity.START | Gravity.CENTER_VERTICAL;
- }
-
- @Override
- public void setIconAppChipMenuParams(IconAppChipView iconAppChipView,
- FrameLayout.LayoutParams iconMenuParams, int iconMenuMargin, int thumbnailTopMargin) {
- boolean isRtl = iconAppChipView.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
- iconMenuParams.gravity = (isRtl ? TOP : BOTTOM) | (isRtl ? END : START);
- iconMenuParams.setMarginStart(0);
- iconMenuParams.topMargin = isRtl ? iconMenuMargin : 0;
- iconMenuParams.bottomMargin = 0;
- iconMenuParams.setMarginEnd(isRtl ? thumbnailTopMargin : 0);
-
- // Use half menu height to place the pivot within the X/Y center of icon in the menu.
- float iconCenter = iconAppChipView.getHeight() / 2f;
- iconAppChipView.setPivotX(isRtl ? iconMenuParams.width / 2f : iconCenter);
- iconAppChipView.setPivotY(
- isRtl ? iconMenuParams.width / 2f : iconCenter - iconMenuMargin);
- iconAppChipView.setSplitTranslationY(0);
- iconAppChipView.setRotation(getDegreesRotated());
- }
-
- @Override
- public void setSplitIconParams(View primaryIconView, View secondaryIconView,
- int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
- int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
- DeviceProfile deviceProfile, SplitBounds splitConfig) {
- super.setSplitIconParams(primaryIconView, secondaryIconView, taskIconHeight,
- primarySnapshotWidth, primarySnapshotHeight, groupedTaskViewHeight,
- groupedTaskViewWidth, isRtl, deviceProfile, splitConfig);
- FrameLayout.LayoutParams primaryIconParams =
- (FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
- FrameLayout.LayoutParams secondaryIconParams =
- (FrameLayout.LayoutParams) secondaryIconView.getLayoutParams();
-
- // We calculate the "midpoint" of the thumbnail area, and place the icons there.
- // This is the place where the thumbnail area splits by default, in a near-50/50 split.
- // It is usually not exactly 50/50, due to insets/screen cutouts.
- int fullscreenInsetThickness = deviceProfile.getInsets().top
- - deviceProfile.getInsets().bottom;
- int fullscreenMidpointFromBottom = ((deviceProfile.heightPx
- - fullscreenInsetThickness) / 2);
- float midpointFromBottomPct = (float) fullscreenMidpointFromBottom / deviceProfile.heightPx;
- float insetPct = (float) fullscreenInsetThickness / deviceProfile.heightPx;
- int spaceAboveSnapshots = deviceProfile.overviewTaskThumbnailTopMarginPx;
- int overviewThumbnailAreaThickness = groupedTaskViewHeight - spaceAboveSnapshots;
- int bottomToMidpointOffset = (int) (overviewThumbnailAreaThickness * midpointFromBottomPct);
- int insetOffset = (int) (overviewThumbnailAreaThickness * insetPct);
-
- int gravity = (isRtl ? TOP : BOTTOM) | (isRtl ? END : START);
- primaryIconParams.gravity = gravity;
- secondaryIconParams.gravity = gravity;
- primaryIconView.setTranslationX(0);
- secondaryIconView.setTranslationX(0);
- if (enableOverviewIconMenu()) {
- IconAppChipView primaryAppChipView = (IconAppChipView) primaryIconView;
- IconAppChipView secondaryAppChipView = (IconAppChipView) secondaryIconView;
- if (isRtl) {
- primaryAppChipView.setSplitTranslationY(
- groupedTaskViewHeight - primarySnapshotHeight);
- secondaryAppChipView.setSplitTranslationY(0);
- } else {
- secondaryAppChipView.setSplitTranslationY(-primarySnapshotHeight);
- primaryAppChipView.setSplitTranslationY(0);
- }
- } else if (splitConfig.initiatedFromSeascape) {
- // if the split was initiated from seascape,
- // the task on the right (secondary) is slightly larger
- if (isRtl) {
- primaryIconView.setTranslationY(bottomToMidpointOffset - insetOffset
- + taskIconHeight);
- secondaryIconView.setTranslationY(bottomToMidpointOffset - insetOffset);
- } else {
- primaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset
- + taskIconHeight);
- secondaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset);
- }
- } else {
- // if not,
- // the task on the left (primary) is slightly larger
- if (isRtl) {
- primaryIconView.setTranslationY(bottomToMidpointOffset + taskIconHeight);
- secondaryIconView.setTranslationY(bottomToMidpointOffset);
- } else {
- primaryIconView.setTranslationY(-bottomToMidpointOffset + taskIconHeight);
- secondaryIconView.setTranslationY(-bottomToMidpointOffset);
- }
- }
-
- primaryIconView.setLayoutParams(primaryIconParams);
- secondaryIconView.setLayoutParams(secondaryIconParams);
- }
-
- @Override
- public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
- int parentWidth, int parentHeight, SplitBounds splitBoundsConfig, DeviceProfile dp,
- boolean isRtl) {
- FrameLayout.LayoutParams primaryParams =
- (FrameLayout.LayoutParams) primarySnapshot.getLayoutParams();
- FrameLayout.LayoutParams secondaryParams =
- (FrameLayout.LayoutParams) secondarySnapshot.getLayoutParams();
-
- // Swap the margins that are set in TaskView#setRecentsOrientedState()
- secondaryParams.topMargin = dp.overviewTaskThumbnailTopMarginPx;
- primaryParams.topMargin = 0;
-
- // Measure and layout the thumbnails bottom up, since the primary is on the visual left
- // (portrait bottom) and secondary is on the right (portrait top)
- int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
- int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
- int dividerBar = Math.round(totalThumbnailHeight * (splitBoundsConfig.appsStackedVertically
- ? splitBoundsConfig.dividerHeightPercent
- : splitBoundsConfig.dividerWidthPercent));
-
- Pair<Point, Point> taskViewSizes =
- getGroupedTaskViewSizes(dp, splitBoundsConfig, parentWidth, parentHeight);
-
- secondarySnapshot.setTranslationY(0);
- primarySnapshot.setTranslationY(taskViewSizes.second.y + spaceAboveSnapshot + dividerBar);
-
- primarySnapshot.measure(
- View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.x, View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.y, View.MeasureSpec.EXACTLY)
- );
- secondarySnapshot.measure(
- View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.x, View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.y, View.MeasureSpec.EXACTLY)
- );
- }
-
- @Override
- public Pair<Point, Point> getGroupedTaskViewSizes(
- DeviceProfile dp,
- SplitBounds splitBoundsConfig,
- int parentWidth,
- int parentHeight) {
- // Measure and layout the thumbnails bottom up, since the primary is on the visual left
- // (portrait bottom) and secondary is on the right (portrait top)
- int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
- int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
- int dividerBar = Math.round(totalThumbnailHeight * (splitBoundsConfig.appsStackedVertically
- ? splitBoundsConfig.dividerHeightPercent
- : splitBoundsConfig.dividerWidthPercent));
- float taskPercent = splitBoundsConfig.appsStackedVertically
- ? splitBoundsConfig.topTaskPercent
- : splitBoundsConfig.leftTaskPercent;
-
- Point firstTaskViewSize = new Point(
- parentWidth,
- (int) (totalThumbnailHeight * taskPercent)
- );
- Point secondTaskViewSize = new Point(
- parentWidth,
- totalThumbnailHeight - firstTaskViewSize.y - dividerBar
- );
-
- return new Pair<>(firstTaskViewSize, secondTaskViewSize);
- }
-
- /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
-
- @Override
- public SingleAxisSwipeDetector.Direction getUpDownSwipeDirection() {
- return HORIZONTAL;
- }
-
- @Override
- public int getUpDirection(boolean isRtl) {
- return isRtl ? SingleAxisSwipeDetector.DIRECTION_POSITIVE
- : SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
- }
-
- @Override
- public boolean isGoingUp(float displacement, boolean isRtl) {
- return isRtl ? displacement > 0 : displacement < 0;
- }
-
- @Override
- public int getTaskDragDisplacementFactor(boolean isRtl) {
- return isRtl ? -1 : 1;
- }
-
- /* -------------------- */
-}
diff --git a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
new file mode 100644
index 0000000..0c78b8f
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
@@ -0,0 +1,433 @@
+/*
+ * 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.orientation
+
+import android.content.res.Resources
+import android.graphics.Point
+import android.graphics.PointF
+import android.graphics.Rect
+import android.util.Pair
+import android.view.Gravity
+import android.view.Surface
+import android.view.View
+import android.view.View.MeasureSpec
+import android.widget.FrameLayout
+import androidx.core.util.component1
+import androidx.core.util.component2
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.Flags
+import com.android.launcher3.R
+import com.android.launcher3.Utilities
+import com.android.launcher3.touch.SingleAxisSwipeDetector
+import com.android.launcher3.util.SplitConfigurationOptions.*
+import com.android.launcher3.views.BaseDragLayer
+import com.android.quickstep.views.IconAppChipView
+
+class SeascapePagedViewHandler : LandscapePagedViewHandler() {
+ override val secondaryTranslationDirectionFactor: Int = -1
+
+ override fun getSplitTranslationDirectionFactor(
+ stagePosition: Int,
+ deviceProfile: DeviceProfile
+ ): Int = if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) -1 else 1
+
+ override fun getRecentsRtlSetting(resources: Resources): Boolean = Utilities.isRtl(resources)
+
+ override val degreesRotated: Float = 270f
+
+ override val rotation: Int = Surface.ROTATION_270
+
+ override fun adjustFloatingIconStartVelocity(velocity: PointF) =
+ velocity.set(velocity.y, -velocity.x)
+
+ override fun getTaskMenuX(
+ x: Float,
+ thumbnailView: View,
+ deviceProfile: DeviceProfile,
+ taskInsetMargin: Float,
+ taskViewIcon: View
+ ): Float = x + taskInsetMargin
+
+ override fun getTaskMenuY(
+ y: Float,
+ thumbnailView: View,
+ stagePosition: Int,
+ taskMenuView: View,
+ taskInsetMargin: Float,
+ taskViewIcon: View
+ ): Float {
+ if (Flags.enableOverviewIconMenu()) {
+ return y
+ }
+ val lp = taskMenuView.layoutParams as BaseDragLayer.LayoutParams
+ val taskMenuWidth = lp.width
+ return if (stagePosition == STAGE_POSITION_UNDEFINED) {
+ y + taskInsetMargin + (thumbnailView.measuredHeight + taskMenuWidth) / 2f
+ } else {
+ y + taskMenuWidth + taskInsetMargin
+ }
+ }
+
+ override fun getTaskMenuHeight(
+ taskInsetMargin: Float,
+ deviceProfile: DeviceProfile,
+ taskMenuX: Float,
+ taskMenuY: Float
+ ): Int = (deviceProfile.availableWidthPx - taskInsetMargin - taskMenuX).toInt()
+
+ override fun setSplitTaskSwipeRect(
+ dp: DeviceProfile,
+ outRect: Rect,
+ splitInfo: SplitBounds,
+ desiredStagePosition: Int
+ ) {
+ val topLeftTaskPercent: Float
+ val dividerBarPercent: Float
+ if (splitInfo.appsStackedVertically) {
+ topLeftTaskPercent = splitInfo.topTaskPercent
+ dividerBarPercent = splitInfo.dividerHeightPercent
+ } else {
+ topLeftTaskPercent = splitInfo.leftTaskPercent
+ dividerBarPercent = splitInfo.dividerWidthPercent
+ }
+
+ // In seascape, the primary thumbnail is counterintuitively placed at the physical bottom of
+ // the screen. This is to preserve consistency when the user rotates: From the user's POV,
+ // the primary should always be on the left.
+ if (desiredStagePosition == STAGE_POSITION_TOP_OR_LEFT) {
+ outRect.top += (outRect.height() * (1 - topLeftTaskPercent)).toInt()
+ } else {
+ outRect.bottom -= (outRect.height() * (topLeftTaskPercent + dividerBarPercent)).toInt()
+ }
+ }
+
+ override fun getDwbLayoutTranslations(
+ taskViewWidth: Int,
+ taskViewHeight: Int,
+ splitBounds: SplitBounds?,
+ deviceProfile: DeviceProfile,
+ thumbnailViews: Array<View>,
+ desiredTaskId: Int,
+ banner: View
+ ): Pair<Float, Float> {
+ val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
+ val isRtl = banner.layoutDirection == View.LAYOUT_DIRECTION_RTL
+
+ val bannerParams = banner.layoutParams as FrameLayout.LayoutParams
+ bannerParams.gravity = Gravity.BOTTOM or if (isRtl) Gravity.END else Gravity.START
+ banner.pivotX = 0f
+ banner.pivotY = 0f
+ banner.rotation = degreesRotated
+
+ val translationX: Float = (taskViewWidth - banner.height).toFloat()
+ if (splitBounds == null) {
+ // Single, fullscreen case
+ bannerParams.width = taskViewHeight - snapshotParams.topMargin
+ return Pair(translationX, banner.height.toFloat())
+ }
+
+ // Set correct width and translations
+ val translationY: Float
+ if (desiredTaskId == splitBounds.leftTopTaskId) {
+ bannerParams.width = thumbnailViews[0].measuredHeight
+ val bottomRightTaskPlusDividerPercent =
+ if (splitBounds.appsStackedVertically) {
+ 1f - splitBounds.topTaskPercent
+ } else {
+ 1f - splitBounds.leftTaskPercent
+ }
+ translationY =
+ banner.height -
+ (taskViewHeight - snapshotParams.topMargin) * bottomRightTaskPlusDividerPercent
+ } else {
+ bannerParams.width = thumbnailViews[1].measuredHeight
+ translationY = banner.height.toFloat()
+ }
+
+ return Pair(translationX, translationY)
+ }
+
+ override fun getDistanceToBottomOfRect(dp: DeviceProfile, rect: Rect): Int =
+ dp.widthPx - rect.right
+
+ override fun getSplitPositionOptions(dp: DeviceProfile): List<SplitPositionOption> =
+ // Add "right" option which is actually the top
+ listOf(
+ SplitPositionOption(
+ R.drawable.ic_split_horizontal,
+ R.string.recent_task_option_split_screen,
+ STAGE_POSITION_BOTTOM_OR_RIGHT,
+ STAGE_TYPE_MAIN
+ )
+ )
+
+ override fun setSplitInstructionsParams(
+ out: View,
+ dp: DeviceProfile,
+ splitInstructionsHeight: Int,
+ splitInstructionsWidth: Int
+ ) {
+ out.pivotX = 0f
+ out.pivotY = splitInstructionsHeight.toFloat()
+ out.rotation = degreesRotated
+ val distanceToEdge =
+ out.resources.getDimensionPixelSize(
+ R.dimen.split_instructions_bottom_margin_phone_landscape
+ )
+ // Adjust for any insets on the right edge
+ val insetCorrectionX = dp.insets.right
+ // Center the view in case of unbalanced insets on top or bottom of screen
+ val insetCorrectionY = (dp.insets.bottom - dp.insets.top) / 2
+ out.translationX = (splitInstructionsWidth - distanceToEdge + insetCorrectionX).toFloat()
+ out.translationY =
+ (-splitInstructionsHeight + splitInstructionsWidth) / 2f + insetCorrectionY
+ // Setting gravity to RIGHT instead of the lint-recommended END because we always want this
+ // view to be screen-right when phone is in seascape, regardless of the RtL setting.
+ val lp = out.layoutParams as FrameLayout.LayoutParams
+ lp.gravity = Gravity.RIGHT or Gravity.CENTER_VERTICAL
+ out.layoutParams = lp
+ }
+
+ override fun setTaskIconParams(
+ iconParams: FrameLayout.LayoutParams,
+ taskIconMargin: Int,
+ taskIconHeight: Int,
+ thumbnailTopMargin: Int,
+ isRtl: Boolean
+ ) {
+ iconParams.gravity =
+ if (isRtl) {
+ Gravity.END or Gravity.CENTER_VERTICAL
+ } else {
+ Gravity.START or Gravity.CENTER_VERTICAL
+ }
+ iconParams.setMargins(-taskIconHeight - taskIconMargin / 2, thumbnailTopMargin / 2, 0, 0)
+ }
+
+ override fun setIconAppChipChildrenParams(
+ iconParams: FrameLayout.LayoutParams,
+ chipChildMarginStart: Int
+ ) {
+ iconParams.setMargins(0, 0, 0, 0)
+ iconParams.marginStart = chipChildMarginStart
+ iconParams.gravity = Gravity.START or Gravity.CENTER_VERTICAL
+ }
+
+ override fun setIconAppChipMenuParams(
+ iconAppChipView: IconAppChipView,
+ iconMenuParams: FrameLayout.LayoutParams,
+ iconMenuMargin: Int,
+ thumbnailTopMargin: Int
+ ) {
+ val isRtl = iconAppChipView.layoutDirection == View.LAYOUT_DIRECTION_RTL
+ val iconCenter = iconAppChipView.getHeight() / 2f
+
+ if (isRtl) {
+ iconMenuParams.gravity = Gravity.TOP or Gravity.END
+ iconMenuParams.topMargin = iconMenuMargin
+ iconMenuParams.marginEnd = thumbnailTopMargin
+ // Use half menu height to place the pivot within the X/Y center of icon in the menu.
+ iconAppChipView.pivotX = iconMenuParams.width / 2f
+ iconAppChipView.pivotY = iconMenuParams.width / 2f
+ } else {
+ iconMenuParams.gravity = Gravity.BOTTOM or Gravity.START
+ iconMenuParams.topMargin = 0
+ iconMenuParams.marginEnd = 0
+ iconAppChipView.pivotX = iconCenter
+ iconAppChipView.pivotY = iconCenter - iconMenuMargin
+ }
+ iconMenuParams.marginStart = 0
+ iconMenuParams.bottomMargin = 0
+ iconAppChipView.setSplitTranslationY(0f)
+ iconAppChipView.setRotation(degreesRotated)
+ }
+
+ override fun setSplitIconParams(
+ primaryIconView: View,
+ secondaryIconView: View,
+ taskIconHeight: Int,
+ primarySnapshotWidth: Int,
+ primarySnapshotHeight: Int,
+ groupedTaskViewHeight: Int,
+ groupedTaskViewWidth: Int,
+ isRtl: Boolean,
+ deviceProfile: DeviceProfile,
+ splitConfig: SplitBounds
+ ) {
+ super.setSplitIconParams(
+ primaryIconView,
+ secondaryIconView,
+ taskIconHeight,
+ primarySnapshotWidth,
+ primarySnapshotHeight,
+ groupedTaskViewHeight,
+ groupedTaskViewWidth,
+ isRtl,
+ deviceProfile,
+ splitConfig
+ )
+ val primaryIconParams = primaryIconView.layoutParams as FrameLayout.LayoutParams
+ val secondaryIconParams = secondaryIconView.layoutParams as FrameLayout.LayoutParams
+
+ // We calculate the "midpoint" of the thumbnail area, and place the icons there.
+ // This is the place where the thumbnail area splits by default, in a near-50/50 split.
+ // It is usually not exactly 50/50, due to insets/screen cutouts.
+ val fullscreenInsetThickness = (deviceProfile.insets.top - deviceProfile.insets.bottom)
+ val fullscreenMidpointFromBottom = (deviceProfile.heightPx - fullscreenInsetThickness) / 2
+ val midpointFromBottomPct = fullscreenMidpointFromBottom.toFloat() / deviceProfile.heightPx
+ val insetPct = fullscreenInsetThickness.toFloat() / deviceProfile.heightPx
+ val spaceAboveSnapshots = deviceProfile.overviewTaskThumbnailTopMarginPx
+ val overviewThumbnailAreaThickness = groupedTaskViewHeight - spaceAboveSnapshots
+ val bottomToMidpointOffset =
+ (overviewThumbnailAreaThickness * midpointFromBottomPct).toInt()
+ val insetOffset = (overviewThumbnailAreaThickness * insetPct).toInt()
+ val gravity = if (isRtl) Gravity.TOP or Gravity.END else Gravity.BOTTOM or Gravity.START
+ primaryIconParams.gravity = gravity
+ secondaryIconParams.gravity = gravity
+ primaryIconView.translationX = 0f
+ secondaryIconView.translationX = 0f
+ when {
+ Flags.enableOverviewIconMenu() -> {
+ val primaryAppChipView = primaryIconView as IconAppChipView
+ val secondaryAppChipView = secondaryIconView as IconAppChipView
+ if (isRtl) {
+ primaryAppChipView.setSplitTranslationY(
+ (groupedTaskViewHeight - primarySnapshotHeight).toFloat()
+ )
+ secondaryAppChipView.setSplitTranslationY(0f)
+ } else {
+ secondaryAppChipView.setSplitTranslationY(-primarySnapshotHeight.toFloat())
+ primaryAppChipView.setSplitTranslationY(0f)
+ }
+ }
+ splitConfig.initiatedFromSeascape -> {
+ // if the split was initiated from seascape,
+ // the task on the right (secondary) is slightly larger
+ if (isRtl) {
+ primaryIconView.translationY =
+ (bottomToMidpointOffset - insetOffset + taskIconHeight).toFloat()
+ secondaryIconView.translationY =
+ (bottomToMidpointOffset - insetOffset).toFloat()
+ } else {
+ primaryIconView.translationY =
+ (-bottomToMidpointOffset - insetOffset + taskIconHeight).toFloat()
+ secondaryIconView.translationY =
+ (-bottomToMidpointOffset - insetOffset).toFloat()
+ }
+ }
+ else -> {
+ // if not,
+ // the task on the left (primary) is slightly larger
+ if (isRtl) {
+ primaryIconView.translationY =
+ (bottomToMidpointOffset + taskIconHeight).toFloat()
+ secondaryIconView.translationY = bottomToMidpointOffset.toFloat()
+ } else {
+ primaryIconView.translationY =
+ (-bottomToMidpointOffset + taskIconHeight).toFloat()
+ secondaryIconView.translationY = -bottomToMidpointOffset.toFloat()
+ }
+ }
+ }
+ primaryIconView.layoutParams = primaryIconParams
+ secondaryIconView.layoutParams = secondaryIconParams
+ }
+
+ override fun measureGroupedTaskViewThumbnailBounds(
+ primarySnapshot: View,
+ secondarySnapshot: View,
+ parentWidth: Int,
+ parentHeight: Int,
+ splitBoundsConfig: SplitBounds,
+ dp: DeviceProfile,
+ isRtl: Boolean
+ ) {
+ val primaryParams = primarySnapshot.layoutParams as FrameLayout.LayoutParams
+ val secondaryParams = secondarySnapshot.layoutParams as FrameLayout.LayoutParams
+
+ // Swap the margins that are set in TaskView#setRecentsOrientedState()
+ secondaryParams.topMargin = dp.overviewTaskThumbnailTopMarginPx
+ primaryParams.topMargin = 0
+
+ // Measure and layout the thumbnails bottom up, since the primary is on the visual left
+ // (portrait bottom) and secondary is on the right (portrait top)
+ val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
+ val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
+ val dividerBar =
+ Math.round(
+ totalThumbnailHeight *
+ if (splitBoundsConfig.appsStackedVertically)
+ splitBoundsConfig.dividerHeightPercent
+ else splitBoundsConfig.dividerWidthPercent
+ )
+ val (taskViewFirst, taskViewSecond) =
+ getGroupedTaskViewSizes(dp, splitBoundsConfig, parentWidth, parentHeight)
+ secondarySnapshot.translationY = 0f
+ primarySnapshot.translationY =
+ (taskViewSecond.y + spaceAboveSnapshot + dividerBar).toFloat()
+ primarySnapshot.measure(
+ MeasureSpec.makeMeasureSpec(taskViewFirst.x, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(taskViewFirst.y, MeasureSpec.EXACTLY)
+ )
+ secondarySnapshot.measure(
+ MeasureSpec.makeMeasureSpec(taskViewSecond.x, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(taskViewSecond.y, MeasureSpec.EXACTLY)
+ )
+ }
+
+ override fun getGroupedTaskViewSizes(
+ dp: DeviceProfile,
+ splitBoundsConfig: SplitBounds,
+ parentWidth: Int,
+ parentHeight: Int
+ ): Pair<Point, Point> {
+ // Measure and layout the thumbnails bottom up, since the primary is on the visual left
+ // (portrait bottom) and secondary is on the right (portrait top)
+ val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
+ val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
+ val dividerBar =
+ Math.round(
+ totalThumbnailHeight *
+ if (splitBoundsConfig.appsStackedVertically)
+ splitBoundsConfig.dividerHeightPercent
+ else splitBoundsConfig.dividerWidthPercent
+ )
+ val taskPercent =
+ if (splitBoundsConfig.appsStackedVertically) {
+ splitBoundsConfig.topTaskPercent
+ } else {
+ splitBoundsConfig.leftTaskPercent
+ }
+ val firstTaskViewSize = Point(parentWidth, (totalThumbnailHeight * taskPercent).toInt())
+ val secondTaskViewSize =
+ Point(parentWidth, totalThumbnailHeight - firstTaskViewSize.y - dividerBar)
+ return Pair(firstTaskViewSize, secondTaskViewSize)
+ }
+
+ /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
+ override val upDownSwipeDirection: SingleAxisSwipeDetector.Direction =
+ SingleAxisSwipeDetector.HORIZONTAL
+
+ override fun getUpDirection(isRtl: Boolean): Int =
+ if (isRtl) SingleAxisSwipeDetector.DIRECTION_POSITIVE
+ else SingleAxisSwipeDetector.DIRECTION_NEGATIVE
+
+ override fun isGoingUp(displacement: Float, isRtl: Boolean): Boolean =
+ if (isRtl) displacement > 0 else displacement < 0
+
+ override fun getTaskDragDisplacementFactor(isRtl: Boolean): Int = if (isRtl) -1 else 1
+ /* -------------------- */
+}
diff --git a/quickstep/src/com/android/quickstep/util/AssistStateManager.java b/quickstep/src/com/android/quickstep/util/AssistStateManager.java
index e9a06f7..660fc22 100644
--- a/quickstep/src/com/android/quickstep/util/AssistStateManager.java
+++ b/quickstep/src/com/android/quickstep/util/AssistStateManager.java
@@ -52,11 +52,6 @@
return Optional.empty();
}
- /** Whether search recovery is available. */
- public boolean isVisRecoveryEnabled() {
- return false;
- }
-
/** Return {@code true} if the Settings toggle is enabled. */
public boolean isSettingsNavHandleEnabled() {
return false;
diff --git a/quickstep/src/com/android/quickstep/util/BaseDepthController.java b/quickstep/src/com/android/quickstep/util/BaseDepthController.java
index 99f564c..5d6bb1d 100644
--- a/quickstep/src/com/android/quickstep/util/BaseDepthController.java
+++ b/quickstep/src/com/android/quickstep/util/BaseDepthController.java
@@ -15,6 +15,8 @@
*/
package com.android.quickstep.util;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
+
import android.app.WallpaperManager;
import android.os.IBinder;
import android.util.FloatProperty;
@@ -33,6 +35,9 @@
* Utility class for applying depth effect
*/
public class BaseDepthController {
+ public static final float DEPTH_0_PERCENT = 0f;
+ public static final float DEPTH_60_PERCENT = 0.6f;
+ public static final float DEPTH_70_PERCENT = 0.7f;
private static final FloatProperty<BaseDepthController> DEPTH =
new FloatProperty<BaseDepthController>("depth") {
@@ -127,10 +132,14 @@
float depth = mDepth;
IBinder windowToken = mLauncher.getRootView().getWindowToken();
if (windowToken != null) {
- // The API's full zoom-out is three times larger than the zoom-out we apply to the
- // icons. To keep the two consistent throughout the animation while keeping Launcher's
- // concept of full depth unchanged, we divide the depth by 3 here.
- mWallpaperManager.setWallpaperZoomOut(windowToken, depth / 3);
+ if (enableScalingRevealHomeAnimation()) {
+ mWallpaperManager.setWallpaperZoomOut(windowToken, depth);
+ } else {
+ // The API's full zoom-out is three times larger than the zoom-out we apply to the
+ // icons. To keep the two consistent throughout the animation while keeping
+ // Launcher's concept of full depth unchanged, we divide the depth by 3 here.
+ mWallpaperManager.setWallpaperZoomOut(windowToken, depth / 3);
+ }
}
if (!BlurUtils.supportsBlursOnWindows()) {
@@ -150,8 +159,15 @@
boolean hasOpaqueBg = mLauncher.getScrimView().isFullyOpaque();
boolean isSurfaceOpaque = !mHasContentBehindLauncher && hasOpaqueBg && !mPauseBlurs;
+ float blurAmount;
+ if (enableScalingRevealHomeAnimation()) {
+ blurAmount = mapDepthToBlur(depth);
+ } else {
+ blurAmount = depth;
+ }
mCurrentBlur = !mCrossWindowBlursEnabled || hasOpaqueBg || mPauseBlurs
- ? 0 : (int) (depth * mMaxBlurRadius);
+ ? 0 : (int) (blurAmount * mMaxBlurRadius);
+
SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()
.setBackgroundBlurRadius(mSurface, mCurrentBlur)
.setOpaque(mSurface, isSurfaceOpaque);
@@ -197,4 +213,12 @@
applyDepthAndBlur();
}
}
+
+ /**
+ * Maps depth values to blur amounts as a percentage of the max blur.
+ * The blur percentage grows linearly with depth, and maxes out at 30% depth.
+ */
+ private static float mapDepthToBlur(float depth) {
+ return Math.min(3 * depth, 1f);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java
index 245dde2..c39056d 100644
--- a/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java
+++ b/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java
@@ -15,6 +15,8 @@
*/
package com.android.quickstep.util;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
+
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.animation.Animator;
@@ -104,6 +106,8 @@
private float mCurrentScaleProgress;
private FlingSpringAnim mRectXAnim;
private FlingSpringAnim mRectYAnim;
+ private SpringAnimation mRectXSpring;
+ private SpringAnimation mRectYSpring;
private SpringAnimation mRectScaleAnim;
private boolean mAnimsStarted;
private boolean mRectXAnimEnded;
@@ -166,27 +170,47 @@
}
public void onTargetPositionChanged() {
- if (mRectXAnim != null && mRectXAnim.getTargetPosition() != mTargetRect.centerX()) {
- mRectXAnim.updatePosition(mCurrentCenterX, mTargetRect.centerX());
- }
+ if (enableScalingRevealHomeAnimation()) {
+ if (mRectXSpring != null) {
+ mRectXSpring.animateToFinalPosition(mTargetRect.centerX());
+ }
- if (mRectYAnim != null) {
- switch (mTracking) {
- case TRACKING_TOP:
- if (mRectYAnim.getTargetPosition() != mTargetRect.top) {
- mRectYAnim.updatePosition(mCurrentY, mTargetRect.top);
- }
- break;
- case TRACKING_BOTTOM:
- if (mRectYAnim.getTargetPosition() != mTargetRect.bottom) {
- mRectYAnim.updatePosition(mCurrentY, mTargetRect.bottom);
- }
- break;
- case TRACKING_CENTER:
- if (mRectYAnim.getTargetPosition() != mTargetRect.centerY()) {
- mRectYAnim.updatePosition(mCurrentY, mTargetRect.centerY());
- }
- break;
+ if (mRectYSpring != null) {
+ switch (mTracking) {
+ case TRACKING_TOP:
+ mRectYSpring.animateToFinalPosition(mTargetRect.top);
+ break;
+ case TRACKING_BOTTOM:
+ mRectYSpring.animateToFinalPosition(mTargetRect.bottom);
+ break;
+ case TRACKING_CENTER:
+ mRectYSpring.animateToFinalPosition(mTargetRect.centerY());
+ break;
+ }
+ }
+ } else {
+ if (mRectXAnim != null && mRectXAnim.getTargetPosition() != mTargetRect.centerX()) {
+ mRectXAnim.updatePosition(mCurrentCenterX, mTargetRect.centerX());
+ }
+
+ if (mRectYAnim != null) {
+ switch (mTracking) {
+ case TRACKING_TOP:
+ if (mRectYAnim.getTargetPosition() != mTargetRect.top) {
+ mRectYAnim.updatePosition(mCurrentY, mTargetRect.top);
+ }
+ break;
+ case TRACKING_BOTTOM:
+ if (mRectYAnim.getTargetPosition() != mTargetRect.bottom) {
+ mRectYAnim.updatePosition(mCurrentY, mTargetRect.bottom);
+ }
+ break;
+ case TRACKING_CENTER:
+ if (mRectYAnim.getTargetPosition() != mTargetRect.centerY()) {
+ mRectYAnim.updatePosition(mCurrentY, mTargetRect.centerY());
+ }
+ break;
+ }
}
}
}
@@ -215,59 +239,126 @@
maybeOnEnd();
});
- // We dampen the user velocity here to keep the natural feeling and to prevent the
- // rect from straying too from a linear path.
- final float xVelocityPxPerS = velocityPxPerMs.x * 1000;
- final float yVelocityPxPerS = velocityPxPerMs.y * 1000;
- final float dampedXVelocityPxPerS = OverScroll.dampedScroll(
- Math.abs(xVelocityPxPerS), mMaxVelocityPxPerS) * Math.signum(xVelocityPxPerS);
- final float dampedYVelocityPxPerS = OverScroll.dampedScroll(
- Math.abs(yVelocityPxPerS), mMaxVelocityPxPerS) * Math.signum(yVelocityPxPerS);
-
+ float xVelocityPxPerS = velocityPxPerMs.x * 1000;
+ float yVelocityPxPerS = velocityPxPerMs.y * 1000;
float startX = mCurrentCenterX;
float endX = mTargetRect.centerX();
- float minXValue = Math.min(startX, endX);
- float maxXValue = Math.max(startX, endX);
-
- mRectXAnim = new FlingSpringAnim(this, context, RECT_CENTER_X, startX, endX,
- dampedXVelocityPxPerS, mMinVisChange, minXValue, maxXValue, mDampingX, mStiffnessX,
- onXEndListener);
-
float startY = mCurrentY;
float endY = getTrackedYFromRect(mTargetRect);
- float minYValue = Math.min(startY, endY);
- float maxYValue = Math.max(startY, endY);
- mRectYAnim = new FlingSpringAnim(this, context, RECT_Y, startY, endY, dampedYVelocityPxPerS,
- mMinVisChange, minYValue, maxYValue, mDampingY, mStiffnessY, onYEndListener);
-
float minVisibleChange = Math.abs(1f / mStartRect.height());
- ResourceProvider rp = DynamicResource.provider(context);
- float damping = rp.getFloat(R.dimen.swipe_up_rect_scale_damping_ratio);
- // Increase the stiffness for devices where we want the window size to transform quicker.
- boolean shouldUseHigherStiffness = profile != null
- && (profile.isLandscape || profile.isTablet);
- float stiffness = shouldUseHigherStiffness
- ? rp.getFloat(R.dimen.swipe_up_rect_scale_higher_stiffness)
- : rp.getFloat(R.dimen.swipe_up_rect_scale_stiffness);
+ if (enableScalingRevealHomeAnimation()) {
+ ResourceProvider rp = DynamicResource.provider(context);
+ long minVelocityXPxPerS = rp.getInt(R.dimen.swipe_up_min_velocity_x_px_per_s);
+ long maxVelocityXPxPerS = rp.getInt(R.dimen.swipe_up_max_velocity_x_px_per_s);
+ long minVelocityYPxPerS = rp.getInt(R.dimen.swipe_up_min_velocity_y_px_per_s);
+ long maxVelocityYPxPerS = rp.getInt(R.dimen.swipe_up_max_velocity_y_px_per_s);
+ float fallOffFactor = rp.getFloat(R.dimen.swipe_up_max_velocity_fall_off_factor);
- mRectScaleAnim = new SpringAnimation(this, RECT_SCALE_PROGRESS)
- .setSpring(new SpringForce(1f)
- .setDampingRatio(damping)
- .setStiffness(stiffness))
- .setStartVelocity(velocityPxPerMs.y * minVisibleChange)
- .setMaxValue(1f)
- .setMinimumVisibleChange(minVisibleChange)
- .addEndListener((animation, canceled, value, velocity) -> {
- mRectScaleAnimEnded = true;
- maybeOnEnd();
- });
+ // We want the actual initial velocity to never dip below the minimum, and to taper off
+ // once it's above the soft cap so that we can prevent the window from flying off
+ // screen, while maintaining a natural feel.
+ xVelocityPxPerS = adjustVelocity(
+ xVelocityPxPerS, minVelocityXPxPerS, maxVelocityXPxPerS, fallOffFactor);
+ yVelocityPxPerS = adjustVelocity(
+ yVelocityPxPerS, minVelocityYPxPerS, maxVelocityYPxPerS, fallOffFactor);
- setCanRelease(false);
- mAnimsStarted = true;
+ float stiffnessX = rp.getFloat(R.dimen.swipe_up_rect_x_stiffness);
+ float dampingX = rp.getFloat(R.dimen.swipe_up_rect_x_damping_ratio);
+ mRectXSpring =
+ new SpringAnimation(this, RECT_CENTER_X)
+ .setSpring(
+ new SpringForce(endX)
+ .setStiffness(stiffnessX)
+ .setDampingRatio(dampingX)
+ ).setStartValue(startX)
+ .setStartVelocity(xVelocityPxPerS)
+ .addEndListener(onXEndListener);
- mRectXAnim.start();
- mRectYAnim.start();
+ float stiffnessY = rp.getFloat(R.dimen.swipe_up_rect_y_stiffness);
+ float dampingY = rp.getFloat(R.dimen.swipe_up_rect_y_damping_ratio);
+ mRectYSpring =
+ new SpringAnimation(this, RECT_Y)
+ .setSpring(
+ new SpringForce(endY)
+ .setStiffness(stiffnessY)
+ .setDampingRatio(dampingY)
+ )
+ .setStartValue(startY)
+ .setStartVelocity(yVelocityPxPerS)
+ .addEndListener(onYEndListener);
+
+ float stiffnessZ = rp.getFloat(R.dimen.swipe_up_rect_scale_stiffness_v2);
+ float dampingZ = rp.getFloat(R.dimen.swipe_up_rect_scale_damping_ratio_v2);
+ mRectScaleAnim =
+ new SpringAnimation(this, RECT_SCALE_PROGRESS)
+ .setSpring(
+ new SpringForce(1f)
+ .setStiffness(stiffnessZ)
+ .setDampingRatio(dampingZ))
+ .setStartVelocity(velocityPxPerMs.y * minVisibleChange)
+ .setMinimumVisibleChange(minVisibleChange)
+ .addEndListener((animation, canceled, value, velocity) -> {
+ mRectScaleAnimEnded = true;
+ maybeOnEnd();
+ });
+
+ setCanRelease(false);
+ mAnimsStarted = true;
+
+ mRectXSpring.start();
+ mRectYSpring.start();
+ } else {
+ // We dampen the user velocity here to keep the natural feeling and to prevent the
+ // rect from straying too from a linear path.
+ final float dampedXVelocityPxPerS = OverScroll.dampedScroll(
+ Math.abs(xVelocityPxPerS), mMaxVelocityPxPerS) * Math.signum(xVelocityPxPerS);
+ final float dampedYVelocityPxPerS = OverScroll.dampedScroll(
+ Math.abs(yVelocityPxPerS), mMaxVelocityPxPerS) * Math.signum(yVelocityPxPerS);
+
+ float minXValue = Math.min(startX, endX);
+ float maxXValue = Math.max(startX, endX);
+
+ mRectXAnim = new FlingSpringAnim(this, context, RECT_CENTER_X, startX, endX,
+ dampedXVelocityPxPerS, mMinVisChange, minXValue, maxXValue, mDampingX,
+ mStiffnessX, onXEndListener);
+
+ float minYValue = Math.min(startY, endY);
+ float maxYValue = Math.max(startY, endY);
+ mRectYAnim = new FlingSpringAnim(this, context, RECT_Y, startY, endY,
+ dampedYVelocityPxPerS, mMinVisChange, minYValue, maxYValue, mDampingY,
+ mStiffnessY, onYEndListener);
+
+ ResourceProvider rp = DynamicResource.provider(context);
+ float damping = rp.getFloat(R.dimen.swipe_up_rect_scale_damping_ratio);
+
+ // Increase the stiffness for devices where we want the window size to transform
+ // quicker.
+ boolean shouldUseHigherStiffness = profile != null
+ && (profile.isLandscape || profile.isTablet);
+ float stiffness = shouldUseHigherStiffness
+ ? rp.getFloat(R.dimen.swipe_up_rect_scale_higher_stiffness)
+ : rp.getFloat(R.dimen.swipe_up_rect_scale_stiffness);
+
+ mRectScaleAnim = new SpringAnimation(this, RECT_SCALE_PROGRESS)
+ .setSpring(new SpringForce(1f)
+ .setDampingRatio(damping)
+ .setStiffness(stiffness))
+ .setStartVelocity(velocityPxPerMs.y * minVisibleChange)
+ .setMaxValue(1f)
+ .setMinimumVisibleChange(minVisibleChange)
+ .addEndListener((animation, canceled, value, velocity) -> {
+ mRectScaleAnimEnded = true;
+ maybeOnEnd();
+ });
+
+ setCanRelease(false);
+ mAnimsStarted = true;
+
+ mRectXAnim.start();
+ mRectYAnim.start();
+ }
+
mRectScaleAnim.start();
for (Animator.AnimatorListener animatorListener : mAnimatorListeners) {
animatorListener.onAnimationStart(null);
@@ -276,8 +367,17 @@
public void end() {
if (mAnimsStarted) {
- mRectXAnim.end();
- mRectYAnim.end();
+ if (enableScalingRevealHomeAnimation()) {
+ if (mRectXSpring.canSkipToEnd()) {
+ mRectXSpring.skipToEnd();
+ }
+ if (mRectYSpring.canSkipToEnd()) {
+ mRectYSpring.skipToEnd();
+ }
+ } else {
+ mRectXAnim.end();
+ mRectYAnim.end();
+ }
if (mRectScaleAnim.canSkipToEnd()) {
mRectScaleAnim.skipToEnd();
}
@@ -357,6 +457,32 @@
end();
}
+ /**
+ * Modify the given velocity so that it's never below the minimum value, and falls off by the
+ * given factor once it goes above the maximum value.
+ * In order for the max soft cap to be enforced, the fall-off factor must be >1.
+ */
+ private static float adjustVelocity(float velocity, long min, long max, float factor) {
+ float sign = Math.signum(velocity);
+ float magnitude = Math.abs(velocity);
+
+ // If the absolute velocity is less than the min, bump it up.
+ if (magnitude < min) {
+ return min * sign;
+ }
+
+ // If the absolute velocity falls between min and max, or the fall-off factor is invalid,
+ // do nothing.
+ if (magnitude <= max || factor <= 1) {
+ return velocity;
+ }
+
+ // Scale the excess velocity by the fall-off factor.
+ float excess = magnitude - max;
+ float scaled = (float) Math.pow(excess, 1f / factor);
+ return (max + scaled) * sign;
+ }
+
public interface OnUpdateListener {
/**
* Called when an update is made to the animation.
diff --git a/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt b/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt
new file mode 100644
index 0000000..33736ad
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt
@@ -0,0 +1,129 @@
+/*
+ * 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.util
+
+import android.view.View
+import com.android.app.animation.Interpolators
+import com.android.app.animation.Interpolators.EMPHASIZED
+import com.android.app.animation.Interpolators.LINEAR
+import com.android.launcher3.Launcher
+import com.android.launcher3.LauncherAnimUtils.HOTSEAT_SCALE_PROPERTY_FACTORY
+import com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_WORKSPACE_STATE
+import com.android.launcher3.LauncherAnimUtils.WORKSPACE_SCALE_PROPERTY_FACTORY
+import com.android.launcher3.LauncherState
+import com.android.launcher3.anim.AnimatorListeners
+import com.android.launcher3.anim.PendingAnimation
+import com.android.launcher3.anim.PropertySetter
+import com.android.launcher3.states.StateAnimationConfig
+import com.android.launcher3.states.StateAnimationConfig.SKIP_DEPTH_CONTROLLER
+import com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW
+import com.android.launcher3.states.StateAnimationConfig.SKIP_SCRIM
+import com.android.launcher3.uioverrides.QuickstepLauncher
+import com.android.quickstep.views.RecentsView
+
+/**
+ * Creates an animation where the workspace and hotseat fade in while revealing from the center of
+ * the screen outwards radially. This is used in conjunction with the swipe up to home animation.
+ */
+class ScalingWorkspaceRevealAnim(launcher: Launcher) {
+ companion object {
+ private const val FADE_DURATION_MS = 200L
+ private const val SCALE_DURATION_MS = 1000L
+ private const val MAX_ALPHA = 1f
+ private const val MIN_ALPHA = 0f
+ private const val MAX_SIZE = 1f
+ private const val MIN_SIZE = 0.85f
+ }
+
+ private val animation = PendingAnimation(SCALE_DURATION_MS)
+
+ init {
+ // Make sure the starting state is right for the animation.
+ val config = StateAnimationConfig()
+ config.animFlags = SKIP_OVERVIEW.or(SKIP_DEPTH_CONTROLLER).or(SKIP_SCRIM)
+ config.duration = 0
+ launcher.stateManager
+ .createAtomicAnimation(LauncherState.BACKGROUND_APP, LauncherState.NORMAL, config)
+ .start()
+ launcher
+ .getOverviewPanel<RecentsView<QuickstepLauncher, LauncherState>>()
+ .forceFinishScroller()
+ launcher.workspace.stateTransitionAnimation.setScrim(
+ PropertySetter.NO_ANIM_PROPERTY_SETTER,
+ LauncherState.BACKGROUND_APP,
+ config
+ )
+
+ val workspace = launcher.workspace
+ val hotseat = launcher.hotseat
+
+ // Scale the Workspace and Hotseat around the same pivot.
+ animation.addFloat(
+ workspace,
+ WORKSPACE_SCALE_PROPERTY_FACTORY[SCALE_INDEX_WORKSPACE_STATE],
+ MIN_SIZE,
+ MAX_SIZE,
+ EMPHASIZED,
+ )
+ workspace.setPivotToScaleWithSelf(hotseat)
+ animation.addFloat(
+ hotseat,
+ HOTSEAT_SCALE_PROPERTY_FACTORY[SCALE_INDEX_WORKSPACE_STATE],
+ MIN_SIZE,
+ MAX_SIZE,
+ EMPHASIZED,
+ )
+
+ // Fade in quickly at the beginning of the animation, so the content doesn't look like it's
+ // popping into existence out of nowhere.
+ val fadeClamp = FADE_DURATION_MS.toFloat() / SCALE_DURATION_MS
+ workspace.alpha = MIN_ALPHA
+ animation.setViewAlpha(
+ workspace,
+ MAX_ALPHA,
+ Interpolators.clampToProgress(LINEAR, 0f, fadeClamp)
+ )
+ hotseat.alpha = MIN_ALPHA
+ animation.setViewAlpha(
+ hotseat,
+ MAX_ALPHA,
+ Interpolators.clampToProgress(LINEAR, 0f, fadeClamp)
+ )
+
+ // Match the Wallpaper animation to the rest of the content.
+ val depthController = (launcher as? QuickstepLauncher)?.depthController
+ val depthConfig = StateAnimationConfig()
+ depthConfig.setInterpolator(StateAnimationConfig.ANIM_DEPTH, EMPHASIZED)
+ depthController?.setStateWithAnimation(LauncherState.NORMAL, depthConfig, animation)
+
+ // Needed to avoid text artefacts during the scale animation.
+ workspace.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+ hotseat.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+ animation.addListener(
+ AnimatorListeners.forEndCallback(
+ Runnable {
+ workspace.setLayerType(View.LAYER_TYPE_NONE, null)
+ hotseat.setLayerType(View.LAYER_TYPE_NONE, null)
+ }
+ )
+ )
+ }
+
+ fun start() {
+ animation.buildAnim().start()
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 6b3199f..d6d6a11 100644
--- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -65,7 +65,7 @@
// Should be used for animations running alongside this StaggeredWorkspaceAnim.
public static final int DURATION_MS = 250;
public static final int DURATION_TASKBAR_MS =
- QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION;
+ QuickstepTransitionManager.getTaskbarToHomeDuration();
private static final float MAX_VELOCITY_PX_PER_S = 22f;
diff --git a/quickstep/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java b/quickstep/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java
index 7bbde30..c63a58e 100644
--- a/quickstep/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java
+++ b/quickstep/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java
@@ -28,6 +28,8 @@
import android.view.MotionEvent;
import android.view.VelocityTracker;
+import androidx.annotation.NonNull;
+
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -41,21 +43,20 @@
private final float mMinFlingVelocity;
private final boolean mDisableHorizontalSwipe;
private final NavBarPosition mNavBarPosition;
- private final Runnable mOnInterceptTouch;
+
+ @NonNull
private final OnSwipeUpListener mOnSwipeUp;
private boolean mInterceptedTouch;
private VelocityTracker mVelocityTracker;
public TriggerSwipeUpTouchTracker(Context context, boolean disableHorizontalSwipe,
- NavBarPosition navBarPosition, Runnable onInterceptTouch,
- OnSwipeUpListener onSwipeUp) {
+ NavBarPosition navBarPosition, @NonNull OnSwipeUpListener onSwipeUp) {
mSquaredTouchSlop = Utilities.squaredTouchSlop(context);
mMinFlingVelocity = context.getResources().getDimension(
R.dimen.quickstep_fling_threshold_speed);
mNavBarPosition = navBarPosition;
mDisableHorizontalSwipe = disableHorizontalSwipe;
- mOnInterceptTouch = onInterceptTouch;
mOnSwipeUp = onSwipeUp;
init();
@@ -103,10 +104,7 @@
}
mInterceptedTouch = true;
-
- if (mOnInterceptTouch != null) {
- mOnInterceptTouch.run();
- }
+ mOnSwipeUp.onSwipeUpTouchIntercepted();
}
}
break;
@@ -124,7 +122,8 @@
}
}
- private void endTouchTracking() {
+ /** Finishes the tracking. All events after this call are ignored */
+ public void endTouchTracking() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
@@ -151,12 +150,10 @@
isSwipeUp = squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop;
}
- if (mOnSwipeUp != null) {
- if (isSwipeUp) {
- mOnSwipeUp.onSwipeUp(wasFling, new PointF(velocityX, velocityY));
- } else {
- mOnSwipeUp.onSwipeUpCancelled();
- }
+ if (isSwipeUp) {
+ mOnSwipeUp.onSwipeUp(wasFling, new PointF(velocityX, velocityY));
+ } else {
+ mOnSwipeUp.onSwipeUpCancelled();
}
}
@@ -172,6 +169,9 @@
void onSwipeUp(boolean wasFling, PointF finalVelocity);
/** Called on touch up if a swipe up was not detected. */
- void onSwipeUpCancelled();
+ default void onSwipeUpCancelled() { }
+
+ /** Called when the touch for swipe up is intercepted. */
+ default void onSwipeUpTouchIntercepted() { }
}
}
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 7a1c49a..e0091a5 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -106,6 +106,7 @@
public @interface AppPairButtonHiddenFlags { }
public static final int FLAG_SINGLE_TASK_HIDE_APP_PAIR = 1 << 0;
public static final int FLAG_SMALL_SCREEN_HIDE_APP_PAIR = 1 << 1;
+ public static final int FLAG_3P_LAUNCHER_HIDE_APP_PAIR = 1 << 2;
private MultiValueAlpha mMultiValueAlpha;
@@ -255,6 +256,13 @@
}
/**
+ * Updates flags to hide and show actions buttons for 1p/3p launchers.
+ */
+ public void updateFor3pLauncher(boolean is3pLauncher) {
+ updateAppPairButtonHiddenFlags(FLAG_3P_LAUNCHER_HIDE_APP_PAIR, is3pLauncher);
+ }
+
+ /**
* Updates the proper flags to indicate whether the "Screenshot" button should be hidden.
*
* @param flag The flag to update.
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 2cbeb31..4985f0b 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -4022,6 +4022,8 @@
mActionsView.updateForGroupedTask(isCurrentSplit);
// Update flags to see if actions bar should show buttons for tablets or phones.
mActionsView.updateForSmallScreen(!mActivity.getDeviceProfile().isTablet);
+ // Update flags for 1p/3p launchers
+ mActionsView.updateFor3pLauncher(!supportsAppPairs());
if (isDesktopModeSupported()) {
boolean isCurrentDesktop = getCurrentPageTaskView() instanceof DesktopTaskView;
@@ -4029,6 +4031,11 @@
}
}
+ /** Returns if app pairs are supported in this launcher. Overridden in subclasses. */
+ public boolean supportsAppPairs() {
+ return true;
+ }
+
/**
* Returns all the tasks in the top row, without the focused task
*/
@@ -4140,10 +4147,10 @@
private boolean snapToPageRelative(int delta, boolean cycle,
@TaskGridNavHelper.TASK_NAV_DIRECTION int direction) {
- // Ignore grid page snap events while scroll animations are running, otherwise the next
- // page gets set before the animation finishes and can cause jumps.
+ // Set next page if scroll animation is still running, otherwise cannot snap to the
+ // next page on successive key presses. Setting the current page aborts the scroll.
if (!mScroller.isFinished()) {
- return true;
+ setCurrentPage(getNextPage());
}
int pageCount = getPageCount();
if (pageCount == 0) {
@@ -4224,30 +4231,31 @@
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- switch (event.getKeyCode()) {
- case KeyEvent.KEYCODE_TAB:
- return snapToPageRelative(event.isShiftPressed() ? -1 : 1, true /* cycle */,
- DIRECTION_TAB);
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- return snapToPageRelative(mIsRtl ? -1 : 1, true /* cycle */, DIRECTION_RIGHT);
- case KeyEvent.KEYCODE_DPAD_LEFT:
- return snapToPageRelative(mIsRtl ? 1 : -1, true /* cycle */, DIRECTION_LEFT);
- case KeyEvent.KEYCODE_DPAD_UP:
- return snapToPageRelative(1, false /* cycle */, DIRECTION_UP);
- case KeyEvent.KEYCODE_DPAD_DOWN:
- return snapToPageRelative(1, false /* cycle */, DIRECTION_DOWN);
- case KeyEvent.KEYCODE_DEL:
- case KeyEvent.KEYCODE_FORWARD_DEL:
+ if (isHandlingTouch() || event.getAction() != KeyEvent.ACTION_DOWN) {
+ return super.dispatchKeyEvent(event);
+ }
+ switch (event.getKeyCode()) {
+ case KeyEvent.KEYCODE_TAB:
+ return snapToPageRelative(event.isShiftPressed() ? -1 : 1, true /* cycle */,
+ DIRECTION_TAB);
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ return snapToPageRelative(mIsRtl ? -1 : 1, true /* cycle */, DIRECTION_RIGHT);
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ return snapToPageRelative(mIsRtl ? 1 : -1, true /* cycle */, DIRECTION_LEFT);
+ case KeyEvent.KEYCODE_DPAD_UP:
+ return snapToPageRelative(1, false /* cycle */, DIRECTION_UP);
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ return snapToPageRelative(1, false /* cycle */, DIRECTION_DOWN);
+ case KeyEvent.KEYCODE_DEL:
+ case KeyEvent.KEYCODE_FORWARD_DEL:
+ dismissCurrentTask();
+ return true;
+ case KeyEvent.KEYCODE_NUMPAD_DOT:
+ if (event.isAltPressed()) {
+ // Numpad DEL pressed while holding Alt.
dismissCurrentTask();
return true;
- case KeyEvent.KEYCODE_NUMPAD_DOT:
- if (event.isAltPressed()) {
- // Numpad DEL pressed while holding Alt.
- dismissCurrentTask();
- return true;
- }
- }
+ }
}
return super.dispatchKeyEvent(event);
}
@@ -5024,7 +5032,7 @@
firstFloatingTaskView.update(mTempRectF, /*progress=*/1f);
RecentsPagedOrientationHandler orientationHandler = getPagedOrientationHandler();
- Pair<FloatProperty, FloatProperty> taskViewsFloat =
+ Pair<FloatProperty<RecentsView>, FloatProperty<RecentsView>> taskViewsFloat =
orientationHandler.getSplitSelectTaskOffset(
TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
mActivity.getDeviceProfile());
@@ -5162,9 +5170,13 @@
public float getMaxScaleForFullScreen() {
if (enableGridOnlyOverview() && mActivity.getDeviceProfile().isTablet
&& !mOverviewGridEnabled) {
+ if (mLastComputedCarouselTaskSize.isEmpty()) {
+ mSizeStrategy.calculateCarouselTaskSize(mActivity, mActivity.getDeviceProfile(),
+ mLastComputedCarouselTaskSize, getPagedOrientationHandler());
+ }
mTempRect.set(mLastComputedCarouselTaskSize);
} else {
- if (mLastComputedTaskSize.height() == 0 || mLastComputedTaskSize.width() == 0) {
+ if (mLastComputedTaskSize.isEmpty()) {
getTaskSize(mLastComputedTaskSize);
}
mTempRect.set(mLastComputedTaskSize);
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 137455e..c9aad1a 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -140,11 +140,9 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (!enableOverviewIconMenu()) {
- int maxMenuHeight = calculateMaxHeight();
- if (MeasureSpec.getSize(heightMeasureSpec) > maxMenuHeight) {
- heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxMenuHeight, MeasureSpec.AT_MOST);
- }
+ int maxMenuHeight = calculateMaxHeight();
+ if (MeasureSpec.getSize(heightMeasureSpec) > maxMenuHeight) {
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxMenuHeight, MeasureSpec.AT_MOST);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@@ -416,10 +414,9 @@
* with a margin on the top and bottom.
*/
private int calculateMaxHeight() {
- float taskBottom = mTaskView.getHeight() + mTaskView.getPersistentTranslationY();
float taskInsetMargin = getResources().getDimension(R.dimen.task_card_margin);
-
- return (int) (taskBottom - taskInsetMargin - getTranslationY());
+ return mTaskView.getPagedOrientationHandler().getTaskMenuHeight(taskInsetMargin,
+ mActivity.getDeviceProfile(), getTranslationX(), getTranslationY());
}
private void setOnClosingStartCallback(Runnable onClosingStartCallback) {
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 9b48082..14d7842 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -30,6 +30,8 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE;
+import static com.android.launcher3.testing.shared.TestProtocol.SUCCESSFUL_GESTURE_MISMATCH_EVENTS;
+import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
@@ -861,6 +863,7 @@
@Nullable
public RunnableList launchTaskAnimated() {
if (mTask != null) {
+ testLogD(SUCCESSFUL_GESTURE_MISMATCH_EVENTS, "TaskView.launchTaskAnimated");
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
ActivityOptionsWrapper opts = mActivity.getActivityLaunchOptions(this, null);
@@ -909,6 +912,7 @@
*/
public void launchTask(@NonNull Consumer<Boolean> callback, boolean isQuickswitch) {
if (mTask != null) {
+ testLogD(SUCCESSFUL_GESTURE_MISMATCH_EVENTS, "TaskView.launchTaskAnimated");
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
diff --git a/quickstep/tests/src/com/android/launcher3/model/AppEventProducerTest.java b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java
similarity index 100%
rename from quickstep/tests/src/com/android/launcher3/model/AppEventProducerTest.java
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/RecentsHitboxExtenderTest.java b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/RecentsHitboxExtenderTest.java
similarity index 100%
rename from quickstep/tests/src/com/android/launcher3/taskbar/RecentsHitboxExtenderTest.java
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/RecentsHitboxExtenderTest.java
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
similarity index 100%
rename from quickstep/tests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationBarRotationContextTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/NavigationBarRotationContextTest.java
similarity index 100%
rename from quickstep/tests/src/com/android/quickstep/NavigationBarRotationContextTest.java
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/NavigationBarRotationContextTest.java
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
similarity index 100%
rename from quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
diff --git a/quickstep/tests/src/com/android/quickstep/util/TaskGridNavHelperTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.java
similarity index 100%
rename from quickstep/tests/src/com/android/quickstep/util/TaskGridNavHelperTest.java
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.java
diff --git a/quickstep/tests/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCacheTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCacheTest.java
similarity index 100%
rename from quickstep/tests/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCacheTest.java
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCacheTest.java
diff --git a/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
similarity index 100%
rename from quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
diff --git a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
index 37dde10..8702f70 100644
--- a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
+++ b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.model;
+import static android.content.pm.ApplicationInfo.CATEGORY_PRODUCTIVITY;
+import static android.content.pm.ApplicationInfo.FLAG_INSTALLED;
import static android.os.Process.myUserHandle;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
@@ -37,6 +39,8 @@
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
import android.os.UserHandle;
import android.platform.test.flag.junit.SetFlagsRule;
import android.text.TextUtils;
@@ -81,6 +85,8 @@
private FakeBgDataModelCallback mCallback = new FakeBgDataModelCallback();
private LauncherModelHelper mModelHelper;
private UserHandle mUserHandle;
+ private LauncherApps mLauncherApps;
+
@Before
public void setup() throws Exception {
@@ -103,12 +109,18 @@
allWidgets = Arrays.asList(mApp1Provider1, mApp1Provider2, mApp2Provider1,
mApp4Provider1, mApp4Provider2, mApp5Provider1);
+ mLauncherApps = mModelHelper.sandboxContext.spyService(LauncherApps.class);
doAnswer(i -> {
String pkg = i.getArgument(0);
- return ApplicationInfoBuilder.newBuilder().setPackageName(pkg).setName(
- "App " + pkg).build();
- }).when(mModelHelper.sandboxContext.getPackageManager())
- .getApplicationInfo(anyString(), anyInt());
+ ApplicationInfo applicationInfo = ApplicationInfoBuilder.newBuilder()
+ .setPackageName(pkg)
+ .setName("App " + pkg)
+ .build();
+ applicationInfo.category = CATEGORY_PRODUCTIVITY;
+ applicationInfo.flags = FLAG_INSTALLED;
+ return applicationInfo;
+ }).when(mLauncherApps).getApplicationInfo(anyString(), anyInt(), any());
+
AppWidgetManager manager = mModelHelper.sandboxContext.spyService(AppWidgetManager.class);
doReturn(allWidgets).when(manager).getInstalledProviders();
doReturn(allWidgets).when(manager).getInstalledProvidersForProfile(eq(myUserHandle()));
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 213f58f..077ca60 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -76,17 +76,21 @@
import org.junit.runner.RunWith;
import org.junit.runners.model.Statement;
+import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class FallbackRecentsTest {
private static final String FALLBACK_LAUNCHER_TITLE = "Test launcher";
+ private static final Pattern COMPONENT_INFO_REGEX = Pattern.compile("ComponentInfo\\{(.*)\\}");
private final UiDevice mDevice;
private final LauncherInstrumentation mLauncher;
@@ -253,7 +257,7 @@
//@NavigationModeSwitch
@Test
@ScreenRecordRule.ScreenRecord // b/321775748
- public void testOverview() {
+ public void testOverview() throws IOException {
startAppFast(getAppPackageName());
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
startTestActivity(2);
@@ -261,7 +265,10 @@
Wait.atMost("Expected three apps in the task list",
() -> mLauncher.getRecentTasks().size() >= 3, DEFAULT_ACTIVITY_TIMEOUT, mLauncher);
+ checkTestLauncher();
BaseOverview overview = mLauncher.getLaunchedAppState().switchToOverview();
+ checkTestLauncher();
+
executeOnRecents(recents -> {
assertTrue("Don't have at least 3 tasks", getTaskCount(recents) >= 3);
});
@@ -303,6 +310,17 @@
mOtherLauncherActivity.packageName).text(FALLBACK_LAUNCHER_TITLE)), WAIT_TIME_MS));
}
+ private void checkTestLauncher() throws IOException {
+ final Matcher matcher = COMPONENT_INFO_REGEX.matcher(
+ mDevice.executeShellCommand("cmd shortcut get-default-launcher"));
+ assertTrue("Incorrect output from get-default-launcher", matcher.find());
+ assertEquals("Current Launcher activity is incorrect",
+ "com.google.android.apps.nexuslauncher.tests/com.android"
+ + ".launcher3.testcomponent.TestLauncherActivity",
+ matcher.group(1)
+ );
+ }
+
private int getCurrentOverviewPage(RecentsActivity recents) {
return recents.<RecentsView>getOverviewPanel().getCurrentPage();
}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index a53bb4e..684c090 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -501,7 +501,6 @@
@Test
@PortraitLandscape
- @ScreenRecord // b/326839375
public void testOverviewDeadzones() throws Exception {
startTestAppsWithCheck();
diff --git a/res/drawable/page_indicator.xml b/res/drawable/page_indicator.xml
index c0ccc49..d4cb13f 100644
--- a/res/drawable/page_indicator.xml
+++ b/res/drawable/page_indicator.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
- <solid android:color="?attr/folderPaginationColor"/>
+ <solid android:color="?attr/pageIndicatorDotColor"/>
<size android:width="@dimen/page_indicator_size" android:height="@dimen/page_indicator_size"/>
</shape>
\ No newline at end of file
diff --git a/res/layout/widget_cell.xml b/res/layout/widget_cell.xml
index 55dd1de..4533873 100644
--- a/res/layout/widget_cell.xml
+++ b/res/layout/widget_cell.xml
@@ -17,7 +17,8 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:paddingHorizontal="@dimen/widget_cell_horizontal_padding"
+ android:layout_marginStart="@dimen/widget_cell_horizontal_padding"
+ android:layout_marginEnd="@dimen/widget_cell_horizontal_padding"
android:paddingVertical="@dimen/widget_cell_vertical_padding"
android:layout_weight="1"
android:orientation="vertical"
diff --git a/res/layout/widget_recommendations_table.xml b/res/layout/widget_recommendations_table.xml
index e3f0562..b53d2d5 100644
--- a/res/layout/widget_recommendations_table.xml
+++ b/res/layout/widget_recommendations_table.xml
@@ -17,5 +17,4 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingHorizontal="@dimen/widget_recommendations_table_horizontal_padding"
android:paddingVertical="@dimen/widget_recommendations_table_vertical_padding" />
diff --git a/res/values-night/styles.xml b/res/values-night/styles.xml
index d41eb7e..613c2e9 100644
--- a/res/values-night/styles.xml
+++ b/res/values-night/styles.xml
@@ -22,4 +22,10 @@
<item name="widgetsTheme">@style/WidgetContainerTheme.Dark</item>
<item name="android:windowTranslucentStatus">true</item>
</style>
+
+ <style name="WidgetPickerActivityTheme" parent="@android:style/Theme.Translucent.NoTitleBar">
+ <item name="widgetsTheme">@style/WidgetContainerTheme.Dark</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="pageIndicatorDotColor">@color/page_indicator_dot_color_dark</item>
+ </style>
</resources>
diff --git a/res/values-sw720dp/dimens.xml b/res/values-sw720dp/dimens.xml
index 3c79588..27aba6b 100644
--- a/res/values-sw720dp/dimens.xml
+++ b/res/values-sw720dp/dimens.xml
@@ -37,6 +37,7 @@
<!-- Widget picker-->
<dimen name="widget_list_horizontal_margin">30dp</dimen>
+ <dimen name="widget_cell_horizontal_padding">16dp</dimen>
<!-- Folder spaces -->
<dimen name="folder_footer_horiz_padding">24dp</dimen>
diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml
index e2f610e..f42b50f 100644
--- a/res/values-v31/colors.xml
+++ b/res/values-v31/colors.xml
@@ -47,8 +47,8 @@
<color name="wallpaper_popup_scrim">@android:color/system_neutral1_900</color>
- <color name="folder_pagination_color_light">@android:color/system_accent1_600</color>
- <color name="folder_pagination_color_dark">@android:color/system_accent1_200</color>
+ <color name="page_indicator_dot_color_light">@android:color/system_accent1_600</color>
+ <color name="page_indicator_dot_color_dark">@android:color/system_accent1_200</color>
<color name="home_settings_header_accent">@android:color/system_accent1_600</color>
<color name="home_settings_header_collapsed">@android:color/system_neutral1_100</color>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 4a0b5e8..57d1228 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -44,7 +44,7 @@
<attr name="notificationDotColor" format="color" />
<attr name="focusOutlineColor" format="color" />
- <attr name="folderPaginationColor" format="color" />
+ <attr name="pageIndicatorDotColor" format="color" />
<attr name="folderPreviewColor" format="color" />
<attr name="folderBackgroundColor" format="color" />
<attr name="folderIconRadius" format="float" />
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 6a484d7..7391990 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -73,8 +73,8 @@
<color name="folder_preview_light">#7FCFFF</color>
<color name="folder_preview_dark">#1E1F20</color>
- <color name="folder_pagination_color_light">#0B57D0</color>
- <color name="folder_pagination_color_dark">#A8C7FA</color>
+ <color name="pagination_indicator_dot_color_light">#0B57D0</color>
+ <color name="pagination_indicator_dot_color_dark">#A8C7FA</color>
<color name="text_color_primary_dark">#FFFFFFFF</color>
<color name="text_color_secondary_dark">#FFFFFFFF</color>
diff --git a/res/values/config.xml b/res/values/config.xml
index 21d6024..048d6cc 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -159,6 +159,9 @@
<item name="swipe_up_rect_scale_damping_ratio" type="dimen" format="float">0.75</item>
<item name="swipe_up_rect_scale_stiffness" type="dimen" format="float">200</item>
<item name="swipe_up_rect_scale_higher_stiffness" type="dimen" format="float">400</item>
+ <!-- Flag: enableScalingRevealHomeAnimation() -->
+ <item name="swipe_up_rect_scale_damping_ratio_v2" type="dimen" format="float">0.8</item>
+ <item name="swipe_up_rect_scale_stiffness_v2" type="dimen" format="float">650</item>
<item name="swipe_up_rect_xy_fling_friction" type="dimen" format="float">1.5</item>
@@ -166,6 +169,11 @@
<item name="swipe_up_rect_xy_damping_ratio" type="dimen" format="float">0.8</item>
<item name="swipe_up_rect_xy_stiffness" type="dimen" format="float">200</item>
+ <!-- Flag: enableScalingRevealHomeAnimation() -->
+ <item name="swipe_up_rect_x_damping_ratio" type="dimen" format="float">0.965</item>
+ <item name="swipe_up_rect_x_stiffness" type="dimen" format="float">300</item>
+ <item name="swipe_up_rect_y_damping_ratio" type="dimen" format="float">0.95</item>
+ <item name="swipe_up_rect_y_stiffness" type="dimen" format="float">190</item>
<!-- Taskbar -->
<!-- This is a float because it is converted to dp later in DeviceProfile -->
@@ -190,6 +198,12 @@
<dimen name="swipe_up_fling_min_visible_change">18dp</dimen>
<dimen name="swipe_up_max_workspace_trans_y">-60dp</dimen>
<dimen name="swipe_up_max_velocity">7.619dp</dimen>
+ <!-- Flag: enableScalingRevealHomeAnimation() -->
+ <item name="swipe_up_min_velocity_x_px_per_s" type="dimen" format="integer">300</item>
+ <item name="swipe_up_max_velocity_x_px_per_s" type="dimen" format="integer">500</item>
+ <item name="swipe_up_min_velocity_y_px_per_s" type="dimen" format="integer">2000</item>
+ <item name="swipe_up_max_velocity_y_px_per_s" type="dimen" format="integer">5000</item>
+ <item name="swipe_up_max_velocity_fall_off_factor" type="dimen" format="float">1.4</item>
<array name="dynamic_resources">
<item>@dimen/swipe_up_scale_start</item>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index a912e2d..c141095 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -22,7 +22,7 @@
<dimen name="dynamic_grid_edge_margin">10.77dp</dimen>
<dimen name="dynamic_grid_left_right_margin">8dp</dimen>
<!-- Minimum amount of next page visible in spring loaded mode -->
- <dimen name="dynamic_grid_spring_loaded_min_next_space_visible">24dp</dimen>
+ <dimen name="dynamic_grid_spring_loaded_min_next_space_visible">48dp</dimen>
<dimen name="dynamic_grid_cell_border_spacing">16dp</dimen>
<dimen name="cell_layout_padding">10.77dp</dimen>
@@ -176,7 +176,7 @@
<!-- Widget tray -->
<dimen name="widget_cell_vertical_padding">8dp</dimen>
- <dimen name="widget_cell_horizontal_padding">16dp</dimen>
+ <dimen name="widget_cell_horizontal_padding">8dp</dimen>
<dimen name="widget_cell_font_size">14sp</dimen>
<dimen name="widget_cell_app_icon_size">24dp</dimen>
<dimen name="widget_cell_app_icon_padding">8dp</dimen>
@@ -187,7 +187,6 @@
<dimen name="widget_picker_landscape_tablet_left_right_margin">117dp</dimen>
<dimen name="widget_picker_two_panels_left_right_margin">0dp</dimen>
<dimen name="widget_recommendations_table_vertical_padding">8dp</dimen>
- <dimen name="widget_recommendations_table_horizontal_padding">16dp</dimen>
<!-- Bottom margin for the search and recommended widgets container without work profile -->
<dimen name="search_and_recommended_widgets_container_bottom_margin">16dp</dimen>
<!-- Bottom margin for the search and recommended widgets container with work profile -->
@@ -198,7 +197,8 @@
<dimen name="widget_list_header_view_vertical_padding">20dp</dimen>
<dimen name="widget_list_entry_spacing">2dp</dimen>
- <dimen name="widget_list_horizontal_margin">16dp</dimen>
+ <!-- Less margin on sides to let widgets table width be close to the workspace width. -->
+ <dimen name="widget_list_horizontal_margin">11dp</dimen>
<!-- Margin applied to the recycler view with search bar & the list of widget apps below it. -->
<dimen name="widget_list_left_pane_horizontal_margin">0dp</dimen>
<dimen name="widget_list_horizontal_margin_two_pane">24dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index aaef15b..7bf1c87 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -75,12 +75,14 @@
<!-- Widget suggestions header title in the full widgets picker for large screen devices
in landscape mode. [CHAR_LIMIT=50] -->
<string name="suggested_widgets_header_title">Suggestions</string>
- <string name="productivity_widget_recommendation_category_label">Your Daily Essentials</string>
- <string name="news_widget_recommendation_category_label">News For You</string>
+ <string name="productivity_widget_recommendation_category_label">Essentials</string>
+ <string name="news_widget_recommendation_category_label">News & magazines</string>
<string name="social_and_entertainment_widget_recommendation_category_label">Your Chill Zone</string>
- <string name="fitness_widget_recommendation_category_label">Reach Your Fitness Goals</string>
- <string name="weather_widget_recommendation_category_label">Stay Ahead of the Weather</string>
- <string name="others_widget_recommendation_category_label">You Might Also Like</string>
+ <string name="entertainment_widget_recommendation_category_label">Entertainment</string>
+ <string name="social_widget_recommendation_category_label">Social</string>
+ <string name="fitness_widget_recommendation_category_label">Health & fitness</string>
+ <string name="weather_widget_recommendation_category_label">Weather</string>
+ <string name="others_widget_recommendation_category_label">Suggested for you</string>
<!-- accessibilityPaneTitle for the right pane when showing suggested widgets. -->
<string name="widget_picker_right_pane_accessibility_title"><xliff:g id="selected_header" example="Calendar">%1$s</xliff:g> widgets on right, search and options on left</string>
<!-- Label for showing the number of widgets an app has in the full widgets picker.
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 401155d..9fc393b 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -52,8 +52,8 @@
<item name="workspaceAmbientShadowColor">#40000000</item>
<item name="workspaceKeyShadowColor">#89000000</item>
<item name="widgetsTheme">@style/WidgetContainerTheme</item>
+ <item name="pageIndicatorDotColor">@color/page_indicator_dot_color_light</item>
<item name="focusOutlineColor">@color/material_color_on_secondary_container</item>
- <item name="folderPaginationColor">@color/folder_pagination_color_light</item>
<item name="folderPreviewColor">@color/folder_preview_light</item>
<item name="folderBackgroundColor">@color/folder_background_light</item>
<item name="folderIconBorderColor">?android:attr/colorPrimary</item>
@@ -164,7 +164,7 @@
<item name="popupShadeThird">@color/popup_shade_third_dark</item>
<item name="notificationDotColor">@color/notification_dot_color_dark</item>
<item name="widgetsTheme">@style/WidgetContainerTheme.Dark</item>
- <item name="folderPaginationColor">@color/folder_pagination_color_dark</item>
+ <item name="pageIndicatorDotColor">@color/page_indicator_dot_color_dark</item>
<item name="folderPreviewColor">@color/folder_preview_dark</item>
<item name="folderBackgroundColor">@color/folder_background_dark</item>
<item name="folderIconBorderColor">?android:attr/colorPrimary</item>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 4f071fe..3ee1c61 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -285,9 +285,6 @@
mDotParams.scale = 0f;
mForceHideDot = false;
setBackground(null);
- if (FeatureFlags.enableTwolineAllapps() || FeatureFlags.ENABLE_TWOLINE_DEVICESEARCH.get()) {
- setMaxLines(1);
- }
setTag(null);
if (mIconLoadRequest != null) {
@@ -299,6 +296,7 @@
setAlpha(1);
setScaleY(1);
setTranslationY(0);
+ setMaxLines(1);
setVisibility(VISIBLE);
}
@@ -428,10 +426,9 @@
* Only if actual text can be displayed in two line, the {@code true} value will be effective.
*/
protected boolean shouldUseTwoLine() {
- return FeatureFlags.enableTwolineAllapps() && isCurrentLanguageEnglish()
- && (mDisplay == DISPLAY_ALL_APPS || mDisplay == DISPLAY_PREDICTION_ROW)
- && (!Flags.enableTwolineToggle() || (Flags.enableTwolineToggle()
- && LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(getContext())));
+ return isCurrentLanguageEnglish() && (mDisplay == DISPLAY_ALL_APPS
+ || mDisplay == DISPLAY_PREDICTION_ROW) && (Flags.enableTwolineToggle()
+ && LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(getContext()));
}
protected boolean isCurrentLanguageEnglish() {
@@ -916,7 +913,8 @@
/** Applies the given progress level to the this icon's progress bar. */
@Nullable
public PreloadIconDrawable applyProgressLevel() {
- if (!(getTag() instanceof ItemInfoWithIcon)) {
+ if (!(getTag() instanceof ItemInfoWithIcon)
+ || !((ItemInfoWithIcon) getTag()).isActiveArchive()) {
return null;
}
@@ -973,6 +971,7 @@
return info.isDisabled() || info.isPendingDownload();
}
+
public void applyDotState(ItemInfo itemInfo, boolean animate) {
if (mIcon instanceof FastBitmapDrawable) {
boolean wasDotted = mDotInfo != null;
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 7e6835b..3ddc7aa 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -18,6 +18,7 @@
import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.Flags.enableOverviewIconMenu;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.InvariantDeviceProfile.INDEX_DEFAULT;
import static com.android.launcher3.InvariantDeviceProfile.INDEX_LANDSCAPE;
import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_LANDSCAPE;
@@ -437,7 +438,11 @@
if (isMultiDisplay && !ENABLE_MULTI_DISPLAY_PARTIAL_DEPTH.get()) {
// TODO(b/259893832): Revert to use maxWallpaperScale to calculate bottomSheetDepth
// when screen recorder bug is fixed.
- bottomSheetDepth = 1f;
+ if (enableScalingRevealHomeAnimation()) {
+ bottomSheetDepth = 0.3f;
+ } else {
+ bottomSheetDepth = 1f;
+ }
} else {
// The goal is to set wallpaper to zoom at workspaceContentScale when in AllApps.
// When depth is 0, wallpaper zoom is set to maxWallpaperScale.
@@ -1234,9 +1239,8 @@
if (isVerticalLayout && !mIsResponsiveGrid) {
hideWorkspaceLabelsIfNotEnoughSpace();
}
- if (FeatureFlags.enableTwolineAllapps()
- && (!Flags.enableTwolineToggle() || (Flags.enableTwolineToggle()
- && LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(context)))) {
+ if ((Flags.enableTwolineToggle()
+ && LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(context))) {
// Add extra textHeight to the existing allAppsCellHeight.
allAppsCellHeightPx += Utilities.calculateTextHeight(allAppsIconTextSizePx);
}
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 42d4d50..2e0f676 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -72,6 +72,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.stream.Collectors;
public class InvariantDeviceProfile {
@@ -578,6 +579,45 @@
}
/**
+ * Returns the GridOption associated to the given file name or null if the fileName is not
+ * supported.
+ * Ej, launcher.db -> "normal grid", launcher_4_by_4.db -> "practical grid"
+ */
+ public GridOption getGridOptionFromFileName(Context context, String fileName) {
+ return parseAllGridOptions(context).stream()
+ .filter(gridOption -> Objects.equals(gridOption.dbFile, fileName))
+ .findFirst()
+ .orElse(null);
+ }
+
+ /**
+ * Returns the name of the given size on the current device or empty string if the size is not
+ * supported. Ej. 4x4 -> normal, 5x4 -> practical, etc.
+ * (Note: the name of the grid can be different for the same grid size depending of
+ * the values of the InvariantDeviceProfile)
+ *
+ */
+ public String getGridNameFromSize(Context context, Point size) {
+ return parseAllGridOptions(context).stream()
+ .filter(gridOption -> gridOption.numColumns == size.x
+ && gridOption.numRows == size.y)
+ .map(gridOption -> gridOption.name)
+ .findFirst()
+ .orElse("");
+ }
+
+ /**
+ * Returns the grid option for the given gridName on the current device (Note: the gridOption
+ * be different for the same gridName depending on the values of the InvariantDeviceProfile).
+ */
+ public GridOption getGridOptionFromName(Context context, String gridName) {
+ return parseAllGridOptions(context).stream()
+ .filter(gridOption -> Objects.equals(gridOption.name, gridName))
+ .findFirst()
+ .orElse(null);
+ }
+
+ /**
* @return all the grid options that can be shown on the device
*/
public List<GridOption> parseAllGridOptions(Context context) {
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 8277c3e..72977ee 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -2365,7 +2365,8 @@
* Similar to {@link #getFirstMatch} but optimized to finding a suitable view for the app close
* animation.
*
- * @param preferredItemId The id of the preferred item to match to if it exists.
+ * @param preferredItemId The id of the preferred item to match to if it exists,
+ * or ItemInfo#NO_MATCHING_ID if you want to not match by item id
* @param packageName The package name of the app to match.
* @param user The user of the app to match.
* @param supportsAllAppsState If true and we are in All Apps state, looks for view in All Apps.
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 34ebaf2..84b8ba1 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -139,6 +139,11 @@
public static final int ITEM_TYPE_SEARCH_ACTION = 9;
/**
+ * Private space install app button.
+ */
+ public static final int ITEM_TYPE_PRIVATE_SPACE_INSTALL_APP_BUTTON = 11;
+
+ /**
* The custom icon bitmap.
* <P>Type: BLOB</P>
*/
@@ -206,6 +211,8 @@
case ITEM_TYPE_TASK: return "TASK";
case ITEM_TYPE_QSB: return "QSB";
case ITEM_TYPE_APP_PAIR: return "APP_PAIR";
+ case ITEM_TYPE_PRIVATE_SPACE_INSTALL_APP_BUTTON:
+ return "PRIVATE_SPACE_INSTALL_APP_BUTTON";
default: return String.valueOf(type);
}
}
diff --git a/src/com/android/launcher3/ModelCallbacks.kt b/src/com/android/launcher3/ModelCallbacks.kt
index 9b65a31..f582be0 100644
--- a/src/com/android/launcher3/ModelCallbacks.kt
+++ b/src/com/android/launcher3/ModelCallbacks.kt
@@ -63,7 +63,8 @@
launcher.dragController.cancelDrag()
launcher.workspace.clearDropTargets()
launcher.workspace.removeAllWorkspaceScreens()
- launcher.appWidgetHolder.clearViews()
+ // Avoid clearing the widget update listeners for staying up-to-date with widget info
+ launcher.appWidgetHolder.clearWidgetViews()
launcher.hotseat?.resetLayout(launcher.deviceProfile.isVerticalBarLayout)
TraceHelper.INSTANCE.endSection()
}
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 01ea9fb..fbeab4e 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -189,6 +189,7 @@
private float mBottomSheetAlpha = 1f;
private boolean mForceBottomSheetVisible;
private int mTabsProtectionAlpha;
+ private float mTotalHeaderProtectionHeight;
@Nullable private AllAppsTransitionController mAllAppsTransitionController;
private PrivateSpaceHeaderViewController mPrivateSpaceHeaderViewController;
@@ -1431,9 +1432,11 @@
mTmpPath.reset();
mTmpPath.addRoundRect(mTmpRectF, mBottomSheetCornerRadii, Direction.CW);
canvas.drawPath(mTmpPath, mHeaderPaint);
+ mTotalHeaderProtectionHeight = headerBottomWithScaleOnTablet;
}
} else {
canvas.drawRect(0, 0, canvas.getWidth(), headerBottomWithScaleOnPhone, mHeaderPaint);
+ mTotalHeaderProtectionHeight = headerBottomWithScaleOnPhone;
}
// If tab exist (such as work profile), extend header with tab height
@@ -1463,10 +1466,19 @@
right,
tabBottomWithScale,
mHeaderPaint);
+ mTotalHeaderProtectionHeight = tabBottomWithScale;
}
}
/**
+ * The height of the header protection is dynamically calculated during the time of drawing the
+ * header.
+ */
+ float getHeaderProtectionHeight() {
+ return mTotalHeaderProtectionHeight;
+ }
+
+ /**
* redraws header protection
*/
public void invalidateHeader() {
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
index ca587c1..5e5795d 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -223,10 +223,9 @@
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case VIEW_TYPE_ICON:
- int layout = (FeatureFlags.enableTwolineAllapps() &&
- (!Flags.enableTwolineToggle() || (Flags.enableTwolineToggle()
- && LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(
- mActivityContext.getApplicationContext()))))
+ int layout = (Flags.enableTwolineToggle()
+ && LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(
+ mActivityContext.getApplicationContext()))
? R.layout.all_apps_icon_twoline : R.layout.all_apps_icon;
BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
layout, parent, false);
diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java
index 1ebd49e..e7bb1d0 100644
--- a/src/com/android/launcher3/allapps/PrivateProfileManager.java
+++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java
@@ -23,15 +23,12 @@
import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_NOTHING;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE;
-import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_PRIVATE_SPACE_INSTALL_APP;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SettingsCache.PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.os.UserHandle;
import android.os.UserManager;
@@ -43,6 +40,7 @@
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.PrivateSpaceInstallAppButtonInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.uioverrides.ApiWrapper;
import com.android.launcher3.util.Preconditions;
@@ -60,10 +58,6 @@
*/
public class PrivateProfileManager extends UserProfileManager {
- // TODO (b/324573634): Fix the intent string.
- public static final Intent PRIVATE_SPACE_INTENT = new
- Intent("com.android.settings.action.PRIVATE_SPACE_SETUP_FLOW");
-
private final ActivityAllAppsContainerView<?> mAllApps;
private final Predicate<UserHandle> mPrivateProfileMatcher;
private Set<String> mPreInstalledSystemPackages = new HashSet<>();
@@ -105,13 +99,13 @@
context, com.android.launcher3.R.drawable.private_space_install_app_icon);
BitmapInfo bitmapInfo = LauncherIcons.obtain(context).createIconBitmap(shortcut);
- AppInfo itemInfo = new AppInfo();
+ PrivateSpaceInstallAppButtonInfo itemInfo = new PrivateSpaceInstallAppButtonInfo();
itemInfo.title = context.getResources().getString(R.string.ps_add_button_label);
itemInfo.intent = mAppInstallerIntent;
itemInfo.bitmap = bitmapInfo;
itemInfo.contentDescription = context.getResources().getString(
com.android.launcher3.R.string.ps_add_button_content_description);
- itemInfo.runtimeStatusFlags |= FLAG_PRIVATE_SPACE_INSTALL_APP | FLAG_NOT_PINNABLE;
+ itemInfo.runtimeStatusFlags |= FLAG_NOT_PINNABLE;
BaseAllAppsAdapter.AdapterItem item = new BaseAllAppsAdapter.AdapterItem(VIEW_TYPE_ICON);
item.itemInfo = itemInfo;
@@ -162,7 +156,8 @@
/** Opens the Private Space Settings Page. */
public void openPrivateSpaceSettings() {
if (mPrivateSpaceSettingsAvailable) {
- mAllApps.getContext().startActivity(PRIVATE_SPACE_INTENT);
+ mAllApps.getContext()
+ .startActivity(ApiWrapper.getPrivateSpaceSettingsIntent(mAllApps.getContext()));
}
}
@@ -194,9 +189,8 @@
private void initializePrivateSpaceSettingsState() {
Preconditions.assertNonUiThread();
- ResolveInfo resolveInfo = mAllApps.getContext().getPackageManager()
- .resolveActivity(PRIVATE_SPACE_INTENT, PackageManager.MATCH_SYSTEM_ONLY);
- setPrivateSpaceSettingsAvailable(resolveInfo != null);
+ Intent psSettingsIntent = ApiWrapper.getPrivateSpaceSettingsIntent(mAllApps.getContext());
+ setPrivateSpaceSettingsAvailable(psSettingsIntent != null);
}
private void setPreInstalledSystemPackages() {
diff --git a/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java b/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java
index 6067454..fdc035e 100644
--- a/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java
+++ b/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java
@@ -41,15 +41,18 @@
import android.widget.RelativeLayout;
import android.widget.TextView;
+import androidx.annotation.VisibleForTesting;
import androidx.recyclerview.widget.LinearSmoothScroller;
import androidx.recyclerview.widget.RecyclerView;
import com.android.app.animation.Interpolators;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Flags;
import com.android.launcher3.R;
import com.android.launcher3.allapps.UserProfileManager.UserProfileState;
import com.android.launcher3.anim.AnimatedPropertySetter;
import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.RecyclerViewFastScroller;
import java.util.List;
@@ -59,7 +62,6 @@
* {@link UserProfileState}
*/
public class PrivateSpaceHeaderViewController {
- private static final int EXPAND_SCROLL_DURATION = 2000;
private static final int EXPAND_COLLAPSE_DURATION = 800;
private static final int SETTINGS_OPACITY_DURATION = 160;
private final ActivityAllAppsContainerView mAllApps;
@@ -174,23 +176,24 @@
&& mAllApps.getActiveRecyclerView() == mainAdapterHolder.mRecyclerView) {
// Animate the text and settings icon.
updatePrivateStateAnimator(true, header);
- mAllApps.getActiveRecyclerView().scrollToBottomWithMotion(EXPAND_SCROLL_DURATION);
+ DeviceProfile deviceProfile =
+ ActivityContext.lookupContext(mAllApps.getContext()).getDeviceProfile();
+ AllAppsRecyclerView allAppsRecyclerView = mAllApps.getActiveRecyclerView();
+ scrollForViewToBeVisibleInContainer(allAppsRecyclerView,
+ allAppsRecyclerView.getApps().getAdapterItems(),
+ header.getHeight(), deviceProfile.allAppsCellHeightPx);
}
}
/** Finds the private space header to scroll to and set the private space icons to GONE. */
private void collapse() {
AllAppsRecyclerView allAppsRecyclerView = mAllApps.getActiveRecyclerView();
- for (int i = allAppsRecyclerView.getChildCount() - 1; i > 0; i--) {
- int adapterPosition = allAppsRecyclerView.getChildAdapterPosition(
- allAppsRecyclerView.getChildAt(i));
- List<BaseAllAppsAdapter.AdapterItem> allAppsAdapters = allAppsRecyclerView.getApps()
- .getAdapterItems();
- if (adapterPosition < 0 || adapterPosition >= allAppsAdapters.size()) {
- continue;
- }
+ List<BaseAllAppsAdapter.AdapterItem> appListAdapterItems =
+ allAppsRecyclerView.getApps().getAdapterItems();
+ for (int i = appListAdapterItems.size() - 1; i > 0; i--) {
+ BaseAllAppsAdapter.AdapterItem currentItem = appListAdapterItems.get(i);
// Scroll to the private space header.
- if (allAppsAdapters.get(adapterPosition).viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER) {
+ if (currentItem.viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER) {
// Note: SmoothScroller is meant to be used once.
RecyclerView.SmoothScroller smoothScroller =
new LinearSmoothScroller(mAllApps.getContext()) {
@@ -198,7 +201,7 @@
return LinearSmoothScroller.SNAP_TO_END;
}
};
- smoothScroller.setTargetPosition(adapterPosition);
+ smoothScroller.setTargetPosition(i);
RecyclerView.LayoutManager layoutManager = allAppsRecyclerView.getLayoutManager();
if (layoutManager != null) {
layoutManager.startSmoothScroll(smoothScroller);
@@ -206,12 +209,67 @@
break;
}
// Make the private space apps gone to "collapse".
- if (allAppsAdapters.get(adapterPosition).decorationInfo != null) {
- allAppsRecyclerView.getChildAt(i).setVisibility(GONE);
+ if (currentItem.decorationInfo != null) {
+ RecyclerView.ViewHolder viewHolder =
+ allAppsRecyclerView.findViewHolderForAdapterPosition(i);
+ if (viewHolder != null) {
+ viewHolder.itemView.setVisibility(GONE);
+ }
}
}
}
+ /**
+ * Upon expanding, only scroll to the item position in the adapter that allows the header to be
+ * visible.
+ */
+ @VisibleForTesting
+ public int scrollForViewToBeVisibleInContainer(
+ AllAppsRecyclerView allAppsRecyclerView,
+ List<BaseAllAppsAdapter.AdapterItem> appListAdapterItems,
+ int psHeaderHeight,
+ int allAppsCellHeight) {
+ int rowToExpandToWithRespectToHeader = -1;
+ int itemToScrollTo = -1;
+ // Looks for the item in the app list to scroll to so that the header is visible.
+ for (int i = 0; i < appListAdapterItems.size(); i++) {
+ BaseAllAppsAdapter.AdapterItem currentItem = appListAdapterItems.get(i);
+ if (currentItem.viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER) {
+ itemToScrollTo = i;
+ continue;
+ }
+ if (itemToScrollTo != -1) {
+ if (rowToExpandToWithRespectToHeader == -1) {
+ rowToExpandToWithRespectToHeader = currentItem.rowIndex;
+ }
+ int rowToScrollTo =
+ (int) Math.floor((double) (mAllApps.getHeight() - psHeaderHeight
+ - mAllApps.getHeaderProtectionHeight()) / allAppsCellHeight);
+ int currentRowDistance = currentItem.rowIndex - rowToExpandToWithRespectToHeader;
+ // rowToScrollTo - 1 since the item to scroll to is 0 indexed.
+ if (currentRowDistance == rowToScrollTo - 1) {
+ itemToScrollTo = i;
+ break;
+ }
+ }
+ }
+ if (itemToScrollTo != -1) {
+ // Note: SmoothScroller is meant to be used once.
+ RecyclerView.SmoothScroller smoothScroller =
+ new LinearSmoothScroller(mAllApps.getContext()) {
+ @Override protected int getVerticalSnapPreference() {
+ return LinearSmoothScroller.SNAP_TO_ANY;
+ }
+ };
+ smoothScroller.setTargetPosition(itemToScrollTo);
+ RecyclerView.LayoutManager layoutManager = allAppsRecyclerView.getLayoutManager();
+ if (layoutManager != null) {
+ layoutManager.startSmoothScroll(smoothScroller);
+ }
+ }
+ return itemToScrollTo;
+ }
+
PrivateProfileManager getPrivateProfileManager() {
return mPrivateProfileManager;
}
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index a0e8571..b28964a 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -165,7 +165,7 @@
// TODO(Block 5): Clean up flags
public static final BooleanFlag ENABLE_TWOLINE_DEVICESEARCH = getDebugFlag(201388851,
- "ENABLE_TWOLINE_DEVICESEARCH", ENABLED,
+ "ENABLE_TWOLINE_DEVICESEARCH", DISABLED,
"Enable two line label for icons with labels on device search.");
public static final BooleanFlag ENABLE_ICON_IN_TEXT_HEADER = getDebugFlag(270395143,
@@ -281,10 +281,7 @@
// Aconfig migration complete for ENABLE_TWOLINE_ALLAPPS.
public static final BooleanFlag ENABLE_TWOLINE_ALLAPPS = getDebugFlag(270390937,
- "ENABLE_TWOLINE_ALLAPPS", ENABLED, "Enables two line label inside all apps.");
- public static boolean enableTwolineAllapps() {
- return ENABLE_TWOLINE_ALLAPPS.get() || Flags.enableTwolineAllapps();
- }
+ "ENABLE_TWOLINE_ALLAPPS", DISABLED, "Enables two line label inside all apps.");
public static final BooleanFlag IME_STICKY_SNACKBAR_EDU = getDebugFlag(270391693,
"IME_STICKY_SNACKBAR_EDU", ENABLED, "Show sticky IME edu in AllApps");
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index ac23868..c8c634a 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -1042,6 +1042,9 @@
public void onDropCompleted(final View target, final DragObject d,
final boolean success) {
if (success) {
+ if (getItemCount() <= 1) {
+ mDeleteFolderOnDropCompleted = true;
+ }
if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon && target != this) {
replaceFolderWithFinalItem();
}
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 8e73660..41e3ef0 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -469,17 +469,22 @@
duplicateIconRequestsMap.get(cn);
if (cn != null) {
- CacheEntry entry = cacheLocked(
- cn,
- /* user = */ sectionKey.first,
- () -> duplicateIconRequests.get(0).launcherActivityInfo,
- mLauncherActivityInfoCachingLogic,
- c,
- /* usePackageIcon= */ false,
- /* useLowResIcons = */ sectionKey.second);
+ if (duplicateIconRequests != null) {
+ CacheEntry entry = cacheLocked(
+ cn,
+ /* user = */ sectionKey.first,
+ () -> duplicateIconRequests.get(0).launcherActivityInfo,
+ mLauncherActivityInfoCachingLogic,
+ c,
+ /* usePackageIcon= */ false,
+ /* useLowResIcons = */ sectionKey.second);
- for (IconRequestInfo<T> iconRequest : duplicateIconRequests) {
- applyCacheEntry(entry, iconRequest.itemInfo);
+ for (IconRequestInfo<T> iconRequest : duplicateIconRequests) {
+ applyCacheEntry(entry, iconRequest.itemInfo);
+ }
+ } else {
+ Log.e(TAG, "Found entry in icon database but no main activity "
+ + "entry for cn: " + cn);
}
}
}
diff --git a/src/com/android/launcher3/model/GridSizeMigrationUtil.java b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
index 30d2cfb..15190c7 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationUtil.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
@@ -40,6 +40,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings;
@@ -122,6 +123,16 @@
if (!needsToMigrate(srcDeviceState, destDeviceState)) {
return true;
}
+
+ if (Flags.enableGridMigrationFix()
+ && srcDeviceState.getColumns().equals(destDeviceState.getColumns())
+ && srcDeviceState.getRows() < destDeviceState.getRows()) {
+ // Only use this strategy when comparing the previous grid to the new grid and the
+ // columns are the same and the destination has more rows
+ copyTable(source, TABLE_NAME, target.getWritableDatabase(), TABLE_NAME, context);
+ destDeviceState.writeToPrefs(context);
+ return true;
+ }
copyTable(source, TABLE_NAME, target.getWritableDatabase(), TMP_TABLE, context);
HashSet<String> validPackages = getValidPackages(context);
diff --git a/src/com/android/launcher3/model/WidgetItem.java b/src/com/android/launcher3/model/WidgetItem.java
index 1dd58c3..3f88717 100644
--- a/src/com/android/launcher3/model/WidgetItem.java
+++ b/src/com/android/launcher3/model/WidgetItem.java
@@ -123,6 +123,7 @@
if (!Flags.enableGeneratedPreviews() || generatedPreviews == null) {
return false;
}
- return generatedPreviews.contains(widgetCategory);
+ return generatedPreviews.contains(widgetCategory)
+ && generatedPreviews.get(widgetCategory) != null;
}
}
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index 55849c2..f7cff78 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -94,6 +94,10 @@
* {@link Favorites#ITEM_TYPE_APP_PAIR},
* {@link Favorites#ITEM_TYPE_APPWIDGET} or
* {@link Favorites#ITEM_TYPE_CUSTOM_APPWIDGET}.
+ * {@link Favorites#ITEM_TYPE_TASK}.
+ * {@link Favorites#ITEM_TYPE_QSB}.
+ * {@link Favorites#ITEM_TYPE_SEARCH_ACTION}.
+ * {@link Favorites#ITEM_TYPE_PRIVATE_SPACE_INSTALL_APP_BUTTON}.
*/
public int itemType;
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index 352c363..9fbc6bf 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -121,11 +121,6 @@
public static final int FLAG_ARCHIVED = 1 << 14;
/**
- * Flag indicating it's the Private Space Install App icon.
- */
- public static final int FLAG_PRIVATE_SPACE_INSTALL_APP = 1 << 15;
-
- /**
* Status associated with the system state of the underlying item. This is calculated every
* time a new info is created and not persisted on the disk.
*/
@@ -160,10 +155,6 @@
* and its install session is active
*/
public boolean isPendingDownload() {
- if (isArchived()) {
- return this.getProgressLevel() == 0
- && (this.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0;
- }
return getProgressLevel() == 0;
}
@@ -177,6 +168,11 @@
return (runtimeStatusFlags & FLAG_ARCHIVED) != 0;
}
+ /** Returns true if the app is archived and has an active install session. */
+ public boolean isActiveArchive() {
+ return isArchived() && (runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0;
+ }
+
/**
* Indicates whether we're using a low res icon
*/
diff --git a/src/com/android/launcher3/model/data/PrivateSpaceInstallAppButtonInfo.java b/src/com/android/launcher3/model/data/PrivateSpaceInstallAppButtonInfo.java
new file mode 100644
index 0000000..1e7281d
--- /dev/null
+++ b/src/com/android/launcher3/model/data/PrivateSpaceInstallAppButtonInfo.java
@@ -0,0 +1,29 @@
+/*
+ * 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.model.data;
+
+import com.android.launcher3.LauncherSettings;
+
+/**
+ * Represents the Private Space Install App button in AllAppsView.
+ */
+public class PrivateSpaceInstallAppButtonInfo extends AppInfo {
+
+ public PrivateSpaceInstallAppButtonInfo() {
+ itemType = LauncherSettings.Favorites.ITEM_TYPE_PRIVATE_SPACE_INSTALL_APP_BUTTON;
+ }
+}
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index 77effca..e44ea1d 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -153,7 +153,7 @@
mPaginationPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaginationPaint.setStyle(Style.FILL);
- mPaginationPaint.setColor(Themes.getAttrColor(context, R.attr.folderPaginationColor));
+ mPaginationPaint.setColor(Themes.getAttrColor(context, R.attr.pageIndicatorDotColor));
mDotRadius = getResources().getDimension(R.dimen.page_indicator_dot_size) / 2;
mCircleGap = DOT_GAP_FACTOR * mDotRadius;
setOutlineProvider(new MyOutlineProver());
diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java
index 1f15947..b992a92 100644
--- a/src/com/android/launcher3/provider/LauncherDbUtils.java
+++ b/src/com/android/launcher3/provider/LauncherDbUtils.java
@@ -152,7 +152,12 @@
}
ShortcutInfo info = infoBuilder.build();
- if (!PinRequestHelper.createRequestForShortcut(context, info).accept()) {
+ try {
+ if (!PinRequestHelper.createRequestForShortcut(context, info).accept()) {
+ deletedShortcuts.add(lc.id);
+ continue;
+ }
+ } catch (Exception e) {
deletedShortcuts.add(lc.id);
continue;
}
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 22bc13b..99542f3 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -50,8 +50,10 @@
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
+import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherFiles;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
@@ -121,7 +123,48 @@
// executed again.
LauncherPrefs.get(context).removeSync(RESTORE_DEVICE);
- idp.reinitializeAfterRestore(context);
+ if (Flags.enableNarrowGridRestore()) {
+ String oldPhoneFileName = idp.dbFile;
+ removeOldDBs(context, oldPhoneFileName);
+ trySettingPreviousGidAsCurrent(context, idp, oldPhoneFileName);
+ } else {
+ idp.reinitializeAfterRestore(context);
+ }
+ }
+
+ /**
+ * Try setting the gird used in the previous phone to the new one. If the current device doesn't
+ * support the previous grid option it will not be set.
+ */
+ private static void trySettingPreviousGidAsCurrent(Context context, InvariantDeviceProfile idp,
+ String oldPhoneDbFileName) {
+ InvariantDeviceProfile.GridOption gridOption = idp.getGridOptionFromFileName(context,
+ oldPhoneDbFileName);
+ if (gridOption != null) {
+ /*
+ * We do this because in some cases different devices have different names for grid
+ * options, in one device the grid option "normal" can be 4x4 while in other it
+ * could be "practical". Calling this changes the current device grid to the same
+ * we had in the other phone, in the case the current phone doesn't support the grid
+ * option we use the default and migrate the db to the default. Migration occurs on
+ * {@code GridSizeMigrationUtil#migrateGridIfNeeded}
+ */
+ idp.setCurrentGrid(context, gridOption.name);
+ }
+ }
+
+ /**
+ * Only keep the last database used on the previous device.
+ */
+ private static void removeOldDBs(Context context, String oldPhoneDbFileName) {
+ // At this point idp.dbFile contains the name of the dbFile from the previous phone
+ LauncherFiles.GRID_DB_FILES.stream()
+ .filter(dbName -> !dbName.equals(oldPhoneDbFileName))
+ .forEach(dbName -> {
+ if (context.getDatabasePath(dbName).delete()) {
+ FileLog.d(TAG, "Removed old grid db file: " + dbName);
+ }
+ });
}
private static boolean performRestore(Context context, ModelDbController controller) {
diff --git a/src/com/android/launcher3/states/EditModeState.kt b/src/com/android/launcher3/states/EditModeState.kt
index aafaaa0..6ff47ae 100644
--- a/src/com/android/launcher3/states/EditModeState.kt
+++ b/src/com/android/launcher3/states/EditModeState.kt
@@ -16,6 +16,7 @@
package com.android.launcher3.states
import android.content.Context
+import com.android.launcher3.Flags.enableScalingRevealHomeAnimation
import com.android.launcher3.Launcher
import com.android.launcher3.LauncherState
import com.android.launcher3.logging.StatsLogManager
@@ -25,6 +26,8 @@
class EditModeState(id: Int) : LauncherState(id, StatsLogManager.LAUNCHER_STATE_HOME, STATE_FLAGS) {
companion object {
+ const val DEPTH_15_PERCENT = 0.15f
+
private val STATE_FLAGS =
(FLAG_MULTI_PAGE or
FLAG_WORKSPACE_INACCESSIBLE or
@@ -40,7 +43,11 @@
}
override fun <T> getDepthUnchecked(context: T): Float where T : Context?, T : ActivityContext? {
- return 0.5f
+ if (enableScalingRevealHomeAnimation()) {
+ return DEPTH_15_PERCENT
+ } else {
+ return 0.5f
+ }
}
override fun getWorkspaceScaleAndTranslation(launcher: Launcher): ScaleAndTranslation {
diff --git a/src/com/android/launcher3/states/HintState.java b/src/com/android/launcher3/states/HintState.java
index 4cfced8..bf2fb30 100644
--- a/src/com/android/launcher3/states/HintState.java
+++ b/src/com/android/launcher3/states/HintState.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.states;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
import android.content.Context;
@@ -34,6 +35,8 @@
private static final int STATE_FLAGS = FLAG_WORKSPACE_INACCESSIBLE | FLAG_DISABLE_RESTORE
| FLAG_HAS_SYS_UI_SCRIM;
+ public static final float DEPTH_5_PERCENT = 0.05f;
+
public HintState(int id) {
this(id, LAUNCHER_STATE_HOME);
}
@@ -49,7 +52,11 @@
@Override
protected float getDepthUnchecked(Context context) {
- return 0.15f;
+ if (enableScalingRevealHomeAnimation()) {
+ return DEPTH_5_PERCENT;
+ } else {
+ return 0.15f;
+ }
}
@Override
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 3286afb..2e57ed8 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.states;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
import android.content.Context;
@@ -33,6 +34,8 @@
| FLAG_WORKSPACE_INACCESSIBLE | FLAG_DISABLE_RESTORE
| FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED | FLAG_WORKSPACE_HAS_BACKGROUNDS;
+ public static final float DEPTH_15_PERCENT = 0.15f;
+
public SpringLoadedState(int id) {
super(id, LAUNCHER_STATE_HOME, STATE_FLAGS);
}
@@ -62,7 +65,11 @@
@Override
protected float getDepthUnchecked(Context context) {
- return 0.5f;
+ if (enableScalingRevealHomeAnimation()) {
+ return DEPTH_15_PERCENT;
+ } else {
+ return 0.5f;
+ }
}
@Override
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 111931e..911568c 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -369,8 +369,8 @@
intent = ApiWrapper.getAppMarketActivityIntent(launcher,
itemInfoWithIcon.getTargetComponent().getPackageName(),
Process.myUserHandle());
- } else if ((itemInfoWithIcon.runtimeStatusFlags
- & ItemInfoWithIcon.FLAG_PRIVATE_SPACE_INSTALL_APP) != 0) {
+ } else if (itemInfoWithIcon.itemType
+ == LauncherSettings.Favorites.ITEM_TYPE_PRIVATE_SPACE_INSTALL_APP_BUTTON) {
intent = ApiWrapper.getAppMarketActivityIntent(launcher,
BuildConfig.APPLICATION_ID,
launcher.getAppsView().getPrivateProfileManager().getProfileUser());
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index 30e0971..ddc3cbb 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -296,17 +296,13 @@
float scaleProgress = mSlideInViewScale.value;
SCALE_PROPERTY.set(this, scaleProgress);
setClipChildren(!mIsBackProgressing);
+ setClipToPadding(!mIsBackProgressing);
mContent.setClipChildren(!mIsBackProgressing);
+ mContent.setClipToPadding(!mIsBackProgressing);
invalidate();
}
@Override
- public void onBackInvoked() {
- super.onBackInvoked();
- animateSlideInViewToNoScale();
- }
-
- @Override
public void onBackCancelled() {
super.onBackCancelled();
animateSlideInViewToNoScale();
diff --git a/src/com/android/launcher3/views/FloatingSurfaceView.java b/src/com/android/launcher3/views/FloatingSurfaceView.java
index c60e1a4..cab7982 100644
--- a/src/com/android/launcher3/views/FloatingSurfaceView.java
+++ b/src/com/android/launcher3/views/FloatingSurfaceView.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.views;
+import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
import static com.android.launcher3.views.FloatingIconView.getLocationBoundsForView;
import static com.android.launcher3.views.IconLabelDotView.setIconAndDotVisible;
@@ -159,7 +160,7 @@
if (mContract == null) {
return;
}
- View icon = mLauncher.getFirstMatchForAppClose(-1,
+ View icon = mLauncher.getFirstMatchForAppClose(NO_MATCHING_ID,
mContract.componentName.getPackageName(), mContract.user,
false /* supportsAllAppsState */);
diff --git a/src/com/android/launcher3/views/WidgetsEduView.java b/src/com/android/launcher3/views/WidgetsEduView.java
index 40c6115..45ff9de 100644
--- a/src/com/android/launcher3/views/WidgetsEduView.java
+++ b/src/com/android/launcher3/views/WidgetsEduView.java
@@ -59,6 +59,7 @@
mContent = findViewById(R.id.edu_view);
findViewById(R.id.edu_close_button)
.setOnClickListener(v -> close(/* animate= */ true));
+ setContentBackgroundWithParent(mContent.getBackground(), mContent);
}
@Override
@@ -68,6 +69,12 @@
mContent.getPaddingTop(), mContent.getPaddingEnd(), insets.bottom);
}
+ @Override
+ protected void onScaleProgressChanged() {
+ super.onScaleProgressChanged();
+ setTranslationY(getMeasuredHeight() * (1 - mSlideInViewScale.value) / 2);
+ }
+
private void show() {
attachToContainer();
animateOpen();
diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
index 15bd6ed..2fcf8c5 100644
--- a/src/com/android/launcher3/widget/LauncherWidgetHolder.java
+++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
@@ -103,7 +103,7 @@
if (WidgetsModel.GO_DISABLE_WIDGETS) {
return;
}
- setListeningFlag(true);
+
try {
mWidgetHost.startListening();
} catch (Exception e) {
@@ -115,6 +115,8 @@
// have been established by this point, and we will end up populating the
// widgets upon bind anyway. See issue 14255011 for more context.
}
+ // TODO: Investigate why widgetHost.startListening() always return non-empty updates
+ setListeningFlag(true);
updateDeferredView();
}
@@ -442,6 +444,13 @@
}
/**
+ * Clears all the internal widget views
+ */
+ public void clearWidgetViews() {
+ clearViews();
+ }
+
+ /**
* @return True if the host is listening to the updates, false otherwise
*/
public boolean isListening() {
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index f2f83c8..aaefe60 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -57,6 +57,8 @@
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.util.CancellableTask;
import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.widget.picker.util.WidgetPreviewContainerSize;
+import com.android.launcher3.widget.util.WidgetSizes;
import java.util.function.Consumer;
@@ -80,7 +82,7 @@
* The requested scale of the preview container. It can be lower than this as well.
*/
private float mPreviewContainerScale = 1f;
-
+ private Size mPreviewContainerSize = new Size(0, 0);
private FrameLayout mWidgetImageContainer;
private WidgetImageView mWidgetImage;
private ImageView mWidgetBadge;
@@ -176,6 +178,8 @@
mWidgetDims.setText(null);
mWidgetDescription.setText(null);
mWidgetDescription.setVisibility(GONE);
+ showDescription(true);
+ showDimensions(true);
if (mActiveRequest != null) {
mActiveRequest.cancel();
@@ -186,6 +190,7 @@
mWidgetImageContainer.removeView(mAppWidgetHostViewPreview);
}
mAppWidgetHostViewPreview = null;
+ mPreviewContainerSize = new Size(0, 0);
mAppWidgetHostViewScale = 1f;
mPreviewContainerScale = 1f;
mItem = null;
@@ -201,30 +206,21 @@
* Applies the item to this view
*/
public void applyFromCellItem(WidgetItem item) {
- applyFromCellItem(item, 1f);
- }
-
- /**
- * Applies the item to this view
- */
- public void applyFromCellItem(WidgetItem item, float previewScale) {
- applyFromCellItem(item, previewScale, this::applyPreview, null);
+ applyFromCellItem(item, this::applyPreview, /*cachedPreview=*/null);
}
/**
* Applies the item to this view
* @param item item to apply
- * @param previewScale factor to scale the preview
* @param callback callback when preview is loaded in case the preview is being loaded or cached
* @param cachedPreview previously cached preview bitmap is present
*/
- public void applyFromCellItem(WidgetItem item, float previewScale,
- @NonNull Consumer<Bitmap> callback, @Nullable Bitmap cachedPreview) {
- mPreviewContainerScale = previewScale;
-
+ public void applyFromCellItem(WidgetItem item, @NonNull Consumer<Bitmap> callback,
+ @Nullable Bitmap cachedPreview) {
Context context = getContext();
mItem = item;
mWidgetSize = getWidgetItemSizePx(getContext(), mActivity.getDeviceProfile(), mItem);
+ initPreviewContainerSizeAndScale();
mWidgetName.setText(mItem.label);
mWidgetName.setContentDescription(
@@ -278,6 +274,17 @@
}
}
+ private void initPreviewContainerSizeAndScale() {
+ WidgetPreviewContainerSize previewSize = WidgetPreviewContainerSize.Companion.forItem(mItem,
+ mActivity.getDeviceProfile());
+ mPreviewContainerSize = WidgetSizes.getWidgetSizePx(mActivity.getDeviceProfile(),
+ previewSize.spanX, previewSize.spanY);
+
+ float scaleX = (float) mPreviewContainerSize.getWidth() / mWidgetSize.getWidth();
+ float scaleY = (float) mPreviewContainerSize.getHeight() / mWidgetSize.getHeight();
+ mPreviewContainerScale = Math.min(scaleX, scaleY);
+ }
+
private void setAppWidgetHostViewPreview(
NavigableAppWidgetHostView appWidgetHostViewPreview,
LauncherAppWidgetProviderInfo providerInfo,
@@ -384,6 +391,16 @@
}
/**
+ * Shows or hides the dimensions displayed below each widget.
+ *
+ * @param show a flag that shows the dimensions of the widget if {@code true}, hides it if
+ * {@code false}.
+ */
+ public void showDimensions(boolean show) {
+ mWidgetDims.setVisibility(show ? VISIBLE : GONE);
+ }
+
+ /**
* Set whether the app icon, for the app that provides the widget, should be shown next to the
* title text of the widget.
*
@@ -448,17 +465,22 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
ViewGroup.LayoutParams containerLp = mWidgetImageContainer.getLayoutParams();
-
- mAppWidgetHostViewScale = mPreviewContainerScale;
int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
- containerLp.width = Math.round(mWidgetSize.getWidth() * mAppWidgetHostViewScale);
+
+ // mPreviewContainerScale ensures the needed scaling with respect to original widget size.
+ mAppWidgetHostViewScale = mPreviewContainerScale;
+ containerLp.width = mPreviewContainerSize.getWidth();
+ containerLp.height = mPreviewContainerSize.getHeight();
+
+ // If we don't have enough available width, scale the preview container to fit.
if (containerLp.width > maxWidth) {
containerLp.width = maxWidth;
- mAppWidgetHostViewScale = (float) containerLp.width / mWidgetSize.getWidth();
+ mAppWidgetHostViewScale = (float) containerLp.width / mPreviewContainerSize.getWidth();
+ containerLp.height = Math.round(
+ mPreviewContainerSize.getHeight() * mAppWidgetHostViewScale);
}
- containerLp.height = Math.round(mWidgetSize.getHeight() * mAppWidgetHostViewScale);
- // No need to call mWidgetImageContainer.setLayoutParams as we are in measure pass
+ // No need to call mWidgetImageContainer.setLayoutParams as we are in measure pass
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
diff --git a/src/com/android/launcher3/widget/WidgetImageView.java b/src/com/android/launcher3/widget/WidgetImageView.java
index 11f4485..f0a23be 100644
--- a/src/com/android/launcher3/widget/WidgetImageView.java
+++ b/src/com/android/launcher3/widget/WidgetImageView.java
@@ -82,15 +82,27 @@
private void updateDstRectF() {
float myWidth = getWidth();
float myHeight = getHeight();
- float bitmapWidth = mDrawable.getIntrinsicWidth();
+ final float bitmapWidth = mDrawable.getIntrinsicWidth();
+ final float bitmapHeight = mDrawable.getIntrinsicHeight();
+ final float bitmapAspectRatio = bitmapWidth / bitmapHeight;
+ final float containerAspectRatio = myWidth / myHeight;
- final float scale = bitmapWidth > myWidth ? myWidth / bitmapWidth : 1;
- float scaledWidth = bitmapWidth * scale;
- float scaledHeight = mDrawable.getIntrinsicHeight() * scale;
+ // Scale by width if image has larger aspect ratio than the container else by height; and
+ // avoid cropping the previews
+ final float scale = bitmapAspectRatio > containerAspectRatio ? myWidth / bitmapWidth
+ : myHeight / bitmapHeight;
- mDstRectF.left = (myWidth - scaledWidth) / 2;
- mDstRectF.right = (myWidth + scaledWidth) / 2;
+ final float scaledWidth = bitmapWidth * scale;
+ final float scaledHeight = bitmapHeight * scale;
+ // Avoid cropping by checking bounds after scaling.
+ if (scaledWidth > myWidth) {
+ mDstRectF.left = 0;
+ mDstRectF.right = scaledWidth;
+ } else {
+ mDstRectF.left = (myWidth - scaledWidth) / 2;
+ mDstRectF.right = (myWidth + scaledWidth) / 2;
+ }
if (scaledHeight > myHeight) {
mDstRectF.top = 0;
mDstRectF.bottom = scaledHeight;
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index ceb0072..89f8181 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -16,7 +16,6 @@
package com.android.launcher3.widget;
-import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_BOTTOM_WIDGETS_TRAY;
import android.content.Context;
@@ -119,6 +118,9 @@
mContent = findViewById(R.id.widgets_bottom_sheet);
setContentBackgroundWithParent(
getContext().getDrawable(R.drawable.bg_rounded_corner_bottom_sheet), mContent);
+ View scrollView = findViewById(R.id.widgets_table_scroll_view);
+ scrollView.setOutlineProvider(mViewOutlineProvider);
+ scrollView.setClipToOutline(true);
}
@Override
@@ -188,13 +190,9 @@
mWidgetCellHorizontalPadding)
.forEach(row -> {
TableRow tableRow = new TableRow(getContext());
- if (enableCategorizedWidgetSuggestions()) {
- // Vertically center align items, so that even if they don't fill bounds,
- // they can look organized when placed together in a row.
- tableRow.setGravity(Gravity.CENTER_VERTICAL);
- } else {
- tableRow.setGravity(Gravity.TOP);
- }
+ // Vertically center align items, so that even if they don't fill bounds,
+ // they can look organized when placed together in a row.
+ tableRow.setGravity(Gravity.CENTER_VERTICAL);
row.forEach(widgetItem -> {
WidgetCell widget = addItemCell(tableRow);
widget.applyFromCellItem(widgetItem);
diff --git a/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java b/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java
index 68f18ae..0d775c3 100644
--- a/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java
@@ -36,41 +36,49 @@
(context, entry) -> entry.mWidgets.stream()
.map(item -> item.label).sorted().collect(Collectors.joining(", "));
- private static final BiFunction<Context, WidgetsListHeaderEntry, String> SUBTITLE_DEFAULT =
- (context, entry) -> {
- List<WidgetItem> items = entry.mWidgets;
- int wc = (int) items.stream().filter(item -> item.widgetInfo != null).count();
- int sc = Math.max(0, items.size() - wc);
+ @Nullable
+ private static String buildWidgetsCountString(Context context, int wc, int sc) {
+ Resources resources = context.getResources();
+ if (wc == 0 && sc == 0) {
+ return null;
+ }
- Resources resources = context.getResources();
- if (wc == 0 && sc == 0) {
- return null;
- }
-
- String subtitle;
- if (wc > 0 && sc > 0) {
- String widgetsCount = PluralMessageFormat.getIcuPluralString(context,
- R.string.widgets_count, wc);
- String shortcutsCount = PluralMessageFormat.getIcuPluralString(context,
- R.string.shortcuts_count, sc);
- subtitle = resources.getString(R.string.widgets_and_shortcuts_count,
- widgetsCount, shortcutsCount);
- } else if (wc > 0) {
- subtitle = PluralMessageFormat.getIcuPluralString(context,
- R.string.widgets_count, wc);
- } else {
- subtitle = PluralMessageFormat.getIcuPluralString(context,
- R.string.shortcuts_count, sc);
- }
- return subtitle;
- };
+ String subtitle;
+ if (wc > 0 && sc > 0) {
+ String widgetsCount = PluralMessageFormat.getIcuPluralString(context,
+ R.string.widgets_count, wc);
+ String shortcutsCount = PluralMessageFormat.getIcuPluralString(context,
+ R.string.shortcuts_count, sc);
+ subtitle = resources.getString(R.string.widgets_and_shortcuts_count,
+ widgetsCount, shortcutsCount);
+ } else if (wc > 0) {
+ subtitle = PluralMessageFormat.getIcuPluralString(context,
+ R.string.widgets_count, wc);
+ } else {
+ subtitle = PluralMessageFormat.getIcuPluralString(context,
+ R.string.shortcuts_count, sc);
+ }
+ return subtitle;
+ }
private final boolean mIsWidgetListShown;
+ /** Selected widgets displayed */
+ private final int mVisibleWidgetsCount;
private final boolean mIsSearchEntry;
private WidgetsListHeaderEntry(PackageItemInfo pkgItem, String titleSectionName,
+ List<WidgetItem> items, int visibleWidgetsCount,
+ boolean isSearchEntry, boolean isWidgetListShown) {
+ super(pkgItem, titleSectionName, items);
+ mVisibleWidgetsCount = visibleWidgetsCount;
+ mIsSearchEntry = isSearchEntry;
+ mIsWidgetListShown = isWidgetListShown;
+ }
+
+ private WidgetsListHeaderEntry(PackageItemInfo pkgItem, String titleSectionName,
List<WidgetItem> items, boolean isSearchEntry, boolean isWidgetListShown) {
super(pkgItem, titleSectionName, items);
+ mVisibleWidgetsCount = (int) items.stream().filter(w -> w.widgetInfo != null).count();
mIsSearchEntry = isSearchEntry;
mIsWidgetListShown = isWidgetListShown;
}
@@ -91,8 +99,13 @@
@Nullable
public String getSubtitle(Context context) {
- return mIsSearchEntry
- ? SUBTITLE_SEARCH.apply(context, this) : SUBTITLE_DEFAULT.apply(context, this);
+ if (mIsSearchEntry) {
+ return SUBTITLE_SEARCH.apply(context, this);
+ } else {
+ int shortcutsCount = Math.max(0,
+ (int) mWidgets.stream().filter(WidgetItem::isShortcut).count());
+ return buildWidgetsCountString(context, mVisibleWidgetsCount, shortcutsCount);
+ }
}
@Override
@@ -102,6 +115,7 @@
return mWidgets.equals(otherEntry.mWidgets) && mPkgItem.equals(otherEntry.mPkgItem)
&& mTitleSectionName.equals(otherEntry.mTitleSectionName)
&& mIsWidgetListShown == otherEntry.mIsWidgetListShown
+ && mVisibleWidgetsCount == otherEntry.mVisibleWidgetsCount
&& mIsSearchEntry == otherEntry.mIsSearchEntry;
}
@@ -112,6 +126,7 @@
mPkgItem,
mTitleSectionName,
mWidgets,
+ mVisibleWidgetsCount,
mIsSearchEntry,
/* isWidgetListShown= */ true);
}
@@ -122,7 +137,28 @@
pkgItem,
titleSectionName,
items,
- /* forSearch */ false,
+ /* isSearchEntry= */ false,
+ /* isWidgetListShown= */ false);
+ }
+
+ /**
+ * Creates a widget list holder for an header ("app" / "suggestions") which has widgets or/and
+ * shortcuts.
+ *
+ * @param pkgItem package item info for the header section
+ * @param titleSectionName title string for the header
+ * @param items all items for the given header
+ * @param visibleWidgetsCount widgets count when only selected widgets are shown due to
+ * limited space.
+ */
+ public static WidgetsListHeaderEntry create(PackageItemInfo pkgItem, String titleSectionName,
+ List<WidgetItem> items, int visibleWidgetsCount) {
+ return new WidgetsListHeaderEntry(
+ pkgItem,
+ titleSectionName,
+ items,
+ visibleWidgetsCount,
+ /* isSearchEntry= */ false,
/* isWidgetListShown= */ false);
}
@@ -132,7 +168,7 @@
pkgItem,
titleSectionName,
items,
- /* forSearch */ true,
+ /* isSearchEntry */ true,
/* isWidgetListShown= */ false);
}
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProvider.java b/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProvider.java
index 801b1f6..c3906a6 100644
--- a/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProvider.java
+++ b/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProvider.java
@@ -18,13 +18,13 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.util.Log;
+import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.android.launcher3.R;
import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.ResourceBasedOverride;
@@ -54,6 +54,7 @@
* to display the recommendation grouped by categories.
*/
@WorkerThread
+ @Nullable
public WidgetRecommendationCategory getWidgetRecommendationCategory(Context context,
WidgetItem item) {
// This is a default implementation that uses application category to derive the category to
@@ -61,17 +62,16 @@
// via the overridden WidgetRecommendationCategoryProvider resource.
Preconditions.assertWorkerThread();
- PackageManager pm = context.getPackageManager();
+ PackageManagerHelper pmHelper = new PackageManagerHelper(context);
if (item.widgetInfo != null && item.widgetInfo.getComponent() != null) {
String widgetComponentName = item.widgetInfo.getComponent().getClassName();
- try {
- int predictionCategory = pm.getApplicationInfo(
- item.widgetInfo.getComponent().getPackageName(), 0 /* flags */).category;
+ ApplicationInfo applicationInfo = pmHelper.getApplicationInfo(
+ item.widgetInfo.getComponent().getPackageName(), item.widgetInfo.getUser(),
+ 0 /* flags */);
+ if (applicationInfo != null) {
+ int predictionCategory = applicationInfo.category;
return getCategoryFromApplicationCategory(context, predictionCategory,
widgetComponentName);
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Failed to retrieve application category when determining the "
- + "widget category for " + widgetComponentName, e);
}
}
return null;
@@ -112,17 +112,22 @@
R.string.news_widget_recommendation_category_label, /*order=*/1);
}
- if (applicationCategory == ApplicationInfo.CATEGORY_SOCIAL
- || applicationCategory == ApplicationInfo.CATEGORY_AUDIO
+ if (applicationCategory == ApplicationInfo.CATEGORY_SOCIAL) {
+ return new WidgetRecommendationCategory(
+ R.string.social_widget_recommendation_category_label,
+ /*order=*/5);
+ }
+
+ if (applicationCategory == ApplicationInfo.CATEGORY_AUDIO
|| applicationCategory == ApplicationInfo.CATEGORY_VIDEO
|| applicationCategory == ApplicationInfo.CATEGORY_IMAGE) {
return new WidgetRecommendationCategory(
- R.string.social_and_entertainment_widget_recommendation_category_label,
- /*order=*/4);
+ R.string.entertainment_widget_recommendation_category_label,
+ /*order=*/6);
}
return new WidgetRecommendationCategory(
- R.string.others_widget_recommendation_category_label, /*order=*/5);
+ R.string.others_widget_recommendation_category_label, /*order=*/4);
}
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
index 426a3ae..811759d 100644
--- a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
@@ -93,18 +93,19 @@
* @param availableWidth width in px that the recommendations should display in
* @param cellPadding padding in px that should be applied to each widget in the
* recommendations
- * @return {@code false} if no recommendations could fit in the available space.
+ * @return number of recommendations that could fit in the available space.
*/
- public boolean setRecommendations(
+ public int setRecommendations(
List<WidgetItem> recommendedWidgets, DeviceProfile deviceProfile,
final @Px float availableHeight, final @Px int availableWidth,
final @Px int cellPadding) {
this.mAvailableHeight = availableHeight;
- removeAllViews();
+ clear();
- maybeDisplayInTable(recommendedWidgets, deviceProfile, availableWidth, cellPadding);
+ int displayedWidgets = maybeDisplayInTable(recommendedWidgets, deviceProfile,
+ availableWidth, cellPadding);
updateTitleAndIndicator();
- return getChildCount() > 0;
+ return displayedWidgets;
}
/**
@@ -118,9 +119,9 @@
* @param availableWidth width in px that the recommendations should display in
* @param cellPadding padding in px that should be applied to each widget in the
* recommendations
- * @return {@code false} if no recommendations could fit in the available space.
+ * @return number of recommendations that could fit in the available space.
*/
- public boolean setRecommendations(
+ public int setRecommendations(
Map<WidgetRecommendationCategory, List<WidgetItem>> recommendations,
DeviceProfile deviceProfile,
final @Px float availableHeight, final @Px int availableWidth,
@@ -128,19 +129,23 @@
this.mAvailableHeight = availableHeight;
Context context = getContext();
mPageIndicator.setPauseScroll(true, deviceProfile.isTwoPanels);
- removeAllViews();
+ clear();
int displayedCategories = 0;
+ int totalDisplayedWidgets = 0;
// Render top MAX_CATEGORIES in separate tables. Each table becomes a page.
for (Map.Entry<WidgetRecommendationCategory, List<WidgetItem>> entry :
new TreeMap<>(recommendations).entrySet()) {
// If none of the recommendations for the category could fit in the mAvailableHeight, we
// don't want to add that category; and we look for the next one.
- if (maybeDisplayInTable(entry.getValue(), deviceProfile, availableWidth, cellPadding)) {
+ int displayedCount = maybeDisplayInTable(entry.getValue(), deviceProfile,
+ availableWidth, cellPadding);
+ if (displayedCount > 0) {
mCategoryTitles.add(
context.getResources().getString(entry.getKey().categoryTitleRes));
displayedCategories++;
+ totalDisplayedWidgets += displayedCount;
}
if (displayedCategories == MAX_CATEGORIES) {
@@ -150,7 +155,12 @@
updateTitleAndIndicator();
mPageIndicator.setPauseScroll(false, deviceProfile.isTwoPanels);
- return getChildCount() > 0;
+ return totalDisplayedWidgets;
+ }
+
+ private void clear() {
+ mCategoryTitles.clear();
+ removeAllViews();
}
/** Displays the page title and paging indicator if there are multiple pages. */
@@ -199,21 +209,8 @@
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
- if (mAvailableHeight == Float.MAX_VALUE) {
- // When we are not limited by height, use currentPage's height. This is the case
- // when the paged layout is placed in a scrollable container. We cannot use
- // height
- // of tallest child in such case, as it will display a scrollbar even for
- // smaller
- // pages that don't have more content.
- if (i == mCurrentPage) {
- int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
- desiredHeight = Math.max(parentHeight, child.getMeasuredHeight());
- }
- } else {
- // Use height of tallest child when we are limited to a certain height.
- desiredHeight = Math.max(desiredHeight, child.getMeasuredHeight());
- }
+ // Use height of tallest child as we have limited height.
+ desiredHeight = Math.max(desiredHeight, child.getMeasuredHeight());
}
int finalHeight = resolveSizeAndState(desiredHeight, heightMeasureSpec, 0);
@@ -228,12 +225,14 @@
* fits.
* <p>Returns false if none of the recommendations could fit.</p>
*/
- private boolean maybeDisplayInTable(List<WidgetItem> recommendedWidgets,
+ private int maybeDisplayInTable(List<WidgetItem> recommendedWidgets,
DeviceProfile deviceProfile,
final @Px int availableWidth, final @Px int cellPadding) {
Context context = getContext();
LayoutInflater inflater = LayoutInflater.from(context);
+ // Since we are limited by space, we don't sort recommendations - to show most relevant
+ // (if possible).
List<ArrayList<WidgetItem>> rows = groupWidgetItemsUsingRowPxWithoutReordering(
recommendedWidgets,
context,
@@ -249,13 +248,13 @@
recommendationsTable.setWidgetCellOnClickListener(mWidgetCellOnClickListener);
recommendationsTable.setWidgetCellLongClickListener(mWidgetCellOnLongClickListener);
- boolean displayedAtLeastOne = recommendationsTable.setRecommendedWidgets(rows,
+ int displayedCount = recommendationsTable.setRecommendedWidgets(rows,
deviceProfile, mAvailableHeight);
- if (displayedAtLeastOne) {
+ if (displayedCount > 0) {
addView(recommendationsTable);
}
- return displayedAtLeastOne;
+ return displayedCount;
}
/** Returns location of a widget cell for displaying the "touch and hold" education tip. */
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index c0f1070..848f6fa 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -107,7 +107,8 @@
entry -> mCurrentUser.equals(entry.mPkgItem.user);
private final Predicate<WidgetsListBaseEntry> mWorkWidgetsFilter;
protected final boolean mHasWorkProfile;
- protected boolean mHasRecommendedWidgets;
+ // Number of recommendations displayed
+ protected int mRecommendedWidgetsCount;
protected final SparseArray<AdapterHolder> mAdapters = new SparseArray();
@Nullable private ArrowTipView mLatestEducationalTip;
private final OnLayoutChangeListener mLayoutChangeListenerToShowTips =
@@ -581,7 +582,7 @@
}
if (enableCategorizedWidgetSuggestions()) {
- mHasRecommendedWidgets = mWidgetRecommendationsView.setRecommendations(
+ mRecommendedWidgetsCount = mWidgetRecommendationsView.setRecommendations(
mActivityContext.getPopupDataProvider().getCategorizedRecommendedWidgets(),
mDeviceProfile,
/* availableHeight= */ getMaxAvailableHeightForRecommendations(),
@@ -589,7 +590,7 @@
/* cellPadding= */ mWidgetCellHorizontalPadding
);
} else {
- mHasRecommendedWidgets = mWidgetRecommendationsView.setRecommendations(
+ mRecommendedWidgetsCount = mWidgetRecommendationsView.setRecommendations(
mActivityContext.getPopupDataProvider().getRecommendedWidgets(),
mDeviceProfile,
/* availableHeight= */ getMaxAvailableHeightForRecommendations(),
@@ -597,7 +598,8 @@
/* cellPadding= */ mWidgetCellHorizontalPadding
);
}
- mWidgetRecommendationsContainer.setVisibility(mHasRecommendedWidgets ? VISIBLE : GONE);
+ mWidgetRecommendationsContainer.setVisibility(
+ mRecommendedWidgetsCount > 0 ? VISIBLE : GONE);
}
@Px
@@ -790,7 +792,7 @@
}
/** private the height, in pixel, + the vertical margins of a given view. */
- private static int measureHeightWithVerticalMargins(View view) {
+ protected static int measureHeightWithVerticalMargins(View view) {
if (view.getVisibility() != VISIBLE) {
return 0;
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
index ef3ccf0..36f8bf9 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
@@ -15,8 +15,6 @@
*/
package com.android.launcher3.widget.picker;
-import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
-
import android.content.Context;
import android.graphics.Bitmap;
import android.util.Log;
@@ -121,7 +119,7 @@
widget.setVisibility(View.VISIBLE);
// When preview loads, notify adapter to rebind the item and possibly animate
- widget.applyFromCellItem(widgetItem, 1f,
+ widget.applyFromCellItem(widgetItem,
bitmap -> holder.onPreviewLoaded(Pair.create(widgetItem, bitmap)),
holder.previewCache.get(widgetItem));
widget.requestLayout();
@@ -150,13 +148,9 @@
tableRow = (TableRow) table.getChildAt(i);
} else {
tableRow = new TableRow(table.getContext());
- if (enableCategorizedWidgetSuggestions()) {
- // Vertically center align items, so that even if they don't fill bounds, they
- // can look organized when placed together in a row.
- tableRow.setGravity(Gravity.CENTER_VERTICAL);
- } else {
- tableRow.setGravity(Gravity.TOP);
- }
+ // Vertically center align items, so that even if they don't fill bounds, they
+ // can look organized when placed together in a row.
+ tableRow.setGravity(Gravity.CENTER_VERTICAL);
table.addView(tableRow);
}
if (tableRow.getChildCount() > widgetItems.size()) {
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
index 12564f4..76b8401 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
@@ -17,11 +17,13 @@
import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
+import static com.android.launcher3.widget.util.WidgetSizes.getWidgetSizePx;
+import static com.android.launcher3.widget.util.WidgetsTableUtils.WIDGETS_TABLE_ROW_SIZE_COMPARATOR;
+
+import static java.lang.Math.max;
import android.content.Context;
import android.util.AttributeSet;
-import android.util.Log;
-import android.util.Size;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -30,26 +32,23 @@
import android.widget.TableRow;
import androidx.annotation.Nullable;
+import androidx.annotation.Px;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.widget.WidgetCell;
-import com.android.launcher3.widget.util.WidgetSizes;
+import com.android.launcher3.widget.picker.util.WidgetPreviewContainerSize;
import java.util.ArrayList;
import java.util.List;
/** A {@link TableLayout} for showing recommended widgets. */
public final class WidgetsRecommendationTableLayout extends TableLayout {
- private static final String TAG = "WidgetsRecommendationTableLayout";
- private static final float DOWN_SCALE_RATIO = 0.9f;
- private static final float MAX_DOWN_SCALE_RATIO = 0.5f;
private final float mWidgetsRecommendationTableVerticalPadding;
private final float mWidgetCellVerticalPadding;
private final float mWidgetCellTextViewsHeight;
- private float mRecommendationTableMaxHeight = Float.MAX_VALUE;
@Nullable private OnLongClickListener mWidgetCellOnLongClickListener;
@Nullable private OnClickListener mWidgetCellOnClickListener;
@@ -82,47 +81,40 @@
* desired {@code recommendationTableMaxHeight}.
*
* <p>If the content can't fit {@code recommendationTableMaxHeight}, this view will remove a
- * last row from the {@code recommendedWidgets} until it fits or only one row left. If the only
- * row still doesn't fit, we scale down the preview image.
+ * last row from the {@code recommendedWidgets} until it fits or only one row left.
*
* <p>Returns {@code false} if none of the widgets could fit</p>
*/
- public boolean setRecommendedWidgets(List<ArrayList<WidgetItem>> recommendedWidgets,
- DeviceProfile deviceProfile,
- float recommendationTableMaxHeight) {
- mRecommendationTableMaxHeight = recommendationTableMaxHeight;
- RecommendationTableData data = fitRecommendedWidgetsToTableSpace(/* previewScale= */ 1f,
- deviceProfile,
- recommendedWidgets);
- bindData(data);
- return !data.mRecommendationTable.isEmpty();
+ public int setRecommendedWidgets(List<ArrayList<WidgetItem>> recommendedWidgets,
+ DeviceProfile deviceProfile, float recommendationTableMaxHeight) {
+ List<ArrayList<WidgetItem>> rows = selectRowsThatFitInAvailableHeight(recommendedWidgets,
+ recommendationTableMaxHeight, deviceProfile);
+ bindData(rows);
+ return rows.stream().mapToInt(ArrayList::size).sum();
}
- private void bindData(RecommendationTableData data) {
- if (data.mRecommendationTable.isEmpty()) {
+ private void bindData(List<ArrayList<WidgetItem>> recommendationTable) {
+ if (recommendationTable.isEmpty()) {
setVisibility(GONE);
return;
}
removeAllViews();
- for (int i = 0; i < data.mRecommendationTable.size(); i++) {
- List<WidgetItem> widgetItems = data.mRecommendationTable.get(i);
+ for (int i = 0; i < recommendationTable.size(); i++) {
+ List<WidgetItem> widgetItems = recommendationTable.get(i);
TableRow tableRow = new TableRow(getContext());
- if (enableCategorizedWidgetSuggestions()) {
- // Vertically center align items, so that even if they don't fill bounds, they can
- // look organized when placed together in a row.
- tableRow.setGravity(Gravity.CENTER_VERTICAL);
- } else {
- tableRow.setGravity(Gravity.TOP);
- }
+ // Vertically center align items, so that even if they don't fill bounds, they can
+ // look organized when placed together in a row.
+ tableRow.setGravity(Gravity.CENTER_VERTICAL);
for (WidgetItem widgetItem : widgetItems) {
WidgetCell widgetCell = addItemCell(tableRow);
- widgetCell.applyFromCellItem(widgetItem, data.mPreviewScale);
+ widgetCell.applyFromCellItem(widgetItem);
widgetCell.showAppIconInWidgetTitle(true);
widgetCell.showBadge();
if (enableCategorizedWidgetSuggestions()) {
widgetCell.showDescription(false);
+ widgetCell.showDimensions(false);
}
}
addView(tableRow);
@@ -144,58 +136,32 @@
return widget;
}
- private RecommendationTableData fitRecommendedWidgetsToTableSpace(
- float previewScale,
- DeviceProfile deviceProfile,
- List<ArrayList<WidgetItem>> recommendedWidgetsInTable) {
- if (previewScale < MAX_DOWN_SCALE_RATIO) {
- Log.w(TAG, "Hide recommended widgets. Can't down scale previews to " + previewScale);
- return new RecommendationTableData(List.of(), previewScale);
- }
+ private List<ArrayList<WidgetItem>> selectRowsThatFitInAvailableHeight(
+ List<ArrayList<WidgetItem>> recommendedWidgets, @Px float recommendationTableMaxHeight,
+ DeviceProfile deviceProfile) {
+ List<ArrayList<WidgetItem>> filteredRows = new ArrayList<>();
// A naive estimation of the widgets recommendation table height without inflation.
float totalHeight = mWidgetsRecommendationTableVerticalPadding;
- for (int i = 0; i < recommendedWidgetsInTable.size(); i++) {
- List<WidgetItem> widgetItems = recommendedWidgetsInTable.get(i);
+
+ for (int i = 0; i < recommendedWidgets.size(); i++) {
+ List<WidgetItem> widgetItems = recommendedWidgets.get(i);
float rowHeight = 0;
for (int j = 0; j < widgetItems.size(); j++) {
WidgetItem widgetItem = widgetItems.get(j);
- Size widgetSize = WidgetSizes.getWidgetItemSizePx(getContext(), deviceProfile,
- widgetItem);
- float previewHeight = widgetSize.getHeight() * previewScale;
- rowHeight = Math.max(rowHeight,
- previewHeight + mWidgetCellTextViewsHeight + mWidgetCellVerticalPadding);
+ WidgetPreviewContainerSize previewContainerSize =
+ WidgetPreviewContainerSize.Companion.forItem(widgetItem, deviceProfile);
+ float widgetItemHeight = getWidgetSizePx(deviceProfile, previewContainerSize.spanX,
+ previewContainerSize.spanY).getHeight();
+ rowHeight = max(rowHeight,
+ widgetItemHeight + mWidgetCellTextViewsHeight + mWidgetCellVerticalPadding);
}
- totalHeight += rowHeight;
+ if (totalHeight + rowHeight <= recommendationTableMaxHeight) {
+ totalHeight += rowHeight;
+ filteredRows.add(new ArrayList<>(widgetItems));
+ }
}
- if (totalHeight < mRecommendationTableMaxHeight) {
- return new RecommendationTableData(recommendedWidgetsInTable, previewScale);
- }
-
- if (recommendedWidgetsInTable.size() > 1) {
- // We don't want to scale down widgets preview unless we really need to. Reduce the
- // num of row by 1 to see if it fits.
- return fitRecommendedWidgetsToTableSpace(
- previewScale,
- deviceProfile,
- recommendedWidgetsInTable.subList(/* fromIndex= */0,
- /* toIndex= */recommendedWidgetsInTable.size() - 1));
- }
-
- float nextPreviewScale = previewScale * DOWN_SCALE_RATIO;
- return fitRecommendedWidgetsToTableSpace(nextPreviewScale, deviceProfile,
- recommendedWidgetsInTable);
- }
-
- /** Data class for the widgets recommendation table and widgets preview scaling. */
- private class RecommendationTableData {
- private final List<ArrayList<WidgetItem>> mRecommendationTable;
- private final float mPreviewScale;
-
- RecommendationTableData(List<ArrayList<WidgetItem>> recommendationTable,
- float previewScale) {
- mRecommendationTable = recommendationTable;
- mPreviewScale = previewScale;
- }
+ // Perform re-ordering once we have filtered out recommendations that fit.
+ return filteredRows.stream().sorted(WIDGETS_TABLE_ROW_SIZE_COMPARATOR).toList();
}
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
index 165b2fe..c3bb993 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
@@ -167,7 +167,7 @@
@Override
public void onWidgetsBound() {
super.onWidgetsBound();
- if (!mHasRecommendedWidgets && mSelectedHeader == null) {
+ if (mRecommendedWidgetsCount == 0 && mSelectedHeader == null) {
mAdapters.get(mActivePage).mWidgetsListAdapter.selectFirstHeaderEntry();
mAdapters.get(mActivePage).mWidgetsRecyclerView.scrollToTop();
}
@@ -177,7 +177,7 @@
public void onRecommendedWidgetsBound() {
super.onRecommendedWidgetsBound();
- if (mSuggestedWidgetsContainer == null && mHasRecommendedWidgets) {
+ if (mSuggestedWidgetsContainer == null && mRecommendedWidgetsCount > 0) {
setupSuggestedWidgets(LayoutInflater.from(getContext()));
mSuggestedWidgetsHeader.callOnClick();
}
@@ -209,8 +209,9 @@
packageItemInfo.title = suggestionsHeaderTitle;
WidgetsListHeaderEntry widgetsListHeaderEntry = WidgetsListHeaderEntry.create(
packageItemInfo,
- suggestionsHeaderTitle,
- mActivityContext.getPopupDataProvider().getRecommendedWidgets())
+ /*titleSectionName=*/ suggestionsHeaderTitle,
+ /*items=*/ mActivityContext.getPopupDataProvider().getRecommendedWidgets(),
+ /*visibleWidgetsCount=*/ mRecommendedWidgetsCount)
.withWidgetListShown();
mSuggestedWidgetsHeader.applyFromItemInfoWithIcon(widgetsListHeaderEntry);
@@ -233,7 +234,7 @@
@Override
@Px
protected float getMaxTableHeight(@Px float noWidgetsViewHeight) {
- return Float.MAX_VALUE;
+ return mContent.getMeasuredHeight() - measureHeightWithVerticalMargins(mHeaderTitle);
}
@Override
diff --git a/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSize.kt b/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSize.kt
new file mode 100644
index 0000000..a0414ba
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSize.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.widget.picker.util
+
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.model.WidgetItem
+import kotlin.math.abs
+
+/** Size of a preview container in terms of the grid spans. */
+data class WidgetPreviewContainerSize(@JvmField val spanX: Int, @JvmField val spanY: Int) {
+ companion object {
+ /**
+ * Returns the size of the preview container in which the given widget's preview should be
+ * displayed (by scaling it if necessary).
+ */
+ fun forItem(item: WidgetItem, dp: DeviceProfile): WidgetPreviewContainerSize {
+ val sizes =
+ if (dp.isTablet && !dp.isTwoPanels) {
+ TABLET_WIDGET_PREVIEW_SIZES
+ } else {
+ HANDHELD_WIDGET_PREVIEW_SIZES
+ }
+
+ for ((index, containerSize) in sizes.withIndex()) {
+ if (containerSize.spanX == item.spanX && containerSize.spanY == item.spanY) {
+ return containerSize // Exact match!
+ }
+ if (containerSize.spanX <= item.spanX && containerSize.spanY <= item.spanY) {
+ return findClosestFittingContainer(
+ containerSizes = sizes.toList(),
+ startIndex = index,
+ item = item
+ )
+ }
+ }
+ // Use largest container if no match found
+ return sizes.elementAt(0)
+ }
+
+ private fun findClosestFittingContainer(
+ containerSizes: List<WidgetPreviewContainerSize>,
+ startIndex: Int,
+ item: WidgetItem
+ ): WidgetPreviewContainerSize {
+ // Checks if it's a smaller container, but close enough to keep the down-scale minimal.
+ fun hasAcceptableSize(currentIndex: Int): Boolean {
+ val container = containerSizes[currentIndex]
+ val isSmallerThanItem =
+ container.spanX <= item.spanX && container.spanY <= item.spanY
+ val isCloseToItemSize =
+ (item.spanY - container.spanY <= 1) && (item.spanX - container.spanX <= 1)
+
+ return isSmallerThanItem && isCloseToItemSize
+ }
+
+ var currentIndex = startIndex
+ var match = containerSizes[currentIndex]
+ val itemCellSizeRatio = item.spanX.toFloat() / item.spanY
+ var lastCellSizeRatioDiff = Float.MAX_VALUE
+
+ // Look for a smaller container (up to an acceptable extent) with closest cell size
+ // ratio.
+ while (currentIndex <= containerSizes.lastIndex && hasAcceptableSize(currentIndex)) {
+ val current = containerSizes[currentIndex]
+ val currentCellSizeRatio = current.spanX.toFloat() / current.spanY
+ val currentCellSizeRatioDiff = abs(itemCellSizeRatio - currentCellSizeRatio)
+
+ if (currentCellSizeRatioDiff < lastCellSizeRatioDiff) {
+ lastCellSizeRatioDiff = currentCellSizeRatioDiff
+ match = containerSizes[currentIndex]
+ }
+ currentIndex++
+ }
+ return match
+ }
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizes.kt b/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizes.kt
new file mode 100644
index 0000000..a016676
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizes.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.widget.picker.util
+
+/**
+ * An ordered list of recommended sizes for the preview containers in handheld devices.
+ *
+ * Size of the preview container in which a widget's preview can be displayed.
+ */
+val HANDHELD_WIDGET_PREVIEW_SIZES: List<WidgetPreviewContainerSize> =
+ listOf(
+ WidgetPreviewContainerSize(spanX = 4, spanY = 3),
+ WidgetPreviewContainerSize(spanX = 4, spanY = 2),
+ WidgetPreviewContainerSize(spanX = 2, spanY = 3),
+ WidgetPreviewContainerSize(spanX = 2, spanY = 2),
+ WidgetPreviewContainerSize(spanX = 4, spanY = 1),
+ WidgetPreviewContainerSize(spanX = 2, spanY = 1),
+ WidgetPreviewContainerSize(spanX = 1, spanY = 1),
+ )
+
+/**
+ * An ordered list of recommended sizes for the preview containers in tablet devices (with larger
+ * grids).
+ *
+ * Size of the preview container in which a widget's preview can be displayed (by scaling the
+ * preview if necessary).
+ */
+val TABLET_WIDGET_PREVIEW_SIZES: List<WidgetPreviewContainerSize> =
+ listOf(
+ WidgetPreviewContainerSize(spanX = 3, spanY = 4),
+ WidgetPreviewContainerSize(spanX = 3, spanY = 3),
+ WidgetPreviewContainerSize(spanX = 3, spanY = 2),
+ WidgetPreviewContainerSize(spanX = 2, spanY = 3),
+ WidgetPreviewContainerSize(spanX = 2, spanY = 2),
+ WidgetPreviewContainerSize(spanX = 3, spanY = 1),
+ WidgetPreviewContainerSize(spanX = 2, spanY = 1),
+ WidgetPreviewContainerSize(spanX = 1, spanY = 1),
+ )
diff --git a/src/com/android/launcher3/widget/util/WidgetsTableUtils.java b/src/com/android/launcher3/widget/util/WidgetsTableUtils.java
index 74d3062..5e0e203 100644
--- a/src/com/android/launcher3/widget/util/WidgetsTableUtils.java
+++ b/src/com/android/launcher3/widget/util/WidgetsTableUtils.java
@@ -16,11 +16,13 @@
package com.android.launcher3.widget.util;
import android.content.Context;
+import android.util.Size;
import androidx.annotation.Px;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.widget.picker.util.WidgetPreviewContainerSize;
import java.util.ArrayList;
import java.util.Comparator;
@@ -33,8 +35,8 @@
/**
* Groups widgets in the following order:
* 1. Widgets always go before shortcuts.
- * 2. Widgets with smaller horizontal spans will be shown first.
- * 3. If widgets have the same horizontal spans, then widgets with a smaller vertical spans will
+ * 2. Widgets with smaller vertical spans will be shown first.
+ * 3. If widgets have the same vertical spans, then widgets with a smaller horizontal spans will
* go first.
* 4. If both widgets have the same horizontal and vertical spans, they will use the same order
* from the given {@code widgetItems}.
@@ -43,14 +45,29 @@
if (item.widgetInfo != null && otherItem.widgetInfo == null) return -1;
if (item.widgetInfo == null && otherItem.widgetInfo != null) return 1;
- if (item.spanX == otherItem.spanX) {
- if (item.spanY == otherItem.spanY) return 0;
- return item.spanY > otherItem.spanY ? 1 : -1;
+ if (item.spanY == otherItem.spanY) {
+ if (item.spanX == otherItem.spanX) return 0;
+ return item.spanX > otherItem.spanX ? 1 : -1;
}
- return item.spanX > otherItem.spanX ? 1 : -1;
+ return item.spanY > otherItem.spanY ? 1 : -1;
};
/**
+ * Comparator that enables displaying rows in increasing order of their size (totalW * H);
+ * except for shortcuts which always show at the bottom.
+ */
+ public static final Comparator<ArrayList<WidgetItem>> WIDGETS_TABLE_ROW_SIZE_COMPARATOR =
+ Comparator.comparingInt(row -> {
+ if (row.stream().anyMatch(WidgetItem::isShortcut)) {
+ return Integer.MAX_VALUE;
+ } else {
+ int rowWidth = row.stream().mapToInt(w -> w.spanX).sum();
+ int rowHeight = row.get(0).spanY;
+ return (rowWidth * rowHeight);
+ }
+ });
+
+ /**
* Groups {@code widgetItems} items into a 2D array which matches their appearance in a UI
* table. This takes liberty to rearrange widgets to make the table visually appealing.
*/
@@ -59,72 +76,70 @@
final @Px int rowPx, final @Px int cellPadding) {
List<WidgetItem> sortedWidgetItems = widgetItems.stream().sorted(WIDGET_SHORTCUT_COMPARATOR)
.collect(Collectors.toList());
- return groupWidgetItemsUsingRowPxWithoutReordering(sortedWidgetItems, context, dp, rowPx,
+ List<ArrayList<WidgetItem>> rows = groupWidgetItemsUsingRowPxWithoutReordering(
+ sortedWidgetItems, context, dp, rowPx,
cellPadding);
+ return rows.stream().sorted(WIDGETS_TABLE_ROW_SIZE_COMPARATOR).toList();
}
/**
* Groups {@code widgetItems} into a 2D array which matches their appearance in a UI table while
* maintaining their order. This function is a variant of
- * {@code groupWidgetItemsIntoTableWithoutReordering} in that this uses widget pixels for
- * calculation.
+ * {@code groupWidgetItemsIntoTableWithoutReordering} in that this uses widget container's
+ * pixels for calculation.
*
* <p>Grouping:
* 1. Widgets and shortcuts never group together in the same row.
- * 2. The ordered widgets are grouped together in the same row until their individual occupying
- * pixels exceed the total allowed pixels for the cell.
+ * 2. Widgets are grouped together only if they have same preview container size.
+ * 3. Widgets are grouped together in the same row until the total of individual container sizes
+ * exceed the total allowed pixels for the row.
* 3. The ordered shortcuts are grouped together in the same row until their individual
* occupying pixels exceed the total allowed pixels for the cell.
* 4. If there is only one widget in a row, its width may exceed the {@code rowPx}.
*
- * <p>Let's say the {@code rowPx} is set to 600 and we have 5 widgets. Widgets can be grouped
- * in the same row if each of their individual occupying pixels does not exceed
- * {@code rowPx} / 5 - 2 * {@code cellPadding}.
- * Example 1: Row 1: 200x200, 200x300, 100x100. Average horizontal pixels is 200 and no widgets
- * exceed that width. This is okay.
- * Example 2: Row 1: 200x200, 400x300, 100x100. Average horizontal pixels is 200 and one widget
- * exceed that width. This is not allowed.
- * Example 3: Row 1: 700x400. This is okay because this is the only item in the row.
+ * <p>See WidgetTableUtilsTest
*/
public static List<ArrayList<WidgetItem>> groupWidgetItemsUsingRowPxWithoutReordering(
List<WidgetItem> widgetItems, Context context, final DeviceProfile dp,
final @Px int rowPx, final @Px int cellPadding) {
-
List<ArrayList<WidgetItem>> widgetItemsTable = new ArrayList<>();
ArrayList<WidgetItem> widgetItemsAtRow = null;
+ // A row displays only items of same container size.
+ WidgetPreviewContainerSize containerSizeForRow = null;
+ @Px int currentRowWidth = 0;
+
for (WidgetItem widgetItem : widgetItems) {
if (widgetItemsAtRow == null) {
widgetItemsAtRow = new ArrayList<>();
widgetItemsTable.add(widgetItemsAtRow);
}
int numOfWidgetItems = widgetItemsAtRow.size();
- @Px int individualSpan = (rowPx / (numOfWidgetItems + 1)) - (2 * cellPadding);
+
+ WidgetPreviewContainerSize containerSize =
+ WidgetPreviewContainerSize.Companion.forItem(widgetItem, dp);
+ Size containerSizePx = WidgetSizes.getWidgetSizePx(dp, containerSize.spanX,
+ containerSize.spanY);
+ @Px int containerWidth = containerSizePx.getWidth() + (2 * cellPadding);
+
if (numOfWidgetItems == 0) {
widgetItemsAtRow.add(widgetItem);
- } else if (
- // Since the size of the widget cell is determined by dividing the maximum span
- // pixels evenly, making sure that each widget would have enough span pixels to
- // show their contents.
- widgetItem.hasSameType(widgetItemsAtRow.get(numOfWidgetItems - 1))
- && widgetItemsAtRow.stream().allMatch(
- item -> WidgetSizes.getWidgetItemSizePx(context, dp, item)
- .getWidth() <= individualSpan)
- && WidgetSizes.getWidgetItemSizePx(context, dp, widgetItem)
- .getWidth() <= individualSpan) {
+ containerSizeForRow = containerSize;
+ currentRowWidth = containerWidth;
+ } else if ((currentRowWidth + containerWidth) <= rowPx
+ && widgetItem.hasSameType(widgetItemsAtRow.get(numOfWidgetItems - 1))
+ && containerSize.equals(containerSizeForRow)) {
// Group items in the same row if
// 1. they are with the same type, i.e. a row can only have widgets or shortcuts but
// never a mix of both.
- // 2. Each widget will have horizontal cell span pixels that is at least as large as
- // it is required to fit in the horizontal content, unless the widget horizontal
- // span pixels is larger than the maximum allowed.
- // If an item has horizontal span pixels larger than the maximum allowed pixels
- // per row, we just place it in its own row regardless of the horizontal span
- // limit.
+ // 2. Each widget in the given row has same preview container size.
widgetItemsAtRow.add(widgetItem);
+ currentRowWidth += containerWidth;
} else {
widgetItemsAtRow = new ArrayList<>();
widgetItemsTable.add(widgetItemsAtRow);
widgetItemsAtRow.add(widgetItem);
+ containerSizeForRow = containerSize;
+ currentRowWidth = containerWidth;
}
}
return widgetItemsTable;
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
index efde7d8..90271c1 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -108,6 +108,13 @@
}
/**
+ * Returns an intent which can be used to open Private Space Settings.
+ */
+ public static Intent getPrivateSpaceSettingsIntent(Context context) {
+ return null;
+ }
+
+ /**
* Checks if an activity is flagged as non-resizeable.
*/
public static boolean isNonResizeableActivity(LauncherActivityInfo lai) {
diff --git a/tests/Android.bp b/tests/Android.bp
index e9111ea..24ae158 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -177,7 +177,7 @@
name: "launcher-testing-shared",
srcs: [
"multivalentTests/shared/com/android/launcher3/testing/shared/**/*.java",
- "multivalentTests/shared/com/android/launcher3/testing/shared/**/*.kt"
+ "multivalentTests/shared/com/android/launcher3/testing/shared/**/*.kt",
],
resource_dirs: [],
manifest: "multivalentTests/shared/AndroidManifest.xml",
@@ -225,8 +225,8 @@
// multivalentTests directory is a shared folder for not only robolectric converted test
// classes but also shared helper classes.
srcs: [
- "multivalentTests/src/com/android/launcher3/util/*.java",
- "multivalentTests/src/com/android/launcher3/util/*.kt",
+ "multivalentTests/src/**/*.java",
+ "multivalentTests/src/**/*.kt",
// Test util classes
":launcher-testing-helpers",
@@ -246,7 +246,8 @@
"androidx.test.uiautomator_uiautomator",
"androidx.core_core-animation-testing",
"androidx.test.ext.junit",
- "inline-mockito-robolectric-prebuilt",
+ "mockito-robolectric-prebuilt",
+ "mockito-kotlin2",
"platform-parametric-runner-lib",
"testables",
"Launcher3TestResources",
diff --git a/tests/assets/databases/BackupAndRestore/launcher.db b/tests/assets/databases/BackupAndRestore/launcher.db
new file mode 100644
index 0000000..126d166
--- /dev/null
+++ b/tests/assets/databases/BackupAndRestore/launcher.db
Binary files differ
diff --git a/tests/assets/databases/BackupAndRestore/launcher_3_by_3.db b/tests/assets/databases/BackupAndRestore/launcher_3_by_3.db
new file mode 100644
index 0000000..6d8cd73
--- /dev/null
+++ b/tests/assets/databases/BackupAndRestore/launcher_3_by_3.db
Binary files differ
diff --git a/tests/assets/databases/BackupAndRestore/launcher_4_by_4.db b/tests/assets/databases/BackupAndRestore/launcher_4_by_4.db
new file mode 100644
index 0000000..00061dd
--- /dev/null
+++ b/tests/assets/databases/BackupAndRestore/launcher_4_by_4.db
Binary files differ
diff --git a/tests/assets/databases/BackupAndRestore/launcher_4_by_5.db b/tests/assets/databases/BackupAndRestore/launcher_4_by_5.db
new file mode 100644
index 0000000..e2e65aa
--- /dev/null
+++ b/tests/assets/databases/BackupAndRestore/launcher_4_by_5.db
Binary files differ
diff --git a/tests/assets/databases/GridMigrationTest/flagged_result5x5to5x8.db b/tests/assets/databases/GridMigrationTest/flagged_result5x5to5x8.db
new file mode 100644
index 0000000..8bea3ce
--- /dev/null
+++ b/tests/assets/databases/GridMigrationTest/flagged_result5x5to5x8.db
Binary files differ
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt
index af8f67f..197e687 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt
@@ -123,7 +123,7 @@
dropTargetBarBottomMarginPx: 42.0px (16.0dp)
getCellLayoutSpringLoadShrunkTop(): 391.0px (148.95238dp)
getCellLayoutSpringLoadShrunkBottom(): 1927.0px (734.0952dp)
- workspaceSpringLoadedMinNextPageVisiblePx: 63.0px (24.0dp)
- getWorkspaceSpringLoadScale(): 0.7781155px (0.29642496dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 126.0px (48.0dp)
+ getWorkspaceSpringLoadScale(): 0.7666667px (0.2920635dp)
getCellLayoutHeight(): 1974.0px (752.0dp)
getCellLayoutWidth(): 1080.0px (411.42856dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt
index 5b83dd7..4a9e2e6 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt
@@ -123,7 +123,7 @@
dropTargetBarBottomMarginPx: 42.0px (16.0dp)
getCellLayoutSpringLoadShrunkTop(): 391.0px (148.95238dp)
getCellLayoutSpringLoadShrunkBottom(): 1906.0px (726.0952dp)
- workspaceSpringLoadedMinNextPageVisiblePx: 63.0px (24.0dp)
- getWorkspaceSpringLoadScale(): 0.77572966px (0.29551607dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 126.0px (48.0dp)
+ getWorkspaceSpringLoadScale(): 0.76666665px (0.29206347dp)
getCellLayoutHeight(): 1953.0px (744.0dp)
getCellLayoutWidth(): 1080.0px (411.42856dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt
index 03a0048..9e89a16 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt
@@ -123,7 +123,7 @@
dropTargetBarBottomMarginPx: 16.0px (6.095238dp)
getCellLayoutSpringLoadShrunkTop(): 201.0px (76.57143dp)
getCellLayoutSpringLoadShrunkBottom(): 961.0px (366.09525dp)
- workspaceSpringLoadedMinNextPageVisiblePx: 63.0px (24.0dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 126.0px (48.0dp)
getWorkspaceSpringLoadScale(): 0.8059385px (0.30702418dp)
getCellLayoutHeight(): 943.0px (359.2381dp)
getCellLayoutWidth(): 2073.0px (789.7143dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt
index 84258b3..ce168b4 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt
@@ -123,7 +123,7 @@
dropTargetBarBottomMarginPx: 16.0px (6.095238dp)
getCellLayoutSpringLoadShrunkTop(): 201.0px (76.57143dp)
getCellLayoutSpringLoadShrunkBottom(): 1017.0px (387.42856dp)
- workspaceSpringLoadedMinNextPageVisiblePx: 63.0px (24.0dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 126.0px (48.0dp)
getWorkspaceSpringLoadScale(): 0.8111332px (0.3090031dp)
getCellLayoutHeight(): 1006.0px (383.2381dp)
getCellLayoutWidth(): 1947.0px (741.7143dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
index 87a9700..7926033 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
@@ -123,7 +123,7 @@
dropTargetBarBottomMarginPx: 64.0px (32.0dp)
getCellLayoutSpringLoadShrunkTop(): 312.0px (156.0dp)
getCellLayoutSpringLoadShrunkBottom(): 1272.0px (636.0dp)
- workspaceSpringLoadedMinNextPageVisiblePx: 48.0px (24.0dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 96.0px (48.0dp)
getWorkspaceSpringLoadScale(): 0.76677316px (0.38338658dp)
getCellLayoutHeight(): 1252.0px (626.0dp)
getCellLayoutWidth(): 2198.0px (1099.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
index 169256d..eb20578 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
@@ -123,7 +123,7 @@
dropTargetBarBottomMarginPx: 64.0px (32.0dp)
getCellLayoutSpringLoadShrunkTop(): 312.0px (156.0dp)
getCellLayoutSpringLoadShrunkBottom(): 1272.0px (636.0dp)
- workspaceSpringLoadedMinNextPageVisiblePx: 48.0px (24.0dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 96.0px (48.0dp)
getWorkspaceSpringLoadScale(): 0.76677316px (0.38338658dp)
getCellLayoutHeight(): 1252.0px (626.0dp)
getCellLayoutWidth(): 2198.0px (1099.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
index 59da1be..dba0ec4 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
@@ -123,7 +123,7 @@
dropTargetBarBottomMarginPx: 96.0px (48.0dp)
getCellLayoutSpringLoadShrunkTop(): 564.0px (282.0dp)
getCellLayoutSpringLoadShrunkBottom(): 2072.0px (1036.0dp)
- workspaceSpringLoadedMinNextPageVisiblePx: 48.0px (24.0dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 96.0px (48.0dp)
getWorkspaceSpringLoadScale(): 0.8125px (0.40625dp)
getCellLayoutHeight(): 1856.0px (928.0dp)
getCellLayoutWidth(): 1528.0px (764.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
index ad1f095..495afb2 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
@@ -123,7 +123,7 @@
dropTargetBarBottomMarginPx: 96.0px (48.0dp)
getCellLayoutSpringLoadShrunkTop(): 564.0px (282.0dp)
getCellLayoutSpringLoadShrunkBottom(): 2072.0px (1036.0dp)
- workspaceSpringLoadedMinNextPageVisiblePx: 48.0px (24.0dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 96.0px (48.0dp)
getWorkspaceSpringLoadScale(): 0.8125px (0.40625dp)
getCellLayoutHeight(): 1856.0px (928.0dp)
getCellLayoutWidth(): 1528.0px (764.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt
index 61d5ee6..e7dd3e0 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt
@@ -123,7 +123,7 @@
dropTargetBarBottomMarginPx: 42.0px (16.0dp)
getCellLayoutSpringLoadShrunkTop(): 299.0px (113.90476dp)
getCellLayoutSpringLoadShrunkBottom(): 1457.0px (555.0476dp)
- workspaceSpringLoadedMinNextPageVisiblePx: 63.0px (24.0dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 126.0px (48.0dp)
getWorkspaceSpringLoadScale(): 0.8452555px (0.32200208dp)
getCellLayoutHeight(): 1370.0px (521.9048dp)
getCellLayoutWidth(): 1083.0px (412.57144dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt
index ac73e2f..fcbd427 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt
@@ -123,7 +123,7 @@
dropTargetBarBottomMarginPx: 42.0px (16.0dp)
getCellLayoutSpringLoadShrunkTop(): 299.0px (113.90476dp)
getCellLayoutSpringLoadShrunkBottom(): 1457.0px (555.0476dp)
- workspaceSpringLoadedMinNextPageVisiblePx: 63.0px (24.0dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 126.0px (48.0dp)
getWorkspaceSpringLoadScale(): 0.8452555px (0.32200208dp)
getCellLayoutHeight(): 1370.0px (521.9048dp)
getCellLayoutWidth(): 1083.0px (412.57144dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt
index 3169f41..319247c 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt
@@ -123,7 +123,7 @@
dropTargetBarBottomMarginPx: 84.0px (32.0dp)
getCellLayoutSpringLoadShrunkTop(): 364.0px (138.66667dp)
getCellLayoutSpringLoadShrunkBottom(): 1773.0px (675.4286dp)
- workspaceSpringLoadedMinNextPageVisiblePx: 63.0px (24.0dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 126.0px (48.0dp)
getWorkspaceSpringLoadScale(): 0.81871px (0.31188953dp)
getCellLayoutHeight(): 1721.0px (655.619dp)
getCellLayoutWidth(): 899.0px (342.4762dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt
index 9d3d7bc..50c1d4b 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt
@@ -123,7 +123,7 @@
dropTargetBarBottomMarginPx: 84.0px (32.0dp)
getCellLayoutSpringLoadShrunkTop(): 364.0px (138.66667dp)
getCellLayoutSpringLoadShrunkBottom(): 1773.0px (675.4286dp)
- workspaceSpringLoadedMinNextPageVisiblePx: 63.0px (24.0dp)
+ workspaceSpringLoadedMinNextPageVisiblePx: 126.0px (48.0dp)
getWorkspaceSpringLoadScale(): 0.81871px (0.31188953dp)
getCellLayoutHeight(): 1721.0px (655.619dp)
getCellLayoutWidth(): 899.0px (342.4762dp)
diff --git a/tests/src/com/android/launcher3/celllayout/CellPosMapperTest.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/CellPosMapperTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/celllayout/CellPosMapperTest.java
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/CellPosMapperTest.java
diff --git a/tests/src/com/android/launcher3/logging/FileLogTest.java b/tests/multivalentTests/src/com/android/launcher3/logging/FileLogTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/logging/FileLogTest.java
rename to tests/multivalentTests/src/com/android/launcher3/logging/FileLogTest.java
diff --git a/tests/src/com/android/launcher3/model/data/ItemInfoWithIconTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/data/ItemInfoWithIconTest.kt
similarity index 100%
rename from tests/src/com/android/launcher3/model/data/ItemInfoWithIconTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/model/data/ItemInfoWithIconTest.kt
diff --git a/tests/src/com/android/launcher3/popup/PopupPopulatorTest.java b/tests/multivalentTests/src/com/android/launcher3/popup/PopupPopulatorTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/popup/PopupPopulatorTest.java
rename to tests/multivalentTests/src/com/android/launcher3/popup/PopupPopulatorTest.java
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/rule/BackAndRestoreRule.kt b/tests/multivalentTests/src/com/android/launcher3/util/rule/BackAndRestoreRule.kt
new file mode 100644
index 0000000..da96939
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/rule/BackAndRestoreRule.kt
@@ -0,0 +1,119 @@
+/*
+ * 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.rule
+
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherPrefs
+import java.io.File
+import java.nio.file.Paths
+import kotlin.io.path.pathString
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Removes all launcher's DBs from the device and copies the dbs in
+ * assets/databases/BackupAndRestore to the device. It also set's the needed LauncherPrefs variables
+ * needed to kickstart a backup and restore.
+ */
+class BackAndRestoreRule : TestRule {
+
+ private val phoneContext = getInstrumentation().targetContext
+
+ private fun dbBackUp() = File(phoneContext.dataDir.path, "/databasesBackUp")
+
+ private fun dbDirectory() = File(phoneContext.dataDir.path, "/databases")
+
+ private fun isWorkspaceDatabase(rawFileName: String): Boolean {
+ val fileName = Paths.get(rawFileName).fileName.pathString
+ return fileName.startsWith("launcher") && fileName.endsWith(".db")
+ }
+
+ fun getDatabaseFiles() = dbDirectory().listFiles().filter { isWorkspaceDatabase(it.name) }
+
+ /**
+ * Setting RESTORE_DEVICE would trigger a restore next time the Launcher starts, and we remove
+ * the widgets and apps ids to prevent issues when loading the database.
+ */
+ private fun setRestoreConstants() {
+ LauncherPrefs.get(phoneContext)
+ .put(LauncherPrefs.RESTORE_DEVICE.to(InvariantDeviceProfile.TYPE_MULTI_DISPLAY))
+ LauncherPrefs.get(phoneContext)
+ .remove(LauncherPrefs.OLD_APP_WIDGET_IDS, LauncherPrefs.APP_WIDGET_IDS)
+ }
+
+ private fun uploadDatabase(dbName: String) {
+ val file = File(File(getInstrumentation().targetContext.dataDir, "/databases"), dbName)
+ file.writeBytes(
+ getInstrumentation()
+ .context
+ .assets
+ .open("databases/BackupAndRestore/$dbName")
+ .readBytes()
+ )
+ file.setWritable(true, false)
+ }
+
+ private fun uploadDbs() {
+ uploadDatabase("launcher.db")
+ uploadDatabase("launcher_4_by_4.db")
+ uploadDatabase("launcher_4_by_5.db")
+ uploadDatabase("launcher_3_by_3.db")
+ }
+
+ private fun savePreviousState() {
+ dbBackUp().deleteRecursively()
+ if (!dbDirectory().renameTo(dbBackUp())) {
+ throw Exception("Unable to move databases to backup directory")
+ }
+ dbDirectory().mkdir()
+ if (!dbDirectory().exists()) {
+ throw Exception("Databases directory doesn't exists")
+ }
+ }
+
+ private fun restorePreviousState() {
+ dbDirectory().deleteRecursively()
+ if (!dbBackUp().renameTo(dbDirectory())) {
+ throw Exception("Unable to restore backup directory to databases directory")
+ }
+ dbBackUp().delete()
+ }
+
+ fun before() {
+ savePreviousState()
+ setRestoreConstants()
+ uploadDbs()
+ }
+
+ fun after() {
+ restorePreviousState()
+ }
+
+ override fun apply(base: Statement?, description: Description?): Statement =
+ object : Statement() {
+ override fun evaluate() {
+ before()
+ try {
+ base?.evaluate()
+ } finally {
+ after()
+ }
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/util/window/WindowManagerProxyTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/window/WindowManagerProxyTest.kt
similarity index 100%
rename from tests/src/com/android/launcher3/util/window/WindowManagerProxyTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/util/window/WindowManagerProxyTest.kt
diff --git a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index 3ba563d..ffcf83f 100644
--- a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -38,17 +38,17 @@
import com.android.launcher3.util.window.CachedDisplayInfo
import com.android.launcher3.util.window.WindowManagerProxy
import com.google.common.truth.Truth
+import org.junit.Rule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
import java.io.BufferedReader
import java.io.File
import java.io.PrintWriter
import java.io.StringWriter
import kotlin.math.max
import kotlin.math.min
-import org.junit.Rule
-import org.mockito.kotlin.any
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.spy
-import org.mockito.kotlin.whenever
/**
* This is an abstract class for DeviceProfile tests that create an InvariantDeviceProfile based on
@@ -274,7 +274,8 @@
isGestureMode: Boolean = true,
densityDpi: Int
) {
- setFlagsRule.setFlags(false, Flags.FLAG_ENABLE_TWOLINE_TOGGLE)
+ setFlagsRule.setFlags(true, Flags.FLAG_ENABLE_TWOLINE_TOGGLE)
+ LauncherPrefs.get(testContext).put(LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE, true)
val windowsBounds = perDisplayBoundsCache[displayInfo]!!
val realBounds = windowsBounds[rotation]
whenever(windowManagerProxy.getDisplayInfo(any())).thenReturn(displayInfo)
diff --git a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java b/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
index 0907f8f..eea4fe5 100644
--- a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
+++ b/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
@@ -47,6 +47,7 @@
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.uioverrides.ApiWrapper;
import com.android.launcher3.util.ActivityContextWrapper;
import com.android.launcher3.util.UserIconInfo;
import com.android.launcher3.util.rule.TestStabilityRule;
@@ -176,17 +177,15 @@
}
@Test
- public void openPrivateSpaceSettings_triggersSecurityAndPrivacyIntent() {
- Intent expectedIntent = PrivateProfileManager.PRIVATE_SPACE_INTENT;
+ public void openPrivateSpaceSettings_triggersCorrectIntent() {
+ Intent expectedIntent = ApiWrapper.getPrivateSpaceSettingsIntent(mContext);
ArgumentCaptor<Intent> acIntent = ArgumentCaptor.forClass(Intent.class);
mPrivateProfileManager.setPrivateSpaceSettingsAvailable(true);
mPrivateProfileManager.openPrivateSpaceSettings();
Mockito.verify(mContext).startActivity(acIntent.capture());
- Intent actualIntent = acIntent.getValue();
- assertEquals("Intent Action is different", expectedIntent.getAction(),
- actualIntent.getAction());
+ assertEquals("Intent Action is different", expectedIntent, acIntent.getValue());
}
private static void awaitTasksCompleted() throws Exception {
diff --git a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewControllerTest.java b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewControllerTest.java
index 490cb47..043461d 100644
--- a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewControllerTest.java
+++ b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewControllerTest.java
@@ -18,6 +18,7 @@
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_HEADER;
import static com.android.launcher3.allapps.UserProfileManager.STATE_DISABLED;
import static com.android.launcher3.allapps.UserProfileManager.STATE_ENABLED;
import static com.android.launcher3.allapps.UserProfileManager.STATE_TRANSITION;
@@ -25,13 +26,19 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.AdditionalAnswers.answer;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
+import android.os.Process;
+import android.os.UserHandle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageButton;
@@ -44,6 +51,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.R;
+import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.util.ActivityContextWrapper;
import org.junit.Before;
@@ -52,32 +60,53 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.List;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class PrivateSpaceHeaderViewControllerTest {
+ private static final UserHandle MAIN_HANDLE = Process.myUserHandle();
+ private static final UserHandle PRIVATE_HANDLE = new UserHandle(11);
private static final int CONTAINER_HEADER_ELEMENT_COUNT = 1;
private static final int LOCK_UNLOCK_BUTTON_COUNT = 1;
private static final int PS_SETTINGS_BUTTON_COUNT_VISIBLE = 1;
private static final int PS_SETTINGS_BUTTON_COUNT_INVISIBLE = 0;
private static final int PS_TRANSITION_IMAGE_COUNT = 1;
+ private static final int NUM_APP_COLS = 4;
+ private static final int NUM_PRIVATE_SPACE_APPS = 50;
+ private static final int ALL_APPS_HEIGHT = 10;
+ private static final int ALL_APPS_CELL_HEIGHT = 1;
+ private static final int PS_HEADER_HEIGHT = 1;
+ private static final int BIGGER_PS_HEADER_HEIGHT = 2;
+ private static final int SCROLL_NO_WHERE = -1;
+ private static final float HEADER_PROTECTION_HEIGHT = 1F;
private Context mContext;
private PrivateSpaceHeaderViewController mPsHeaderViewController;
private RelativeLayout mPsHeaderLayout;
+ private AlphabeticalAppsList<?> mAlphabeticalAppsList;
@Mock
private PrivateProfileManager mPrivateProfileManager;
@Mock
private ActivityAllAppsContainerView mAllApps;
+ @Mock
+ private AllAppsStore<?> mAllAppsStore;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = new ActivityContextWrapper(getApplicationContext());
+ when(mPrivateProfileManager.getItemInfoMatcher()).thenReturn(info ->
+ info != null && info.user.equals(PRIVATE_HANDLE));
mPsHeaderViewController = new PrivateSpaceHeaderViewController(mAllApps,
mPrivateProfileManager);
mPsHeaderLayout = (RelativeLayout) LayoutInflater.from(mContext).inflate(
R.layout.private_space_header, null);
+ mAlphabeticalAppsList = new AlphabeticalAppsList<>(mContext, mAllAppsStore,
+ null, mPrivateProfileManager);
+ mAlphabeticalAppsList.setNumAppsPerRowAllApps(NUM_APP_COLS);
}
@Test
@@ -223,6 +252,88 @@
assertEquals(PS_TRANSITION_IMAGE_COUNT, totalLockUnlockButtonView);
}
+ @Test
+ public void scrollForViewToBeVisibleInContainer_withHeader() {
+ when(mAllAppsStore.getApps()).thenReturn(createAppInfoList());
+ when(mPrivateProfileManager.addPrivateSpaceHeader(any()))
+ .thenAnswer(answer(this::addPrivateSpaceHeader));
+ when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED);
+ when(mPrivateProfileManager.splitIntoUserInstalledAndSystemApps())
+ .thenReturn(iteminfo -> iteminfo.componentName == null
+ || !iteminfo.componentName.getPackageName()
+ .equals("com.android.launcher3.tests.camera"));
+ when(mAllApps.getContext()).thenReturn(mContext);
+ mAlphabeticalAppsList.updateItemFilter(info -> info != null
+ && info.user.equals(MAIN_HANDLE));
+ when(mAllApps.getHeight()).thenReturn(ALL_APPS_HEIGHT);
+ when(mAllApps.getHeaderProtectionHeight()).thenReturn(HEADER_PROTECTION_HEIGHT);
+ int rows = (int) (ALL_APPS_HEIGHT - PS_HEADER_HEIGHT - HEADER_PROTECTION_HEIGHT);
+ int position = rows * NUM_APP_COLS - (NUM_APP_COLS-1) + 1;
+
+ // The number of adapterItems should be the private space apps + one main app + header.
+ assertEquals(NUM_PRIVATE_SPACE_APPS + 1 + 1,
+ mAlphabeticalAppsList.getAdapterItems().size());
+ assertEquals(position,
+ mPsHeaderViewController.scrollForViewToBeVisibleInContainer(
+ new AllAppsRecyclerView(mContext),
+ mAlphabeticalAppsList.getAdapterItems(),
+ PS_HEADER_HEIGHT,
+ ALL_APPS_CELL_HEIGHT));
+ }
+
+ @Test
+ public void scrollForViewToBeVisibleInContainer_withHeaderAndLessAppRowSpace() {
+ when(mAllAppsStore.getApps()).thenReturn(createAppInfoList());
+ when(mPrivateProfileManager.addPrivateSpaceHeader(any()))
+ .thenAnswer(answer(this::addPrivateSpaceHeader));
+ when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED);
+ when(mPrivateProfileManager.splitIntoUserInstalledAndSystemApps())
+ .thenReturn(iteminfo -> iteminfo.componentName == null
+ || !iteminfo.componentName.getPackageName()
+ .equals("com.android.launcher3.tests.camera"));
+ when(mAllApps.getContext()).thenReturn(mContext);
+ mAlphabeticalAppsList.updateItemFilter(info -> info != null
+ && info.user.equals(MAIN_HANDLE));
+ when(mAllApps.getHeight()).thenReturn(ALL_APPS_HEIGHT);
+ when(mAllApps.getHeaderProtectionHeight()).thenReturn(HEADER_PROTECTION_HEIGHT);
+ int rows = (int) (ALL_APPS_HEIGHT - BIGGER_PS_HEADER_HEIGHT - HEADER_PROTECTION_HEIGHT);
+ int position = rows * NUM_APP_COLS - (NUM_APP_COLS-1) + 1;
+
+ // The number of adapterItems should be the private space apps + one main app + header.
+ assertEquals(NUM_PRIVATE_SPACE_APPS + 1 + 1,
+ mAlphabeticalAppsList.getAdapterItems().size());
+ assertEquals(position,
+ mPsHeaderViewController.scrollForViewToBeVisibleInContainer(
+ new AllAppsRecyclerView(mContext),
+ mAlphabeticalAppsList.getAdapterItems(),
+ BIGGER_PS_HEADER_HEIGHT,
+ ALL_APPS_CELL_HEIGHT));
+ }
+
+ @Test
+ public void scrollForViewToBeVisibleInContainer_withNoHeader() {
+ when(mAllAppsStore.getApps()).thenReturn(createAppInfoList());
+ when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED);
+ when(mPrivateProfileManager.splitIntoUserInstalledAndSystemApps())
+ .thenReturn(iteminfo -> iteminfo.componentName == null
+ || !iteminfo.componentName.getPackageName()
+ .equals("com.android.launcher3.tests.camera"));
+ when(mAllApps.getContext()).thenReturn(mContext);
+ mAlphabeticalAppsList.updateItemFilter(info -> info != null
+ && info.user.equals(MAIN_HANDLE));
+ when(mAllApps.getHeight()).thenReturn(ALL_APPS_HEIGHT);
+ when(mAllApps.getHeaderProtectionHeight()).thenReturn(HEADER_PROTECTION_HEIGHT);
+
+ // The number of adapterItems should be the private space apps + one main app.
+ assertEquals(NUM_PRIVATE_SPACE_APPS + 1,
+ mAlphabeticalAppsList.getAdapterItems().size());
+ assertEquals(SCROLL_NO_WHERE, mPsHeaderViewController.scrollForViewToBeVisibleInContainer(
+ new AllAppsRecyclerView(mContext),
+ mAlphabeticalAppsList.getAdapterItems(),
+ BIGGER_PS_HEADER_HEIGHT,
+ ALL_APPS_CELL_HEIGHT));
+ }
+
private Bitmap getBitmap(Drawable drawable) {
Bitmap result;
if (drawable instanceof BitmapDrawable) {
@@ -249,4 +360,28 @@
private static void awaitTasksCompleted() throws Exception {
UI_HELPER_EXECUTOR.submit(() -> null).get();
}
+
+ private int addPrivateSpaceHeader(List<BaseAllAppsAdapter.AdapterItem> adapterItemList) {
+ BaseAllAppsAdapter.AdapterItem privateSpaceHeader =
+ new BaseAllAppsAdapter.AdapterItem(VIEW_TYPE_PRIVATE_SPACE_HEADER);
+ adapterItemList.add(privateSpaceHeader);
+ return adapterItemList.size();
+ }
+
+ private AppInfo[] createAppInfoList() {
+ List<AppInfo> appInfos = new ArrayList<>();
+ ComponentName gmailComponentName = new ComponentName(mContext,
+ "com.android.launcher3.tests.Activity" + "Gmail");
+ AppInfo gmailAppInfo = new
+ AppInfo(gmailComponentName, "Gmail", MAIN_HANDLE, new Intent());
+ appInfos.add(gmailAppInfo);
+ ComponentName privateCameraComponentName = new ComponentName(
+ "com.android.launcher3.tests.camera", "CameraActivity");
+ for (int i = 0; i < NUM_PRIVATE_SPACE_APPS; i++) {
+ AppInfo privateCameraAppInfo = new AppInfo(privateCameraComponentName,
+ "Private Camera " + i, PRIVATE_HANDLE, new Intent());
+ appInfos.add(privateCameraAppInfo);
+ }
+ return appInfos.toArray(AppInfo[]::new);
+ }
}
diff --git a/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt b/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
new file mode 100644
index 0000000..479b201
--- /dev/null
+++ b/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.backuprestore
+
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.Flags
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.model.ModelDbController
+import com.android.launcher3.util.Executors.MODEL_EXECUTOR
+import com.android.launcher3.util.TestUtil
+import com.android.launcher3.util.rule.BackAndRestoreRule
+import com.android.launcher3.util.rule.setFlags
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Makes sure to test {@code RestoreDbTask#removeOldDBs}, we need to remove all the dbs that are not
+ * the last one used when we restore the device.
+ */
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class BackupAndRestoreDBSelectionTest {
+
+ @JvmField @Rule var backAndRestoreRule = BackAndRestoreRule()
+
+ @JvmField
+ @Rule
+ val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
+
+ @Before
+ fun setUp() {
+ setFlagsRule.setFlags(true, Flags.FLAG_ENABLE_NARROW_GRID_RESTORE)
+ }
+
+ @Test
+ fun oldDatabasesNotPresentAfterRestore() {
+ val dbController = ModelDbController(getInstrumentation().targetContext)
+ dbController.tryMigrateDB(null)
+ TestUtil.runOnExecutorSync(MODEL_EXECUTOR) {
+ assert(backAndRestoreRule.getDatabaseFiles().size == 1) {
+ "There should only be one database after restoring, the last one used. Actual databases ${backAndRestoreRule.getDatabaseFiles()}"
+ }
+ assert(
+ !LauncherPrefs.get(getInstrumentation().targetContext)
+ .has(LauncherPrefs.RESTORE_DEVICE)
+ ) {
+ "RESTORE_DEVICE shouldn't be present after a backup and restore."
+ }
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/dragging/TaplDragTest.java b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
index d1227d8..bf1ba39 100644
--- a/tests/src/com/android/launcher3/dragging/TaplDragTest.java
+++ b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
@@ -96,6 +96,21 @@
MAPS_APP_NAME);
}
+ /**
+ * Adds two icons to the Workspace and combines them into a folder, then makes sure we are able
+ * to remove an icon from the folder and that the folder ceases to exist since it only has one
+ * icon left.
+ */
+ @Test
+ public void testDragOutOfFolder() {
+ final HomeAppIcon playStoreIcon = createShortcutIfNotExist(STORE_APP_NAME, 0, 1);
+ final HomeAppIcon photosIcon = createShortcutInCenterIfNotExist(PHOTOS_APP_NAME);
+ FolderIcon folderIcon = photosIcon.dragToIcon(playStoreIcon);
+ Folder folder = folderIcon.open();
+ folder.getAppIcon(STORE_APP_NAME).internalDragToWorkspace(false, false);
+ assertNotNull(mLauncher.getWorkspace().tryGetWorkspaceAppIcon(STORE_APP_NAME));
+ assertNotNull(mLauncher.getWorkspace().tryGetWorkspaceAppIcon(PHOTOS_APP_NAME));
+ }
/** Drags a shortcut from a long press menu into the workspace.
* 1. Open all apps and wait for load complete.
diff --git a/tests/src/com/android/launcher3/model/GridMigrationTest.kt b/tests/src/com/android/launcher3/model/GridMigrationTest.kt
index eb8604e..15222a4 100644
--- a/tests/src/com/android/launcher3/model/GridMigrationTest.kt
+++ b/tests/src/com/android/launcher3/model/GridMigrationTest.kt
@@ -16,14 +16,18 @@
package com.android.launcher3.model
+import android.platform.test.flag.junit.SetFlagsRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.Flags
import com.android.launcher3.InvariantDeviceProfile.TYPE_PHONE
import com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME
import com.android.launcher3.celllayout.board.CellLayoutBoard
import com.android.launcher3.pm.UserCache
import com.android.launcher3.util.rule.TestToPhoneFileCopier
+import com.android.launcher3.util.rule.setFlags
+import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -65,11 +69,24 @@
class GridMigrationTest {
private val DB_FILE = "test_launcher.db"
+ @JvmField
+ @Rule
+ val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
+
// Copying the src db for all tests.
@JvmField
@Rule
val fileCopier =
- TestToPhoneFileCopier("databases/GridMigrationTest/$DB_FILE", "databases/$DB_FILE", true)
+ TestToPhoneFileCopier(
+ src = "databases/GridMigrationTest/$DB_FILE",
+ dest = "databases/$DB_FILE",
+ removeOnFinish = true
+ )
+
+ @Before
+ fun setup() {
+ setFlagsRule.setFlags(false, Flags.FLAG_ENABLE_GRID_MIGRATION_FIX)
+ }
private fun migrate(src: GridMigrationData, dst: GridMigrationData) {
GridSizeMigrationUtil.migrateGridIfNeeded(
@@ -86,8 +103,10 @@
* same space in the db.
*/
private fun validateDb(data: GridMigrationData) {
- val cellLayoutBoard = CellLayoutBoard(data.gridState.columns, data.gridState.rows)
+ // The array size is just a big enough number to fit all the number of workspaces
+ val boards = Array(100) { CellLayoutBoard(data.gridState.columns, data.gridState.rows) }
data.readEntries().forEach {
+ val cellLayoutBoard = boards[it.screenId]
assert(cellLayoutBoard.isEmpty(it.cellX, it.cellY, it.spanX, it.spanY)) {
"Db has overlapping items"
}
@@ -96,13 +115,13 @@
}
private fun compare(dst: GridMigrationData, target: GridMigrationData) {
- val sortX = { it: GridSizeMigrationUtil.DbEntry -> it.cellX }
- val sortY = { it: GridSizeMigrationUtil.DbEntry -> it.cellX }
+ val sort = compareBy<GridSizeMigrationUtil.DbEntry>({ it.cellX }, { it.cellY })
val mapF = { it: GridSizeMigrationUtil.DbEntry ->
EntryData(it.cellX, it.cellY, it.spanX, it.spanY, it.rank)
}
- val entriesDst = dst.readEntries().sortedBy(sortX).sortedBy(sortY).map(mapF)
- val entriesTarget = target.readEntries().sortedBy(sortX).sortedBy(sortY).map(mapF)
+ val entriesDst = dst.readEntries().sortedWith(sort).map(mapF)
+ val entriesTarget = target.readEntries().sortedWith(sort).map(mapF)
+
assert(entriesDst == entriesTarget) {
"The elements on the dst database is not the same as in the target"
}
@@ -128,9 +147,9 @@
@Rule
val result5x5to3x3 =
TestToPhoneFileCopier(
- "databases/GridMigrationTest/result5x5to3x3.db",
- "databases/result5x5to3x3.db",
- true
+ src = "databases/GridMigrationTest/result5x5to3x3.db",
+ dest = "databases/result5x5to3x3.db",
+ removeOnFinish = true
)
@Test
@@ -151,9 +170,9 @@
@Rule
val result5x5to4x7 =
TestToPhoneFileCopier(
- "databases/GridMigrationTest/result5x5to4x7.db",
- "databases/result5x5to4x7.db",
- true
+ src = "databases/GridMigrationTest/result5x5to4x7.db",
+ dest = "databases/result5x5to4x7.db",
+ removeOnFinish = true
)
@Test
@@ -174,9 +193,9 @@
@Rule
val result5x5to5x8 =
TestToPhoneFileCopier(
- "databases/GridMigrationTest/result5x5to5x8.db",
- "databases/result5x5to5x8.db",
- true
+ src = "databases/GridMigrationTest/result5x5to5x8.db",
+ dest = "databases/result5x5to5x8.db",
+ removeOnFinish = true
)
@Test
@@ -192,4 +211,32 @@
target =
GridMigrationData("result5x5to5x8.db", DeviceGridState(5, 8, 5, TYPE_PHONE, ""))
)
+
+ @JvmField
+ @Rule
+ val flaggedResult5x5to5x8 =
+ TestToPhoneFileCopier(
+ src = "databases/GridMigrationTest/flagged_result5x5to5x8.db",
+ dest = "databases/flagged_result5x5to5x8.db",
+ removeOnFinish = true
+ )
+
+ @Test
+ fun `flagged 5x5 to 5x8`() {
+ setFlagsRule.setFlags(true, Flags.FLAG_ENABLE_GRID_MIGRATION_FIX)
+ runTest(
+ src = GridMigrationData(DB_FILE, DeviceGridState(5, 5, 5, TYPE_PHONE, DB_FILE)),
+ dst =
+ GridMigrationData(
+ null, // in memory db, to download a new db change null for the filename of the
+ // db name to store it. Do not use existing names.
+ DeviceGridState(5, 8, 5, TYPE_PHONE, "")
+ ),
+ target =
+ GridMigrationData(
+ "flagged_result5x5to5x8.db",
+ DeviceGridState(5, 8, 5, TYPE_PHONE, "")
+ )
+ )
+ }
}
diff --git a/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java b/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
index 6df3ecd..90ded10 100644
--- a/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
+++ b/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
@@ -22,7 +22,7 @@
import static com.android.launcher3.BubbleTextView.DISPLAY_PREDICTION_ROW;
import static com.android.launcher3.BubbleTextView.DISPLAY_SEARCH_RESULT;
import static com.android.launcher3.BubbleTextView.DISPLAY_SEARCH_RESULT_SMALL;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_TWOLINE_ALLAPPS;
+import static com.android.launcher3.LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE;
import static com.google.common.truth.Truth.assertThat;
@@ -39,6 +39,7 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Flags;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.Utilities;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -46,7 +47,6 @@
import com.android.launcher3.util.ActivityContextWrapper;
import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.TestUtil;
import com.android.launcher3.views.BaseDragLayer;
import org.junit.Before;
@@ -96,13 +96,14 @@
private Context mContext;
private int mLimitedWidth;
private AppInfo mGmailAppInfo;
+ private LauncherPrefs mLauncherPrefs;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
Utilities.enableRunningInTestHarnessForTests();
- mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE);
mContext = new ActivityContextWrapper(getApplicationContext());
+ mLauncherPrefs = LauncherPrefs.get(mContext);
mBubbleTextView = new BubbleTextView(mContext);
mBubbleTextView.reset();
@@ -130,190 +131,155 @@
@Test
public void testEmptyString_flagOn() {
- mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
- try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true)) {
- mItemInfoWithIcon.title = EMPTY_STRING;
- mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
- mBubbleTextView.applyLabel(mItemInfoWithIcon);
- mBubbleTextView.setTypeface(Typeface.MONOSPACE);
- mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE);
+ mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true);
+ mItemInfoWithIcon.title = EMPTY_STRING;
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+ mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+ mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
- mBubbleTextView.onPreDraw();
+ mBubbleTextView.onPreDraw();
- assertNotEquals(TWO_LINE, mBubbleTextView.getMaxLines());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ assertNotEquals(TWO_LINE, mBubbleTextView.getMaxLines());
}
@Test
public void testEmptyString_flagOff() {
- mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
- try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false)) {
- mItemInfoWithIcon.title = EMPTY_STRING;
- mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
- mBubbleTextView.applyLabel(mItemInfoWithIcon);
- mBubbleTextView.setTypeface(Typeface.MONOSPACE);
- mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE);
+ mItemInfoWithIcon.title = EMPTY_STRING;
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+ mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+ mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
- mBubbleTextView.onPreDraw();
+ mBubbleTextView.onPreDraw();
- assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
}
@Test
public void testStringWithSpaceLongerThanCharLimit_flagOn() {
- mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
- try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true)) {
- // test string: "Battery Stats"
- mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
- mBubbleTextView.applyLabel(mItemInfoWithIcon);
- mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
- mBubbleTextView.setTypeface(Typeface.MONOSPACE);
- mBubbleTextView.measure(mLimitedWidth, MAX_HEIGHT);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE);
+ mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true);
+ // test string: "Battery Stats"
+ mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
+ mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+ mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+ mBubbleTextView.measure(mLimitedWidth, MAX_HEIGHT);
- mBubbleTextView.onPreDraw();
+ mBubbleTextView.onPreDraw();
- assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
}
@Test
public void testStringWithSpaceLongerThanCharLimit_flagOff() {
- mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
- try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false)) {
- // test string: "Battery Stats"
- mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
- mBubbleTextView.applyLabel(mItemInfoWithIcon);
- mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
- mBubbleTextView.setTypeface(Typeface.MONOSPACE);
- mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE);
+ // test string: "Battery Stats"
+ mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
+ mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+ mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+ mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
- mBubbleTextView.onPreDraw();
+ mBubbleTextView.onPreDraw();
- assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
}
@Test
public void testLongStringNoSpaceLongerThanCharLimit_flagOn() {
- mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
- try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true)) {
- // test string: "flutterappflorafy"
- mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT;
- mBubbleTextView.applyLabel(mItemInfoWithIcon);
- mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
- mBubbleTextView.setTypeface(Typeface.MONOSPACE);
- mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE);
+ mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true);
+ // test string: "flutterappflorafy"
+ mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT;
+ mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+ mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+ mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
- mBubbleTextView.onPreDraw();
+ mBubbleTextView.onPreDraw();
- assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
}
@Test
public void testLongStringNoSpaceLongerThanCharLimit_flagOff() {
- mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
- try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false)) {
- // test string: "flutterappflorafy"
- mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT;
- mBubbleTextView.applyLabel(mItemInfoWithIcon);
- mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
- mBubbleTextView.setTypeface(Typeface.MONOSPACE);
- mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE);
+ // test string: "flutterappflorafy"
+ mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT;
+ mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+ mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+ mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
- mBubbleTextView.onPreDraw();
+ mBubbleTextView.onPreDraw();
- assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
}
@Test
public void testLongStringWithSpaceLongerThanCharLimit_flagOn() {
- mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
- try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true)) {
- // test string: "System UWB Field Test"
- mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
- mBubbleTextView.applyLabel(mItemInfoWithIcon);
- mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
- mBubbleTextView.setTypeface(Typeface.MONOSPACE);
- mBubbleTextView.measure(mLimitedWidth, MAX_HEIGHT);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE);
+ mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true);
+ // test string: "System UWB Field Test"
+ mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
+ mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+ mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+ mBubbleTextView.measure(mLimitedWidth, MAX_HEIGHT);
- mBubbleTextView.onPreDraw();
+ mBubbleTextView.onPreDraw();
- assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
}
@Test
public void testLongStringWithSpaceLongerThanCharLimit_flagOff() {
- mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
- try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false)) {
- // test string: "System UWB Field Test"
- mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
- mBubbleTextView.applyLabel(mItemInfoWithIcon);
- mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
- mBubbleTextView.setTypeface(Typeface.MONOSPACE);
- mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE);
+ // test string: "System UWB Field Test"
+ mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
+ mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+ mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+ mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
- mBubbleTextView.onPreDraw();
+ mBubbleTextView.onPreDraw();
- assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
}
@Test
public void testLongStringSymbolLongerThanCharLimit_flagOn() {
- mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
- try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true)) {
- // test string: "LEGO®Builder"
- mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
- mBubbleTextView.applyLabel(mItemInfoWithIcon);
- mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
- mBubbleTextView.setTypeface(Typeface.MONOSPACE);
- mBubbleTextView.measure(mLimitedWidth, MAX_HEIGHT);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE);
+ mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true);
+ // test string: "LEGO®Builder"
+ mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
+ mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+ mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+ mBubbleTextView.measure(mLimitedWidth, MAX_HEIGHT);
- mBubbleTextView.onPreDraw();
+ mBubbleTextView.onPreDraw();
- assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
}
@Test
public void testLongStringSymbolLongerThanCharLimit_flagOff() {
- mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
- try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false)) {
- // test string: "LEGO®Builder"
- mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
- mBubbleTextView.applyLabel(mItemInfoWithIcon);
- mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
- mBubbleTextView.setTypeface(Typeface.MONOSPACE);
- mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE);
+ // test string: "LEGO®Builder"
+ mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
+ mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+ mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+ mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
- mBubbleTextView.onPreDraw();
+ mBubbleTextView.onPreDraw();
- assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
}
@Test
@@ -374,58 +340,49 @@
@Test
public void testEnsurePredictionRowIsTwoLine() {
- mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
- try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true)) {
- // test string: "Battery Stats"
- mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
- mBubbleTextView.setDisplay(DISPLAY_PREDICTION_ROW);
- mBubbleTextView.applyLabel(mItemInfoWithIcon);
- mBubbleTextView.setTypeface(Typeface.MONOSPACE);
- mBubbleTextView.measure(mLimitedWidth, MAX_HEIGHT);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE);
+ mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true);
+ // test string: "Battery Stats"
+ mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
+ mBubbleTextView.setDisplay(DISPLAY_PREDICTION_ROW);
+ mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+ mBubbleTextView.measure(mLimitedWidth, MAX_HEIGHT);
- mBubbleTextView.onPreDraw();
+ mBubbleTextView.onPreDraw();
- assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
}
@Test
public void modifyTitleToSupportMultiLine_whenLimitedHeight_shouldBeOneLine() {
- mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
- try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true)) {
- // test string: "LEGO®Builder"
- mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
- mBubbleTextView.applyLabel(mItemInfoWithIcon);
- mBubbleTextView.setTypeface(Typeface.MONOSPACE);
- mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE);
+ mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true);
+ // test string: "LEGO®Builder"
+ mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
+ mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+ mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
- mBubbleTextView.onPreDraw();
+ mBubbleTextView.onPreDraw();
- assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
}
@Test
public void modifyTitleToSupportMultiLine_whenUnlimitedHeight_shouldBeTwoLine() {
- mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
- try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true)) {
- // test string: "LEGO®Builder"
- mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
- mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
- mBubbleTextView.applyLabel(mItemInfoWithIcon);
- mBubbleTextView.setTypeface(Typeface.MONOSPACE);
- mBubbleTextView.measure(mLimitedWidth, MAX_HEIGHT);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE);
+ mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true);
+ // test string: "LEGO®Builder"
+ mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+ mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+ mBubbleTextView.measure(mLimitedWidth, MAX_HEIGHT);
- mBubbleTextView.onPreDraw();
+ mBubbleTextView.onPreDraw();
- assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
}
@Test
diff --git a/tests/src/com/android/launcher3/util/rule/TestToPhoneFileCopier.kt b/tests/src/com/android/launcher3/util/rule/TestToPhoneFileCopier.kt
index 72c4f16..d3516d1 100644
--- a/tests/src/com/android/launcher3/util/rule/TestToPhoneFileCopier.kt
+++ b/tests/src/com/android/launcher3/util/rule/TestToPhoneFileCopier.kt
@@ -49,7 +49,11 @@
object : Statement() {
override fun evaluate() {
before()
- base.evaluate()
+ try {
+ base.evaluate()
+ } finally {
+ after()
+ }
}
}
}
diff --git a/tests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt b/tests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
index 11855e6..edd2652 100644
--- a/tests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
+++ b/tests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
@@ -111,6 +111,18 @@
}
@Test
+ @RequiresFlagsEnabled(FLAG_ENABLE_GENERATED_PREVIEWS)
+ fun widgetItem_hasGeneratedPreview_nullPreview() {
+ appWidgetProviderInfo.generatedPreviewCategories =
+ WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_KEYGUARD
+ createWidgetItem()
+ assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_HOME_SCREEN)).isTrue()
+ // loadGeneratedPreview returns null for KEYGUARD, so this should still be false.
+ assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_KEYGUARD)).isFalse()
+ assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_SEARCHBOX)).isFalse()
+ }
+
+ @Test
@RequiresFlagsDisabled(FLAG_ENABLE_GENERATED_PREVIEWS)
fun widgetItem_hasGeneratedPreview_flagDisabled() {
assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_HOME_SCREEN)).isFalse()
diff --git a/tests/src/com/android/launcher3/widget/picker/WidgetImageViewTest.kt b/tests/src/com/android/launcher3/widget/picker/WidgetImageViewTest.kt
new file mode 100644
index 0000000..6e751e0
--- /dev/null
+++ b/tests/src/com/android/launcher3/widget/picker/WidgetImageViewTest.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.widget.picker
+
+import android.content.Context
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.android.launcher3.util.ActivityContextWrapper
+import com.android.launcher3.widget.WidgetImageView
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.spy
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.whenever
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class WidgetImageViewTest {
+ private lateinit var context: Context
+ private lateinit var widgetImageView: WidgetImageView
+
+ @Mock private lateinit var testDrawable: Drawable
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
+ widgetImageView = spy(WidgetImageView(context))
+ }
+
+ @Test
+ fun getBitmapBounds_aspectRatioLargerThanView_scaledByWidth() {
+ // view - 100 x 100
+ whenever(widgetImageView.width).thenReturn(100)
+ whenever(widgetImageView.height).thenReturn(100)
+ // bitmap - 200 x 100
+ whenever(testDrawable.intrinsicWidth).thenReturn(200)
+ whenever(testDrawable.intrinsicHeight).thenReturn(100)
+
+ widgetImageView.drawable = testDrawable
+ val bitmapBounds = widgetImageView.bitmapBounds
+
+ // new scaled width of bitmap is = 100, and height is scaled to 1/2 = 50
+ assertThat(bitmapBounds).isEqualTo(Rect(0, 25, 100, 75))
+ }
+
+ @Test
+ fun getBitmapBounds_aspectRatioSmallerThanView_scaledByHeight() {
+ // view - 100 x 100
+ whenever(widgetImageView.width).thenReturn(100)
+ whenever(widgetImageView.height).thenReturn(100)
+ // bitmap - 100 x 200
+ whenever(testDrawable.intrinsicWidth).thenReturn(100)
+ whenever(testDrawable.intrinsicHeight).thenReturn(200)
+ widgetImageView.drawable = testDrawable
+
+ val bitmapBounds = widgetImageView.bitmapBounds
+
+ // new scaled height of bitmap is = 100, and width is scaled to 1/2 = 50
+ assertThat(bitmapBounds).isEqualTo(Rect(25, 0, 75, 100))
+ }
+
+ @Test
+ fun getBitmapBounds_noScale_returnsOriginalDrawableBounds() {
+ // view - 200 x 100
+ whenever(widgetImageView.width).thenReturn(200)
+ whenever(widgetImageView.height).thenReturn(100)
+ // bitmap - 200 x 100
+ whenever(testDrawable.intrinsicWidth).thenReturn(200)
+ whenever(testDrawable.intrinsicHeight).thenReturn(100)
+
+ widgetImageView.drawable = testDrawable
+ val bitmapBounds = widgetImageView.bitmapBounds
+
+ // no scaling
+ assertThat(bitmapBounds).isEqualTo(Rect(0, 0, 200, 100))
+ }
+}
diff --git a/tests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java b/tests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
index c807771..60a4cd3 100644
--- a/tests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
+++ b/tests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
@@ -23,6 +23,7 @@
import static android.content.pm.ApplicationInfo.CATEGORY_SOCIAL;
import static android.content.pm.ApplicationInfo.CATEGORY_UNDEFINED;
import static android.content.pm.ApplicationInfo.CATEGORY_VIDEO;
+import static android.content.pm.ApplicationInfo.FLAG_INSTALLED;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -30,8 +31,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.when;
@@ -41,7 +41,7 @@
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
+import android.content.pm.LauncherApps;
import android.os.Process;
import androidx.test.core.content.pm.ApplicationInfoBuilder;
@@ -70,10 +70,25 @@
public class WidgetRecommendationCategoryProviderTest {
private static final String TEST_PACKAGE = "com.foo.test";
private static final String TEST_APP_NAME = "foo";
- public static final WidgetRecommendationCategory SOCIAL_AND_ENTERTAINMENT_CATEGORY =
+ private static final WidgetRecommendationCategory PRODUCTIVITY =
new WidgetRecommendationCategory(
- R.string.social_and_entertainment_widget_recommendation_category_label,
- /*order=*/4);
+ R.string.productivity_widget_recommendation_category_label,
+ /*order=*/0);
+ private static final WidgetRecommendationCategory NEWS =
+ new WidgetRecommendationCategory(
+ R.string.news_widget_recommendation_category_label, /*order=*/1);
+ private static final WidgetRecommendationCategory SUGGESTED_FOR_YOU =
+ new WidgetRecommendationCategory(
+ R.string.others_widget_recommendation_category_label, /*order=*/4);
+ private static final WidgetRecommendationCategory SOCIAL =
+ new WidgetRecommendationCategory(
+ R.string.social_widget_recommendation_category_label,
+ /*order=*/5);
+ private static final WidgetRecommendationCategory ENTERTAINMENT =
+ new WidgetRecommendationCategory(
+ R.string.entertainment_widget_recommendation_category_label,
+ /*order=*/6);
+
private final ApplicationInfo mTestAppInfo = ApplicationInfoBuilder.newBuilder().setPackageName(
TEST_PACKAGE).setName(TEST_APP_NAME).build();
private Context mContext;
@@ -82,7 +97,7 @@
private WidgetItem mTestWidgetItem;
@Mock
- private PackageManager mPackageManager;
+ private LauncherApps mLauncherApps;
private InvariantDeviceProfile mTestProfile;
@Before
@@ -90,10 +105,12 @@
MockitoAnnotations.initMocks(this);
mContext = new ContextWrapper(getInstrumentation().getTargetContext()) {
@Override
- public PackageManager getPackageManager() {
- return mPackageManager;
+ public Object getSystemService(String name) {
+ return LAUNCHER_APPS_SERVICE.equals(name) ? mLauncherApps : super.getSystemService(
+ name);
}
};
+ mTestAppInfo.flags = FLAG_INSTALLED;
mTestProfile = new InvariantDeviceProfile();
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
@@ -103,25 +120,22 @@
@Test
public void getWidgetRecommendationCategory_returnsMappedCategory() throws Exception {
ImmutableMap<Integer, WidgetRecommendationCategory> testCategories = ImmutableMap.of(
- CATEGORY_PRODUCTIVITY, new WidgetRecommendationCategory(
- R.string.productivity_widget_recommendation_category_label,
- /*order=*/
- 0),
- CATEGORY_NEWS, new WidgetRecommendationCategory(
- R.string.news_widget_recommendation_category_label, /*order=*/1),
- CATEGORY_SOCIAL, SOCIAL_AND_ENTERTAINMENT_CATEGORY,
- CATEGORY_AUDIO, SOCIAL_AND_ENTERTAINMENT_CATEGORY,
- CATEGORY_IMAGE, SOCIAL_AND_ENTERTAINMENT_CATEGORY,
- CATEGORY_VIDEO, SOCIAL_AND_ENTERTAINMENT_CATEGORY,
- CATEGORY_UNDEFINED, new WidgetRecommendationCategory(
- R.string.others_widget_recommendation_category_label, /*order=*/5));
+ CATEGORY_PRODUCTIVITY, PRODUCTIVITY,
+ CATEGORY_NEWS, NEWS,
+ CATEGORY_SOCIAL, SOCIAL,
+ CATEGORY_AUDIO, ENTERTAINMENT,
+ CATEGORY_IMAGE, ENTERTAINMENT,
+ CATEGORY_VIDEO, ENTERTAINMENT,
+ CATEGORY_UNDEFINED, SUGGESTED_FOR_YOU);
for (Map.Entry<Integer, WidgetRecommendationCategory> testCategory :
testCategories.entrySet()) {
mTestAppInfo.category = testCategory.getKey();
- when(mPackageManager.getApplicationInfo(anyString(), anyInt())).thenReturn(
- mTestAppInfo);
+ when(mLauncherApps.getApplicationInfo(/*packageName=*/ eq(TEST_PACKAGE),
+ /*flags=*/ eq(0),
+ /*user=*/ eq(Process.myUserHandle())))
+ .thenReturn(mTestAppInfo);
WidgetRecommendationCategory category = Executors.MODEL_EXECUTOR.submit(() ->
new WidgetRecommendationCategoryProvider().getWidgetRecommendationCategory(
diff --git a/tests/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizesTest.kt b/tests/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizesTest.kt
new file mode 100644
index 0000000..040fbf5
--- /dev/null
+++ b/tests/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizesTest.kt
@@ -0,0 +1,154 @@
+/*
+ * 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.widget.picker.util
+
+import android.content.ComponentName
+import android.content.Context
+import android.graphics.Point
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.model.WidgetItem
+import com.android.launcher3.util.ActivityContextWrapper
+import com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WidgetPreviewContainerSizesTest {
+ private lateinit var context: Context
+ private lateinit var deviceProfile: DeviceProfile
+ private lateinit var testInvariantProfile: InvariantDeviceProfile
+
+ @Mock private lateinit var iconCache: IconCache
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
+ testInvariantProfile = LauncherAppState.getIDP(context)
+ deviceProfile = testInvariantProfile.getDeviceProfile(context).copy(context)
+ }
+
+ @Test
+ fun widgetPreviewContainerSize_forItem_returnsCorrectContainerSize() {
+ val testSizes = getTestSizes(deviceProfile)
+ val expectedPreviewContainers = testSizes.values.toList()
+
+ for ((index, widgetSize) in testSizes.keys.withIndex()) {
+ val widgetItem = createWidgetItem(widgetSize, context, testInvariantProfile, iconCache)
+
+ assertWithMessage("size for $widgetSize should be: ${expectedPreviewContainers[index]}")
+ .that(WidgetPreviewContainerSize.forItem(widgetItem, deviceProfile))
+ .isEqualTo(expectedPreviewContainers[index])
+ }
+ }
+
+ companion object {
+ private const val TEST_PACKAGE = "com.google.test"
+
+ private val HANDHELD_TEST_SIZES: Map<Point, WidgetPreviewContainerSize> =
+ mapOf(
+ // 1x1
+ Point(1, 1) to WidgetPreviewContainerSize(1, 1),
+ // 2x1
+ Point(2, 1) to WidgetPreviewContainerSize(2, 1),
+ Point(3, 1) to WidgetPreviewContainerSize(2, 1),
+ // 4x1
+ Point(4, 1) to WidgetPreviewContainerSize(4, 1),
+ // 2x2
+ Point(2, 2) to WidgetPreviewContainerSize(2, 2),
+ Point(3, 3) to WidgetPreviewContainerSize(2, 2),
+ Point(3, 2) to WidgetPreviewContainerSize(2, 2),
+ // 2x3
+ Point(2, 3) to WidgetPreviewContainerSize(2, 3),
+ Point(3, 4) to WidgetPreviewContainerSize(2, 3),
+ Point(3, 5) to WidgetPreviewContainerSize(2, 3),
+ // 4x2
+ Point(4, 2) to WidgetPreviewContainerSize(4, 2),
+ // 4x3
+ Point(4, 3) to WidgetPreviewContainerSize(4, 3),
+ Point(4, 4) to WidgetPreviewContainerSize(4, 3),
+ )
+
+ private val TABLET_TEST_SIZES: Map<Point, WidgetPreviewContainerSize> =
+ mapOf(
+ // 1x1
+ Point(1, 1) to WidgetPreviewContainerSize(1, 1),
+ // 2x1
+ Point(2, 1) to WidgetPreviewContainerSize(2, 1),
+ // 3x1
+ Point(3, 1) to WidgetPreviewContainerSize(3, 1),
+ Point(4, 1) to WidgetPreviewContainerSize(3, 1),
+ // 2x2
+ Point(2, 2) to WidgetPreviewContainerSize(2, 2),
+ // 2x3
+ Point(2, 3) to WidgetPreviewContainerSize(2, 3),
+ // 3x2
+ Point(3, 2) to WidgetPreviewContainerSize(3, 2),
+ Point(4, 2) to WidgetPreviewContainerSize(3, 2),
+ Point(5, 2) to WidgetPreviewContainerSize(3, 2),
+ // 3x3
+ Point(3, 3) to WidgetPreviewContainerSize(3, 3),
+ Point(4, 4) to WidgetPreviewContainerSize(3, 3),
+ // 3x4
+ Point(5, 4) to WidgetPreviewContainerSize(3, 4),
+ Point(3, 4) to WidgetPreviewContainerSize(3, 4),
+ Point(5, 5) to WidgetPreviewContainerSize(3, 4),
+ Point(6, 4) to WidgetPreviewContainerSize(3, 4),
+ Point(6, 5) to WidgetPreviewContainerSize(3, 4),
+ )
+
+ private fun getTestSizes(dp: DeviceProfile) =
+ if (dp.isTablet && !dp.isTwoPanels) {
+ TABLET_TEST_SIZES
+ } else {
+ HANDHELD_TEST_SIZES
+ }
+
+ private fun createWidgetItem(
+ widgetSize: Point,
+ context: Context,
+ invariantDeviceProfile: InvariantDeviceProfile,
+ iconCache: IconCache
+ ): WidgetItem {
+ val providerInfo =
+ createAppWidgetProviderInfo(
+ ComponentName.createRelative(
+ TEST_PACKAGE,
+ /*cls=*/ ".WidgetProvider_" + widgetSize.x + "x" + widgetSize.y
+ )
+ )
+ val widgetInfo =
+ LauncherAppWidgetProviderInfo.fromProviderInfo(context, providerInfo).apply {
+ spanX = widgetSize.x
+ spanY = widgetSize.y
+ }
+ return WidgetItem(widgetInfo, invariantDeviceProfile, iconCache, context)
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java b/tests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java
index 2c5a396..b2cb266 100644
--- a/tests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java
+++ b/tests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java
@@ -63,6 +63,7 @@
private static final String TEST_PACKAGE = "com.google.test";
private static final int SPACE_SIZE = 10;
+ // Note - actual widget size includes SPACE_SIZE (border) + cell padding.
private static final int CELL_SIZE = 50;
private static final int NUM_OF_COLS = 5;
private static final int NUM_OF_ROWS = 5;
@@ -105,7 +106,7 @@
@Test
- public void groupWidgetItemsIntoTableWithReordering_widgetsOnly_maxSpanPxPerRow220_cellPadding0_shouldGroupWidgetsInTable() {
+ public void groupWithReordering_widgetsOnly_maxSpanPxPerRow220_cellPadding0() {
List<WidgetItem> widgetItems = List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4,
mWidget2x2);
@@ -113,17 +114,20 @@
WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgetItems, mContext,
mTestDeviceProfile, 220, 0);
- // Row 0: 1x1(50px), 2x2(110px)
- // Row 1: 2x3(110px), 2x4(110px)
- // Row 2: 4x4(230px)
- assertThat(widgetItemInTable).hasSize(3);
- assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1, mWidget2x2);
- assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x3, mWidget2x4);
- assertThat(widgetItemInTable.get(2)).containsExactly(mWidget4x4);
+ // With reordering, rows displayed in order of increasing size.
+ // Row 0: 1x1(50px)
+ // Row 1: 2x2(in a 2x2 container - 110px)
+ // Row 2: 2x3(in a 2x3 container - 110px), 2x4(in a 2x3 container - 110px)
+ // Row 3: 4x4(in a 3x3 container in tablet - 170px; 4x3 on phone - 230px)
+ assertThat(widgetItemInTable).hasSize(4);
+ assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1);
+ assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x2);
+ assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x3, mWidget2x4);
+ assertThat(widgetItemInTable.get(3)).containsExactly(mWidget4x4);
}
@Test
- public void groupWidgetItemsIntoTableWithReordering_widgetsOnly_maxSpanPxPerRow220_cellPadding10_shouldGroupWidgetsInTable() {
+ public void groupWithReordering_widgetsOnly_maxSpanPxPerRow220_cellPadding10() {
List<WidgetItem> widgetItems = List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4,
mWidget2x2);
@@ -131,9 +135,13 @@
WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgetItems, mContext,
mTestDeviceProfile, 220, 10);
- // Row 0: 1x1(50px), 2x2(110px)
- // Row 1: 2x3(110px), 2x4(110px)
- // Row 2: 4x4(230px)
+ // With reordering, but space taken up by cell padding, so, no grouping (even if 2x2 and 2x3
+ // use same preview container).
+ // Row 0: 1x1(50px)
+ // Row 1: 2x2(in a 2x2 container: 130px)
+ // Row 2: 2x3(in a 2x3 container: 130px)
+ // Row 3: 2x4(in a 2x3 container: 130px)
+ // Row 4: 4x4(in a 3x3 container in tablet - 190px; 4x3 on phone - 250px)
assertThat(widgetItemInTable).hasSize(5);
assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1);
assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x2);
@@ -143,7 +151,29 @@
}
@Test
- public void groupWidgetItemsIntoTableWithReordering_widgetsOnly_maxSpanPxPerRow350_cellPadding0_shouldGroupWidgetsInTable() {
+ public void groupWithReordering_widgetsOnly_maxSpanPxPerRow260_cellPadding10() {
+ List<WidgetItem> widgetItems = List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4,
+ mWidget2x2);
+
+ List<ArrayList<WidgetItem>> widgetItemInTable =
+ WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgetItems, mContext,
+ mTestDeviceProfile, 260, 10);
+
+ // With reordering, even with cellPadding, enough space to group 2x3 and 2x4 (which also use
+ // same container)
+ // Row 0: 1x1(50px)
+ // Row 1: 2x2(in a 2x2 container: 130px)
+ // Row 2: 2x3(in a 2x3 container: 130px), 2x4(in a 2x3 container: 130px)
+ // Row 3: 4x4(in a 3x3 container in tablet - 190px; 4x3 on phone - 250px)
+ assertThat(widgetItemInTable).hasSize(4);
+ assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1);
+ assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x2);
+ assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x3, mWidget2x4);
+ assertThat(widgetItemInTable.get(3)).containsExactly(mWidget4x4);
+ }
+
+ @Test
+ public void groupWithReordering_widgetsOnly_maxSpanPxPerRow350_cellPadding0() {
List<WidgetItem> widgetItems = List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4,
mWidget2x2);
@@ -151,17 +181,20 @@
WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgetItems, mContext,
mTestDeviceProfile, 350, 0);
- // Row 0: 1x1(50px), 2x2(110px), 2x3(110px)
- // Row 1: 2x4(110px)
- // Row 2: 4x4(230px)
- assertThat(widgetItemInTable).hasSize(3);
- assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1, mWidget2x2, mWidget2x3);
- assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x4);
- assertThat(widgetItemInTable.get(2)).containsExactly(mWidget4x4);
+ // With reordering, rows displayed in order of increasing size.
+ // Row 0: 1x1(50px)
+ // Row 1: 2x2(in a 2x2 container: 110px)
+ // Row 2: 2x3(in a 2x3 container: 110px), 2x4(in a 2x3 container: 110px)
+ // Row 3: 4x4(in a 3x3 container in tablet - 170px; 4x3 on phone - 230px)
+ assertThat(widgetItemInTable).hasSize(4);
+ assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1);
+ assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x2);
+ assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x3, mWidget2x4);
+ assertThat(widgetItemInTable.get(3)).containsExactly(mWidget4x4);
}
@Test
- public void groupWidgetItemsIntoTableWithReordering_mixItems_maxSpanPxPerRow350_cellPadding0_shouldGroupWidgetsInTable() {
+ public void groupWithReordering_mixItems_maxSpanPxPerRow350_cellPadding0() {
List<WidgetItem> widgetItems = List.of(mWidget4x4, mShortcut3, mWidget2x3, mShortcut1,
mWidget1x1, mShortcut2, mWidget2x4, mWidget2x2);
@@ -169,19 +202,22 @@
WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgetItems, mContext,
mTestDeviceProfile, 350, 0);
- // Row 0: 1x1(50px), 2x2(110px), 2x3(110px)
- // Row 1: 2x4(110px),
- // Row 2: 4x4(230px)
- // Row 3: shortcut3(50px), shortcut1(50px), shortcut2(50px)
- assertThat(widgetItemInTable).hasSize(4);
- assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1, mWidget2x2, mWidget2x3);
- assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x4);
- assertThat(widgetItemInTable.get(2)).containsExactly(mWidget4x4);
- assertThat(widgetItemInTable.get(3)).containsExactly(mShortcut3, mShortcut2, mShortcut1);
+ // With reordering - rows displays in order of increasing size:
+ // Row 0: 1x1(50px)
+ // Row 1: 2x2(110px)
+ // Row 2: 2x3 (in a 2x3 container 110px), 2x4 (in a 2x3 container 110px)
+ // Row 3: 4x4 (in a 3x3 container in tablet - 170px; 4x3 on phone - 230px)
+ // Row 4: shortcut3, shortcut1, shortcut2 (shortcuts are always displayed at bottom)
+ assertThat(widgetItemInTable).hasSize(5);
+ assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1);
+ assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x2);
+ assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x3, mWidget2x4);
+ assertThat(widgetItemInTable.get(3)).containsExactly(mWidget4x4);
+ assertThat(widgetItemInTable.get(4)).containsExactly(mShortcut3, mShortcut2, mShortcut1);
}
@Test
- public void groupWidgetItemsIntoTableWithoutReordering_maxSpanPxPerRow220_cellPadding0_shouldMaintainTheOrder() {
+ public void groupWithoutReordering_maxSpanPxPerRow220_cellPadding0() {
List<WidgetItem> widgetItems =
List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4, mWidget2x2);
@@ -189,13 +225,19 @@
WidgetsTableUtils.groupWidgetItemsUsingRowPxWithoutReordering(widgetItems, mContext,
mTestDeviceProfile, 220, 0);
- // Row 0: 4x4(230px)
- // Row 1: 2x3(110px), 1x1(50px)
- // Row 2: 2x4(110px), 2x2(110px)
- assertThat(widgetItemInTable).hasSize(3);
+ // Without reordering, widgets are grouped only if the next one fits and uses same preview
+ // container:
+ // Row 0: 4x4(in a 3x3 container in tablet - 170px; 4x3 on phone - 230px)
+ // Row 1: 2x3(in a 2x3 container - 110px)
+ // Row 2: 1x1(50px)
+ // Row 3: 2x4(in a 2x3 container - 110px)
+ // Row 4: 2x2(in a 2x2 container - 110px)
+ assertThat(widgetItemInTable).hasSize(5);
assertThat(widgetItemInTable.get(0)).containsExactly(mWidget4x4);
- assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x3, mWidget1x1);
- assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x4, mWidget2x2);
+ assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x3);
+ assertThat(widgetItemInTable.get(2)).containsExactly(mWidget1x1);
+ assertThat(widgetItemInTable.get(3)).containsExactly(mWidget2x4);
+ assertThat(widgetItemInTable.get(4)).containsExactly(mWidget2x2);
}
private void initDP() {
diff --git a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
index b5414b7..3f96999 100644
--- a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
+++ b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
@@ -26,11 +26,11 @@
import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_TASKBAR_FROM_NAV_THRESHOLD;
import static com.android.launcher3.testing.shared.TestProtocol.SUCCESSFUL_GESTURE_MISMATCH_EVENTS;
import static com.android.launcher3.testing.shared.TestProtocol.TEST_INFO_RESPONSE_FIELD;
-import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.SystemClock;
+import android.util.Log;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
@@ -141,7 +141,7 @@
return new Taskbar(mLauncher);
} finally {
- testLogD(SUCCESSFUL_GESTURE_MISMATCH_EVENTS,
+ Log.d(SUCCESSFUL_GESTURE_MISMATCH_EVENTS,
"swipeUpToUnstashTaskbar: completed gesture");
mLauncher.getTestInfo(REQUEST_DISABLE_BLOCK_TIMEOUT);
}
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index afe5722..1cfbf09 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -19,8 +19,10 @@
import static com.android.launcher3.tapl.OverviewTask.OverviewSplitTask.DEFAULT;
import static com.android.launcher3.tapl.OverviewTask.OverviewSplitTask.SPLIT_BOTTOM_OR_RIGHT;
import static com.android.launcher3.tapl.OverviewTask.OverviewSplitTask.SPLIT_TOP_OR_LEFT;
+import static com.android.launcher3.testing.shared.TestProtocol.SUCCESSFUL_GESTURE_MISMATCH_EVENTS;
import android.graphics.Rect;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.test.uiautomator.By;
@@ -219,6 +221,7 @@
return new LaunchedAppState(mLauncher);
}
} else {
+ Log.d(SUCCESSFUL_GESTURE_MISMATCH_EVENTS, "TaskView.launchTaskAnimated");
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
return new LaunchedAppState(mLauncher);
}