Merge "Rename the color attr for paging dot indicator used in launcher." 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/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 cf9a68b..5f69a9c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -1326,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();
}
@@ -1557,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/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 8c92c7d..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
@@ -136,7 +137,8 @@
// Sync scroll so that it ends before or at the same time as the taskbar animation.
if (mActivity.getDeviceProfile().isTaskbarPresent) {
- config.duration = Math.min(config.duration, TASKBAR_TO_HOME_DURATION);
+ 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/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index d6a727f..c56a621 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -33,7 +33,6 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
@@ -399,8 +398,7 @@
&& (mSystemUiStateFlags & SYSUI_STATE_MAGNIFICATION_OVERLAP) == 0
&& ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
|| (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0)
- && (mSystemUiStateFlags & SYSUI_STATE_DEVICE_DREAMING) == 0
- && (mSystemUiStateFlags & SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION) == 0;
+ && (mSystemUiStateFlags & SYSUI_STATE_DEVICE_DREAMING) == 0;
}
/**
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 7880124..719c4f7 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -754,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/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/orientation/LandscapePagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.java
deleted file mode 100644
index f345aeb..0000000
--- a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.java
+++ /dev/null
@@ -1,704 +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 int getTaskMenuHeight(float taskInsetMargin, DeviceProfile deviceProfile,
- float taskMenuX, float taskMenuY) {
- return (int) (taskMenuX - taskInsetMargin);
- }
-
- @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 5cd9776..62dfd82 100644
--- a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
+++ b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
@@ -795,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 4b65d53..0000000
--- a/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.java
+++ /dev/null
@@ -1,261 +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);
-
- int getTaskMenuHeight(float taskInsetMargin, DeviceProfile deviceProfile, float taskMenuX,
- float taskMenuY);
- /**
- * 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 89c678c..0000000
--- a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.java
+++ /dev/null
@@ -1,418 +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 int getTaskMenuHeight(float taskInsetMargin, DeviceProfile deviceProfile,
- float taskMenuX, float taskMenuY) {
- return (int) (deviceProfile.availableWidthPx - taskInsetMargin - taskMenuX);
- }
-
- @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/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 133749d..4985f0b 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -4147,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) {
@@ -4231,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);
}
@@ -5031,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());
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 97737fb..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>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index a9cf2ff..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() {
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 4b4bdc2..c5cb811 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;
@@ -436,7 +437,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.
@@ -1233,9 +1238,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/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/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/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 c40484d..15190c7 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationUtil.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
@@ -124,7 +124,7 @@
return true;
}
- if (Flags.gridMigrationFix()
+ 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
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 f2b7d18..99542f3 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -123,7 +123,7 @@
// executed again.
LauncherPrefs.get(context).removeSync(RESTORE_DEVICE);
- if (Flags.narrowGridRestore()) {
+ if (Flags.enableNarrowGridRestore()) {
String oldPhoneFileName = idp.dbFile;
removeOldDBs(context, oldPhoneFileName);
trySettingPreviousGidAsCurrent(context, idp, oldPhoneFileName);
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/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/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/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index ab1ad70..89f8181 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -118,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
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/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/backuprestore/BackupAndRestoreDBSelectionTest.kt b/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
index 3e36bbb..479b201 100644
--- a/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
+++ b/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
@@ -48,7 +48,7 @@
@Before
fun setUp() {
- setFlagsRule.setFlags(true, Flags.FLAG_NARROW_GRID_RESTORE)
+ setFlagsRule.setFlags(true, Flags.FLAG_ENABLE_NARROW_GRID_RESTORE)
}
@Test
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 da4a208..15222a4 100644
--- a/tests/src/com/android/launcher3/model/GridMigrationTest.kt
+++ b/tests/src/com/android/launcher3/model/GridMigrationTest.kt
@@ -85,7 +85,7 @@
@Before
fun setup() {
- setFlagsRule.setFlags(false, Flags.FLAG_GRID_MIGRATION_FIX)
+ setFlagsRule.setFlags(false, Flags.FLAG_ENABLE_GRID_MIGRATION_FIX)
}
private fun migrate(src: GridMigrationData, dst: GridMigrationData) {
@@ -223,7 +223,7 @@
@Test
fun `flagged 5x5 to 5x8`() {
- setFlagsRule.setFlags(true, Flags.FLAG_GRID_MIGRATION_FIX)
+ setFlagsRule.setFlags(true, Flags.FLAG_ENABLE_GRID_MIGRATION_FIX)
runTest(
src = GridMigrationData(DB_FILE, DeviceGridState(5, 5, 5, TYPE_PHONE, DB_FILE)),
dst =
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