Merge "Add aconfig flag for gesture nav handling on connected displays." into main
diff --git a/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml b/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml
index 7530c28..8ca59c4 100644
--- a/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml
+++ b/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml
@@ -137,6 +137,7 @@
android:layout_above="@id/gesture_tutorial_fragment_action_button"
android:layout_centerHorizontal="true"
android:background="@android:color/transparent"
+ android:screenReaderFocusable="true"
android:paddingTop="24dp"
android:paddingHorizontal="24dp"
android:layout_marginBottom="16dp">
@@ -146,8 +147,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="104dp"
- android:accessibilityHeading="true"
- android:accessibilityTraversalBefore="@id/gesture_tutorial_fragment_feedback_subtitle"
android:gravity="top"
android:lineSpacingExtra="-1sp"
android:textAppearance="@style/TextAppearance.GestureTutorial.MainTitle"
@@ -162,8 +161,6 @@
android:layout_marginTop="24dp"
android:lineSpacingExtra="4sp"
android:textAppearance="@style/TextAppearance.GestureTutorial.MainSubtitle"
- android:accessibilityTraversalAfter="@id/gesture_tutorial_fragment_feedback_title"
- android:accessibilityTraversalBefore="@id/gesture_tutorial_fragment_action_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/gesture_tutorial_fragment_feedback_title" />
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
index 04e1905..b230fa6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
@@ -38,7 +38,6 @@
import com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW
import com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE
import com.android.quickstep.util.AnimUtils
-import com.android.quickstep.views.ClearAllButton
import com.android.quickstep.views.RecentsView
import com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET
import com.android.quickstep.views.RecentsView.CONTENT_ALPHA
@@ -288,8 +287,8 @@
val clearAllButtonAlpha =
if (state.areElementsVisible(launcher, LauncherState.CLEAR_ALL_BUTTON)) 1f else 0f
propertySetter.setFloat(
- recentsView.clearAllButton,
- ClearAllButton.VISIBILITY_ALPHA,
+ recentsView.clearAllButton.visibilityAlphaProperty,
+ MULTI_PROPERTY_VALUE,
clearAllButtonAlpha,
LINEAR,
)
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 537092f..51f2a33 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -52,7 +52,6 @@
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
-import com.android.quickstep.views.ClearAllButton;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.RecentsViewContainer;
@@ -98,8 +97,8 @@
private void setProperties(RecentsState state, StateAnimationConfig config,
PropertySetter setter) {
float clearAllButtonAlpha = state.hasClearAllButton() ? 1 : 0;
- setter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
- clearAllButtonAlpha, LINEAR);
+ setter.setFloat(mRecentsView.getClearAllButton().visibilityAlphaProperty,
+ MULTI_PROPERTY_VALUE, clearAllButtonAlpha, LINEAR);
if (mRecentsView.getAddDeskButton() != null) {
float addDeskButtonAlpha = state.hasAddDeskButton() ? 1 : 0;
setter.setFloat(mRecentsView.getAddDeskButton().getVisibilityAlphaProperty(),
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index e73fb3b..22227c9 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -36,7 +36,6 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
-import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.Button;
import android.widget.FrameLayout;
@@ -124,13 +123,10 @@
// These runnables should be used when posting callbacks to their views and cleared from their
// views before posting new callbacks.
- private final Runnable mTitleViewCallback;
- private final Runnable mSubtitleViewCallback;
@Nullable private Runnable mFeedbackViewCallback;
@Nullable private Runnable mFakeTaskViewCallback;
@Nullable private Runnable mFakeTaskbarViewCallback;
private final Runnable mShowFeedbackRunnable;
- private final AccessibilityManager mAccessibilityManager;
TutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) {
mTutorialFragment = tutorialFragment;
@@ -187,17 +183,6 @@
outline.setRoundRect(mExitingAppRect, mExitingAppRadius);
}
});
-
- mAccessibilityManager = AccessibilityManager.getInstance(mContext);
- mTitleViewCallback = () -> {
- mFeedbackTitleView.requestFocus();
- mFeedbackTitleView.sendAccessibilityEvent(
- AccessibilityEvent.TYPE_VIEW_FOCUSED);
- };
- mSubtitleViewCallback = () -> {
- mFeedbackSubtitleView.requestFocus();
- mFeedbackSubtitleView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
- };
mShowFeedbackRunnable = () -> {
mFeedbackView.setAlpha(0f);
mFeedbackView.setScaleX(0.95f);
@@ -216,14 +201,14 @@
mFeedbackViewCallback = mTutorialFragment::continueTutorial;
mFeedbackView.postDelayed(
mFeedbackViewCallback,
- mAccessibilityManager.getRecommendedTimeoutMillis(
- ADVANCE_TUTORIAL_TIMEOUT_MS,
- AccessibilityManager.FLAG_CONTENT_TEXT
+ AccessibilityManager.getInstance(mContext)
+ .getRecommendedTimeoutMillis(
+ ADVANCE_TUTORIAL_TIMEOUT_MS,
+ AccessibilityManager.FLAG_CONTENT_TEXT
| AccessibilityManager.FLAG_CONTENT_CONTROLS));
}
})
.start();
- mFeedbackTitleView.postDelayed(mTitleViewCallback, FEEDBACK_ANIMATION_MS);
};
}
@@ -416,8 +401,6 @@
int titleResId,
int subtitleResId,
boolean isGestureSuccessful) {
- mFeedbackTitleView.removeCallbacks(mTitleViewCallback);
- mFeedbackSubtitleView.removeCallbacks(mSubtitleViewCallback);
if (mFeedbackViewCallback != null) {
mFeedbackView.removeCallbacks(mFeedbackViewCallback);
mFeedbackViewCallback = null;
@@ -425,15 +408,6 @@
mFeedbackTitleView.setText(titleResId);
mFeedbackSubtitleView.setText(subtitleResId);
- mFeedbackTitleView.postDelayed(mTitleViewCallback, mAccessibilityManager
- .getRecommendedTimeoutMillis(
- FEEDBACK_ANIMATION_MS,
- AccessibilityManager.FLAG_CONTENT_TEXT));
- mFeedbackSubtitleView.postDelayed(mSubtitleViewCallback, mAccessibilityManager
- .getRecommendedTimeoutMillis(
- SUBTITLE_ANNOUNCE_DELAY_MS,
- AccessibilityManager.FLAG_CONTENT_TEXT));
-
if (isGestureSuccessful) {
if (mTutorialFragment.isAtFinalStep()) {
TypefaceUtils.setTypeface(
@@ -494,8 +468,6 @@
mFakeTaskbarView.removeCallbacks(mFakeTaskbarViewCallback);
mFakeTaskbarViewCallback = null;
}
- mFeedbackTitleView.removeCallbacks(mTitleViewCallback);
- mFeedbackSubtitleView.removeCallbacks(mSubtitleViewCallback);
}
private void playFeedbackAnimation() {
@@ -588,13 +560,6 @@
mSkipButton.setVisibility(GONE);
mDoneButton.setVisibility(View.VISIBLE);
mDoneButton.setOnClickListener(this::onActionButtonClicked);
- mDoneButton.postDelayed(() -> {
- mDoneButton.requestFocus();
- mDoneButton.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
- }, mAccessibilityManager
- .getRecommendedTimeoutMillis(
- DONE_BUTTON_ANNOUNCE_DELAY_MS,
- AccessibilityManager.FLAG_CONTENT_CONTROLS));
}
void hideFakeTaskbar(boolean animateToHotseat) {
diff --git a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
index 17f861d..ee9b3e8 100644
--- a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
@@ -103,7 +103,7 @@
target: T,
action: Int2DAction<T>,
primaryParam: Int,
- secondaryParam: Int
+ secondaryParam: Int,
) = action.call(target, secondaryParam, primaryParam)
override fun getPrimaryDirection(event: MotionEvent, pointerIndex: Int): Float =
@@ -171,7 +171,7 @@
override fun getSplitTranslationDirectionFactor(
stagePosition: Int,
- deviceProfile: DeviceProfile
+ deviceProfile: DeviceProfile,
): Int = if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) -1 else 1
override fun getTaskMenuX(
@@ -179,7 +179,7 @@
thumbnailView: View,
deviceProfile: DeviceProfile,
taskInsetMargin: Float,
- taskViewIcon: View
+ taskViewIcon: View,
): Float = thumbnailView.measuredWidth + x - taskInsetMargin
override fun getTaskMenuY(
@@ -188,7 +188,7 @@
stagePosition: Int,
taskMenuView: View,
taskInsetMargin: Float,
- taskViewIcon: View
+ taskViewIcon: View,
): Float {
val layoutParams = taskMenuView.layoutParams as BaseDragLayer.LayoutParams
var taskMenuY = y + taskInsetMargin
@@ -203,7 +203,7 @@
override fun getTaskMenuWidth(
thumbnailView: View,
deviceProfile: DeviceProfile,
- @StagePosition stagePosition: Int
+ @StagePosition stagePosition: Int,
): Int =
when {
Flags.enableOverviewIconMenu() ->
@@ -218,14 +218,14 @@
taskInsetMargin: Float,
deviceProfile: DeviceProfile,
taskMenuX: Float,
- taskMenuY: Float
+ taskMenuY: Float,
): Int = (taskMenuX - taskInsetMargin).toInt()
override fun setTaskOptionsMenuLayoutOrientation(
deviceProfile: DeviceProfile,
taskMenuLayout: LinearLayout,
dividerSpacing: Int,
- dividerDrawable: ShapeDrawable
+ dividerDrawable: ShapeDrawable,
) {
taskMenuLayout.orientation = LinearLayout.VERTICAL
dividerDrawable.intrinsicHeight = dividerSpacing
@@ -235,7 +235,7 @@
override fun setLayoutParamsForTaskMenuOptionItem(
lp: LinearLayout.LayoutParams,
viewGroup: LinearLayout,
- deviceProfile: DeviceProfile
+ deviceProfile: DeviceProfile,
) {
// Phone fake landscape
viewGroup.orientation = LinearLayout.HORIZONTAL
@@ -250,7 +250,7 @@
deviceProfile: DeviceProfile,
snapshotViewWidth: Int,
snapshotViewHeight: Int,
- banner: View
+ banner: View,
) {
banner.pivotX = 0f
banner.pivotY = 0f
@@ -273,7 +273,7 @@
deviceProfile: DeviceProfile,
thumbnailViews: Array<View>,
desiredTaskId: Int,
- banner: View
+ banner: View,
): Pair<Float, Float> {
val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
val translationX = banner.height.toFloat()
@@ -317,7 +317,7 @@
child: View,
childStart: Int,
pageCenter: Int,
- layoutChild: Boolean
+ layoutChild: Boolean,
): ChildBounds {
val childHeight = child.measuredHeight
val childWidth = child.measuredWidth
@@ -338,7 +338,7 @@
R.drawable.ic_split_horizontal,
R.string.recent_task_option_split_screen,
STAGE_POSITION_TOP_OR_LEFT,
- STAGE_TYPE_MAIN
+ STAGE_TYPE_MAIN,
)
)
@@ -347,7 +347,7 @@
placeholderInset: Int,
dp: DeviceProfile,
@StagePosition stagePosition: Int,
- out: Rect
+ 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.
@@ -374,7 +374,7 @@
drawableWidth: Int,
drawableHeight: Int,
dp: DeviceProfile,
- @StagePosition stagePosition: Int
+ @StagePosition stagePosition: Int,
) {
val insetAdjustment = getPlaceholderSizeAdjustment(dp) / 2f
out.x = (onScreenRectCenterX / fullscreenScaleX - 1.0f * drawableWidth / 2)
@@ -393,7 +393,7 @@
out: View,
dp: DeviceProfile,
splitInstructionsHeight: Int,
- splitInstructionsWidth: Int
+ splitInstructionsWidth: Int,
) {
out.pivotX = 0f
out.pivotY = splitInstructionsHeight.toFloat()
@@ -421,7 +421,7 @@
dp: DeviceProfile,
@StagePosition stagePosition: Int,
out1: Rect,
- out2: Rect
+ out2: Rect,
) {
// In fake land/seascape, the window bounds are always top and bottom half
val screenHeight = dp.heightPx
@@ -434,7 +434,7 @@
dp: DeviceProfile,
outRect: Rect,
splitInfo: SplitBounds,
- desiredStagePosition: Int
+ desiredStagePosition: Int,
) {
val topLeftTaskPercent = splitInfo.leftTopTaskPercent
val dividerBarPercent = splitInfo.dividerPercent
@@ -448,7 +448,7 @@
/**
* @param inSplitSelection Whether user currently has a task from this task group staged for
- * split screen. Currently this state is not reachable in fake landscape.
+ * split screen. Currently this state is not reachable in fake landscape.
*/
override fun measureGroupedTaskViewThumbnailBounds(
primarySnapshot: View,
@@ -458,7 +458,7 @@
splitBoundsConfig: SplitBounds,
dp: DeviceProfile,
isRtl: Boolean,
- inSplitSelection: Boolean
+ inSplitSelection: Boolean,
) {
val primaryParams = primarySnapshot.layoutParams as FrameLayout.LayoutParams
val secondaryParams = secondarySnapshot.layoutParams as FrameLayout.LayoutParams
@@ -479,13 +479,13 @@
primarySnapshot.translationY = spaceAboveSnapshot.toFloat()
primarySnapshot.measure(
MeasureSpec.makeMeasureSpec(taskViewFirst.x, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(taskViewFirst.y, 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)
+ MeasureSpec.makeMeasureSpec(taskViewSecond.y, MeasureSpec.EXACTLY),
)
}
@@ -493,7 +493,7 @@
dp: DeviceProfile,
splitBoundsConfig: SplitBounds,
parentWidth: Int,
- parentHeight: Int
+ parentHeight: Int,
): Pair<Point, Point> {
val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
@@ -511,7 +511,7 @@
taskIconMargin: Int,
taskIconHeight: Int,
thumbnailTopMargin: Int,
- isRtl: Boolean
+ isRtl: Boolean,
) {
iconParams.gravity =
if (isRtl) {
@@ -527,7 +527,7 @@
override fun setIconAppChipChildrenParams(
iconParams: FrameLayout.LayoutParams,
- chipChildMarginStart: Int
+ chipChildMarginStart: Int,
) {
iconParams.gravity = Gravity.START or Gravity.CENTER_VERTICAL
iconParams.marginStart = chipChildMarginStart
@@ -538,7 +538,7 @@
iconAppChipView: IconAppChipView,
iconMenuParams: FrameLayout.LayoutParams,
iconMenuMargin: Int,
- thumbnailTopMargin: Int
+ thumbnailTopMargin: Int,
) {
val isRtl = iconAppChipView.layoutDirection == View.LAYOUT_DIRECTION_RTL
@@ -564,7 +564,7 @@
/**
* @param inSplitSelection Whether user currently has a task from this task group staged for
- * split screen. Currently this state is not reachable in fake landscape.
+ * split screen. Currently this state is not reachable in fake landscape.
*/
override fun setSplitIconParams(
primaryIconView: View,
@@ -606,20 +606,20 @@
override fun <T> getSplitSelectTaskOffset(
primary: FloatProperty<T>,
secondary: FloatProperty<T>,
- deviceProfile: DeviceProfile
+ deviceProfile: DeviceProfile,
): Pair<FloatProperty<T>, FloatProperty<T>> = Pair(primary, secondary)
override fun getFloatingTaskOffscreenTranslationTarget(
floatingTask: View,
onScreenRect: RectF,
@StagePosition stagePosition: Int,
- dp: DeviceProfile
+ dp: DeviceProfile,
): Float = floatingTask.translationY - onScreenRect.height()
override fun setFloatingTaskPrimaryTranslation(
floatingTask: View,
translation: Float,
- dp: DeviceProfile
+ dp: DeviceProfile,
) {
floatingTask.translationY = translation
}
@@ -660,12 +660,11 @@
} else {
if (oneIconHiddenDueToSmallWidth) {
// Center both icons
- val centerY = primarySnapshotHeight + overviewTaskMarginPx +
+ val centerY =
+ primarySnapshotHeight +
+ overviewTaskMarginPx +
((taskIconHeight + dividerSize) / 2)
- SplitIconPositions(
- topLeftY = centerY,
- bottomRightY = centerY,
- )
+ SplitIconPositions(topLeftY = centerY, bottomRightY = centerY)
} else {
val topLeftY = primarySnapshotHeight + overviewTaskMarginPx
SplitIconPositions(
diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
deleted file mode 100644
index c4e82d6..0000000
--- a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
+++ /dev/null
@@ -1,872 +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_HORIZONTAL;
-import static android.view.Gravity.END;
-import static android.view.Gravity.START;
-import static android.view.Gravity.TOP;
-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.VERTICAL;
-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_TYPE_MAIN;
-
-import android.graphics.Matrix;
-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.Surface;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.logger.LauncherAtom;
-import com.android.launcher3.touch.DefaultPagedViewHandler;
-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.ArrayList;
-import java.util.List;
-
-public class PortraitPagedViewHandler extends DefaultPagedViewHandler implements
- RecentsPagedOrientationHandler {
-
- private final Matrix mTmpMatrix = new Matrix();
- private final RectF mTmpRectF = new RectF();
-
- @Override
- public <T> T getPrimaryValue(T x, T y) {
- return x;
- }
-
- @Override
- public <T> T getSecondaryValue(T x, T y) {
- return y;
- }
-
- @Override
- public boolean isLayoutNaturalToLauncher() {
- return true;
- }
-
- @Override
- public void adjustFloatingIconStartVelocity(PointF velocity) {
- //no-op
- }
-
- @Override
- public void fixBoundsForHomeAnimStartRect(RectF outStartRect, DeviceProfile deviceProfile) {
- if (outStartRect.left > deviceProfile.widthPx) {
- outStartRect.offsetTo(0, outStartRect.top);
- } else if (outStartRect.left < -deviceProfile.widthPx) {
- outStartRect.offsetTo(0, outStartRect.top);
- }
- }
-
- @Override
- public <T> void setSecondary(T target, Float2DAction<T> action, float param) {
- action.call(target, 0, param);
- }
-
- @Override
- public <T> void set(T target, Int2DAction<T> action, int primaryParam,
- int secondaryParam) {
- action.call(target, primaryParam, secondaryParam);
- }
-
- @Override
- public int getPrimarySize(View view) {
- return view.getWidth();
- }
-
- @Override
- public float getPrimarySize(RectF rect) {
- return rect.width();
- }
-
- @Override
- public float getStart(RectF rect) {
- return rect.left;
- }
-
- @Override
- public float getEnd(RectF rect) {
- return rect.right;
- }
-
- @Override
- public void rotateInsets(@NonNull Rect insets, @NonNull Rect outInsets) {
- outInsets.set(insets);
- }
-
- @Override
- public int getClearAllSidePadding(View view, boolean isRtl) {
- return (isRtl ? view.getPaddingRight() : - view.getPaddingLeft()) / 2;
- }
-
- @Override
- public int getSecondaryDimension(View view) {
- return view.getHeight();
- }
-
- @Override
- public FloatProperty<View> getPrimaryViewTranslate() {
- return VIEW_TRANSLATE_X;
- }
-
- @Override
- public FloatProperty<View> getSecondaryViewTranslate() {
- return VIEW_TRANSLATE_Y;
- }
-
- @Override
- public float getDegreesRotated() {
- return 0;
- }
-
- @Override
- public int getRotation() {
- return Surface.ROTATION_0;
- }
-
- @Override
- public void setPrimaryScale(View view, float scale) {
- view.setScaleX(scale);
- }
-
- @Override
- public void setSecondaryScale(View view, float scale) {
- view.setScaleY(scale);
- }
-
- public int getSecondaryTranslationDirectionFactor() {
- return -1;
- }
-
- @Override
- public int getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile) {
- if (deviceProfile.isLeftRightSplit && 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) {
- if (deviceProfile.isLandscape) {
- return x + taskInsetMargin
- + (thumbnailView.getMeasuredWidth() - thumbnailView.getMeasuredHeight()) / 2f;
- } else {
- return x + taskInsetMargin;
- }
- }
-
- @Override
- public float getTaskMenuY(float y, View thumbnailView, int stagePosition,
- View taskMenuView, float taskInsetMargin, View taskViewIcon) {
- 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);
- }
- int padding = thumbnailView.getResources()
- .getDimensionPixelSize(R.dimen.task_menu_edge_padding);
- return (deviceProfile.isLandscape && !deviceProfile.isTablet
- ? thumbnailView.getMeasuredHeight()
- : thumbnailView.getMeasuredWidth()) - (2 * padding);
- }
-
- @Override
- public int getTaskMenuHeight(float taskInsetMargin, DeviceProfile deviceProfile,
- float taskMenuX, float taskMenuY) {
- return (int) (deviceProfile.heightPx - deviceProfile.getInsets().top - taskMenuY
- - deviceProfile.getOverviewActionsClaimedSpaceBelow());
- }
-
- @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) {
- viewGroup.setOrientation(LinearLayout.HORIZONTAL);
- lp.width = LinearLayout.LayoutParams.MATCH_PARENT;
- lp.height = WRAP_CONTENT;
- }
-
- @Override
- public void updateDwbBannerLayout(int taskViewWidth, int taskViewHeight,
- boolean isGroupedTaskView, @NonNull DeviceProfile deviceProfile,
- int snapshotViewWidth, int snapshotViewHeight, @NonNull View banner) {
- FrameLayout.LayoutParams bannerParams = (FrameLayout.LayoutParams) banner.getLayoutParams();
- banner.setPivotX(0);
- banner.setPivotY(0);
- banner.setRotation(getDegreesRotated());
- if (isGroupedTaskView) {
- bannerParams.gravity =
- BOTTOM | (deviceProfile.isLeftRightSplit ? START : CENTER_HORIZONTAL);
- bannerParams.width = snapshotViewWidth;
- } else {
- bannerParams.width = MATCH_PARENT;
- bannerParams.gravity = BOTTOM | CENTER_HORIZONTAL;
- }
- banner.setLayoutParams(bannerParams);
- }
-
- @NonNull
- @Override
- public Pair<Float, Float> getDwbBannerTranslations(int taskViewWidth,
- int taskViewHeight, SplitBounds splitBounds, @NonNull DeviceProfile deviceProfile,
- @NonNull View[] thumbnailViews, int desiredTaskId, @NonNull View banner) {
- float translationX = 0;
- float translationY = 0;
- if (splitBounds != null) {
- if (deviceProfile.isLeftRightSplit) {
- if (desiredTaskId == splitBounds.rightBottomTaskId) {
- float leftTopTaskPercent = splitBounds.getLeftTopTaskPercent();
- float dividerThicknessPercent = splitBounds.getDividerPercent();
- translationX = ((taskViewWidth * leftTopTaskPercent)
- + (taskViewWidth * dividerThicknessPercent));
- }
- } else {
- if (desiredTaskId == splitBounds.leftTopTaskId) {
- FrameLayout.LayoutParams snapshotParams =
- (FrameLayout.LayoutParams) thumbnailViews[0]
- .getLayoutParams();
- float bottomRightTaskPlusDividerPercent =
- splitBounds.getRightBottomTaskPercent()
- + splitBounds.getDividerPercent();
- translationY = -((taskViewHeight - snapshotParams.topMargin)
- * bottomRightTaskPlusDividerPercent);
- }
- }
- }
- return new Pair<>(translationX, translationY);
- }
-
- /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
-
- @Override
- public SingleAxisSwipeDetector.Direction getUpDownSwipeDirection() {
- return VERTICAL;
- }
-
- @Override
- public int getUpDirection(boolean isRtl) {
- // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
- return SingleAxisSwipeDetector.DIRECTION_POSITIVE;
- }
-
- @Override
- public int getDownDirection(boolean isRtl) {
- // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
- return SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
- }
-
- @Override
- public boolean isGoingUp(float displacement, boolean isRtl) {
- // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
- return displacement < 0;
- }
-
- @Override
- public int getTaskDragDisplacementFactor(boolean isRtl) {
- // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
- return 1;
- }
-
- /* -------------------- */
- @Override
- public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
- return dp.heightPx - rect.bottom;
- }
-
- @Override
- public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
- if (dp.isTablet) {
- return Utilities.getSplitPositionOptions(dp);
- }
-
- List<SplitPositionOption> options = new ArrayList<>();
- if (dp.isSeascape()) {
- options.add(new SplitPositionOption(
- R.drawable.ic_split_horizontal, R.string.recent_task_option_split_screen,
- STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_MAIN));
- } else if (dp.isLeftRightSplit) {
- options.add(new SplitPositionOption(
- R.drawable.ic_split_horizontal, R.string.recent_task_option_split_screen,
- STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
- } else {
- // Only add top option
- options.add(new SplitPositionOption(
- R.drawable.ic_split_vertical, R.string.recent_task_option_split_screen,
- STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
- }
- return options;
- }
-
- @Override
- public void getInitialSplitPlaceholderBounds(int placeholderHeight, int placeholderInset,
- DeviceProfile dp, @StagePosition int stagePosition, Rect out) {
- int screenWidth = dp.widthPx;
- int screenHeight = dp.heightPx;
- boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
- int insetSizeAdjustment = getPlaceholderSizeAdjustment(dp, pinToRight);
-
- out.set(0, 0, screenWidth, placeholderHeight + insetSizeAdjustment);
- if (!dp.isLeftRightSplit) {
- // portrait, phone or tablet - spans width of screen, nothing else to do
- 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 totalHeight = (int) (1.0f * screenHeight / 2 * (screenWidth - 2 * placeholderInset)
- / screenWidth);
- out.top -= (totalHeight - placeholderHeight);
- return;
- }
-
- // Now we rotate the portrait rect depending on what side we want pinned
-
- float postRotateScale = (float) screenHeight / screenWidth;
- mTmpMatrix.reset();
- mTmpMatrix.postRotate(pinToRight ? 90 : 270);
- mTmpMatrix.postTranslate(pinToRight ? screenWidth : 0, pinToRight ? 0 : screenWidth);
- // The placeholder height stays constant after rotation, so we don't change width scale
- mTmpMatrix.postScale(1, postRotateScale);
-
- mTmpRectF.set(out);
- mTmpMatrix.mapRect(mTmpRectF);
- mTmpRectF.inset(0, placeholderInset);
- mTmpRectF.roundOut(out);
-
- // Adjust the top to account for content off screen. This will help to animate the view in
- // with rounded corners.
- int totalWidth = (int) (1.0f * screenWidth / 2 * (screenHeight - 2 * placeholderInset)
- / screenHeight);
- int width = out.width();
- if (pinToRight) {
- out.right += totalWidth - width;
- } else {
- out.left -= totalWidth - width;
- }
- }
-
- @Override
- public void updateSplitIconParams(View out, float onScreenRectCenterX,
- float onScreenRectCenterY, float fullscreenScaleX, float fullscreenScaleY,
- int drawableWidth, int drawableHeight, DeviceProfile dp,
- @StagePosition int stagePosition) {
- boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
- float insetAdjustment = getPlaceholderSizeAdjustment(dp, pinToRight) / 2f;
- if (!dp.isLeftRightSplit) {
- out.setX(onScreenRectCenterX / fullscreenScaleX
- - 1.0f * drawableWidth / 2);
- out.setY((onScreenRectCenterY + insetAdjustment) / fullscreenScaleY
- - 1.0f * drawableHeight / 2);
- } else {
- if (pinToRight) {
- out.setX((onScreenRectCenterX - insetAdjustment) / fullscreenScaleX
- - 1.0f * drawableWidth / 2);
- } else {
- out.setX((onScreenRectCenterX + insetAdjustment) / fullscreenScaleX
- - 1.0f * drawableWidth / 2);
- }
- out.setY(onScreenRectCenterY / 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, boolean pinToRight) {
- int insetThickness;
- if (!dp.isLandscape) {
- insetThickness = dp.getInsets().top;
- } else {
- insetThickness = pinToRight ? dp.getInsets().right : dp.getInsets().left;
- }
- return Math.max(insetThickness - 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;
- if (dp.isPhone) {
- if (dp.isLandscape) {
- distanceToEdge = out.getResources().getDimensionPixelSize(
- R.dimen.split_instructions_bottom_margin_phone_landscape);
- } else {
- distanceToEdge = out.getResources().getDimensionPixelSize(
- R.dimen.split_instructions_bottom_margin_phone_portrait);
- }
- } else {
- distanceToEdge = dp.getOverviewActionsClaimedSpaceBelow();
- }
-
- // Center the view in case of unbalanced insets on left or right of screen
- int insetCorrectionX = (dp.getInsets().right - dp.getInsets().left) / 2;
- // Adjust for any insets on the bottom edge
- int insetCorrectionY = dp.getInsets().bottom;
- out.setTranslationX(insetCorrectionX);
- out.setTranslationY(-distanceToEdge + insetCorrectionY);
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
- lp.gravity = CENTER_HORIZONTAL | BOTTOM;
- out.setLayoutParams(lp);
- }
-
- @Override
- public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
- @StagePosition int stagePosition, Rect out1, Rect out2) {
- int screenHeight = dp.heightPx;
- int screenWidth = dp.widthPx;
- out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize);
- out2.set(0, screenHeight / 2 + splitDividerSize, screenWidth, screenHeight);
- if (!dp.isLeftRightSplit) {
- // Portrait - the window bounds are always top and bottom half
- return;
- }
-
- // Now we rotate the portrait rect depending on what side we want pinned
- boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
- float postRotateScale = (float) screenHeight / screenWidth;
-
- mTmpMatrix.reset();
- mTmpMatrix.postRotate(pinToRight ? 90 : 270);
- mTmpMatrix.postTranslate(pinToRight ? screenHeight : 0, pinToRight ? 0 : screenWidth);
- mTmpMatrix.postScale(1 / postRotateScale, postRotateScale);
-
- mTmpRectF.set(out1);
- mTmpMatrix.mapRect(mTmpRectF);
- mTmpRectF.roundOut(out1);
-
- mTmpRectF.set(out2);
- mTmpMatrix.mapRect(mTmpRectF);
- mTmpRectF.roundOut(out2);
- }
-
- @Override
- public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect,
- SplitBounds splitInfo, int desiredStagePosition) {
- float topLeftTaskPercent = splitInfo.getLeftTopTaskPercent();
- float dividerBarPercent = splitInfo.getDividerPercent();
-
- int taskbarHeight = dp.isTransientTaskbar ? 0 : dp.taskbarHeight;
- float scale = (float) outRect.height() / (dp.availableHeightPx - taskbarHeight);
- float topTaskHeight = dp.availableHeightPx * topLeftTaskPercent;
- float scaledTopTaskHeight = topTaskHeight * scale;
- float dividerHeight = dp.availableHeightPx * dividerBarPercent;
- float scaledDividerHeight = dividerHeight * scale;
-
- if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
- if (dp.isLeftRightSplit) {
- outRect.right = outRect.left + Math.round(outRect.width() * topLeftTaskPercent);
- } else {
- outRect.bottom = Math.round(outRect.top + scaledTopTaskHeight);
- }
- } else {
- if (dp.isLeftRightSplit) {
- outRect.left += Math.round(outRect.width()
- * (topLeftTaskPercent + dividerBarPercent));
- } else {
- outRect.top += Math.round(scaledTopTaskHeight + scaledDividerHeight);
- }
- }
- }
-
- /**
- * @param inSplitSelection Whether user currently has a task from this task group staged for
- * split screen. If true, we have custom translations/scaling in place
- * for the remaining snapshot, so we'll skip setting translation/scale
- * here.
- */
- @Override
- public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
- int parentWidth, int parentHeight, SplitBounds splitBoundsConfig,
- DeviceProfile dp, boolean isRtl, boolean inSplitSelection) {
- int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
-
- FrameLayout.LayoutParams primaryParams =
- (FrameLayout.LayoutParams) primarySnapshot.getLayoutParams();
- FrameLayout.LayoutParams secondaryParams =
- (FrameLayout.LayoutParams) secondarySnapshot.getLayoutParams();
-
- // Reset margins that aren't used in this method, but are used in other
- // `RecentsPagedOrientationHandler` variants.
- secondaryParams.topMargin = 0;
- primaryParams.topMargin = spaceAboveSnapshot;
-
- int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
- float dividerScale = splitBoundsConfig.getDividerPercent();
- Pair<Point, Point> taskViewSizes =
- getGroupedTaskViewSizes(dp, splitBoundsConfig, parentWidth, parentHeight);
- if (!inSplitSelection) {
- // Reset translations that aren't used in this method, but are used in other
- // `RecentsPagedOrientationHandler` variants.
- primarySnapshot.setTranslationY(0);
-
- if (dp.isLeftRightSplit) {
- int scaledDividerBar = Math.round(parentWidth * dividerScale);
- if (isRtl) {
- int translationX = taskViewSizes.second.x + scaledDividerBar;
- primarySnapshot.setTranslationX(-translationX);
- secondarySnapshot.setTranslationX(0);
- } else {
- int translationX = taskViewSizes.first.x + scaledDividerBar;
- secondarySnapshot.setTranslationX(translationX);
- primarySnapshot.setTranslationX(0);
- }
- secondarySnapshot.setTranslationY(spaceAboveSnapshot);
- } else {
- float finalDividerHeight = Math.round(totalThumbnailHeight * dividerScale);
- float translationY =
- taskViewSizes.first.y + spaceAboveSnapshot + finalDividerHeight;
- secondarySnapshot.setTranslationY(translationY);
-
- // Reset unused translations.
- secondarySnapshot.setTranslationX(0);
- primarySnapshot.setTranslationX(0);
- }
- }
-
- 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;
- float dividerScale = splitBoundsConfig.getDividerPercent();
- float taskPercent = splitBoundsConfig.getLeftTopTaskPercent();
-
- Point firstTaskViewSize = new Point();
- Point secondTaskViewSize = new Point();
-
- if (dp.isLeftRightSplit) {
- int scaledDividerBar = Math.round(parentWidth * dividerScale);
- firstTaskViewSize.x = Math.round(parentWidth * taskPercent);
- firstTaskViewSize.y = totalThumbnailHeight;
-
- secondTaskViewSize.x = parentWidth - firstTaskViewSize.x - scaledDividerBar;
- secondTaskViewSize.y = totalThumbnailHeight;
- } else {
- int taskbarHeight = dp.isTransientTaskbar ? 0 : dp.taskbarHeight;
- float scale = (float) totalThumbnailHeight / (dp.availableHeightPx - taskbarHeight);
- float topTaskHeight = dp.availableHeightPx * taskPercent;
- float finalDividerHeight = Math.round(totalThumbnailHeight * dividerScale);
- float scaledTopTaskHeight = topTaskHeight * scale;
- firstTaskViewSize.x = parentWidth;
- firstTaskViewSize.y = Math.round(scaledTopTaskHeight);
-
- secondTaskViewSize.x = parentWidth;
- secondTaskViewSize.y = Math.round(totalThumbnailHeight - firstTaskViewSize.y
- - finalDividerHeight);
- }
-
- return new Pair<>(firstTaskViewSize, secondTaskViewSize);
- }
-
- @Override
- public void setTaskIconParams(FrameLayout.LayoutParams iconParams, int taskIconMargin,
- int taskIconHeight, int thumbnailTopMargin, boolean isRtl) {
- iconParams.gravity = TOP | CENTER_HORIZONTAL;
- // Reset margins, since they may have been set on rotation
- iconParams.leftMargin = iconParams.rightMargin = 0;
- iconParams.topMargin = iconParams.bottomMargin = 0;
- }
-
- @Override
- public void setIconAppChipChildrenParams(FrameLayout.LayoutParams iconParams,
- int chipChildMarginStart) {
- iconParams.setMarginStart(chipChildMarginStart);
- iconParams.gravity = Gravity.START | Gravity.CENTER_VERTICAL;
- iconParams.topMargin = 0;
- }
-
- @Override
- public void setIconAppChipMenuParams(IconAppChipView iconAppChipView,
- FrameLayout.LayoutParams iconMenuParams, int iconMenuMargin, int thumbnailTopMargin) {
- iconMenuParams.gravity = TOP | START;
- iconMenuParams.setMarginStart(iconMenuMargin);
- iconMenuParams.topMargin = thumbnailTopMargin;
- iconMenuParams.bottomMargin = 0;
- iconMenuParams.setMarginEnd(0);
-
- iconAppChipView.setPivotX(0);
- iconAppChipView.setPivotY(0);
- iconAppChipView.setSplitTranslationY(0);
- iconAppChipView.setRotation(getDegreesRotated());
- }
-
- /**
- * @param inSplitSelection Whether user currently has a task from this task group staged for
- * split screen. If true, we have custom translations in place for the
- * remaining icon, so we'll skip setting translations here.
- */
- @Override
- public void setSplitIconParams(View primaryIconView, View secondaryIconView,
- int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
- int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
- DeviceProfile deviceProfile, SplitBounds splitConfig, boolean inSplitSelection,
- boolean oneIconHiddenDueToSmallWidth) {
- FrameLayout.LayoutParams primaryIconParams =
- (FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
- FrameLayout.LayoutParams secondaryIconParams = enableOverviewIconMenu()
- ? (FrameLayout.LayoutParams) secondaryIconView.getLayoutParams()
- : new FrameLayout.LayoutParams(primaryIconParams);
-
- if (enableOverviewIconMenu()) {
- IconAppChipView primaryAppChipView = (IconAppChipView) primaryIconView;
- IconAppChipView secondaryAppChipView = (IconAppChipView) secondaryIconView;
- primaryIconParams.gravity = TOP | START;
- secondaryIconParams.gravity = TOP | START;
- secondaryIconParams.topMargin = primaryIconParams.topMargin;
- secondaryIconParams.setMarginStart(primaryIconParams.getMarginStart());
- if (!inSplitSelection) {
- if (deviceProfile.isLeftRightSplit) {
- if (isRtl) {
- int secondarySnapshotWidth = groupedTaskViewWidth - primarySnapshotWidth;
- primaryAppChipView.setSplitTranslationX(-secondarySnapshotWidth);
- } else {
- secondaryAppChipView.setSplitTranslationX(primarySnapshotWidth);
- }
- } else {
- primaryAppChipView.setSplitTranslationX(0);
- secondaryAppChipView.setSplitTranslationX(0);
- int dividerThickness = Math.min(splitConfig.visualDividerBounds.width(),
- splitConfig.visualDividerBounds.height());
- secondaryAppChipView.setSplitTranslationY(
- primarySnapshotHeight + (deviceProfile.isTablet ? 0
- : dividerThickness));
- }
- }
- } else if (deviceProfile.isLeftRightSplit) {
- // 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.isSeascape()
- ? deviceProfile.getInsets().right
- : deviceProfile.getInsets().left;
- int fullscreenMidpointFromBottom = ((deviceProfile.widthPx
- - fullscreenInsetThickness) / 2);
- float midpointFromEndPct = (float) fullscreenMidpointFromBottom
- / deviceProfile.widthPx;
- float insetPct = (float) fullscreenInsetThickness / deviceProfile.widthPx;
- int spaceAboveSnapshots = 0;
- int overviewThumbnailAreaThickness = groupedTaskViewWidth - spaceAboveSnapshots;
- int bottomToMidpointOffset = (int) (overviewThumbnailAreaThickness
- * midpointFromEndPct);
- int insetOffset = (int) (overviewThumbnailAreaThickness * insetPct);
-
- if (deviceProfile.isSeascape()) {
- primaryIconParams.gravity = TOP | (isRtl ? END : START);
- secondaryIconParams.gravity = TOP | (isRtl ? END : START);
- if (!inSplitSelection) {
- if (splitConfig.initiatedFromSeascape) {
- if (oneIconHiddenDueToSmallWidth) {
- // Center both icons
- float centerX = bottomToMidpointOffset - (taskIconHeight / 2f);
- primaryIconView.setTranslationX(centerX);
- secondaryIconView.setTranslationX(centerX);
- } else {
- // the task on the right (secondary) is slightly larger
- primaryIconView.setTranslationX(
- bottomToMidpointOffset - taskIconHeight);
- secondaryIconView.setTranslationX(bottomToMidpointOffset);
- }
- } else {
- if (oneIconHiddenDueToSmallWidth) {
- // Center both icons
- float centerX =
- bottomToMidpointOffset + insetOffset - (taskIconHeight / 2f);
- primaryIconView.setTranslationX(centerX);
- secondaryIconView.setTranslationX(centerX);
- } else {
- // the task on the left (primary) is slightly larger
- primaryIconView.setTranslationX(bottomToMidpointOffset + insetOffset
- - taskIconHeight);
- secondaryIconView.setTranslationX(bottomToMidpointOffset + insetOffset);
- }
- }
- }
- } else {
- primaryIconParams.gravity = TOP | (isRtl ? START : END);
- secondaryIconParams.gravity = TOP | (isRtl ? START : END);
- if (!inSplitSelection) {
- if (!splitConfig.initiatedFromSeascape) {
- if (oneIconHiddenDueToSmallWidth) {
- // Center both icons
- float centerX = -bottomToMidpointOffset + (taskIconHeight / 2f);
- primaryIconView.setTranslationX(centerX);
- secondaryIconView.setTranslationX(centerX);
- } else {
- // the task on the left (primary) is slightly larger
- primaryIconView.setTranslationX(-bottomToMidpointOffset);
- secondaryIconView.setTranslationX(
- -bottomToMidpointOffset + taskIconHeight);
- }
- } else {
- if (oneIconHiddenDueToSmallWidth) {
- // Center both icons
- float centerX =
- -bottomToMidpointOffset - insetOffset + (taskIconHeight / 2f);
- primaryIconView.setTranslationX(centerX);
- secondaryIconView.setTranslationX(centerX);
- } else {
- // the task on the right (secondary) is slightly larger
- primaryIconView.setTranslationX(-bottomToMidpointOffset - insetOffset);
- secondaryIconView.setTranslationX(-bottomToMidpointOffset - insetOffset
- + taskIconHeight);
- }
- }
- }
- }
- } else {
- primaryIconParams.gravity = TOP | CENTER_HORIZONTAL;
- secondaryIconParams.gravity = TOP | CENTER_HORIZONTAL;
- if (!inSplitSelection) {
- if (oneIconHiddenDueToSmallWidth) {
- // Center both icons
- primaryIconView.setTranslationX(0);
- secondaryIconView.setTranslationX(0);
- } else {
- // shifts icon half a width left (height is used here since icons are square)
- primaryIconView.setTranslationX(-(taskIconHeight / 2f));
- secondaryIconView.setTranslationX(taskIconHeight / 2f);
- }
- }
- }
- if (!enableOverviewIconMenu() && !inSplitSelection) {
- primaryIconView.setTranslationY(0);
- secondaryIconView.setTranslationY(0);
- }
-
-
- primaryIconView.setLayoutParams(primaryIconParams);
- secondaryIconView.setLayoutParams(secondaryIconParams);
- }
-
- @Override
- public int getDefaultSplitPosition(DeviceProfile deviceProfile) {
- if (!deviceProfile.isTablet) {
- throw new IllegalStateException("Default position available only for large screens");
- }
- if (deviceProfile.isLeftRightSplit) {
- return STAGE_POSITION_BOTTOM_OR_RIGHT;
- } else {
- return STAGE_POSITION_TOP_OR_LEFT;
- }
- }
-
- @Override
- public Pair<FloatProperty, FloatProperty> getSplitSelectTaskOffset(FloatProperty primary,
- FloatProperty secondary, DeviceProfile deviceProfile) {
- if (deviceProfile.isLeftRightSplit) { // or seascape
- return new Pair<>(primary, secondary);
- } else {
- return new Pair<>(secondary, primary);
- }
- }
-
- @Override
- public float getFloatingTaskOffscreenTranslationTarget(View floatingTask, RectF onScreenRect,
- @StagePosition int stagePosition, DeviceProfile dp) {
- if (dp.isLeftRightSplit) {
- float currentTranslationX = floatingTask.getTranslationX();
- return stagePosition == STAGE_POSITION_TOP_OR_LEFT
- ? currentTranslationX - onScreenRect.width()
- : currentTranslationX + onScreenRect.width();
- } else {
- float currentTranslationY = floatingTask.getTranslationY();
- return currentTranslationY - onScreenRect.height();
- }
- }
-
- @Override
- public void setFloatingTaskPrimaryTranslation(View floatingTask, float translation,
- DeviceProfile dp) {
- if (dp.isLeftRightSplit) {
- floatingTask.setTranslationX(translation);
- } else {
- floatingTask.setTranslationY(translation);
- }
-
- }
-
- @Override
- public float getFloatingTaskPrimaryTranslation(View floatingTask, DeviceProfile dp) {
- return dp.isLeftRightSplit
- ? floatingTask.getTranslationX()
- : floatingTask.getTranslationY();
- }
-
- @NonNull
- @Override
- public LauncherAtom.TaskSwitcherContainer.OrientationHandler getHandlerTypeForLogging() {
- return LauncherAtom.TaskSwitcherContainer.OrientationHandler.PORTRAIT;
- }
-}
diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt
new file mode 100644
index 0000000..f2e53fc
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt
@@ -0,0 +1,903 @@
+/*
+ * Copyright (C) 2025 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.Matrix
+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.Surface
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import androidx.core.view.updateLayoutParams
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.Flags.enableOverviewIconMenu
+import com.android.launcher3.LauncherAnimUtils
+import com.android.launcher3.R
+import com.android.launcher3.Utilities
+import com.android.launcher3.logger.LauncherAtom
+import com.android.launcher3.touch.DefaultPagedViewHandler
+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
+import kotlin.math.max
+import kotlin.math.min
+
+class PortraitPagedViewHandler : DefaultPagedViewHandler(), RecentsPagedOrientationHandler {
+ private val tmpMatrix = Matrix()
+ private val tmpRectF = RectF()
+
+ override fun <T> getPrimaryValue(x: T, y: T): T = x
+
+ override fun <T> getSecondaryValue(x: T, y: T): T = y
+
+ override val isLayoutNaturalToLauncher: Boolean = true
+
+ override fun adjustFloatingIconStartVelocity(velocity: PointF) {
+ // no-op
+ }
+
+ override fun fixBoundsForHomeAnimStartRect(outStartRect: RectF, deviceProfile: DeviceProfile) {
+ if (outStartRect.left > deviceProfile.widthPx) {
+ outStartRect.offsetTo(0f, outStartRect.top)
+ } else if (outStartRect.left < -deviceProfile.widthPx) {
+ outStartRect.offsetTo(0f, outStartRect.top)
+ }
+ }
+
+ override fun <T> setSecondary(target: T, action: Float2DAction<T>, param: Float) =
+ action.call(target, 0f, param)
+
+ override fun <T> set(
+ target: T,
+ action: Int2DAction<T>,
+ primaryParam: Int,
+ secondaryParam: Int,
+ ) = action.call(target, primaryParam, secondaryParam)
+
+ override fun getPrimarySize(view: View): Int = view.width
+
+ override fun getPrimarySize(rect: RectF): Float = rect.width()
+
+ override fun getStart(rect: RectF): Float = rect.left
+
+ override fun getEnd(rect: RectF): Float = rect.right
+
+ override fun rotateInsets(insets: Rect, outInsets: Rect) = outInsets.set(insets)
+
+ override fun getClearAllSidePadding(view: View, isRtl: Boolean): Int =
+ (if (isRtl) view.paddingRight else -view.paddingLeft) / 2
+
+ override fun getSecondaryDimension(view: View): Int = view.height
+
+ override val primaryViewTranslate: FloatProperty<View> = LauncherAnimUtils.VIEW_TRANSLATE_X
+
+ override val secondaryViewTranslate: FloatProperty<View> = LauncherAnimUtils.VIEW_TRANSLATE_Y
+
+ override val degreesRotated: Float = 0f
+
+ override val rotation: Int = Surface.ROTATION_0
+
+ override fun setPrimaryScale(view: View, scale: Float) {
+ view.scaleX = scale
+ }
+
+ override fun setSecondaryScale(view: View, scale: Float) {
+ view.scaleY = scale
+ }
+
+ override val secondaryTranslationDirectionFactor: Int
+ get() = -1
+
+ override fun getSplitTranslationDirectionFactor(
+ stagePosition: Int,
+ deviceProfile: DeviceProfile,
+ ): Int =
+ if (
+ deviceProfile.isLeftRightSplit &&
+ stagePosition == SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+ ) {
+ -1
+ } else {
+ 1
+ }
+
+ override fun getTaskMenuX(
+ x: Float,
+ thumbnailView: View,
+ deviceProfile: DeviceProfile,
+ taskInsetMargin: Float,
+ taskViewIcon: View,
+ ): Float =
+ if (deviceProfile.isLandscape) {
+ (x +
+ taskInsetMargin +
+ (thumbnailView.measuredWidth - thumbnailView.measuredHeight) / 2f)
+ } else {
+ x + taskInsetMargin
+ }
+
+ override fun getTaskMenuY(
+ y: Float,
+ thumbnailView: View,
+ stagePosition: Int,
+ taskMenuView: View,
+ taskInsetMargin: Float,
+ taskViewIcon: View,
+ ): Float = y + taskInsetMargin
+
+ override fun getTaskMenuWidth(
+ thumbnailView: View,
+ deviceProfile: DeviceProfile,
+ @StagePosition stagePosition: Int,
+ ): Int =
+ when {
+ enableOverviewIconMenu() -> {
+ thumbnailView.resources.getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_expanded_width
+ )
+ }
+
+ (deviceProfile.isLandscape && !deviceProfile.isTablet) -> {
+ val padding =
+ thumbnailView.resources.getDimensionPixelSize(R.dimen.task_menu_edge_padding)
+ thumbnailView.measuredHeight - (2 * padding)
+ }
+
+ else -> {
+ val padding =
+ thumbnailView.resources.getDimensionPixelSize(R.dimen.task_menu_edge_padding)
+ thumbnailView.measuredWidth - (2 * padding)
+ }
+ }
+
+ override fun getTaskMenuHeight(
+ taskInsetMargin: Float,
+ deviceProfile: DeviceProfile,
+ taskMenuX: Float,
+ taskMenuY: Float,
+ ): Int =
+ deviceProfile.heightPx -
+ deviceProfile.insets.top -
+ taskMenuY.toInt() -
+ deviceProfile.overviewActionsClaimedSpaceBelow
+
+ 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,
+ ) {
+ viewGroup.orientation = LinearLayout.HORIZONTAL
+ lp.width = LinearLayout.LayoutParams.MATCH_PARENT
+ lp.height = ViewGroup.LayoutParams.WRAP_CONTENT
+ }
+
+ override fun updateDwbBannerLayout(
+ taskViewWidth: Int,
+ taskViewHeight: Int,
+ isGroupedTaskView: Boolean,
+ deviceProfile: DeviceProfile,
+ snapshotViewWidth: Int,
+ snapshotViewHeight: Int,
+ banner: View,
+ ) {
+ banner.pivotX = 0f
+ banner.pivotY = 0f
+ banner.rotation = degreesRotated
+ banner.updateLayoutParams<FrameLayout.LayoutParams> {
+ if (isGroupedTaskView) {
+ gravity =
+ Gravity.BOTTOM or
+ (if (deviceProfile.isLeftRightSplit) Gravity.START
+ else Gravity.CENTER_HORIZONTAL)
+ width = snapshotViewWidth
+ } else {
+ width = ViewGroup.LayoutParams.MATCH_PARENT
+ gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
+ }
+ }
+ }
+
+ override fun getDwbBannerTranslations(
+ taskViewWidth: Int,
+ taskViewHeight: Int,
+ splitBounds: SplitConfigurationOptions.SplitBounds?,
+ deviceProfile: DeviceProfile,
+ thumbnailViews: Array<View>,
+ desiredTaskId: Int,
+ banner: View,
+ ): Pair<Float, Float> {
+ var translationX = 0f
+ var translationY = 0f
+ if (splitBounds != null) {
+ if (deviceProfile.isLeftRightSplit) {
+ if (desiredTaskId == splitBounds.rightBottomTaskId) {
+ val leftTopTaskPercent = splitBounds.leftTopTaskPercent
+ val dividerThicknessPercent = splitBounds.dividerPercent
+ translationX =
+ ((taskViewWidth * leftTopTaskPercent) +
+ (taskViewWidth * dividerThicknessPercent))
+ }
+ } else {
+ if (desiredTaskId == splitBounds.leftTopTaskId) {
+ val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
+ val bottomRightTaskPlusDividerPercent =
+ (splitBounds.rightBottomTaskPercent + splitBounds.dividerPercent)
+ translationY =
+ -((taskViewHeight - snapshotParams.topMargin) *
+ bottomRightTaskPlusDividerPercent)
+ }
+ }
+ }
+ return Pair(translationX, translationY)
+ }
+
+ /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
+
+ override val upDownSwipeDirection: SingleAxisSwipeDetector.Direction =
+ SingleAxisSwipeDetector.VERTICAL
+
+ // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
+ override fun getUpDirection(isRtl: Boolean): Int = SingleAxisSwipeDetector.DIRECTION_POSITIVE
+
+ // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
+ override fun getDownDirection(isRtl: Boolean): Int = SingleAxisSwipeDetector.DIRECTION_NEGATIVE
+
+ // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
+ override fun isGoingUp(displacement: Float, isRtl: Boolean): Boolean = displacement < 0
+
+ // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
+ override fun getTaskDragDisplacementFactor(isRtl: Boolean): Int = 1
+
+ /* -------------------- */
+
+ override fun getDistanceToBottomOfRect(dp: DeviceProfile, rect: Rect): Int =
+ dp.heightPx - rect.bottom
+
+ override fun getSplitPositionOptions(dp: DeviceProfile): List<SplitPositionOption> =
+ when {
+ dp.isTablet -> {
+ Utilities.getSplitPositionOptions(dp)
+ }
+
+ dp.isSeascape -> {
+ listOf(
+ SplitPositionOption(
+ R.drawable.ic_split_horizontal,
+ R.string.recent_task_option_split_screen,
+ SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT,
+ SplitConfigurationOptions.STAGE_TYPE_MAIN,
+ )
+ )
+ }
+
+ dp.isLeftRightSplit -> {
+ listOf(
+ SplitPositionOption(
+ R.drawable.ic_split_horizontal,
+ R.string.recent_task_option_split_screen,
+ SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
+ SplitConfigurationOptions.STAGE_TYPE_MAIN,
+ )
+ )
+ }
+
+ else -> {
+ // Only add top option
+ listOf(
+ SplitPositionOption(
+ R.drawable.ic_split_vertical,
+ R.string.recent_task_option_split_screen,
+ SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
+ SplitConfigurationOptions.STAGE_TYPE_MAIN,
+ )
+ )
+ }
+ }
+
+ override fun getInitialSplitPlaceholderBounds(
+ placeholderHeight: Int,
+ placeholderInset: Int,
+ dp: DeviceProfile,
+ @StagePosition stagePosition: Int,
+ out: Rect,
+ ) {
+ val screenWidth = dp.widthPx
+ val screenHeight = dp.heightPx
+ val pinToRight = stagePosition == SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+ val insetSizeAdjustment = getPlaceholderSizeAdjustment(dp, pinToRight)
+
+ out.set(0, 0, screenWidth, placeholderHeight + insetSizeAdjustment)
+ if (!dp.isLeftRightSplit) {
+ // portrait, phone or tablet - spans width of screen, nothing else to do
+ 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 totalHeight =
+ (1.0f * screenHeight / 2 * (screenWidth - 2 * placeholderInset) / screenWidth)
+ .toInt()
+ out.top -= (totalHeight - placeholderHeight)
+ return
+ }
+
+ // Now we rotate the portrait rect depending on what side we want pinned
+ val postRotateScale = screenHeight.toFloat() / screenWidth
+ tmpMatrix.reset()
+ tmpMatrix.postRotate(if (pinToRight) 90f else 270f)
+ tmpMatrix.postTranslate(
+ (if (pinToRight) screenWidth else 0).toFloat(),
+ (if (pinToRight) 0 else screenWidth).toFloat(),
+ )
+ // The placeholder height stays constant after rotation, so we don't change width scale
+ tmpMatrix.postScale(1f, postRotateScale)
+
+ tmpRectF.set(out)
+ tmpMatrix.mapRect(tmpRectF)
+ tmpRectF.inset(0f, placeholderInset.toFloat())
+ tmpRectF.roundOut(out)
+
+ // Adjust the top to account for content off screen. This will help to animate the view in
+ // with rounded corners.
+ val totalWidth =
+ (1.0f * screenWidth / 2 * (screenHeight - 2 * placeholderInset) / screenHeight).toInt()
+ val width = out.width()
+ if (pinToRight) {
+ out.right += totalWidth - width
+ } else {
+ out.left -= totalWidth - width
+ }
+ }
+
+ override fun updateSplitIconParams(
+ out: View,
+ onScreenRectCenterX: Float,
+ onScreenRectCenterY: Float,
+ fullscreenScaleX: Float,
+ fullscreenScaleY: Float,
+ drawableWidth: Int,
+ drawableHeight: Int,
+ dp: DeviceProfile,
+ @StagePosition stagePosition: Int,
+ ) {
+ val pinToRight = stagePosition == SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+ val insetAdjustment = getPlaceholderSizeAdjustment(dp, pinToRight) / 2f
+ if (!dp.isLeftRightSplit) {
+ out.x = (onScreenRectCenterX / fullscreenScaleX - 1.0f * drawableWidth / 2)
+ out.y =
+ ((onScreenRectCenterY + insetAdjustment) / fullscreenScaleY -
+ 1.0f * drawableHeight / 2)
+ } else {
+ if (pinToRight) {
+ out.x =
+ ((onScreenRectCenterX - insetAdjustment) / fullscreenScaleX -
+ 1.0f * drawableWidth / 2)
+ } else {
+ out.x =
+ ((onScreenRectCenterX + insetAdjustment) / fullscreenScaleX -
+ 1.0f * drawableWidth / 2)
+ }
+ out.y = (onScreenRectCenterY / 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, pinToRight: Boolean): Int {
+ val insetThickness =
+ if (!dp.isLandscape) {
+ dp.insets.top
+ } else {
+ if (pinToRight) dp.insets.right else dp.insets.left
+ }
+ return max((insetThickness - 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 =
+ if (dp.isPhone) {
+ if (dp.isLandscape) {
+ out.resources.getDimensionPixelSize(
+ R.dimen.split_instructions_bottom_margin_phone_landscape
+ )
+ } else {
+ out.resources.getDimensionPixelSize(
+ R.dimen.split_instructions_bottom_margin_phone_portrait
+ )
+ }
+ } else {
+ dp.overviewActionsClaimedSpaceBelow
+ }
+
+ // Center the view in case of unbalanced insets on left or right of screen
+ val insetCorrectionX = (dp.insets.right - dp.insets.left) / 2
+ // Adjust for any insets on the bottom edge
+ val insetCorrectionY = dp.insets.bottom
+ out.translationX = insetCorrectionX.toFloat()
+ out.translationY = (-distanceToEdge + insetCorrectionY).toFloat()
+ val lp = out.layoutParams as FrameLayout.LayoutParams
+ lp.gravity = Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
+ out.layoutParams = lp
+ }
+
+ override fun getFinalSplitPlaceholderBounds(
+ splitDividerSize: Int,
+ dp: DeviceProfile,
+ @StagePosition stagePosition: Int,
+ out1: Rect,
+ out2: Rect,
+ ) {
+ val screenHeight = dp.heightPx
+ val screenWidth = dp.widthPx
+ out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize)
+ out2.set(0, screenHeight / 2 + splitDividerSize, screenWidth, screenHeight)
+ if (!dp.isLeftRightSplit) {
+ // Portrait - the window bounds are always top and bottom half
+ return
+ }
+
+ // Now we rotate the portrait rect depending on what side we want pinned
+ val pinToRight = stagePosition == SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+ val postRotateScale = screenHeight.toFloat() / screenWidth
+
+ tmpMatrix.reset()
+ tmpMatrix.postRotate(if (pinToRight) 90f else 270f)
+ tmpMatrix.postTranslate(
+ (if (pinToRight) screenHeight else 0).toFloat(),
+ (if (pinToRight) 0 else screenWidth).toFloat(),
+ )
+ tmpMatrix.postScale(1 / postRotateScale, postRotateScale)
+
+ tmpRectF.set(out1)
+ tmpMatrix.mapRect(tmpRectF)
+ tmpRectF.roundOut(out1)
+
+ tmpRectF.set(out2)
+ tmpMatrix.mapRect(tmpRectF)
+ tmpRectF.roundOut(out2)
+ }
+
+ override fun setSplitTaskSwipeRect(
+ dp: DeviceProfile,
+ outRect: Rect,
+ splitInfo: SplitConfigurationOptions.SplitBounds,
+ desiredStagePosition: Int,
+ ) {
+ val topLeftTaskPercent = splitInfo.leftTopTaskPercent
+ val dividerBarPercent = splitInfo.dividerPercent
+
+ val taskbarHeight = if (dp.isTransientTaskbar) 0 else dp.taskbarHeight
+ val scale = outRect.height().toFloat() / (dp.availableHeightPx - taskbarHeight)
+ val topTaskHeight = dp.availableHeightPx * topLeftTaskPercent
+ val scaledTopTaskHeight = topTaskHeight * scale
+ val dividerHeight = dp.availableHeightPx * dividerBarPercent
+ val scaledDividerHeight = dividerHeight * scale
+
+ if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
+ if (dp.isLeftRightSplit) {
+ outRect.right = outRect.left + Math.round(outRect.width() * topLeftTaskPercent)
+ } else {
+ outRect.bottom = Math.round(outRect.top + scaledTopTaskHeight)
+ }
+ } else {
+ if (dp.isLeftRightSplit) {
+ outRect.left +=
+ Math.round(outRect.width() * (topLeftTaskPercent + dividerBarPercent))
+ } else {
+ outRect.top += Math.round(scaledTopTaskHeight + scaledDividerHeight)
+ }
+ }
+ }
+
+ /**
+ * @param inSplitSelection Whether user currently has a task from this task group staged for
+ * split screen. If true, we have custom translations/scaling in place for the remaining
+ * snapshot, so we'll skip setting translation/scale here.
+ */
+ override fun measureGroupedTaskViewThumbnailBounds(
+ primarySnapshot: View,
+ secondarySnapshot: View,
+ parentWidth: Int,
+ parentHeight: Int,
+ splitBoundsConfig: SplitConfigurationOptions.SplitBounds,
+ dp: DeviceProfile,
+ isRtl: Boolean,
+ inSplitSelection: Boolean,
+ ) {
+ val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
+
+ val primaryParams = primarySnapshot.layoutParams as FrameLayout.LayoutParams
+ val secondaryParams = secondarySnapshot.layoutParams as FrameLayout.LayoutParams
+
+ // Reset margins that aren't used in this method, but are used in other
+ // `RecentsPagedOrientationHandler` variants.
+ secondaryParams.topMargin = 0
+ primaryParams.topMargin = spaceAboveSnapshot
+
+ val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
+ val dividerScale = splitBoundsConfig.dividerPercent
+ val taskViewSizes =
+ getGroupedTaskViewSizes(dp, splitBoundsConfig, parentWidth, parentHeight)
+ if (!inSplitSelection) {
+ // Reset translations that aren't used in this method, but are used in other
+ // `RecentsPagedOrientationHandler` variants.
+ primarySnapshot.translationY = 0f
+
+ if (dp.isLeftRightSplit) {
+ val scaledDividerBar = Math.round(parentWidth * dividerScale)
+ if (isRtl) {
+ val translationX = taskViewSizes.second.x + scaledDividerBar
+ primarySnapshot.translationX = -translationX.toFloat()
+ secondarySnapshot.translationX = 0f
+ } else {
+ val translationX = taskViewSizes.first.x + scaledDividerBar
+ secondarySnapshot.translationX = translationX.toFloat()
+ primarySnapshot.translationX = 0f
+ }
+ secondarySnapshot.translationY = spaceAboveSnapshot.toFloat()
+ } else {
+ val finalDividerHeight = Math.round(totalThumbnailHeight * dividerScale).toFloat()
+ val translationY = taskViewSizes.first.y + spaceAboveSnapshot + finalDividerHeight
+ secondarySnapshot.translationY = translationY
+
+ // Reset unused translations.
+ secondarySnapshot.translationX = 0f
+ primarySnapshot.translationX = 0f
+ }
+ }
+
+ 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 fun getGroupedTaskViewSizes(
+ dp: DeviceProfile,
+ splitBoundsConfig: SplitConfigurationOptions.SplitBounds,
+ parentWidth: Int,
+ parentHeight: Int,
+ ): Pair<Point, Point> {
+ val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
+ val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
+ val dividerScale = splitBoundsConfig.dividerPercent
+ val taskPercent = splitBoundsConfig.leftTopTaskPercent
+
+ val firstTaskViewSize = Point()
+ val secondTaskViewSize = Point()
+
+ if (dp.isLeftRightSplit) {
+ val scaledDividerBar = Math.round(parentWidth * dividerScale)
+ firstTaskViewSize.x = Math.round(parentWidth * taskPercent)
+ firstTaskViewSize.y = totalThumbnailHeight
+
+ secondTaskViewSize.x = parentWidth - firstTaskViewSize.x - scaledDividerBar
+ secondTaskViewSize.y = totalThumbnailHeight
+ } else {
+ val taskbarHeight = if (dp.isTransientTaskbar) 0 else dp.taskbarHeight
+ val scale = totalThumbnailHeight.toFloat() / (dp.availableHeightPx - taskbarHeight)
+ val topTaskHeight = dp.availableHeightPx * taskPercent
+ val finalDividerHeight = Math.round(totalThumbnailHeight * dividerScale).toFloat()
+ val scaledTopTaskHeight = topTaskHeight * scale
+ firstTaskViewSize.x = parentWidth
+ firstTaskViewSize.y = Math.round(scaledTopTaskHeight)
+
+ secondTaskViewSize.x = parentWidth
+ secondTaskViewSize.y =
+ Math.round((totalThumbnailHeight - firstTaskViewSize.y - finalDividerHeight))
+ }
+
+ return Pair(firstTaskViewSize, secondTaskViewSize)
+ }
+
+ override fun setTaskIconParams(
+ iconParams: FrameLayout.LayoutParams,
+ taskIconMargin: Int,
+ taskIconHeight: Int,
+ thumbnailTopMargin: Int,
+ isRtl: Boolean,
+ ) {
+ iconParams.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
+ // Reset margins, since they may have been set on rotation
+ iconParams.rightMargin = 0
+ iconParams.leftMargin = iconParams.rightMargin
+ iconParams.bottomMargin = 0
+ iconParams.topMargin = iconParams.bottomMargin
+ }
+
+ override fun setIconAppChipChildrenParams(
+ iconParams: FrameLayout.LayoutParams,
+ chipChildMarginStart: Int,
+ ) {
+ iconParams.marginStart = chipChildMarginStart
+ iconParams.gravity = Gravity.START or Gravity.CENTER_VERTICAL
+ iconParams.topMargin = 0
+ }
+
+ override fun setIconAppChipMenuParams(
+ iconAppChipView: IconAppChipView,
+ iconMenuParams: FrameLayout.LayoutParams,
+ iconMenuMargin: Int,
+ thumbnailTopMargin: Int,
+ ) {
+ iconMenuParams.gravity = Gravity.TOP or Gravity.START
+ iconMenuParams.marginStart = iconMenuMargin
+ iconMenuParams.topMargin = thumbnailTopMargin
+ iconMenuParams.bottomMargin = 0
+ iconMenuParams.marginEnd = 0
+
+ iconAppChipView.pivotX = 0f
+ iconAppChipView.pivotY = 0f
+ iconAppChipView.setSplitTranslationY(0f)
+ iconAppChipView.rotation = degreesRotated
+ }
+
+ /**
+ * @param inSplitSelection Whether user currently has a task from this task group staged for
+ * split screen. If true, we have custom translations in place for the remaining icon, so
+ * we'll skip setting translations here.
+ */
+ override fun setSplitIconParams(
+ primaryIconView: View,
+ secondaryIconView: View,
+ taskIconHeight: Int,
+ primarySnapshotWidth: Int,
+ primarySnapshotHeight: Int,
+ groupedTaskViewHeight: Int,
+ groupedTaskViewWidth: Int,
+ isRtl: Boolean,
+ deviceProfile: DeviceProfile,
+ splitConfig: SplitConfigurationOptions.SplitBounds,
+ inSplitSelection: Boolean,
+ oneIconHiddenDueToSmallWidth: Boolean,
+ ) {
+ val primaryIconParams = primaryIconView.layoutParams as FrameLayout.LayoutParams
+ val secondaryIconParams =
+ if (enableOverviewIconMenu()) secondaryIconView.layoutParams as FrameLayout.LayoutParams
+ else FrameLayout.LayoutParams(primaryIconParams)
+
+ if (enableOverviewIconMenu()) {
+ val primaryAppChipView = primaryIconView as IconAppChipView
+ val secondaryAppChipView = secondaryIconView as IconAppChipView
+ primaryIconParams.gravity = Gravity.TOP or Gravity.START
+ secondaryIconParams.gravity = Gravity.TOP or Gravity.START
+ secondaryIconParams.topMargin = primaryIconParams.topMargin
+ secondaryIconParams.marginStart = primaryIconParams.marginStart
+ if (!inSplitSelection) {
+ if (deviceProfile.isLeftRightSplit) {
+ if (isRtl) {
+ val secondarySnapshotWidth = groupedTaskViewWidth - primarySnapshotWidth
+ primaryAppChipView.setSplitTranslationX(-secondarySnapshotWidth.toFloat())
+ } else {
+ secondaryAppChipView.setSplitTranslationX(primarySnapshotWidth.toFloat())
+ }
+ } else {
+ primaryAppChipView.setSplitTranslationX(0f)
+ secondaryAppChipView.setSplitTranslationX(0f)
+ val dividerThickness =
+ min(
+ splitConfig.visualDividerBounds.width().toDouble(),
+ splitConfig.visualDividerBounds.height().toDouble(),
+ )
+ .toInt()
+ secondaryAppChipView.setSplitTranslationY(
+ (primarySnapshotHeight +
+ (if (deviceProfile.isTablet) 0 else dividerThickness))
+ .toFloat()
+ )
+ }
+ }
+ } else if (deviceProfile.isLeftRightSplit) {
+ // 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 =
+ if (deviceProfile.isSeascape) deviceProfile.insets.right
+ else deviceProfile.insets.left
+ val fullscreenMidpointFromBottom =
+ ((deviceProfile.widthPx - fullscreenInsetThickness) / 2)
+ val midpointFromEndPct = fullscreenMidpointFromBottom.toFloat() / deviceProfile.widthPx
+ val insetPct = fullscreenInsetThickness.toFloat() / deviceProfile.widthPx
+ val spaceAboveSnapshots = 0
+ val overviewThumbnailAreaThickness = groupedTaskViewWidth - spaceAboveSnapshots
+ val bottomToMidpointOffset =
+ (overviewThumbnailAreaThickness * midpointFromEndPct).toInt()
+ val insetOffset = (overviewThumbnailAreaThickness * insetPct).toInt()
+
+ if (deviceProfile.isSeascape) {
+ primaryIconParams.gravity =
+ Gravity.TOP or (if (isRtl) Gravity.END else Gravity.START)
+ secondaryIconParams.gravity =
+ Gravity.TOP or (if (isRtl) Gravity.END else Gravity.START)
+ if (!inSplitSelection) {
+ if (splitConfig.initiatedFromSeascape) {
+ if (oneIconHiddenDueToSmallWidth) {
+ // Center both icons
+ val centerX = bottomToMidpointOffset - (taskIconHeight / 2f)
+ primaryIconView.translationX = centerX
+ secondaryIconView.translationX = centerX
+ } else {
+ // the task on the right (secondary) is slightly larger
+ primaryIconView.translationX =
+ (bottomToMidpointOffset - taskIconHeight).toFloat()
+ secondaryIconView.translationX = bottomToMidpointOffset.toFloat()
+ }
+ } else {
+ if (oneIconHiddenDueToSmallWidth) {
+ // Center both icons
+ val centerX =
+ bottomToMidpointOffset + insetOffset - (taskIconHeight / 2f)
+ primaryIconView.translationX = centerX
+ secondaryIconView.translationX = centerX
+ } else {
+ // the task on the left (primary) is slightly larger
+ primaryIconView.translationX =
+ (bottomToMidpointOffset + insetOffset - taskIconHeight).toFloat()
+ secondaryIconView.translationX =
+ (bottomToMidpointOffset + insetOffset).toFloat()
+ }
+ }
+ }
+ } else {
+ primaryIconParams.gravity =
+ Gravity.TOP or (if (isRtl) Gravity.START else Gravity.END)
+ secondaryIconParams.gravity =
+ Gravity.TOP or (if (isRtl) Gravity.START else Gravity.END)
+ if (!inSplitSelection) {
+ if (!splitConfig.initiatedFromSeascape) {
+ if (oneIconHiddenDueToSmallWidth) {
+ // Center both icons
+ val centerX = -bottomToMidpointOffset + (taskIconHeight / 2f)
+ primaryIconView.translationX = centerX
+ secondaryIconView.translationX = centerX
+ } else {
+ // the task on the left (primary) is slightly larger
+ primaryIconView.translationX = -bottomToMidpointOffset.toFloat()
+ secondaryIconView.translationX =
+ (-bottomToMidpointOffset + taskIconHeight).toFloat()
+ }
+ } else {
+ if (oneIconHiddenDueToSmallWidth) {
+ // Center both icons
+ val centerX =
+ -bottomToMidpointOffset - insetOffset + (taskIconHeight / 2f)
+ primaryIconView.translationX = centerX
+ secondaryIconView.translationX = centerX
+ } else {
+ // the task on the right (secondary) is slightly larger
+ primaryIconView.translationX =
+ (-bottomToMidpointOffset - insetOffset).toFloat()
+ secondaryIconView.translationX =
+ (-bottomToMidpointOffset - insetOffset + taskIconHeight).toFloat()
+ }
+ }
+ }
+ }
+ } else {
+ primaryIconParams.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
+ secondaryIconParams.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
+ if (!inSplitSelection) {
+ if (oneIconHiddenDueToSmallWidth) {
+ // Center both icons
+ primaryIconView.translationX = 0f
+ secondaryIconView.translationX = 0f
+ } else {
+ // shifts icon half a width left (height is used here since icons are square)
+ primaryIconView.translationX = -(taskIconHeight / 2f)
+ secondaryIconView.translationX = taskIconHeight / 2f
+ }
+ }
+ }
+ if (!enableOverviewIconMenu() && !inSplitSelection) {
+ primaryIconView.translationY = 0f
+ secondaryIconView.translationY = 0f
+ }
+
+ primaryIconView.layoutParams = primaryIconParams
+ secondaryIconView.layoutParams = secondaryIconParams
+ }
+
+ override fun getDefaultSplitPosition(deviceProfile: DeviceProfile): Int {
+ check(deviceProfile.isTablet) { "Default position available only for large screens" }
+ return if (deviceProfile.isLeftRightSplit) {
+ SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+ } else {
+ SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT
+ }
+ }
+
+ override fun <T> getSplitSelectTaskOffset(
+ primary: FloatProperty<T>,
+ secondary: FloatProperty<T>,
+ deviceProfile: DeviceProfile,
+ ): Pair<FloatProperty<T>, FloatProperty<T>> =
+ if (deviceProfile.isLeftRightSplit) { // or seascape
+ Pair(primary, secondary)
+ } else {
+ Pair(secondary, primary)
+ }
+
+ override fun getFloatingTaskOffscreenTranslationTarget(
+ floatingTask: View,
+ onScreenRect: RectF,
+ @StagePosition stagePosition: Int,
+ dp: DeviceProfile,
+ ): Float {
+ if (dp.isLeftRightSplit) {
+ val currentTranslationX = floatingTask.translationX
+ return if (stagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT)
+ currentTranslationX - onScreenRect.width()
+ else currentTranslationX + onScreenRect.width()
+ } else {
+ val currentTranslationY = floatingTask.translationY
+ return currentTranslationY - onScreenRect.height()
+ }
+ }
+
+ override fun setFloatingTaskPrimaryTranslation(
+ floatingTask: View,
+ translation: Float,
+ dp: DeviceProfile,
+ ) {
+ if (dp.isLeftRightSplit) {
+ floatingTask.translationX = translation
+ } else {
+ floatingTask.translationY = translation
+ }
+ }
+
+ override fun getFloatingTaskPrimaryTranslation(floatingTask: View, dp: DeviceProfile): Float =
+ if (dp.isLeftRightSplit) floatingTask.translationX else floatingTask.translationY
+
+ override fun getHandlerTypeForLogging(): LauncherAtom.TaskSwitcherContainer.OrientationHandler =
+ LauncherAtom.TaskSwitcherContainer.OrientationHandler.PORTRAIT
+}
diff --git a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
index 0cb983d..7fea55d 100644
--- a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
@@ -53,7 +53,7 @@
override fun getSplitTranslationDirectionFactor(
stagePosition: Int,
- deviceProfile: DeviceProfile
+ deviceProfile: DeviceProfile,
): Int = if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) -1 else 1
override fun getRecentsRtlSetting(resources: Resources): Boolean = Utilities.isRtl(resources)
@@ -70,7 +70,7 @@
thumbnailView: View,
deviceProfile: DeviceProfile,
taskInsetMargin: Float,
- taskViewIcon: View
+ taskViewIcon: View,
): Float = x + taskInsetMargin
override fun getTaskMenuY(
@@ -79,7 +79,7 @@
stagePosition: Int,
taskMenuView: View,
taskInsetMargin: Float,
- taskViewIcon: View
+ taskViewIcon: View,
): Float {
if (Flags.enableOverviewIconMenu()) {
return y
@@ -97,14 +97,14 @@
taskInsetMargin: Float,
deviceProfile: DeviceProfile,
taskMenuX: Float,
- taskMenuY: Float
+ taskMenuY: Float,
): Int = (deviceProfile.availableWidthPx - taskInsetMargin - taskMenuX).toInt()
override fun setSplitTaskSwipeRect(
dp: DeviceProfile,
outRect: Rect,
splitInfo: SplitBounds,
- desiredStagePosition: Int
+ desiredStagePosition: Int,
) {
val topLeftTaskPercent = splitInfo.leftTopTaskPercent
val dividerBarPercent = splitInfo.dividerPercent
@@ -126,7 +126,7 @@
deviceProfile: DeviceProfile,
snapshotViewWidth: Int,
snapshotViewHeight: Int,
- banner: View
+ banner: View,
) {
banner.pivotX = 0f
banner.pivotY = 0f
@@ -149,7 +149,7 @@
deviceProfile: DeviceProfile,
thumbnailViews: Array<View>,
desiredTaskId: Int,
- banner: View
+ banner: View,
): Pair<Float, Float> {
val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
val translationX: Float = (taskViewWidth - banner.height).toFloat()
@@ -181,7 +181,7 @@
R.drawable.ic_split_horizontal,
R.string.recent_task_option_split_screen,
STAGE_POSITION_BOTTOM_OR_RIGHT,
- STAGE_TYPE_MAIN
+ STAGE_TYPE_MAIN,
)
)
@@ -189,7 +189,7 @@
out: View,
dp: DeviceProfile,
splitInstructionsHeight: Int,
- splitInstructionsWidth: Int
+ splitInstructionsWidth: Int,
) {
out.pivotX = 0f
out.pivotY = splitInstructionsHeight.toFloat()
@@ -217,7 +217,7 @@
taskIconMargin: Int,
taskIconHeight: Int,
thumbnailTopMargin: Int,
- isRtl: Boolean
+ isRtl: Boolean,
) {
iconParams.gravity =
if (isRtl) {
@@ -230,7 +230,7 @@
override fun setIconAppChipChildrenParams(
iconParams: FrameLayout.LayoutParams,
- chipChildMarginStart: Int
+ chipChildMarginStart: Int,
) {
iconParams.setMargins(0, 0, 0, 0)
iconParams.marginStart = chipChildMarginStart
@@ -241,7 +241,7 @@
iconAppChipView: IconAppChipView,
iconMenuParams: FrameLayout.LayoutParams,
iconMenuMargin: Int,
- thumbnailTopMargin: Int
+ thumbnailTopMargin: Int,
) {
val isRtl = iconAppChipView.layoutDirection == View.LAYOUT_DIRECTION_RTL
val iconCenter = iconAppChipView.getHeight() / 2f
@@ -268,7 +268,7 @@
/**
* @param inSplitSelection Whether user currently has a task from this task group staged for
- * split screen. Currently this state is not reachable in fake seascape.
+ * split screen. Currently this state is not reachable in fake seascape.
*/
override fun measureGroupedTaskViewThumbnailBounds(
primarySnapshot: View,
@@ -278,7 +278,7 @@
splitBoundsConfig: SplitBounds,
dp: DeviceProfile,
isRtl: Boolean,
- inSplitSelection: Boolean
+ inSplitSelection: Boolean,
) {
val primaryParams = primarySnapshot.layoutParams as FrameLayout.LayoutParams
val secondaryParams = secondarySnapshot.layoutParams as FrameLayout.LayoutParams
@@ -300,11 +300,11 @@
(taskViewSecond.y + spaceAboveSnapshot + dividerBar).toFloat()
primarySnapshot.measure(
MeasureSpec.makeMeasureSpec(taskViewFirst.x, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(taskViewFirst.y, MeasureSpec.EXACTLY)
+ MeasureSpec.makeMeasureSpec(taskViewFirst.y, MeasureSpec.EXACTLY),
)
secondarySnapshot.measure(
MeasureSpec.makeMeasureSpec(taskViewSecond.x, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(taskViewSecond.y, MeasureSpec.EXACTLY)
+ MeasureSpec.makeMeasureSpec(taskViewSecond.y, MeasureSpec.EXACTLY),
)
}
@@ -312,7 +312,7 @@
dp: DeviceProfile,
splitBoundsConfig: SplitBounds,
parentWidth: Int,
- parentHeight: 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)
@@ -376,10 +376,7 @@
if (oneIconHiddenDueToSmallWidth) {
// Center both icons
val centerY = primarySnapshotHeight + ((dividerSize - taskIconHeight) / 2)
- SplitIconPositions(
- topLeftY = centerY,
- bottomRightY = centerY,
- )
+ SplitIconPositions(topLeftY = centerY, bottomRightY = centerY)
} else {
SplitIconPositions(
topLeftY = primarySnapshotHeight - taskIconHeight,
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index a9dbbf2..96a5733 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -143,7 +143,7 @@
} else {
// Initiating split from overview on fullscreen task TaskView
val taskView = taskViewSupplier.get()
- taskView.taskContainers.first().let {
+ taskView.firstTaskContainer!!.let {
val drawable = getDrawable(it.iconView, splitSelectSource)
return SplitAnimInitProps(
it.snapshotView,
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
deleted file mode 100644
index 2426697..0000000
--- a/quickstep/src/com/android/quickstep/views/ClearAllButton.java
+++ /dev/null
@@ -1,333 +0,0 @@
-/*
- * Copyright (C) 2018 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.views;
-
-import static com.android.quickstep.util.BorderAnimator.DEFAULT_BORDER_COLOR;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.util.FloatProperty;
-import android.widget.Button;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.Flags;
-import com.android.launcher3.R;
-import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
-import com.android.quickstep.util.BorderAnimator;
-
-import kotlin.Unit;
-
-public class ClearAllButton extends Button {
-
- public static final FloatProperty<ClearAllButton> VISIBILITY_ALPHA =
- new FloatProperty<ClearAllButton>("visibilityAlpha") {
- @Override
- public Float get(ClearAllButton view) {
- return view.mVisibilityAlpha;
- }
-
- @Override
- public void setValue(ClearAllButton view, float v) {
- view.setVisibilityAlpha(v);
- }
- };
-
- public static final FloatProperty<ClearAllButton> DISMISS_ALPHA =
- new FloatProperty<ClearAllButton>("dismissAlpha") {
- @Override
- public Float get(ClearAllButton view) {
- return view.mDismissAlpha;
- }
-
- @Override
- public void setValue(ClearAllButton view, float v) {
- view.setDismissAlpha(v);
- }
- };
-
- private float mScrollAlpha = 1;
- private float mContentAlpha = 1;
- private float mVisibilityAlpha = 1;
- private float mDismissAlpha = 1;
- private float mFullscreenProgress = 1;
- private float mGridProgress = 1;
-
- private boolean mIsRtl;
- private float mNormalTranslationPrimary;
- private float mFullscreenTranslationPrimary;
- private float mGridTranslationPrimary;
- private float mTaskAlignmentTranslationY;
- private float mGridScrollOffset;
- private float mScrollOffsetPrimary;
-
- private int mSidePadding;
- private int mOutlinePadding;
- private boolean mBorderEnabled;
- @Nullable
- private final BorderAnimator mFocusBorderAnimator;
-
- public ClearAllButton(Context context, AttributeSet attrs) {
- super(context, attrs);
- mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
-
- if (Flags.enableFocusOutline()) {
- TypedArray styledAttrs = context.obtainStyledAttributes(attrs,
- R.styleable.ClearAllButton);
- Resources resources = getResources();
- mOutlinePadding = resources.getDimensionPixelSize(
- R.dimen.recents_clear_all_outline_padding);
- mFocusBorderAnimator =
- BorderAnimator.createSimpleBorderAnimator(
- /* borderRadiusPx= */ resources.getDimensionPixelSize(
- R.dimen.recents_clear_all_outline_radius),
- /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
- R.dimen.keyboard_quick_switch_border_width),
- /* boundsBuilder= */ this::updateBorderBounds,
- /* targetView= */ this,
- /* borderColor= */ styledAttrs.getColor(
- R.styleable.ClearAllButton_focusBorderColor,
- DEFAULT_BORDER_COLOR));
- styledAttrs.recycle();
- } else {
- mFocusBorderAnimator = null;
- }
- }
-
- private Unit updateBorderBounds(@NonNull Rect bounds) {
- bounds.set(0, 0, getWidth(), getHeight());
- // Make the value negative to form a padding between button and outline
- bounds.inset(-mOutlinePadding, -mOutlinePadding);
- return Unit.INSTANCE;
- }
-
- @Override
- public void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
- super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
- if (mFocusBorderAnimator != null && mBorderEnabled) {
- mFocusBorderAnimator.setBorderVisibility(gainFocus, /* animated= */ true);
- }
- }
-
- /**
- * Enable or disable showing border on focus change
- */
- public void setBorderEnabled(boolean enabled) {
- if (mBorderEnabled == enabled) {
- return;
- }
-
- mBorderEnabled = enabled;
- if (mFocusBorderAnimator != null) {
- mFocusBorderAnimator.setBorderVisibility(/* visible= */
- enabled && isFocused(), /* animated= */true);
- }
- }
-
- @Override
- public void draw(Canvas canvas) {
- if (mFocusBorderAnimator != null) {
- mFocusBorderAnimator.drawBorder(canvas);
- }
- super.draw(canvas);
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- RecentsPagedOrientationHandler orientationHandler =
- getRecentsView().getPagedOrientationHandler();
- mSidePadding = orientationHandler.getClearAllSidePadding(getRecentsView(), mIsRtl);
- }
-
- private RecentsView getRecentsView() {
- return (RecentsView) getParent();
- }
-
- @Override
- public void onRtlPropertiesChanged(int layoutDirection) {
- super.onRtlPropertiesChanged(layoutDirection);
- mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
- }
-
- @Override
- public boolean hasOverlappingRendering() {
- return false;
- }
-
- public float getScrollAlpha() {
- return mScrollAlpha;
- }
-
- public void setContentAlpha(float alpha) {
- if (mContentAlpha != alpha) {
- mContentAlpha = alpha;
- updateAlpha();
- }
- }
-
- public void setVisibilityAlpha(float alpha) {
- if (mVisibilityAlpha != alpha) {
- mVisibilityAlpha = alpha;
- updateAlpha();
- }
- }
-
- public void setDismissAlpha(float alpha) {
- if (mDismissAlpha != alpha) {
- mDismissAlpha = alpha;
- updateAlpha();
- }
- }
-
- public void onRecentsViewScroll(int scroll, boolean gridEnabled) {
- RecentsView recentsView = getRecentsView();
- if (recentsView == null) {
- return;
- }
-
- RecentsPagedOrientationHandler orientationHandler =
- recentsView.getPagedOrientationHandler();
- float orientationSize = orientationHandler.getPrimaryValue(getWidth(), getHeight());
- if (orientationSize == 0) {
- return;
- }
-
- int clearAllScroll = recentsView.getClearAllScroll();
- int adjustedScrollFromEdge = Math.abs(scroll - clearAllScroll);
- float shift = Math.min(adjustedScrollFromEdge, orientationSize);
- mNormalTranslationPrimary = mIsRtl ? -shift : shift;
- if (!gridEnabled) {
- mNormalTranslationPrimary += mSidePadding;
- }
- applyPrimaryTranslation();
- applySecondaryTranslation();
- float clearAllSpacing =
- recentsView.getPageSpacing() + recentsView.getClearAllExtraPageSpacing();
- clearAllSpacing = mIsRtl ? -clearAllSpacing : clearAllSpacing;
- mScrollAlpha = Math.max((clearAllScroll + clearAllSpacing - scroll) / clearAllSpacing, 0);
- updateAlpha();
- }
-
- private void updateAlpha() {
- final float alpha = mScrollAlpha * mContentAlpha * mVisibilityAlpha * mDismissAlpha;
- setAlpha(alpha);
- setClickable(Math.min(alpha, 1) == 1);
- }
-
- public void setFullscreenTranslationPrimary(float fullscreenTranslationPrimary) {
- mFullscreenTranslationPrimary = fullscreenTranslationPrimary;
- applyPrimaryTranslation();
- }
-
- /**
- * Sets `mTaskAlignmentTranslationY` to the given `value`. In order to put the button at the
- * middle in the secondary coordinate.
- */
- public void setTaskAlignmentTranslationY(float value) {
- mTaskAlignmentTranslationY = value;
- applySecondaryTranslation();
- }
-
- public void setGridTranslationPrimary(float gridTranslationPrimary) {
- mGridTranslationPrimary = gridTranslationPrimary;
- applyPrimaryTranslation();
- }
-
- public void setGridScrollOffset(float gridScrollOffset) {
- mGridScrollOffset = gridScrollOffset;
- }
-
- public void setScrollOffsetPrimary(float scrollOffsetPrimary) {
- mScrollOffsetPrimary = scrollOffsetPrimary;
- }
-
- public float getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
- float scrollAdjustment = 0;
- if (fullscreenEnabled) {
- scrollAdjustment += mFullscreenTranslationPrimary;
- }
- if (gridEnabled) {
- scrollAdjustment += mGridTranslationPrimary + mGridScrollOffset;
- }
- scrollAdjustment += mScrollOffsetPrimary;
- return scrollAdjustment;
- }
-
- public float getOffsetAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
- return getScrollAdjustment(fullscreenEnabled, gridEnabled);
- }
-
- /**
- * Adjust translation when this TaskView is about to be shown fullscreen.
- *
- * @param progress: 0 = no translation; 1 = translate according to TaskVIew translations.
- */
- public void setFullscreenProgress(float progress) {
- mFullscreenProgress = progress;
- applyPrimaryTranslation();
- }
-
- /**
- * Moves ClearAllButton between carousel and 2 row grid.
- *
- * @param gridProgress 0 = carousel; 1 = 2 row grid.
- */
- public void setGridProgress(float gridProgress) {
- mGridProgress = gridProgress;
- applyPrimaryTranslation();
- }
-
- private void applyPrimaryTranslation() {
- RecentsView recentsView = getRecentsView();
- if (recentsView == null) {
- return;
- }
-
- RecentsPagedOrientationHandler orientationHandler =
- recentsView.getPagedOrientationHandler();
- orientationHandler.getPrimaryViewTranslate().set(this,
- orientationHandler.getPrimaryValue(0f, mTaskAlignmentTranslationY)
- + mNormalTranslationPrimary + getFullscreenTrans(
- mFullscreenTranslationPrimary) + getGridTrans(mGridTranslationPrimary));
- }
-
- private void applySecondaryTranslation() {
- RecentsView recentsView = getRecentsView();
- if (recentsView == null) {
- return;
- }
-
- RecentsPagedOrientationHandler orientationHandler =
- recentsView.getPagedOrientationHandler();
- orientationHandler.getSecondaryViewTranslate().set(this,
- orientationHandler.getSecondaryValue(0f, mTaskAlignmentTranslationY));
- }
-
- private float getFullscreenTrans(float endTranslation) {
- return mFullscreenProgress > 0 ? endTranslation : 0;
- }
-
- private float getGridTrans(float endTranslation) {
- return mGridProgress > 0 ? endTranslation : 0;
- }
-}
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.kt b/quickstep/src/com/android/quickstep/views/ClearAllButton.kt
new file mode 100644
index 0000000..6420ece
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.kt
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2025 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.views
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Rect
+import android.util.AttributeSet
+import android.view.View
+import android.widget.Button
+import com.android.launcher3.Flags.enableFocusOutline
+import com.android.launcher3.R
+import com.android.launcher3.util.MultiPropertyFactory
+import com.android.launcher3.util.MultiValueAlpha
+import com.android.quickstep.util.BorderAnimator
+import com.android.quickstep.util.BorderAnimator.Companion.createSimpleBorderAnimator
+import kotlin.math.abs
+import kotlin.math.min
+
+class ClearAllButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
+ Button(context, attrs) {
+
+ private enum class Alpha {
+ SCROLL,
+ CONTENT,
+ VISIBILITY,
+ DISMISS,
+ }
+
+ private val clearAllButtonAlpha =
+ object : MultiValueAlpha(this, Alpha.entries.size) {
+ override fun apply(value: Float) {
+ super.apply(value)
+ isClickable = value >= 1f
+ }
+ }
+
+ var scrollAlpha
+ set(value) {
+ clearAllButtonAlpha.get(Alpha.SCROLL.ordinal).value = value
+ }
+ get() = clearAllButtonAlpha.get(Alpha.SCROLL.ordinal).value
+
+ var contentAlpha
+ set(value) {
+ clearAllButtonAlpha.get(Alpha.CONTENT.ordinal).value = value
+ }
+ get() = clearAllButtonAlpha.get(Alpha.CONTENT.ordinal).value
+
+ @JvmField
+ val visibilityAlphaProperty: MultiPropertyFactory<View>.MultiProperty =
+ clearAllButtonAlpha.get(Alpha.VISIBILITY.ordinal)
+
+ var dismissAlpha
+ set(value) {
+ dismissAlphaProperty.value = value
+ }
+ get() = dismissAlphaProperty.value
+
+ @JvmField
+ val dismissAlphaProperty: MultiPropertyFactory<View>.MultiProperty =
+ clearAllButtonAlpha.get(Alpha.DISMISS.ordinal)
+
+ var fullscreenProgress = 1f
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ applyPrimaryTranslation()
+ }
+
+ /**
+ * Moves ClearAllButton between carousel and 2 row grid.
+ *
+ * 0 = carousel; 1 = 2 row grid.
+ */
+ var gridProgress = 1f
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ applyPrimaryTranslation()
+ }
+
+ private var normalTranslationPrimary = 0f
+ var fullscreenTranslationPrimary = 0f
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ applyPrimaryTranslation()
+ }
+
+ var gridTranslationPrimary = 0f
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ applyPrimaryTranslation()
+ }
+
+ /** Used to put the button at the middle in the secondary coordinate. */
+ var taskAlignmentTranslationY = 0f
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ applySecondaryTranslation()
+ }
+
+ var gridScrollOffset = 0f
+ var scrollOffsetPrimary = 0f
+
+ private var sidePadding = 0
+ var borderEnabled = false
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ focusBorderAnimator?.setBorderVisibility(visible = field && isFocused, animated = true)
+ }
+
+ private val focusBorderAnimator: BorderAnimator? =
+ if (enableFocusOutline())
+ createSimpleBorderAnimator(
+ context.resources.getDimensionPixelSize(R.dimen.recents_clear_all_outline_radius),
+ context.resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_border_width),
+ this::getBorderBounds,
+ this,
+ context
+ .obtainStyledAttributes(attrs, R.styleable.ClearAllButton)
+ .getColor(
+ R.styleable.ClearAllButton_focusBorderColor,
+ BorderAnimator.DEFAULT_BORDER_COLOR,
+ ),
+ )
+ else null
+
+ private fun getBorderBounds(bounds: Rect) {
+ bounds.set(0, 0, width, height)
+ val outlinePadding =
+ context.resources.getDimensionPixelSize(R.dimen.recents_clear_all_outline_padding)
+ // Make the value negative to form a padding between button and outline
+ bounds.inset(-outlinePadding, -outlinePadding)
+ }
+
+ public override fun onFocusChanged(
+ gainFocus: Boolean,
+ direction: Int,
+ previouslyFocusedRect: Rect?,
+ ) {
+ super.onFocusChanged(gainFocus, direction, previouslyFocusedRect)
+ if (borderEnabled) {
+ focusBorderAnimator?.setBorderVisibility(gainFocus, /* animated= */ true)
+ }
+ }
+
+ override fun draw(canvas: Canvas) {
+ focusBorderAnimator?.drawBorder(canvas)
+ super.draw(canvas)
+ }
+
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+ super.onLayout(changed, left, top, right, bottom)
+ sidePadding =
+ recentsView?.let { it.pagedOrientationHandler?.getClearAllSidePadding(it, isLayoutRtl) }
+ ?: 0
+ }
+
+ private val recentsView: RecentsView<*, *>?
+ get() = parent as? RecentsView<*, *>?
+
+ override fun hasOverlappingRendering() = false
+
+ fun onRecentsViewScroll(scroll: Int, gridEnabled: Boolean) {
+ val recentsView = recentsView ?: return
+
+ val orientationSize =
+ recentsView.pagedOrientationHandler.getPrimaryValue(width, height).toFloat()
+ if (orientationSize == 0f) {
+ return
+ }
+
+ val clearAllScroll = recentsView.clearAllScroll
+ val adjustedScrollFromEdge = abs((scroll - clearAllScroll)).toFloat()
+ val shift = min(adjustedScrollFromEdge, orientationSize)
+ normalTranslationPrimary = if (isLayoutRtl) -shift else shift
+ if (!gridEnabled) {
+ normalTranslationPrimary += sidePadding.toFloat()
+ }
+ applyPrimaryTranslation()
+ applySecondaryTranslation()
+ var clearAllSpacing = recentsView.pageSpacing + recentsView.clearAllExtraPageSpacing
+ clearAllSpacing = if (isLayoutRtl) -clearAllSpacing else clearAllSpacing
+ scrollAlpha =
+ ((clearAllScroll + clearAllSpacing - scroll) / clearAllSpacing.toFloat()).coerceAtLeast(
+ 0f
+ )
+ }
+
+ fun getScrollAdjustment(fullscreenEnabled: Boolean, gridEnabled: Boolean): Float {
+ var scrollAdjustment = 0f
+ if (fullscreenEnabled) {
+ scrollAdjustment += fullscreenTranslationPrimary
+ }
+ if (gridEnabled) {
+ scrollAdjustment += gridTranslationPrimary + gridScrollOffset
+ }
+ scrollAdjustment += scrollOffsetPrimary
+ return scrollAdjustment
+ }
+
+ fun getOffsetAdjustment(fullscreenEnabled: Boolean, gridEnabled: Boolean) =
+ getScrollAdjustment(fullscreenEnabled, gridEnabled)
+
+ private fun applyPrimaryTranslation() {
+ val recentsView = recentsView ?: return
+ val orientationHandler = recentsView.pagedOrientationHandler
+ orientationHandler.primaryViewTranslate.set(
+ this,
+ (orientationHandler.getPrimaryValue(0f, taskAlignmentTranslationY) +
+ normalTranslationPrimary +
+ getFullscreenTrans(fullscreenTranslationPrimary) +
+ getGridTrans(gridTranslationPrimary)),
+ )
+ }
+
+ private fun applySecondaryTranslation() {
+ val recentsView = recentsView ?: return
+ val orientationHandler = recentsView.pagedOrientationHandler
+ orientationHandler.secondaryViewTranslate.set(
+ this,
+ orientationHandler.getSecondaryValue(0f, taskAlignmentTranslationY),
+ )
+ }
+
+ private fun getFullscreenTrans(endTranslation: Float) =
+ if (fullscreenProgress > 0) endTranslation else 0f
+
+ private fun getGridTrans(endTranslation: Float) = if (gridProgress > 0) endTranslation else 0f
+}
diff --git a/quickstep/src/com/android/quickstep/views/IconAppChipView.kt b/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
index 85d14cc..8d53552 100644
--- a/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
+++ b/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
@@ -296,7 +296,7 @@
fun getMenuTranslationY(): MultiPropertyFactory<View>.MultiProperty =
viewTranslationY[INDEX_MENU_TRANSLATION]
- internal fun revealAnim(isRevealing: Boolean) {
+ internal fun revealAnim(isRevealing: Boolean, animated: Boolean = true) {
cancelInProgressAnimations()
val collapsedBackgroundBounds = getCollapsedBackgroundLtrBounds()
val expandedBackgroundBounds = getExpandedBackgroundLtrBounds()
@@ -392,6 +392,7 @@
animator!!.setDuration(MENU_BACKGROUND_HIDE_DURATION.toLong())
}
+ if (!animated) animator!!.duration = 0
animator!!.interpolator = Interpolators.EMPHASIZED
animator!!.start()
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 5909bf9..e186838 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -66,7 +66,6 @@
import static com.android.quickstep.BaseContainerInterface.getTaskDimension;
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
import static com.android.quickstep.util.LogUtils.splitFailureMessage;
-import static com.android.quickstep.views.ClearAllButton.DISMISS_ALPHA;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_ACTIONS_IN_MENU;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_DESKTOP;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
@@ -3975,7 +3974,8 @@
// Change alpha of clear all if translating grid to hide it
if (isClearAllHidden) {
- anim.setFloat(mClearAllButton, DISMISS_ALPHA, 0, LINEAR);
+ anim.setFloat(mClearAllButton.dismissAlphaProperty, MULTI_PROPERTY_VALUE, 0,
+ LINEAR);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.kt b/quickstep/src/com/android/quickstep/views/TaskMenuView.kt
index 95336cf..4777f4f 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.kt
@@ -75,7 +75,7 @@
if (ev.action == MotionEvent.ACTION_DOWN) {
if (!recentsViewContainer.dragLayer.isEventOverView(this, ev)) {
// TODO: log this once we have a new container type for it?
- close(true)
+ animateOpenOrClosed(true)
return true
}
}
@@ -83,7 +83,7 @@
}
override fun handleClose(animate: Boolean) {
- animateClose()
+ animateOpenOrClosed(true, animated = false)
}
override fun isOfType(type: Int): Boolean = (type and TYPE_TASK_MENU) != 0
@@ -260,11 +260,7 @@
private val iconView: View
get() = taskContainer.iconView.asView()
- private fun animateClose() {
- animateOpenOrClosed(true)
- }
-
- private fun animateOpenOrClosed(closing: Boolean) {
+ private fun animateOpenOrClosed(closing: Boolean, animated: Boolean = true) {
openCloseAnimator?.let { if (it.isRunning) it.cancel() }
openCloseAnimator = AnimatorSet()
// If we're opening, we just start from the beginning as a new `TaskMenuView` is created
@@ -312,7 +308,12 @@
}
}
)
- val animationDuration = if (closing) REVEAL_CLOSE_DURATION else REVEAL_OPEN_DURATION
+ val animationDuration =
+ when {
+ animated && closing -> REVEAL_CLOSE_DURATION
+ animated && !closing -> REVEAL_OPEN_DURATION
+ else -> 0L
+ }
openCloseAnimator!!.setDuration(animationDuration)
openCloseAnimator!!.start()
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 27db6d6..ad60329 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -301,7 +301,7 @@
var sysUiStatusNavFlags: Int = 0
get() =
if (enableRefactorTaskThumbnail()) field
- else taskContainers.first().thumbnailViewDeprecated.sysUiStatusNavFlags
+ else firstTaskContainer?.thumbnailViewDeprecated?.sysUiStatusNavFlags ?: 0
private set
// Various animation progress variables.
@@ -1476,7 +1476,8 @@
return if (enableOverviewIconMenu() && menuContainer.iconView is IconAppChipView) {
menuContainer.iconView.revealAnim(/* isRevealing= */ true)
TaskMenuView.showForTask(menuContainer) {
- menuContainer.iconView.revealAnim(/* isRevealing= */ false)
+ val isAnimated = !recentsView.isSplitSelectionActive
+ menuContainer.iconView.revealAnim(/* isRevealing= */ false, isAnimated)
if (enableHoverOfChildElementsInTaskview()) {
recentsView.setTaskBorderEnabled(true)
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
index 9d000a4..c9d7e1d 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
@@ -94,6 +94,7 @@
whenever(mockTaskContainer.task).thenReturn(mockTask)
whenever(mockIconView.drawable).thenReturn(mockTaskViewDrawable)
whenever(mockTaskView.taskContainers).thenReturn(List(1) { mockTaskContainer })
+ whenever(mockTaskView.firstTaskContainer).thenReturn(mockTaskContainer)
whenever(splitSelectSource.drawable).thenReturn(mockSplitSourceDrawable)
whenever(splitSelectSource.view).thenReturn(mockSplitSourceView)
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 6277e41..84c8040 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -481,10 +481,8 @@
public static <T extends BaseActivity> T fromContext(Context context) {
if (context instanceof BaseActivity) {
return (T) context;
- } else if (context instanceof ActivityContextDelegate) {
- return (T) ((ActivityContextDelegate) context).mDelegate;
- } else if (context instanceof ContextWrapper) {
- return fromContext(((ContextWrapper) context).getBaseContext());
+ } else if (context instanceof ContextWrapper cw) {
+ return fromContext(cw.getBaseContext());
} else {
throw new IllegalArgumentException("Cannot find BaseActivity in parent tree");
}
diff --git a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
index e4c50f0..2d1a5f5 100644
--- a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
+++ b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.util.Log
+import android.view.ContextThemeWrapper
import android.view.InflateException
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.Companion.PROTECTED
@@ -33,8 +34,6 @@
import com.android.launcher3.util.Executors.VIEW_PREINFLATION_EXECUTOR
import com.android.launcher3.util.Themes
import com.android.launcher3.views.ActivityContext
-import com.android.launcher3.views.ActivityContext.ActivityContextDelegate
-import java.lang.IllegalStateException
const val PREINFLATE_ICONS_ROW_COUNT = 4
const val EXTRA_ICONS_COUNT = 2
@@ -80,11 +79,9 @@
// create a separate AssetManager obj internally to avoid lock contention with
// AssetManager obj that is associated with the launcher context on the main thread.
val allAppsPreInflationContext =
- ActivityContextDelegate(
- context.createConfigurationContext(context.resources.configuration),
- Themes.getActivityThemeRes(context),
- context,
- )
+ ContextThemeWrapper(context, Themes.getActivityThemeRes(context)).apply {
+ applyOverrideConfiguration(context.resources.configuration)
+ }
// Because we perform onCreateViewHolder() on worker thread, we need a separate
// adapter/inflator object as they are not thread-safe. Note that the adapter
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index 81968fc..bbea043 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -43,7 +43,6 @@
import android.os.Process;
import android.os.UserHandle;
import android.util.Log;
-import android.view.ContextThemeWrapper;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.View;
@@ -552,21 +551,10 @@
static <T extends Context & ActivityContext> T lookupContextNoThrow(Context context) {
if (context instanceof ActivityContext) {
return (T) context;
- } else if (context instanceof ActivityContextDelegate acd) {
- return (T) acd.mDelegate;
- } else if (context instanceof ContextWrapper) {
- return lookupContextNoThrow(((ContextWrapper) context).getBaseContext());
+ } else if (context instanceof ContextWrapper cw) {
+ return lookupContextNoThrow(cw.getBaseContext());
} else {
return null;
}
}
-
- class ActivityContextDelegate extends ContextThemeWrapper {
- public final ActivityContext mDelegate;
-
- public ActivityContextDelegate(Context base, int themeResId, ActivityContext delegate) {
- super(base, themeResId);
- mDelegate = delegate;
- }
- }
}
diff --git a/tests/multivalentTests/shared/com/android/launcher3/testing/OWNERS b/tests/multivalentTests/shared/com/android/launcher3/testing/OWNERS
new file mode 100644
index 0000000..02e8ebc
--- /dev/null
+++ b/tests/multivalentTests/shared/com/android/launcher3/testing/OWNERS
@@ -0,0 +1,4 @@
+vadimt@google.com
+sunnygoyal@google.com
+winsonc@google.com
+hyunyoungs@google.com