Snap for 11591048 from 7d5531757b to 24Q3-release
Change-Id: I57d8ba26cd49308334a91f6cb7da0ab4b053e9a2
This commit is contained in:
@@ -201,6 +201,8 @@ android_library {
|
||||
"Launcher3ResLib",
|
||||
"launcher-testing-shared",
|
||||
"animationlib",
|
||||
"kotlinx_coroutines_android",
|
||||
"kotlinx_coroutines",
|
||||
"com_android_launcher3_flags_lib",
|
||||
"com_android_wm_shell_flags_lib",
|
||||
"android.appwidget.flags-aconfig-java",
|
||||
|
||||
@@ -173,6 +173,13 @@ flag {
|
||||
bug: "318410881"
|
||||
}
|
||||
|
||||
flag {
|
||||
name: "force_monochrome_app_icons"
|
||||
namespace: "launcher"
|
||||
description: "Enable the ability to generate monochromatic icons, if it is not provided by the app"
|
||||
bug: "270396209"
|
||||
}
|
||||
|
||||
flag {
|
||||
name: "enable_add_app_widget_via_config_activity_v2"
|
||||
namespace: "launcher"
|
||||
|
||||
@@ -379,13 +379,6 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
Gravity.apply(gravity, outWidth, outHeight, potentialTaskRect, outRect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the task size for the desktop task
|
||||
*/
|
||||
public final void calculateDesktopTaskSize(Context context, DeviceProfile dp, Rect outRect) {
|
||||
calculateFocusTaskSize(context, dp, outRect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the modal taskView size for the provided device configuration
|
||||
*/
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.android.quickstep.orientation
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Point
|
||||
import android.graphics.PointF
|
||||
@@ -33,6 +34,7 @@ import android.view.ViewGroup
|
||||
import android.view.accessibility.AccessibilityEvent
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.LinearLayout
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.core.util.component1
|
||||
import androidx.core.util.component2
|
||||
import com.android.launcher3.DeviceProfile
|
||||
@@ -44,7 +46,11 @@ import com.android.launcher3.touch.PagedOrientationHandler.ChildBounds
|
||||
import com.android.launcher3.touch.PagedOrientationHandler.Float2DAction
|
||||
import com.android.launcher3.touch.PagedOrientationHandler.Int2DAction
|
||||
import com.android.launcher3.touch.SingleAxisSwipeDetector
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.*
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition
|
||||
import com.android.launcher3.views.BaseDragLayer
|
||||
@@ -451,13 +457,8 @@ open class LandscapePagedViewHandler : RecentsPagedOrientationHandler {
|
||||
// (portrait bottom) and secondary is on the right (portrait top)
|
||||
val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
|
||||
val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
|
||||
val dividerBar =
|
||||
Math.round(
|
||||
totalThumbnailHeight *
|
||||
if (splitBoundsConfig.appsStackedVertically)
|
||||
splitBoundsConfig.dividerHeightPercent
|
||||
else splitBoundsConfig.dividerWidthPercent
|
||||
)
|
||||
val dividerBar = getDividerBarSize(totalThumbnailHeight, splitBoundsConfig)
|
||||
|
||||
val (taskViewFirst, taskViewSecond) =
|
||||
getGroupedTaskViewSizes(dp, splitBoundsConfig, parentWidth, parentHeight)
|
||||
|
||||
@@ -482,13 +483,8 @@ open class LandscapePagedViewHandler : RecentsPagedOrientationHandler {
|
||||
): Pair<Point, Point> {
|
||||
val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
|
||||
val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
|
||||
val dividerBar =
|
||||
Math.round(
|
||||
totalThumbnailHeight *
|
||||
if (splitBoundsConfig.appsStackedVertically)
|
||||
splitBoundsConfig.dividerHeightPercent
|
||||
else splitBoundsConfig.dividerWidthPercent
|
||||
)
|
||||
val dividerBar = getDividerBarSize(totalThumbnailHeight, splitBoundsConfig)
|
||||
|
||||
val taskPercent =
|
||||
if (splitBoundsConfig.appsStackedVertically) {
|
||||
splitBoundsConfig.topTaskPercent
|
||||
@@ -569,64 +565,22 @@ open class LandscapePagedViewHandler : RecentsPagedOrientationHandler {
|
||||
deviceProfile: DeviceProfile,
|
||||
splitConfig: SplitBounds
|
||||
) {
|
||||
val primaryIconParams = primaryIconView.layoutParams as FrameLayout.LayoutParams
|
||||
val secondaryIconParams =
|
||||
if (Flags.enableOverviewIconMenu())
|
||||
secondaryIconView.layoutParams as FrameLayout.LayoutParams
|
||||
else FrameLayout.LayoutParams(primaryIconParams)
|
||||
val spaceAboveSnapshot = deviceProfile.overviewTaskThumbnailTopMarginPx
|
||||
val totalThumbnailHeight = groupedTaskViewHeight - spaceAboveSnapshot
|
||||
val dividerBar: Int = getDividerBarSize(totalThumbnailHeight, splitConfig)
|
||||
|
||||
// We calculate the "midpoint" of the thumbnail area, and place the icons there.
|
||||
// This is the place where the thumbnail area splits by default, in a near-50/50 split.
|
||||
// It is usually not exactly 50/50, due to insets/screen cutouts.
|
||||
val fullscreenInsetThickness = (deviceProfile.insets.top - deviceProfile.insets.bottom)
|
||||
val fullscreenMidpointFromBottom = ((deviceProfile.heightPx - fullscreenInsetThickness) / 2)
|
||||
val midpointFromBottomPct = fullscreenMidpointFromBottom.toFloat() / deviceProfile.heightPx
|
||||
val insetPct = fullscreenInsetThickness.toFloat() / deviceProfile.heightPx
|
||||
val spaceAboveSnapshots = deviceProfile.overviewTaskThumbnailTopMarginPx
|
||||
val overviewThumbnailAreaThickness = groupedTaskViewHeight - spaceAboveSnapshots
|
||||
val bottomToMidpointOffset =
|
||||
(overviewThumbnailAreaThickness * midpointFromBottomPct).toInt()
|
||||
val insetOffset = (overviewThumbnailAreaThickness * insetPct).toInt()
|
||||
if (Flags.enableOverviewIconMenu()) {
|
||||
val gravity = if (isRtl) Gravity.BOTTOM or Gravity.START else Gravity.TOP or Gravity.END
|
||||
primaryIconParams.gravity = gravity
|
||||
secondaryIconParams.gravity = gravity
|
||||
} else {
|
||||
primaryIconParams.gravity = Gravity.BOTTOM or if (isRtl) Gravity.START else Gravity.END
|
||||
secondaryIconParams.gravity =
|
||||
Gravity.BOTTOM or if (isRtl) Gravity.START else Gravity.END
|
||||
}
|
||||
primaryIconView.translationX = 0f
|
||||
secondaryIconView.translationX = 0f
|
||||
when {
|
||||
Flags.enableOverviewIconMenu() -> {
|
||||
val primaryAppChipView = primaryIconView as IconAppChipView
|
||||
val secondaryAppChipView = secondaryIconView as IconAppChipView
|
||||
if (primaryIconView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
|
||||
secondaryAppChipView.setSplitTranslationY(-primarySnapshotHeight.toFloat())
|
||||
primaryAppChipView.setSplitTranslationY(0f)
|
||||
} else {
|
||||
val secondarySnapshotHeight = groupedTaskViewHeight - primarySnapshotHeight
|
||||
primaryAppChipView.setSplitTranslationY(secondarySnapshotHeight.toFloat())
|
||||
}
|
||||
}
|
||||
splitConfig.initiatedFromSeascape -> {
|
||||
// if the split was initiated from seascape,
|
||||
// the task on the right (secondary) is slightly larger
|
||||
primaryIconView.translationY = (-bottomToMidpointOffset - insetOffset).toFloat()
|
||||
secondaryIconView.translationY =
|
||||
(-bottomToMidpointOffset - insetOffset + taskIconHeight).toFloat()
|
||||
}
|
||||
else -> {
|
||||
// if not,
|
||||
// the task on the left (primary) is slightly larger
|
||||
primaryIconView.translationY = -bottomToMidpointOffset.toFloat()
|
||||
secondaryIconView.translationY =
|
||||
(-bottomToMidpointOffset + taskIconHeight).toFloat()
|
||||
}
|
||||
}
|
||||
primaryIconView.layoutParams = primaryIconParams
|
||||
secondaryIconView.layoutParams = secondaryIconParams
|
||||
val (topLeftY, bottomRightY) =
|
||||
getSplitIconsPosition(
|
||||
taskIconHeight,
|
||||
primarySnapshotHeight,
|
||||
totalThumbnailHeight,
|
||||
isRtl,
|
||||
deviceProfile.overviewTaskMarginPx,
|
||||
dividerBar
|
||||
)
|
||||
|
||||
updateSplitIconsPosition(primaryIconView, topLeftY, isRtl)
|
||||
updateSplitIconsPosition(secondaryIconView, bottomRightY, isRtl)
|
||||
}
|
||||
|
||||
override fun getDefaultSplitPosition(deviceProfile: DeviceProfile): Int {
|
||||
@@ -656,4 +610,91 @@ open class LandscapePagedViewHandler : RecentsPagedOrientationHandler {
|
||||
|
||||
override fun getFloatingTaskPrimaryTranslation(floatingTask: View, dp: DeviceProfile): Float =
|
||||
floatingTask.translationY
|
||||
|
||||
/**
|
||||
* Retrieves split icons position
|
||||
*
|
||||
* @param taskIconHeight The height of the task icon.
|
||||
* @param primarySnapshotHeight The height for the primary snapshot (i.e., top-left snapshot).
|
||||
* @param totalThumbnailHeight The total height for the group task view.
|
||||
* @param isRtl Whether the layout direction is RTL (or false for LTR).
|
||||
* @param overviewTaskMarginPx The space under the focused task icon provided by Device Profile.
|
||||
* @param dividerSize The size of the divider for the group task view.
|
||||
* @return The top-left and right-bottom positions for the icon views.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
open fun getSplitIconsPosition(
|
||||
taskIconHeight: Int,
|
||||
primarySnapshotHeight: Int,
|
||||
totalThumbnailHeight: Int,
|
||||
isRtl: Boolean,
|
||||
overviewTaskMarginPx: Int,
|
||||
dividerSize: Int,
|
||||
): SplitIconPositions {
|
||||
return if (Flags.enableOverviewIconMenu()) {
|
||||
if (isRtl) {
|
||||
SplitIconPositions(0, -(totalThumbnailHeight - primarySnapshotHeight))
|
||||
} else {
|
||||
SplitIconPositions(0, primarySnapshotHeight + dividerSize)
|
||||
}
|
||||
} else {
|
||||
val topLeftY = primarySnapshotHeight + overviewTaskMarginPx
|
||||
SplitIconPositions(
|
||||
topLeftY = topLeftY,
|
||||
bottomRightY = topLeftY + dividerSize + taskIconHeight
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates icon view gravity and translation for split tasks
|
||||
*
|
||||
* @param iconView View to be updated
|
||||
* @param translationY the translationY that should be applied
|
||||
* @param isRtl Whether the layout direction is RTL (or false for LTR).
|
||||
*/
|
||||
@SuppressLint("RtlHardcoded")
|
||||
@VisibleForTesting
|
||||
open fun updateSplitIconsPosition(iconView: View, translationY: Int, isRtl: Boolean) {
|
||||
val layoutParams = iconView.layoutParams as FrameLayout.LayoutParams
|
||||
|
||||
if (Flags.enableOverviewIconMenu()) {
|
||||
val appChipView = iconView as IconAppChipView
|
||||
layoutParams.gravity =
|
||||
if (isRtl) Gravity.BOTTOM or Gravity.START else Gravity.TOP or Gravity.END
|
||||
appChipView.layoutParams = layoutParams
|
||||
appChipView.setSplitTranslationX(0f)
|
||||
appChipView.setSplitTranslationY(translationY.toFloat())
|
||||
} else {
|
||||
layoutParams.gravity = Gravity.TOP or Gravity.RIGHT
|
||||
layoutParams.topMargin = translationY
|
||||
iconView.translationX = 0f
|
||||
iconView.translationY = 0f
|
||||
iconView.layoutParams = layoutParams
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* It calculates the divider's size in the group task view.
|
||||
*
|
||||
* @param totalThumbnailHeight The total height for the group task view
|
||||
* @param splitConfig Contains information about sizes and proportions for split task.
|
||||
* @return The divider size for the group task view.
|
||||
*/
|
||||
protected fun getDividerBarSize(totalThumbnailHeight: Int, splitConfig: SplitBounds): Int {
|
||||
return Math.round(
|
||||
totalThumbnailHeight *
|
||||
if (splitConfig.appsStackedVertically) splitConfig.dividerHeightPercent
|
||||
else splitConfig.dividerWidthPercent
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Data structure to keep the y position to be used for the split task icon views translation.
|
||||
*
|
||||
* @param topLeftY The y-axis position for the task view position on the Top or Left side.
|
||||
* @param bottomRightY The y-axis position for the task view position on the Bottom or Right
|
||||
* side.
|
||||
*/
|
||||
data class SplitIconPositions(val topLeftY: Int, val bottomRightY: Int)
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ public class PortraitPagedViewHandler extends DefaultPagedViewHandler implements
|
||||
public <T> T getSecondaryValue(T x, T y) {
|
||||
return y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLayoutNaturalToLauncher() {
|
||||
return true;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.android.quickstep.orientation
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Point
|
||||
import android.graphics.PointF
|
||||
@@ -32,7 +33,12 @@ import com.android.launcher3.Flags
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.Utilities
|
||||
import com.android.launcher3.touch.SingleAxisSwipeDetector
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.*
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption
|
||||
import com.android.launcher3.views.BaseDragLayer
|
||||
import com.android.quickstep.views.IconAppChipView
|
||||
|
||||
@@ -255,97 +261,6 @@ class SeascapePagedViewHandler : LandscapePagedViewHandler() {
|
||||
iconAppChipView.setRotation(degreesRotated)
|
||||
}
|
||||
|
||||
override fun setSplitIconParams(
|
||||
primaryIconView: View,
|
||||
secondaryIconView: View,
|
||||
taskIconHeight: Int,
|
||||
primarySnapshotWidth: Int,
|
||||
primarySnapshotHeight: Int,
|
||||
groupedTaskViewHeight: Int,
|
||||
groupedTaskViewWidth: Int,
|
||||
isRtl: Boolean,
|
||||
deviceProfile: DeviceProfile,
|
||||
splitConfig: SplitBounds
|
||||
) {
|
||||
super.setSplitIconParams(
|
||||
primaryIconView,
|
||||
secondaryIconView,
|
||||
taskIconHeight,
|
||||
primarySnapshotWidth,
|
||||
primarySnapshotHeight,
|
||||
groupedTaskViewHeight,
|
||||
groupedTaskViewWidth,
|
||||
isRtl,
|
||||
deviceProfile,
|
||||
splitConfig
|
||||
)
|
||||
val primaryIconParams = primaryIconView.layoutParams as FrameLayout.LayoutParams
|
||||
val secondaryIconParams = secondaryIconView.layoutParams as FrameLayout.LayoutParams
|
||||
|
||||
// We calculate the "midpoint" of the thumbnail area, and place the icons there.
|
||||
// This is the place where the thumbnail area splits by default, in a near-50/50 split.
|
||||
// It is usually not exactly 50/50, due to insets/screen cutouts.
|
||||
val fullscreenInsetThickness = (deviceProfile.insets.top - deviceProfile.insets.bottom)
|
||||
val fullscreenMidpointFromBottom = (deviceProfile.heightPx - fullscreenInsetThickness) / 2
|
||||
val midpointFromBottomPct = fullscreenMidpointFromBottom.toFloat() / deviceProfile.heightPx
|
||||
val insetPct = fullscreenInsetThickness.toFloat() / deviceProfile.heightPx
|
||||
val spaceAboveSnapshots = deviceProfile.overviewTaskThumbnailTopMarginPx
|
||||
val overviewThumbnailAreaThickness = groupedTaskViewHeight - spaceAboveSnapshots
|
||||
val bottomToMidpointOffset =
|
||||
(overviewThumbnailAreaThickness * midpointFromBottomPct).toInt()
|
||||
val insetOffset = (overviewThumbnailAreaThickness * insetPct).toInt()
|
||||
val gravity = if (isRtl) Gravity.TOP or Gravity.END else Gravity.BOTTOM or Gravity.START
|
||||
primaryIconParams.gravity = gravity
|
||||
secondaryIconParams.gravity = gravity
|
||||
primaryIconView.translationX = 0f
|
||||
secondaryIconView.translationX = 0f
|
||||
when {
|
||||
Flags.enableOverviewIconMenu() -> {
|
||||
val primaryAppChipView = primaryIconView as IconAppChipView
|
||||
val secondaryAppChipView = secondaryIconView as IconAppChipView
|
||||
if (isRtl) {
|
||||
primaryAppChipView.setSplitTranslationY(
|
||||
(groupedTaskViewHeight - primarySnapshotHeight).toFloat()
|
||||
)
|
||||
secondaryAppChipView.setSplitTranslationY(0f)
|
||||
} else {
|
||||
secondaryAppChipView.setSplitTranslationY(-primarySnapshotHeight.toFloat())
|
||||
primaryAppChipView.setSplitTranslationY(0f)
|
||||
}
|
||||
}
|
||||
splitConfig.initiatedFromSeascape -> {
|
||||
// if the split was initiated from seascape,
|
||||
// the task on the right (secondary) is slightly larger
|
||||
if (isRtl) {
|
||||
primaryIconView.translationY =
|
||||
(bottomToMidpointOffset - insetOffset + taskIconHeight).toFloat()
|
||||
secondaryIconView.translationY =
|
||||
(bottomToMidpointOffset - insetOffset).toFloat()
|
||||
} else {
|
||||
primaryIconView.translationY =
|
||||
(-bottomToMidpointOffset - insetOffset + taskIconHeight).toFloat()
|
||||
secondaryIconView.translationY =
|
||||
(-bottomToMidpointOffset - insetOffset).toFloat()
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// if not,
|
||||
// the task on the left (primary) is slightly larger
|
||||
if (isRtl) {
|
||||
primaryIconView.translationY =
|
||||
(bottomToMidpointOffset + taskIconHeight).toFloat()
|
||||
secondaryIconView.translationY = bottomToMidpointOffset.toFloat()
|
||||
} else {
|
||||
primaryIconView.translationY =
|
||||
(-bottomToMidpointOffset + taskIconHeight).toFloat()
|
||||
secondaryIconView.translationY = -bottomToMidpointOffset.toFloat()
|
||||
}
|
||||
}
|
||||
}
|
||||
primaryIconView.layoutParams = primaryIconParams
|
||||
secondaryIconView.layoutParams = secondaryIconParams
|
||||
}
|
||||
|
||||
override fun measureGroupedTaskViewThumbnailBounds(
|
||||
primarySnapshot: View,
|
||||
secondarySnapshot: View,
|
||||
@@ -366,13 +281,8 @@ class SeascapePagedViewHandler : LandscapePagedViewHandler() {
|
||||
// (portrait bottom) and secondary is on the right (portrait top)
|
||||
val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
|
||||
val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
|
||||
val dividerBar =
|
||||
Math.round(
|
||||
totalThumbnailHeight *
|
||||
if (splitBoundsConfig.appsStackedVertically)
|
||||
splitBoundsConfig.dividerHeightPercent
|
||||
else splitBoundsConfig.dividerWidthPercent
|
||||
)
|
||||
val dividerBar = getDividerBarSize(totalThumbnailHeight, splitBoundsConfig)
|
||||
|
||||
val (taskViewFirst, taskViewSecond) =
|
||||
getGroupedTaskViewSizes(dp, splitBoundsConfig, parentWidth, parentHeight)
|
||||
secondarySnapshot.translationY = 0f
|
||||
@@ -398,13 +308,8 @@ class SeascapePagedViewHandler : LandscapePagedViewHandler() {
|
||||
// (portrait bottom) and secondary is on the right (portrait top)
|
||||
val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
|
||||
val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
|
||||
val dividerBar =
|
||||
Math.round(
|
||||
totalThumbnailHeight *
|
||||
if (splitBoundsConfig.appsStackedVertically)
|
||||
splitBoundsConfig.dividerHeightPercent
|
||||
else splitBoundsConfig.dividerWidthPercent
|
||||
)
|
||||
val dividerBar = getDividerBarSize(totalThumbnailHeight, splitBoundsConfig)
|
||||
|
||||
val taskPercent =
|
||||
if (splitBoundsConfig.appsStackedVertically) {
|
||||
splitBoundsConfig.topTaskPercent
|
||||
@@ -430,4 +335,64 @@ class SeascapePagedViewHandler : LandscapePagedViewHandler() {
|
||||
|
||||
override fun getTaskDragDisplacementFactor(isRtl: Boolean): Int = if (isRtl) -1 else 1
|
||||
/* -------------------- */
|
||||
|
||||
override fun getSplitIconsPosition(
|
||||
taskIconHeight: Int,
|
||||
primarySnapshotHeight: Int,
|
||||
totalThumbnailHeight: Int,
|
||||
isRtl: Boolean,
|
||||
overviewTaskMarginPx: Int,
|
||||
dividerSize: Int,
|
||||
): SplitIconPositions {
|
||||
return if (Flags.enableOverviewIconMenu()) {
|
||||
if (isRtl) {
|
||||
SplitIconPositions(
|
||||
topLeftY = totalThumbnailHeight - primarySnapshotHeight,
|
||||
bottomRightY = 0
|
||||
)
|
||||
} else {
|
||||
SplitIconPositions(
|
||||
topLeftY = 0,
|
||||
bottomRightY = -(primarySnapshotHeight + dividerSize)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// In seascape, the icons are initially placed at the bottom start of the
|
||||
// display (portrait locked). The values defined here are used to translate the icons
|
||||
// from the bottom to the almost-center of the screen using the bottom margin.
|
||||
// The primary snapshot is placed at the bottom, thus we translate the icons using
|
||||
// the size of the primary snapshot minus the icon size for the top-left icon.
|
||||
SplitIconPositions(
|
||||
topLeftY = primarySnapshotHeight - taskIconHeight,
|
||||
bottomRightY = primarySnapshotHeight + dividerSize
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates icon view gravity and translation for split tasks
|
||||
*
|
||||
* @param iconView View to be updated
|
||||
* @param translationY the translationY that should be applied
|
||||
* @param isRtl Whether the layout direction is RTL (or false for LTR).
|
||||
*/
|
||||
@SuppressLint("RtlHardcoded")
|
||||
override fun updateSplitIconsPosition(iconView: View, translationY: Int, isRtl: Boolean) {
|
||||
val layoutParams = iconView.layoutParams as FrameLayout.LayoutParams
|
||||
|
||||
if (Flags.enableOverviewIconMenu()) {
|
||||
val appChipView = iconView as IconAppChipView
|
||||
layoutParams.gravity =
|
||||
if (isRtl) Gravity.TOP or Gravity.END else Gravity.BOTTOM or Gravity.START
|
||||
appChipView.layoutParams = layoutParams
|
||||
appChipView.setSplitTranslationX(0f)
|
||||
appChipView.setSplitTranslationY(translationY.toFloat())
|
||||
} else {
|
||||
layoutParams.gravity = Gravity.BOTTOM or Gravity.LEFT
|
||||
iconView.translationX = 0f
|
||||
iconView.translationY = 0f
|
||||
layoutParams.bottomMargin = translationY
|
||||
iconView.layoutParams = layoutParams
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,7 +463,6 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
protected final Rect mLastComputedTaskSize = new Rect();
|
||||
protected final Rect mLastComputedGridSize = new Rect();
|
||||
protected final Rect mLastComputedGridTaskSize = new Rect();
|
||||
protected final Rect mLastComputedDesktopTaskSize = new Rect();
|
||||
private TaskView mSelectedTask = null;
|
||||
// How much a task that is directly offscreen will be pushed out due to RecentsView scale/pivot.
|
||||
@Nullable
|
||||
@@ -1658,15 +1657,8 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
removeView(runningTaskView);
|
||||
mMovingTaskView = null;
|
||||
runningTaskView.resetPersistentViewTransforms();
|
||||
int frontTaskIndex = 0;
|
||||
if (isDesktopModeSupported() && mDesktopTaskView != null
|
||||
&& !runningTaskView.isDesktopTask()) {
|
||||
// If desktop mode is enabled, desktop task view is pinned at first position if present.
|
||||
// Move running task to position 1.
|
||||
frontTaskIndex = 1;
|
||||
}
|
||||
addView(runningTaskView, frontTaskIndex);
|
||||
setCurrentPage(frontTaskIndex);
|
||||
addView(runningTaskView, 0);
|
||||
setCurrentPage(0);
|
||||
|
||||
updateTaskSize();
|
||||
}
|
||||
@@ -1743,7 +1735,6 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
|
||||
// Clear out desktop view if it is set
|
||||
mDesktopTaskView = null;
|
||||
DesktopTask desktopTask = null;
|
||||
|
||||
// Add views as children based on whether it's grouped or single task. Looping through
|
||||
// taskGroups backwards populates the thumbnail grid from least recent to most recent.
|
||||
@@ -1752,12 +1743,6 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
boolean isRemovalNeeded = stagedTaskIdToBeRemovedFromGrid != INVALID_TASK_ID
|
||||
&& groupTask.containsTask(stagedTaskIdToBeRemovedFromGrid);
|
||||
|
||||
if (groupTask instanceof DesktopTask) {
|
||||
desktopTask = (DesktopTask) groupTask;
|
||||
// Desktop task will be added separately in the end
|
||||
continue;
|
||||
}
|
||||
|
||||
TaskView taskView;
|
||||
if (isRemovalNeeded && groupTask.hasMultipleTasks()) {
|
||||
// If we need to remove half of a pair of tasks, force a TaskView with Type.SINGLE
|
||||
@@ -1788,6 +1773,10 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
|
||||
((GroupedTaskView) taskView).bind(leftTopTask, rightBottomTask, mOrientationState,
|
||||
groupTask.mSplitBounds);
|
||||
} else if (taskView instanceof DesktopTaskView) {
|
||||
((DesktopTaskView) taskView).bind(((DesktopTask) groupTask).tasks,
|
||||
mOrientationState);
|
||||
mDesktopTaskView = (DesktopTaskView) taskView;
|
||||
} else {
|
||||
taskView.bind(groupTask.task1, mOrientationState);
|
||||
}
|
||||
@@ -1800,19 +1789,6 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
|
||||
if (!taskGroups.isEmpty()) {
|
||||
addView(mClearAllButton);
|
||||
if (isDesktopModeSupported()) {
|
||||
// Check if we have apps on the desktop
|
||||
if (desktopTask != null && !desktopTask.tasks.isEmpty()) {
|
||||
// If we are actively choosing apps for split, skip the desktop tile
|
||||
if (!getSplitSelectController().isSplitSelectActive()) {
|
||||
mDesktopTaskView = (DesktopTaskView) getTaskViewFromPool(
|
||||
TaskView.Type.DESKTOP);
|
||||
// Always add a desktop task to the first position
|
||||
addView(mDesktopTaskView, 0);
|
||||
mDesktopTaskView.bind(desktopTask.tasks, mOrientationState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Keep same previous focused task
|
||||
@@ -1820,12 +1796,6 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
// If the list changed, maybe the focused task doesn't exist anymore
|
||||
if (newFocusedTaskView == null && getTaskViewCount() > 0) {
|
||||
newFocusedTaskView = getTaskViewAt(0);
|
||||
// Check if the first task is the desktop.
|
||||
// If first task is desktop, try to find another task to set as the focused task
|
||||
if (newFocusedTaskView != null && newFocusedTaskView.isDesktopTask()
|
||||
&& getTaskViewCount() > 1) {
|
||||
newFocusedTaskView = getTaskViewAt(1);
|
||||
}
|
||||
}
|
||||
mFocusedTaskViewId = newFocusedTaskView != null && !enableGridOnlyOverview()
|
||||
? newFocusedTaskView.getTaskViewId() : INVALID_TASK_ID;
|
||||
@@ -1869,12 +1839,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
if (hasAnyValidTaskIds(runningTaskId)) {
|
||||
targetPage = indexOfChild(newRunningTaskView);
|
||||
} else if (getTaskViewCount() > 0) {
|
||||
TaskView taskView = requireTaskViewAt(0);
|
||||
// If first task id desktop, try to find another task to set the target page
|
||||
if (taskView.isDesktopTask() && getTaskViewCount() > 1) {
|
||||
taskView = requireTaskViewAt(1);
|
||||
}
|
||||
targetPage = indexOfChild(taskView);
|
||||
targetPage = indexOfChild(requireTaskViewAt(0));
|
||||
}
|
||||
}
|
||||
if (targetPage != -1 && mCurrentPage != targetPage) {
|
||||
@@ -2119,9 +2084,6 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
mSizeStrategy.calculateGridSize(dp, mActivity, mLastComputedGridSize);
|
||||
mSizeStrategy.calculateGridTaskSize(mActivity, dp, mLastComputedGridTaskSize,
|
||||
getPagedOrientationHandler());
|
||||
if (isDesktopModeSupported()) {
|
||||
mSizeStrategy.calculateDesktopTaskSize(mActivity, dp, mLastComputedDesktopTaskSize);
|
||||
}
|
||||
if (enableGridOnlyOverview()) {
|
||||
mSizeStrategy.calculateCarouselTaskSize(mActivity, dp, mLastComputedCarouselTaskSize,
|
||||
getPagedOrientationHandler());
|
||||
@@ -2222,11 +2184,6 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
return mLastComputedGridTaskSize;
|
||||
}
|
||||
|
||||
/** Gets the last computed desktop task size */
|
||||
public Rect getLastComputedDesktopTaskSize() {
|
||||
return mLastComputedDesktopTaskSize;
|
||||
}
|
||||
|
||||
public Rect getLastComputedCarouselTaskSize() {
|
||||
return mLastComputedCarouselTaskSize;
|
||||
}
|
||||
@@ -2977,8 +2934,6 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
TaskView homeTaskView = getHomeTaskView();
|
||||
TaskView nextFocusedTaskView = null;
|
||||
|
||||
int desktopTaskIndex = Integer.MAX_VALUE;
|
||||
|
||||
if (!isTaskDismissal) {
|
||||
mTopRowIdSet.clear();
|
||||
}
|
||||
@@ -3005,21 +2960,6 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
// If focused task is snapped, the row width is just task width and spacing.
|
||||
snappedTaskRowWidth = taskWidthAndSpacing;
|
||||
}
|
||||
} else if (taskView.isDesktopTask()) {
|
||||
// Desktop task was not focused. Pin it to the right of focused
|
||||
desktopTaskIndex = i;
|
||||
if (taskView.getVisibility() == View.GONE) {
|
||||
// Desktop task view is hidden, skip it from grid calculations
|
||||
continue;
|
||||
}
|
||||
if (!enableGridOnlyOverview()) {
|
||||
// Only apply x-translation when using legacy overview grid
|
||||
gridTranslations[i] += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
|
||||
}
|
||||
|
||||
// Center view vertically in case it's from different orientation.
|
||||
taskView.setGridTranslationY((mLastComputedDesktopTaskSize.height() + taskTopMargin
|
||||
- taskView.getLayoutParams().height) / 2f);
|
||||
} else {
|
||||
if (i > focusedTaskIndex) {
|
||||
// For tasks after the focused task, shift by focused task's width and spacing.
|
||||
@@ -3060,7 +3000,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
// Move horizontally into empty space.
|
||||
float widthOffset = 0;
|
||||
for (int j = i - 1; !topSet.contains(j) && j >= 0; j--) {
|
||||
if (j == focusedTaskIndex || j == desktopTaskIndex) {
|
||||
if (j == focusedTaskIndex) {
|
||||
continue;
|
||||
}
|
||||
widthOffset += requireTaskViewAt(j).getLayoutParams().width + mPageSpacing;
|
||||
@@ -3079,7 +3019,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
// Move horizontally into empty space.
|
||||
float widthOffset = 0;
|
||||
for (int j = i - 1; !bottomSet.contains(j) && j >= 0; j--) {
|
||||
if (j == focusedTaskIndex || j == desktopTaskIndex) {
|
||||
if (j == focusedTaskIndex) {
|
||||
continue;
|
||||
}
|
||||
widthOffset += requireTaskViewAt(j).getLayoutParams().width + mPageSpacing;
|
||||
@@ -5545,10 +5485,6 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
}
|
||||
|
||||
private int getFirstViewIndex() {
|
||||
if (isDesktopModeSupported() && mDesktopTaskView != null) {
|
||||
// Desktop task is at position 0, that is the first view
|
||||
return 0;
|
||||
}
|
||||
TaskView focusedTaskView = mShowAsGridLastOnLayout ? getFocusedTaskView() : null;
|
||||
return focusedTaskView != null ? indexOfChild(focusedTaskView) : 0;
|
||||
}
|
||||
|
||||
@@ -1737,12 +1737,7 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
int boxWidth;
|
||||
int boxHeight;
|
||||
boolean isFocusedTask = isFocusedTask();
|
||||
if (isDesktopTask()) {
|
||||
Rect lastComputedDesktopTaskSize =
|
||||
getRecentsView().getLastComputedDesktopTaskSize();
|
||||
boxWidth = lastComputedDesktopTaskSize.width();
|
||||
boxHeight = lastComputedDesktopTaskSize.height();
|
||||
} else if (isFocusedTask) {
|
||||
if (isFocusedTask) {
|
||||
// Task will be focused and should use focused task size. Use focusTaskRatio
|
||||
// that is associated with the original orientation of the focused task.
|
||||
boxWidth = taskWidth;
|
||||
|
||||
@@ -501,6 +501,7 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest {
|
||||
|
||||
@Test
|
||||
@PortraitLandscape
|
||||
@ScreenRecord // b/326839375
|
||||
public void testOverviewDeadzones() throws Exception {
|
||||
startTestAppsWithCheck();
|
||||
|
||||
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.quickstep.orientation
|
||||
|
||||
import android.platform.test.flag.junit.SetFlagsRule
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.launcher3.Flags
|
||||
import com.android.quickstep.orientation.LandscapePagedViewHandler.SplitIconPositions
|
||||
import com.android.quickstep.views.IconAppChipView
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.verify
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class LandscapePagedViewHandlerTest {
|
||||
|
||||
@get:Rule val setFlagsRule = SetFlagsRule()
|
||||
|
||||
private val sut = LandscapePagedViewHandler()
|
||||
|
||||
private fun enableGridOnlyOverview(isEnabled: Boolean) {
|
||||
if (isEnabled) {
|
||||
setFlagsRule.enableFlags(
|
||||
Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW,
|
||||
Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU
|
||||
)
|
||||
} else {
|
||||
setFlagsRule.disableFlags(
|
||||
Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW,
|
||||
Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** [ Test getSplitIconsPosition ] */
|
||||
private fun getSplitIconsPosition(isRTL: Boolean): SplitIconPositions {
|
||||
return sut.getSplitIconsPosition(
|
||||
TASK_ICON_HEIGHT_PX,
|
||||
PRIMARY_SNAPSHOT,
|
||||
TOTAL_THUMBNAIL_HEIGHT,
|
||||
isRTL,
|
||||
OVERVIEW_TASK_MARGIN_PX,
|
||||
DIVIDER_SIZE_PX,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIcon_getSplitIconsPositions() {
|
||||
enableGridOnlyOverview(false)
|
||||
|
||||
val (topLeftY, bottomRightY) = getSplitIconsPosition(isRTL = false)
|
||||
|
||||
// Top-Left icon should be at the end of the primary snapshot height
|
||||
assertThat(topLeftY).isEqualTo(250)
|
||||
// Bottom-Right icon should be at the end of the primary height + divider + icon size
|
||||
assertThat(bottomRightY).isEqualTo(374)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIcon_getSplitIconsPositions_isRTL() {
|
||||
enableGridOnlyOverview(false)
|
||||
|
||||
val (topLeftY, bottomRightY) = getSplitIconsPosition(isRTL = true)
|
||||
|
||||
// Top-Left icon should be at the end of the primary snapshot height
|
||||
assertThat(topLeftY).isEqualTo(250)
|
||||
// Bottom-Right icon should be at the end of the primary height + divider + icon size
|
||||
assertThat(bottomRightY).isEqualTo(374)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChip_getSplitIconsPositions() {
|
||||
enableGridOnlyOverview(true)
|
||||
|
||||
val (topLeftY, bottomRightY) = getSplitIconsPosition(isRTL = false)
|
||||
|
||||
// Top-Left app chip should always be at the initial position of the first snapshot
|
||||
assertThat(topLeftY).isEqualTo(0)
|
||||
// Bottom-Right app chip should be at the end of the primary height + divider
|
||||
assertThat(bottomRightY).isEqualTo(266)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChip_getSplitIconsPositions_isRTL() {
|
||||
enableGridOnlyOverview(true)
|
||||
|
||||
val (topLeftY, bottomRightY) = getSplitIconsPosition(isRTL = true)
|
||||
|
||||
// TODO(b/326377497): When started in fake seascape and rotated to landscape,
|
||||
// the icon chips are in RTL and wrongly positioned at the right side of the snapshot.
|
||||
// Top-Left app chip should be placed at the top left of the first snapshot, but because
|
||||
// this issue, it's displayed at the top-right of the second snapshot.
|
||||
// The Bottom-Right app chip is displayed at the top-right of the first snapshot because
|
||||
// of this issue.
|
||||
assertThat(topLeftY).isEqualTo(0)
|
||||
assertThat(bottomRightY).isEqualTo(-316)
|
||||
}
|
||||
|
||||
/** Test updateSplitIconsPosition */
|
||||
@Test
|
||||
fun testIcon_updateSplitIconsPosition() {
|
||||
enableGridOnlyOverview(false)
|
||||
|
||||
val expectedTranslationY = 250
|
||||
val expectedGravity = Gravity.TOP or Gravity.RIGHT
|
||||
|
||||
val iconView = mock<View>()
|
||||
val frameLayout = FrameLayout.LayoutParams(100, 100)
|
||||
`when`(iconView.layoutParams).thenReturn(frameLayout)
|
||||
|
||||
sut.updateSplitIconsPosition(iconView, expectedTranslationY, false)
|
||||
assertThat(frameLayout.gravity).isEqualTo(expectedGravity)
|
||||
assertThat(frameLayout.topMargin).isEqualTo(expectedTranslationY)
|
||||
verify(iconView).translationX = 0f
|
||||
verify(iconView).translationY = 0f
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIcon_updateSplitIconsPosition_isRTL() {
|
||||
enableGridOnlyOverview(false)
|
||||
|
||||
val expectedTranslationY = 250
|
||||
val expectedGravity = Gravity.TOP or Gravity.RIGHT
|
||||
|
||||
val iconView = mock<View>()
|
||||
val frameLayout = FrameLayout.LayoutParams(100, 100)
|
||||
`when`(iconView.layoutParams).thenReturn(frameLayout)
|
||||
|
||||
sut.updateSplitIconsPosition(iconView, expectedTranslationY, true)
|
||||
assertThat(frameLayout.gravity).isEqualTo(expectedGravity)
|
||||
assertThat(frameLayout.topMargin).isEqualTo(expectedTranslationY)
|
||||
verify(iconView).translationX = 0f
|
||||
verify(iconView).translationY = 0f
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChip_updateSplitIconsPosition() {
|
||||
enableGridOnlyOverview(true)
|
||||
|
||||
val expectedTranslationY = 250
|
||||
val frameLayout = FrameLayout.LayoutParams(100, 100)
|
||||
val iconView = mock<IconAppChipView>()
|
||||
`when`(iconView.layoutParams).thenReturn(frameLayout)
|
||||
|
||||
sut.updateSplitIconsPosition(iconView, expectedTranslationY, false)
|
||||
assertThat(frameLayout.gravity).isEqualTo(Gravity.TOP or Gravity.END)
|
||||
verify(iconView).setSplitTranslationX(0f)
|
||||
verify(iconView).setSplitTranslationY(expectedTranslationY.toFloat())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChip_updateSplitIconsPosition_isRTL() {
|
||||
enableGridOnlyOverview(true)
|
||||
|
||||
val expectedTranslationY = 250
|
||||
val frameLayout = FrameLayout.LayoutParams(100, 100)
|
||||
val iconView = mock<IconAppChipView>()
|
||||
`when`(iconView.layoutParams).thenReturn(frameLayout)
|
||||
|
||||
sut.updateSplitIconsPosition(iconView, expectedTranslationY, true)
|
||||
assertThat(frameLayout.gravity).isEqualTo(Gravity.BOTTOM or Gravity.START)
|
||||
verify(iconView).setSplitTranslationX(0f)
|
||||
verify(iconView).setSplitTranslationY(expectedTranslationY.toFloat())
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val TASK_ICON_HEIGHT_PX = 108
|
||||
const val OVERVIEW_TASK_MARGIN_PX = 0
|
||||
const val DIVIDER_SIZE_PX = 16
|
||||
const val PRIMARY_SNAPSHOT = 250
|
||||
const val SECONDARY_SNAPSHOT = 300
|
||||
const val TOTAL_THUMBNAIL_HEIGHT = PRIMARY_SNAPSHOT + SECONDARY_SNAPSHOT + DIVIDER_SIZE_PX
|
||||
}
|
||||
}
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.quickstep.orientation
|
||||
|
||||
import android.platform.test.flag.junit.SetFlagsRule
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.launcher3.Flags
|
||||
import com.android.quickstep.orientation.LandscapePagedViewHandler.SplitIconPositions
|
||||
import com.android.quickstep.views.IconAppChipView
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.verify
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class SeascapePagedViewHandlerTest {
|
||||
|
||||
@get:Rule val setFlagsRule = SetFlagsRule()
|
||||
|
||||
private val sut = SeascapePagedViewHandler()
|
||||
|
||||
private fun enableGridOnlyOverview(isEnabled: Boolean) {
|
||||
if (isEnabled) {
|
||||
setFlagsRule.enableFlags(
|
||||
Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW,
|
||||
Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU
|
||||
)
|
||||
} else {
|
||||
setFlagsRule.disableFlags(
|
||||
Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW,
|
||||
Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** [ Test getSplitIconsPosition ] */
|
||||
private fun getSplitIconsPosition(isRTL: Boolean): SplitIconPositions {
|
||||
return sut.getSplitIconsPosition(
|
||||
TASK_ICON_HEIGHT_PX,
|
||||
PRIMARY_SNAPSHOT,
|
||||
TOTAL_THUMBNAIL_HEIGHT,
|
||||
isRTL,
|
||||
OVERVIEW_TASK_MARGIN_PX,
|
||||
DIVIDER_SIZE_PX,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIcon_getSplitIconsPositions() {
|
||||
enableGridOnlyOverview(false)
|
||||
|
||||
val (topLeftY, bottomRightY) = getSplitIconsPosition(isRTL = false)
|
||||
|
||||
// The top-left icon is translated from the bottom of the screen to the end of
|
||||
// the primary snapshot minus the icon size.
|
||||
assertThat(topLeftY).isEqualTo(142)
|
||||
// The bottom-right icon is placed at the end of the primary snapshot plus the divider.
|
||||
assertThat(bottomRightY).isEqualTo(266)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIcon_getSplitIconsPositions_isRTL() {
|
||||
enableGridOnlyOverview(false)
|
||||
|
||||
val (topLeftY, bottomRightY) = getSplitIconsPosition(isRTL = true)
|
||||
|
||||
// The top-left icon is translated from the bottom of the screen to the end of
|
||||
// the primary snapshot minus the icon size.
|
||||
assertThat(topLeftY).isEqualTo(142)
|
||||
// The bottom-right icon is placed at the end of the primary snapshot plus the divider.
|
||||
assertThat(bottomRightY).isEqualTo(266)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChip_getSplitIconsPositions() {
|
||||
enableGridOnlyOverview(true)
|
||||
|
||||
val (topLeftY, bottomRightY) = getSplitIconsPosition(isRTL = false)
|
||||
|
||||
// Top-Left app chip should always be at the initial position of the first snapshot
|
||||
assertThat(topLeftY).isEqualTo(0)
|
||||
// Bottom-Right app chip should be at the end of the primary height + divider
|
||||
assertThat(bottomRightY).isEqualTo(-266)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChip_getSplitIconsPositions_isRTL() {
|
||||
enableGridOnlyOverview(true)
|
||||
|
||||
val (topLeftY, bottomRightY) = getSplitIconsPosition(isRTL = true)
|
||||
|
||||
// TODO(b/326377497): When started in fake seascape and rotated to landscape,
|
||||
// the icon chips are in RTL and wrongly positioned at the right side of the snapshot.
|
||||
// Top-Left app chip should be placed at the top left of the first snapshot, but because
|
||||
// this issue, it's displayed at the top-right of the second snapshot.
|
||||
// The Bottom-Right app chip is displayed at the top-right of the first snapshot because
|
||||
// of this issue.
|
||||
assertThat(topLeftY).isEqualTo(316)
|
||||
assertThat(bottomRightY).isEqualTo(0)
|
||||
}
|
||||
|
||||
/** Test updateSplitIconsPosition */
|
||||
@Test
|
||||
fun testIcon_updateSplitIconsPosition() {
|
||||
enableGridOnlyOverview(false)
|
||||
|
||||
val expectedTranslationY = 250
|
||||
val expectedGravity = Gravity.BOTTOM or Gravity.LEFT
|
||||
|
||||
val iconView = mock<View>()
|
||||
val frameLayout = FrameLayout.LayoutParams(100, 100)
|
||||
`when`(iconView.layoutParams).thenReturn(frameLayout)
|
||||
|
||||
sut.updateSplitIconsPosition(iconView, expectedTranslationY, false)
|
||||
assertThat(frameLayout.gravity).isEqualTo(expectedGravity)
|
||||
assertThat(frameLayout.bottomMargin).isEqualTo(expectedTranslationY)
|
||||
verify(iconView).translationX = 0f
|
||||
verify(iconView).translationY = 0f
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIcon_updateSplitIconsPosition_isRTL() {
|
||||
enableGridOnlyOverview(false)
|
||||
|
||||
val expectedTranslationY = 250
|
||||
val expectedGravity = Gravity.BOTTOM or Gravity.LEFT
|
||||
|
||||
val iconView = mock<View>()
|
||||
val frameLayout = FrameLayout.LayoutParams(100, 100)
|
||||
`when`(iconView.layoutParams).thenReturn(frameLayout)
|
||||
|
||||
sut.updateSplitIconsPosition(iconView, expectedTranslationY, true)
|
||||
assertThat(frameLayout.gravity).isEqualTo(expectedGravity)
|
||||
assertThat(frameLayout.bottomMargin).isEqualTo(expectedTranslationY)
|
||||
verify(iconView).translationX = 0f
|
||||
verify(iconView).translationY = 0f
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChip_updateSplitIconsPosition() {
|
||||
enableGridOnlyOverview(true)
|
||||
|
||||
val expectedTranslationY = 250
|
||||
val frameLayout = FrameLayout.LayoutParams(100, 100)
|
||||
val iconView = mock<IconAppChipView>()
|
||||
`when`(iconView.layoutParams).thenReturn(frameLayout)
|
||||
|
||||
sut.updateSplitIconsPosition(iconView, expectedTranslationY, false)
|
||||
assertThat(frameLayout.gravity).isEqualTo(Gravity.BOTTOM or Gravity.START)
|
||||
verify(iconView).setSplitTranslationX(0f)
|
||||
verify(iconView).setSplitTranslationY(expectedTranslationY.toFloat())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChip_updateSplitIconsPosition_isRTL() {
|
||||
enableGridOnlyOverview(true)
|
||||
|
||||
val expectedTranslationY = 250
|
||||
val frameLayout = FrameLayout.LayoutParams(100, 100)
|
||||
val iconView = mock<IconAppChipView>()
|
||||
`when`(iconView.layoutParams).thenReturn(frameLayout)
|
||||
|
||||
sut.updateSplitIconsPosition(iconView, expectedTranslationY, true)
|
||||
assertThat(frameLayout.gravity).isEqualTo(Gravity.TOP or Gravity.END)
|
||||
verify(iconView).setSplitTranslationX(0f)
|
||||
verify(iconView).setSplitTranslationY(expectedTranslationY.toFloat())
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val TASK_ICON_HEIGHT_PX = 108
|
||||
const val OVERVIEW_TASK_MARGIN_PX = 0
|
||||
const val DIVIDER_SIZE_PX = 16
|
||||
const val PRIMARY_SNAPSHOT = 250
|
||||
const val SECONDARY_SNAPSHOT = 300
|
||||
const val TOTAL_THUMBNAIL_HEIGHT = PRIMARY_SNAPSHOT + SECONDARY_SNAPSHOT + DIVIDER_SIZE_PX
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURC
|
||||
|
||||
import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
|
||||
import static com.android.launcher3.config.FeatureFlags.IS_STUDIO_BUILD;
|
||||
import static com.android.launcher3.icons.cache.BaseIconCache.EMPTY_CLASS_NAME;
|
||||
import static com.android.launcher3.model.PackageUpdatedTask.OP_UPDATE;
|
||||
import static com.android.launcher3.pm.UserCache.ACTION_PROFILE_AVAILABLE;
|
||||
import static com.android.launcher3.pm.UserCache.ACTION_PROFILE_UNAVAILABLE;
|
||||
@@ -27,6 +28,7 @@ import static com.android.launcher3.testing.shared.TestProtocol.sDebugTracing;
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInstaller;
|
||||
@@ -70,6 +72,7 @@ import com.android.launcher3.pm.UserCache;
|
||||
import com.android.launcher3.shortcuts.ShortcutRequest;
|
||||
import com.android.launcher3.util.IntSet;
|
||||
import com.android.launcher3.util.ItemInfoMatcher;
|
||||
import com.android.launcher3.util.PackageManagerHelper;
|
||||
import com.android.launcher3.util.PackageUserKey;
|
||||
import com.android.launcher3.util.Preconditions;
|
||||
|
||||
@@ -443,9 +446,18 @@ public class LauncherModel implements InstallSessionTracker.Callback {
|
||||
@NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
|
||||
IconCache iconCache = app.getIconCache();
|
||||
final IntSet removedIds = new IntSet();
|
||||
HashSet<WorkspaceItemInfo> archivedItemsToCacheRefresh = new HashSet<>();
|
||||
HashSet<String> archivedPackagesToCacheRefresh = new HashSet<>();
|
||||
HashSet<WorkspaceItemInfo> archivedWorkspaceItemsToCacheRefresh = new HashSet<>();
|
||||
boolean isAppArchived = new PackageManagerHelper(
|
||||
mApp.getContext()).isAppArchivedForUser(packageName, user);
|
||||
synchronized (dataModel) {
|
||||
if (isAppArchived) {
|
||||
// Remove package icon cache entry for archived app in case of a session
|
||||
// failure.
|
||||
mApp.getIconCache().remove(
|
||||
new ComponentName(packageName, packageName + EMPTY_CLASS_NAME),
|
||||
user);
|
||||
}
|
||||
|
||||
for (ItemInfo info : dataModel.itemsIdMap) {
|
||||
if (info instanceof WorkspaceItemInfo
|
||||
&& ((WorkspaceItemInfo) info).hasPromiseIconUi()
|
||||
@@ -456,19 +468,16 @@ public class LauncherModel implements InstallSessionTracker.Callback {
|
||||
}
|
||||
if (((WorkspaceItemInfo) info).isArchived()) {
|
||||
WorkspaceItemInfo workspaceItem = (WorkspaceItemInfo) info;
|
||||
// Remove package cache icon for archived app in case of a session
|
||||
// failure.
|
||||
mApp.getIconCache().removeIconsForPkg(packageName, user);
|
||||
// Refresh icons on the workspace for archived apps.
|
||||
iconCache.getTitleAndIcon(workspaceItem,
|
||||
workspaceItem.usingLowResIcon());
|
||||
archivedPackagesToCacheRefresh.add(packageName);
|
||||
archivedItemsToCacheRefresh.add(workspaceItem);
|
||||
archivedWorkspaceItemsToCacheRefresh.add(workspaceItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!archivedPackagesToCacheRefresh.isEmpty()) {
|
||||
apps.updateIconsAndLabels(archivedPackagesToCacheRefresh, user);
|
||||
|
||||
if (isAppArchived) {
|
||||
apps.updateIconsAndLabels(new HashSet<>(List.of(packageName)), user);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,8 +486,11 @@ public class LauncherModel implements InstallSessionTracker.Callback {
|
||||
ItemInfoMatcher.ofItemIds(removedIds),
|
||||
"removed because install session failed");
|
||||
}
|
||||
if (!archivedItemsToCacheRefresh.isEmpty()) {
|
||||
bindUpdatedWorkspaceItems(archivedItemsToCacheRefresh.stream().toList());
|
||||
if (!archivedWorkspaceItemsToCacheRefresh.isEmpty()) {
|
||||
bindUpdatedWorkspaceItems(
|
||||
archivedWorkspaceItemsToCacheRefresh.stream().toList());
|
||||
}
|
||||
if (isAppArchived) {
|
||||
bindApplicationsIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,7 +405,9 @@ public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate<Lau
|
||||
} else if (item instanceof PendingAddItemInfo) {
|
||||
PendingAddItemInfo info = (PendingAddItemInfo) item;
|
||||
Workspace<?> workspace = mContext.getWorkspace();
|
||||
workspace.snapToPage(workspace.getPageIndexForScreenId(screenId));
|
||||
workspace.post(
|
||||
() -> workspace.snapToPage(workspace.getPageIndexForScreenId(screenId))
|
||||
);
|
||||
mContext.addPendingItem(info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
|
||||
screenId, coordinates, info.spanX, info.spanY);
|
||||
} else if (item instanceof WorkspaceItemInfo) {
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.launcher3.allapps;
|
||||
import android.content.Context;
|
||||
import android.os.Process;
|
||||
import android.os.UserHandle;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.launcher3.model.data.AppInfo;
|
||||
import com.android.launcher3.pm.UserCache;
|
||||
@@ -64,7 +65,7 @@ public class AppInfoComparator implements Comparator<AppInfo> {
|
||||
}
|
||||
|
||||
private String getSortingTitle(AppInfo info) {
|
||||
if (info.appTitle != null) {
|
||||
if (!TextUtils.isEmpty(info.appTitle)) {
|
||||
return info.appTitle.toString();
|
||||
}
|
||||
if (info.title != null) {
|
||||
|
||||
@@ -184,11 +184,6 @@ public final class FeatureFlags {
|
||||
"SECONDARY_DRAG_N_DROP_TO_PIN", DISABLED,
|
||||
"Enable dragging and dropping to pin apps within secondary display");
|
||||
|
||||
// TODO(Block 7): Clean up flags
|
||||
public static final BooleanFlag ENABLE_FORCED_MONO_ICON = getDebugFlag(270396209,
|
||||
"ENABLE_FORCED_MONO_ICON", DISABLED,
|
||||
"Enable the ability to generate monochromatic icons, if it is not provided by the app");
|
||||
|
||||
// TODO(Block 8): Clean up flags
|
||||
|
||||
// TODO(Block 9): Clean up flags
|
||||
|
||||
@@ -222,6 +222,7 @@ public class IconCache extends BaseIconCache {
|
||||
* Updates {@param application} only if a valid entry is found.
|
||||
*/
|
||||
public synchronized void updateTitleAndIcon(AppInfo application) {
|
||||
boolean preferPackageIcon = application.isArchived();
|
||||
CacheEntry entry = cacheLocked(application.componentName,
|
||||
application.user, () -> null, mLauncherActivityInfoCachingLogic,
|
||||
false, application.usingLowResIcon());
|
||||
@@ -229,13 +230,12 @@ public class IconCache extends BaseIconCache {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean preferPackageIcon = application.isArchived();
|
||||
if (preferPackageIcon) {
|
||||
String packageName = application.getTargetPackage();
|
||||
CacheEntry packageEntry =
|
||||
cacheLocked(new ComponentName(packageName, packageName + EMPTY_CLASS_NAME),
|
||||
application.user, () -> null, mLauncherActivityInfoCachingLogic,
|
||||
false, application.usingLowResIcon());
|
||||
true, application.usingLowResIcon());
|
||||
applyPackageEntry(packageEntry, application, entry);
|
||||
} else {
|
||||
applyCacheEntry(entry, application);
|
||||
|
||||
@@ -16,14 +16,13 @@
|
||||
|
||||
package com.android.launcher3.icons;
|
||||
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_FORCED_MONO_ICON;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.UserHandle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.launcher3.Flags;
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.graphics.IconShape;
|
||||
import com.android.launcher3.graphics.LauncherPreviewRenderer;
|
||||
@@ -103,7 +102,7 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable {
|
||||
@Override
|
||||
protected Drawable getMonochromeDrawable(Drawable base) {
|
||||
Drawable mono = super.getMonochromeDrawable(base);
|
||||
if (mono != null || !ENABLE_FORCED_MONO_ICON.get()) {
|
||||
if (mono != null || !Flags.forceMonochromeAppIcons()) {
|
||||
return mono;
|
||||
}
|
||||
if (mMonochromeIconFactory == null) {
|
||||
|
||||
Reference in New Issue
Block a user