Snap for 12313714 from fae91d5e57 to 24Q4-release
Change-Id: I1d508a219251bd568de38361a51367668e433b44
This commit is contained in:
@@ -52,6 +52,7 @@ filegroup {
|
||||
"tests/src/com/android/quickstep/TaplOverviewIconTest.java",
|
||||
"tests/src/com/android/quickstep/TaplTestsQuickstep.java",
|
||||
"tests/src/com/android/quickstep/TaplTestsSplitscreen.java",
|
||||
"tests/src/com/android/quickstep/util/SplitScreenTestUtils.kt",
|
||||
"tests/src/com/android/launcher3/testcomponent/ExcludeFromRecentsTestActivity.java",
|
||||
],
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<TextView
|
||||
<com.android.quickstep.views.DigitalWellBeingToast
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
||||
style="@style/TextTitle"
|
||||
|
||||
@@ -47,4 +47,8 @@
|
||||
android:inflatedId="@id/icon"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content" />
|
||||
|
||||
<include layout="@layout/digital_wellbeing_toast"
|
||||
android:id="@+id/digital_wellbeing_toast"
|
||||
android:visibility="invisible"/>
|
||||
</com.android.quickstep.views.TaskView>
|
||||
@@ -73,4 +73,12 @@
|
||||
android:inflatedId="@id/bottomRight_icon"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content" />
|
||||
|
||||
<include layout="@layout/digital_wellbeing_toast"
|
||||
android:id="@+id/digital_wellbeing_toast"
|
||||
android:visibility="invisible"/>
|
||||
|
||||
<include layout="@layout/digital_wellbeing_toast"
|
||||
android:id="@+id/bottomRight_digital_wellbeing_toast"
|
||||
android:visibility="invisible"/>
|
||||
</com.android.quickstep.views.GroupedTaskView>
|
||||
@@ -152,12 +152,12 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
/**
|
||||
* How long to delay the icon/stash handle alpha.
|
||||
*/
|
||||
private static final long TASKBAR_STASH_ALPHA_START_DELAY = 33;
|
||||
public static final long TASKBAR_STASH_ALPHA_START_DELAY = 33;
|
||||
|
||||
/**
|
||||
* How long the icon/stash handle alpha animation plays.
|
||||
*/
|
||||
private static final long TASKBAR_STASH_ALPHA_DURATION = 50;
|
||||
public static final long TASKBAR_STASH_ALPHA_DURATION = 50;
|
||||
|
||||
/**
|
||||
* How long to delay the icon/stash handle alpha for the home to app taskbar animation.
|
||||
|
||||
@@ -179,9 +179,6 @@ interface BubbleStashController {
|
||||
/** How long to stash/unstash. */
|
||||
const val BAR_STASH_DURATION = InsetsController.ANIMATION_DURATION_RESIZE.toLong()
|
||||
|
||||
const val BAR_STASH_ALPHA_DURATION = 50L
|
||||
const val BAR_STASH_ALPHA_DELAY = 33L
|
||||
|
||||
/** How long to translate Y coordinate of the BubbleBar. */
|
||||
const val BAR_TRANSLATION_DURATION = 300L
|
||||
}
|
||||
|
||||
+4
-4
@@ -31,10 +31,10 @@ import com.android.launcher3.R
|
||||
import com.android.launcher3.anim.AnimatedFloat
|
||||
import com.android.launcher3.anim.SpringAnimationBuilder
|
||||
import com.android.launcher3.taskbar.TaskbarInsetsController
|
||||
import com.android.launcher3.taskbar.TaskbarStashController.TASKBAR_STASH_ALPHA_DURATION
|
||||
import com.android.launcher3.taskbar.TaskbarStashController.TASKBAR_STASH_ALPHA_START_DELAY
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController
|
||||
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.BAR_STASH_ALPHA_DELAY
|
||||
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.BAR_STASH_ALPHA_DURATION
|
||||
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.BAR_STASH_DURATION
|
||||
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.BAR_TRANSLATION_DURATION
|
||||
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.ControllersAfterInitAction
|
||||
@@ -285,8 +285,8 @@ class TransientBubbleStashController(
|
||||
private fun createStashAnimator(isStashed: Boolean, duration: Long): AnimatorSet {
|
||||
val animatorSet = AnimatorSet()
|
||||
|
||||
val alphaDuration = if (isStashed) duration else BAR_STASH_ALPHA_DURATION
|
||||
val alphaDelay = if (isStashed) BAR_STASH_ALPHA_DELAY else 0L
|
||||
val alphaDuration = if (isStashed) duration else TASKBAR_STASH_ALPHA_DURATION
|
||||
val alphaDelay = if (isStashed) TASKBAR_STASH_ALPHA_START_DELAY else 0L
|
||||
animatorSet.play(
|
||||
createStashAlphaAnimator(isStashed).apply {
|
||||
this.duration = max(0L, alphaDuration - alphaDelay)
|
||||
|
||||
@@ -37,6 +37,7 @@ import android.widget.LinearLayout
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.core.util.component1
|
||||
import androidx.core.util.component2
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import com.android.launcher3.DeviceProfile
|
||||
import com.android.launcher3.Flags
|
||||
import com.android.launcher3.LauncherAnimUtils
|
||||
@@ -242,7 +243,30 @@ open class LandscapePagedViewHandler : RecentsPagedOrientationHandler {
|
||||
lp.height = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
}
|
||||
|
||||
override fun getDwbLayoutTranslations(
|
||||
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> {
|
||||
gravity = Gravity.TOP or if (banner.isLayoutRtl) Gravity.END else Gravity.START
|
||||
width =
|
||||
if (isGroupedTaskView) {
|
||||
snapshotViewHeight
|
||||
} else {
|
||||
taskViewHeight - deviceProfile.overviewTaskThumbnailTopMarginPx
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDwbBannerTranslations(
|
||||
taskViewWidth: Int,
|
||||
taskViewHeight: Int,
|
||||
splitBounds: SplitBounds?,
|
||||
@@ -252,39 +276,25 @@ open class LandscapePagedViewHandler : RecentsPagedOrientationHandler {
|
||||
banner: View
|
||||
): Pair<Float, Float> {
|
||||
val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
|
||||
val isRtl = banner.layoutDirection == View.LAYOUT_DIRECTION_RTL
|
||||
val translationX = banner.height.toFloat()
|
||||
|
||||
val bannerParams = banner.layoutParams as FrameLayout.LayoutParams
|
||||
bannerParams.gravity = Gravity.TOP or if (isRtl) Gravity.END else Gravity.START
|
||||
banner.pivotX = 0f
|
||||
banner.pivotY = 0f
|
||||
banner.rotation = degreesRotated
|
||||
|
||||
if (splitBounds == null) {
|
||||
// Single, fullscreen case
|
||||
bannerParams.width = taskViewHeight - snapshotParams.topMargin
|
||||
return Pair(translationX, snapshotParams.topMargin.toFloat())
|
||||
}
|
||||
|
||||
// Set correct width and translations
|
||||
val translationY: Float
|
||||
if (desiredTaskId == splitBounds.leftTopTaskId) {
|
||||
bannerParams.width = thumbnailViews[0].measuredHeight
|
||||
if (splitBounds == null) {
|
||||
translationY = snapshotParams.topMargin.toFloat()
|
||||
} else {
|
||||
bannerParams.width = thumbnailViews[1].measuredHeight
|
||||
val topLeftTaskPlusDividerPercent =
|
||||
if (splitBounds.appsStackedVertically) {
|
||||
splitBounds.topTaskPercent + splitBounds.dividerHeightPercent
|
||||
} else {
|
||||
splitBounds.leftTaskPercent + splitBounds.dividerWidthPercent
|
||||
}
|
||||
translationY =
|
||||
snapshotParams.topMargin +
|
||||
(taskViewHeight - snapshotParams.topMargin) * topLeftTaskPlusDividerPercent
|
||||
if (desiredTaskId == splitBounds.leftTopTaskId) {
|
||||
translationY = snapshotParams.topMargin.toFloat()
|
||||
} else {
|
||||
val topLeftTaskPlusDividerPercent =
|
||||
if (splitBounds.appsStackedVertically) {
|
||||
splitBounds.topTaskPercent + splitBounds.dividerHeightPercent
|
||||
} else {
|
||||
splitBounds.leftTaskPercent + splitBounds.dividerWidthPercent
|
||||
}
|
||||
translationY =
|
||||
snapshotParams.topMargin +
|
||||
(taskViewHeight - snapshotParams.topMargin) * topLeftTaskPlusDividerPercent
|
||||
}
|
||||
}
|
||||
|
||||
return Pair(translationX, translationY)
|
||||
}
|
||||
|
||||
@@ -300,6 +310,7 @@ open class LandscapePagedViewHandler : RecentsPagedOrientationHandler {
|
||||
if (isRtl) displacement < 0 else displacement > 0
|
||||
|
||||
override fun getTaskDragDisplacementFactor(isRtl: Boolean): Int = if (isRtl) 1 else -1
|
||||
|
||||
/* -------------------- */
|
||||
|
||||
override fun getChildBounds(
|
||||
|
||||
@@ -243,54 +243,54 @@ public class PortraitPagedViewHandler extends DefaultPagedViewHandler implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth,
|
||||
int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile,
|
||||
View[] thumbnailViews, int desiredTaskId, View banner) {
|
||||
float translationX = 0;
|
||||
float translationY = 0;
|
||||
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 (splitBounds == null) {
|
||||
// Single, fullscreen case
|
||||
if (isGroupedTaskView) {
|
||||
bannerParams.gravity =
|
||||
BOTTOM | (deviceProfile.isLeftRightSplit ? START : CENTER_HORIZONTAL);
|
||||
bannerParams.width = snapshotViewWidth;
|
||||
} else {
|
||||
bannerParams.width = MATCH_PARENT;
|
||||
bannerParams.gravity = BOTTOM | CENTER_HORIZONTAL;
|
||||
return new Pair<>(translationX, translationY);
|
||||
}
|
||||
banner.setLayoutParams(bannerParams);
|
||||
}
|
||||
|
||||
bannerParams.gravity =
|
||||
BOTTOM | (deviceProfile.isLeftRightSplit ? START : CENTER_HORIZONTAL);
|
||||
|
||||
// Set correct width
|
||||
if (desiredTaskId == splitBounds.leftTopTaskId) {
|
||||
bannerParams.width = thumbnailViews[0].getMeasuredWidth();
|
||||
} else {
|
||||
bannerParams.width = thumbnailViews[1].getMeasuredWidth();
|
||||
}
|
||||
|
||||
// Set translations
|
||||
if (deviceProfile.isLeftRightSplit) {
|
||||
if (desiredTaskId == splitBounds.rightBottomTaskId) {
|
||||
float leftTopTaskPercent = splitBounds.appsStackedVertically
|
||||
? splitBounds.topTaskPercent
|
||||
: splitBounds.leftTaskPercent;
|
||||
float dividerThicknessPercent = splitBounds.appsStackedVertically
|
||||
? splitBounds.dividerHeightPercent
|
||||
: splitBounds.dividerWidthPercent;
|
||||
translationX = ((taskViewWidth * leftTopTaskPercent)
|
||||
+ (taskViewWidth * dividerThicknessPercent));
|
||||
}
|
||||
} else {
|
||||
if (desiredTaskId == splitBounds.leftTopTaskId) {
|
||||
FrameLayout.LayoutParams snapshotParams =
|
||||
(FrameLayout.LayoutParams) thumbnailViews[0]
|
||||
.getLayoutParams();
|
||||
float bottomRightTaskPlusDividerPercent = splitBounds.appsStackedVertically
|
||||
? (1f - splitBounds.topTaskPercent)
|
||||
: (1f - splitBounds.leftTaskPercent);
|
||||
translationY = -((taskViewHeight - snapshotParams.topMargin)
|
||||
* bottomRightTaskPlusDividerPercent);
|
||||
@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.appsStackedVertically
|
||||
? splitBounds.topTaskPercent
|
||||
: splitBounds.leftTaskPercent;
|
||||
float dividerThicknessPercent = splitBounds.appsStackedVertically
|
||||
? splitBounds.dividerHeightPercent
|
||||
: splitBounds.dividerWidthPercent;
|
||||
translationX = ((taskViewWidth * leftTopTaskPercent)
|
||||
+ (taskViewWidth * dividerThicknessPercent));
|
||||
}
|
||||
} else {
|
||||
if (desiredTaskId == splitBounds.leftTopTaskId) {
|
||||
FrameLayout.LayoutParams snapshotParams =
|
||||
(FrameLayout.LayoutParams) thumbnailViews[0]
|
||||
.getLayoutParams();
|
||||
float bottomRightTaskPlusDividerPercent = splitBounds.appsStackedVertically
|
||||
? (1f - splitBounds.topTaskPercent)
|
||||
: (1f - splitBounds.leftTaskPercent);
|
||||
translationY = -((taskViewHeight - snapshotParams.topMargin)
|
||||
* bottomRightTaskPlusDividerPercent);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Pair<>(translationX, translationY);
|
||||
|
||||
@@ -199,6 +199,7 @@ interface RecentsPagedOrientationHandler : PagedOrientationHandler {
|
||||
parentWidth: Int,
|
||||
parentHeight: Int
|
||||
): Pair<Point, Point>
|
||||
|
||||
// Overview TaskMenuView methods
|
||||
/** Sets layout params on a task's app icon. Only use this when app chip is disabled. */
|
||||
fun setTaskIconParams(
|
||||
@@ -294,13 +295,24 @@ interface RecentsPagedOrientationHandler : PagedOrientationHandler {
|
||||
deviceProfile: DeviceProfile
|
||||
)
|
||||
|
||||
/** Layout a Digital Wellbeing Banner on its parent. TaskView. */
|
||||
fun updateDwbBannerLayout(
|
||||
taskViewWidth: Int,
|
||||
taskViewHeight: Int,
|
||||
isGroupedTaskView: Boolean,
|
||||
deviceProfile: DeviceProfile,
|
||||
snapshotViewWidth: Int,
|
||||
snapshotViewHeight: Int,
|
||||
banner: View
|
||||
)
|
||||
|
||||
/**
|
||||
* Calculates the position where a Digital Wellbeing Banner should be placed on its parent
|
||||
* Calculates the translations where a Digital Wellbeing Banner should be apply on its parent
|
||||
* TaskView.
|
||||
*
|
||||
* @return A Pair of Floats representing the proper x and y translations.
|
||||
*/
|
||||
fun getDwbLayoutTranslations(
|
||||
fun getDwbBannerTranslations(
|
||||
taskViewWidth: Int,
|
||||
taskViewHeight: Int,
|
||||
splitBounds: SplitConfigurationOptions.SplitBounds?,
|
||||
@@ -309,6 +321,7 @@ interface RecentsPagedOrientationHandler : PagedOrientationHandler {
|
||||
desiredTaskId: Int,
|
||||
banner: View
|
||||
): Pair<Float, Float>
|
||||
|
||||
// The following are only used by TaskViewTouchHandler.
|
||||
|
||||
/** @return Either VERTICAL or HORIZONTAL. */
|
||||
|
||||
@@ -28,6 +28,7 @@ import android.view.View.MeasureSpec
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.util.component1
|
||||
import androidx.core.util.component2
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import com.android.launcher3.DeviceProfile
|
||||
import com.android.launcher3.Flags
|
||||
import com.android.launcher3.R
|
||||
@@ -125,7 +126,30 @@ class SeascapePagedViewHandler : LandscapePagedViewHandler() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDwbLayoutTranslations(
|
||||
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> {
|
||||
gravity = Gravity.BOTTOM or if (banner.isLayoutRtl) Gravity.END else Gravity.START
|
||||
width =
|
||||
if (isGroupedTaskView) {
|
||||
snapshotViewHeight
|
||||
} else {
|
||||
taskViewHeight - deviceProfile.overviewTaskThumbnailTopMarginPx
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDwbBannerTranslations(
|
||||
taskViewWidth: Int,
|
||||
taskViewHeight: Int,
|
||||
splitBounds: SplitBounds?,
|
||||
@@ -135,39 +159,26 @@ class SeascapePagedViewHandler : LandscapePagedViewHandler() {
|
||||
banner: View
|
||||
): Pair<Float, Float> {
|
||||
val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
|
||||
val isRtl = banner.layoutDirection == View.LAYOUT_DIRECTION_RTL
|
||||
|
||||
val bannerParams = banner.layoutParams as FrameLayout.LayoutParams
|
||||
bannerParams.gravity = Gravity.BOTTOM or if (isRtl) Gravity.END else Gravity.START
|
||||
banner.pivotX = 0f
|
||||
banner.pivotY = 0f
|
||||
banner.rotation = degreesRotated
|
||||
|
||||
val translationX: Float = (taskViewWidth - banner.height).toFloat()
|
||||
if (splitBounds == null) {
|
||||
// Single, fullscreen case
|
||||
bannerParams.width = taskViewHeight - snapshotParams.topMargin
|
||||
return Pair(translationX, banner.height.toFloat())
|
||||
}
|
||||
|
||||
// Set correct width and translations
|
||||
val translationY: Float
|
||||
if (desiredTaskId == splitBounds.leftTopTaskId) {
|
||||
bannerParams.width = thumbnailViews[0].measuredHeight
|
||||
val bottomRightTaskPlusDividerPercent =
|
||||
if (splitBounds.appsStackedVertically) {
|
||||
1f - splitBounds.topTaskPercent
|
||||
} else {
|
||||
1f - splitBounds.leftTaskPercent
|
||||
}
|
||||
translationY =
|
||||
banner.height -
|
||||
(taskViewHeight - snapshotParams.topMargin) * bottomRightTaskPlusDividerPercent
|
||||
} else {
|
||||
bannerParams.width = thumbnailViews[1].measuredHeight
|
||||
if (splitBounds == null) {
|
||||
translationY = banner.height.toFloat()
|
||||
} else {
|
||||
if (desiredTaskId == splitBounds.leftTopTaskId) {
|
||||
val bottomRightTaskPlusDividerPercent =
|
||||
if (splitBounds.appsStackedVertically) {
|
||||
1f - splitBounds.topTaskPercent
|
||||
} else {
|
||||
1f - splitBounds.leftTaskPercent
|
||||
}
|
||||
translationY =
|
||||
banner.height -
|
||||
(taskViewHeight - snapshotParams.topMargin) *
|
||||
bottomRightTaskPlusDividerPercent
|
||||
} else {
|
||||
translationY = banner.height.toFloat()
|
||||
}
|
||||
}
|
||||
|
||||
return Pair(translationX, translationY)
|
||||
}
|
||||
|
||||
@@ -339,6 +350,7 @@ class SeascapePagedViewHandler : LandscapePagedViewHandler() {
|
||||
if (isRtl) displacement > 0 else displacement < 0
|
||||
|
||||
override fun getTaskDragDisplacementFactor(isRtl: Boolean): Int = if (isRtl) -1 else 1
|
||||
|
||||
/* -------------------- */
|
||||
|
||||
override fun getSplitIconsPosition(
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
*/
|
||||
package com.android.quickstep.views
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.ActivityOptions
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.LauncherApps
|
||||
import android.content.pm.LauncherApps.AppUsageLimit
|
||||
@@ -27,46 +29,58 @@ import android.icu.util.Measure
|
||||
import android.icu.util.MeasureUnit
|
||||
import android.os.UserHandle
|
||||
import android.provider.Settings
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
import android.view.ViewOutlineProvider
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.core.util.component1
|
||||
import androidx.core.util.component2
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.Utilities
|
||||
import com.android.launcher3.util.Executors
|
||||
import com.android.launcher3.util.SplitConfigurationOptions
|
||||
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.StagePosition
|
||||
import com.android.quickstep.TaskUtils
|
||||
import com.android.systemui.shared.recents.model.Task
|
||||
import java.time.Duration
|
||||
import java.util.Locale
|
||||
|
||||
class DigitalWellBeingToast(
|
||||
private val container: RecentsViewContainer,
|
||||
private val taskView: TaskView
|
||||
) {
|
||||
private val launcherApps: LauncherApps? =
|
||||
container.asContext().getSystemService(LauncherApps::class.java)
|
||||
@SuppressLint("AppCompatCustomView")
|
||||
class DigitalWellBeingToast
|
||||
@JvmOverloads
|
||||
constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0,
|
||||
defStyleRes: Int = 0
|
||||
) : TextView(context, attrs, defStyleAttr, defStyleRes) {
|
||||
private val recentsViewContainer =
|
||||
RecentsViewContainer.containerFromContext<RecentsViewContainer>(context)
|
||||
|
||||
private val launcherApps: LauncherApps? = context.getSystemService(LauncherApps::class.java)
|
||||
|
||||
private val bannerHeight =
|
||||
container
|
||||
.asContext()
|
||||
.resources
|
||||
.getDimensionPixelSize(R.dimen.digital_wellbeing_toast_height)
|
||||
context.resources.getDimensionPixelSize(R.dimen.digital_wellbeing_toast_height)
|
||||
|
||||
private lateinit var task: Task
|
||||
private lateinit var taskView: TaskView
|
||||
private lateinit var snapshotView: View
|
||||
@StagePosition private var stagePosition = STAGE_POSITION_UNDEFINED
|
||||
|
||||
private var appRemainingTimeMs: Long = 0
|
||||
private var banner: View? = null
|
||||
private var oldBannerOutlineProvider: ViewOutlineProvider? = null
|
||||
private var splitOffsetTranslationY = 0f
|
||||
private var splitOffsetTranslationX = 0f
|
||||
set(value) {
|
||||
if (field != value) {
|
||||
field = value
|
||||
updateTranslationY()
|
||||
}
|
||||
}
|
||||
|
||||
private var isDestroyed = false
|
||||
|
||||
@@ -76,65 +90,63 @@ class DigitalWellBeingToast(
|
||||
set(value) {
|
||||
if (field != value) {
|
||||
field = value
|
||||
banner?.let {
|
||||
updateTranslationY()
|
||||
it.invalidateOutline()
|
||||
}
|
||||
updateTranslationY()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
setOnClickListener(::openAppUsageSettings)
|
||||
outlineProvider =
|
||||
object : ViewOutlineProvider() {
|
||||
override fun getOutline(view: View, outline: Outline) {
|
||||
BACKGROUND.getOutline(view, outline)
|
||||
val verticalTranslation = splitOffsetTranslationY - translationY
|
||||
outline.offset(0, Math.round(verticalTranslation))
|
||||
}
|
||||
}
|
||||
clipToOutline = true
|
||||
}
|
||||
|
||||
private fun setNoLimit() {
|
||||
hasLimit = false
|
||||
taskView.contentDescription = task.titleDescription
|
||||
replaceBanner(null)
|
||||
setContentDescription(appUsageLimitTimeMs = -1, appRemainingTimeMs = -1)
|
||||
visibility = INVISIBLE
|
||||
appRemainingTimeMs = -1
|
||||
}
|
||||
|
||||
private fun setLimit(appUsageLimitTimeMs: Long, appRemainingTimeMs: Long) {
|
||||
this.appRemainingTimeMs = appRemainingTimeMs
|
||||
hasLimit = true
|
||||
val toast =
|
||||
container.viewCache
|
||||
.getView<TextView>(
|
||||
R.layout.digital_wellbeing_toast,
|
||||
container.asContext(),
|
||||
taskView
|
||||
)
|
||||
.apply {
|
||||
text =
|
||||
Utilities.prefixTextWithIcon(
|
||||
container.asContext(),
|
||||
R.drawable.ic_hourglass_top,
|
||||
getBannerText()
|
||||
)
|
||||
setOnClickListener(::openAppUsageSettings)
|
||||
}
|
||||
replaceBanner(toast)
|
||||
|
||||
taskView.contentDescription =
|
||||
getContentDescriptionForTask(task, appUsageLimitTimeMs, appRemainingTimeMs)
|
||||
text = Utilities.prefixTextWithIcon(context, R.drawable.ic_hourglass_top, getBannerText())
|
||||
visibility = VISIBLE
|
||||
setContentDescription(appUsageLimitTimeMs, appRemainingTimeMs)
|
||||
}
|
||||
|
||||
fun initialize(task: Task) {
|
||||
private fun setContentDescription(appUsageLimitTimeMs: Long, appRemainingTimeMs: Long) {
|
||||
val contentDescription =
|
||||
getContentDescriptionForTask(task, appUsageLimitTimeMs, appRemainingTimeMs)
|
||||
snapshotView.contentDescription = contentDescription
|
||||
}
|
||||
|
||||
fun initialize() {
|
||||
check(!isDestroyed) { "Cannot re-initialize a destroyed toast" }
|
||||
this.task = task
|
||||
setupTranslations()
|
||||
Executors.ORDERED_BG_EXECUTOR.execute {
|
||||
var usageLimit: AppUsageLimit? = null
|
||||
try {
|
||||
usageLimit =
|
||||
launcherApps?.getAppUsageLimit(
|
||||
this.task.topComponent.packageName,
|
||||
UserHandle.of(this.task.key.userId)
|
||||
task.topComponent.packageName,
|
||||
UserHandle.of(task.key.userId)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error initializing digital well being toast", e)
|
||||
}
|
||||
val appUsageLimitTimeMs = usageLimit?.totalUsageLimit ?: -1
|
||||
val appRemainingTimeMs = usageLimit?.usageRemaining ?: -1
|
||||
|
||||
taskView.post {
|
||||
if (isDestroyed) {
|
||||
return@post
|
||||
}
|
||||
if (isDestroyed) return@post
|
||||
if (appUsageLimitTimeMs < 0 || appRemainingTimeMs < 0) {
|
||||
setNoLimit()
|
||||
} else {
|
||||
@@ -144,20 +156,36 @@ class DigitalWellBeingToast(
|
||||
}
|
||||
}
|
||||
|
||||
/** Mark the DWB toast as destroyed and remove banner from TaskView. */
|
||||
/** Bind the DWB toast to its dependencies. */
|
||||
fun bind(
|
||||
task: Task,
|
||||
taskView: TaskView,
|
||||
snapshotView: View,
|
||||
@StagePosition stagePosition: Int
|
||||
) {
|
||||
this.task = task
|
||||
this.taskView = taskView
|
||||
this.snapshotView = snapshotView
|
||||
this.stagePosition = stagePosition
|
||||
isDestroyed = false
|
||||
}
|
||||
|
||||
/** Mark the DWB toast as destroyed and hide it. */
|
||||
fun destroy() {
|
||||
visibility = INVISIBLE
|
||||
isDestroyed = true
|
||||
taskView.post { replaceBanner(null) }
|
||||
}
|
||||
|
||||
private fun getSplitBannerConfig(): SplitBannerConfig {
|
||||
val splitBounds = splitBounds
|
||||
return when {
|
||||
splitBounds == null || !container.deviceProfile.isTablet || taskView.isLargeTile ->
|
||||
SplitBannerConfig.SPLIT_BANNER_FULLSCREEN
|
||||
splitBounds == null ||
|
||||
!recentsViewContainer.deviceProfile.isTablet ||
|
||||
taskView.isLargeTile -> SplitBannerConfig.SPLIT_BANNER_FULLSCREEN
|
||||
// For portrait grid only height of task changes, not width. So we keep the text the
|
||||
// same
|
||||
!container.deviceProfile.isLeftRightSplit -> SplitBannerConfig.SPLIT_GRID_BANNER_LARGE
|
||||
!recentsViewContainer.deviceProfile.isLeftRightSplit ->
|
||||
SplitBannerConfig.SPLIT_GRID_BANNER_LARGE
|
||||
// For landscape grid, for 30% width we only show icon, otherwise show icon and time
|
||||
task.key.id == splitBounds.leftTopTaskId ->
|
||||
if (splitBounds.leftTaskPercent < THRESHOLD_LEFT_ICON_ONLY)
|
||||
@@ -193,8 +221,7 @@ class DigitalWellBeingToast(
|
||||
MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE)
|
||||
.formatMeasures(Measure(minutes, MeasureUnit.MINUTE))
|
||||
// Use a specific string for usage less than one minute but non-zero.
|
||||
duration > Duration.ZERO ->
|
||||
container.asContext().getString(durationLessThanOneMinuteStringId)
|
||||
duration > Duration.ZERO -> context.getString(durationLessThanOneMinuteStringId)
|
||||
// Otherwise, return 0-minute string.
|
||||
else ->
|
||||
MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE)
|
||||
@@ -208,6 +235,7 @@ class DigitalWellBeingToast(
|
||||
* [.SPLIT_BANNER_FULLSCREEN]
|
||||
*/
|
||||
@JvmOverloads
|
||||
@VisibleForTesting
|
||||
fun getBannerText(
|
||||
remainingTime: Long = appRemainingTimeMs,
|
||||
forContentDesc: Boolean = false
|
||||
@@ -226,7 +254,7 @@ class DigitalWellBeingToast(
|
||||
val splitBannerConfig = getSplitBannerConfig()
|
||||
return when {
|
||||
forContentDesc || splitBannerConfig == SplitBannerConfig.SPLIT_BANNER_FULLSCREEN ->
|
||||
container.asContext().getString(R.string.time_left_for_app, readableDuration)
|
||||
context.getString(R.string.time_left_for_app, readableDuration)
|
||||
// show no text
|
||||
splitBannerConfig == SplitBannerConfig.SPLIT_GRID_BANNER_SMALL -> ""
|
||||
// SPLIT_GRID_BANNER_LARGE only show time
|
||||
@@ -241,7 +269,7 @@ class DigitalWellBeingToast(
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
try {
|
||||
val options = ActivityOptions.makeScaleUpAnimation(view, 0, 0, view.width, view.height)
|
||||
container.asContext().startActivity(intent, options.toBundle())
|
||||
context.startActivity(intent, options.toBundle())
|
||||
|
||||
// TODO: add WW logging on the app usage settings click.
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
@@ -259,99 +287,77 @@ class DigitalWellBeingToast(
|
||||
appRemainingTimeMs: Long
|
||||
): String? =
|
||||
if (appUsageLimitTimeMs >= 0 && appRemainingTimeMs >= 0)
|
||||
container
|
||||
.asContext()
|
||||
.getString(
|
||||
R.string.task_contents_description_with_remaining_time,
|
||||
task.titleDescription,
|
||||
getBannerText(appRemainingTimeMs, true /* forContentDesc */)
|
||||
)
|
||||
context.getString(
|
||||
R.string.task_contents_description_with_remaining_time,
|
||||
task.titleDescription,
|
||||
getBannerText(appRemainingTimeMs, true /* forContentDesc */)
|
||||
)
|
||||
else task.titleDescription
|
||||
|
||||
private fun replaceBanner(view: View?) {
|
||||
resetOldBanner()
|
||||
setBanner(view)
|
||||
}
|
||||
|
||||
private fun resetOldBanner() {
|
||||
val banner = banner ?: return
|
||||
banner.outlineProvider = oldBannerOutlineProvider
|
||||
taskView.removeView(banner)
|
||||
banner.setOnClickListener(null)
|
||||
container.viewCache.recycleView(R.layout.digital_wellbeing_toast, banner)
|
||||
}
|
||||
|
||||
private fun setBanner(banner: View?) {
|
||||
this.banner = banner
|
||||
if (banner != null && taskView.recentsView != null) {
|
||||
setupAndAddBanner()
|
||||
setBannerOutline()
|
||||
fun setupLayout() {
|
||||
val snapshotWidth: Int
|
||||
val snapshotHeight: Int
|
||||
val splitBounds = splitBounds
|
||||
if (splitBounds == null) {
|
||||
snapshotWidth = taskView.layoutParams.width
|
||||
snapshotHeight =
|
||||
taskView.layoutParams.height -
|
||||
recentsViewContainer.deviceProfile.overviewTaskThumbnailTopMarginPx
|
||||
} else {
|
||||
val groupedTaskSize =
|
||||
taskView.pagedOrientationHandler.getGroupedTaskViewSizes(
|
||||
recentsViewContainer.deviceProfile,
|
||||
splitBounds,
|
||||
taskView.layoutParams.width,
|
||||
taskView.layoutParams.height
|
||||
)
|
||||
if (stagePosition == STAGE_POSITION_TOP_OR_LEFT) {
|
||||
snapshotWidth = groupedTaskSize.first.x
|
||||
snapshotHeight = groupedTaskSize.first.y
|
||||
} else {
|
||||
snapshotWidth = groupedTaskSize.second.x
|
||||
snapshotHeight = groupedTaskSize.second.y
|
||||
}
|
||||
}
|
||||
taskView.pagedOrientationHandler.updateDwbBannerLayout(
|
||||
taskView.layoutParams.width,
|
||||
taskView.layoutParams.height,
|
||||
taskView is GroupedTaskView,
|
||||
recentsViewContainer.deviceProfile,
|
||||
snapshotWidth,
|
||||
snapshotHeight,
|
||||
this
|
||||
)
|
||||
}
|
||||
|
||||
private fun setupAndAddBanner() {
|
||||
val banner = banner ?: return
|
||||
banner.updateLayoutParams<FrameLayout.LayoutParams> {
|
||||
bottomMargin =
|
||||
(taskView.firstSnapshotView.layoutParams as MarginLayoutParams).bottomMargin
|
||||
}
|
||||
private fun setupTranslations() {
|
||||
val (translationX, translationY) =
|
||||
taskView.pagedOrientationHandler.getDwbLayoutTranslations(
|
||||
taskView.measuredWidth,
|
||||
taskView.measuredHeight,
|
||||
taskView.pagedOrientationHandler.getDwbBannerTranslations(
|
||||
taskView.layoutParams.width,
|
||||
taskView.layoutParams.height,
|
||||
splitBounds,
|
||||
container.deviceProfile,
|
||||
recentsViewContainer.deviceProfile,
|
||||
taskView.snapshotViews,
|
||||
task.key.id,
|
||||
banner
|
||||
this
|
||||
)
|
||||
splitOffsetTranslationX = translationX
|
||||
splitOffsetTranslationY = translationY
|
||||
updateTranslationY()
|
||||
updateTranslationX()
|
||||
taskView.addView(banner)
|
||||
}
|
||||
|
||||
private fun setBannerOutline() {
|
||||
val banner = banner ?: return
|
||||
// TODO(b\273367585) to investigate why mBanner.getOutlineProvider() can be null
|
||||
val oldBannerOutlineProvider =
|
||||
if (banner.outlineProvider != null) banner.outlineProvider
|
||||
else ViewOutlineProvider.BACKGROUND
|
||||
this.oldBannerOutlineProvider = oldBannerOutlineProvider
|
||||
|
||||
banner.outlineProvider =
|
||||
object : ViewOutlineProvider() {
|
||||
override fun getOutline(view: View, outline: Outline) {
|
||||
oldBannerOutlineProvider.getOutline(view, outline)
|
||||
val verticalTranslation = -view.translationY + splitOffsetTranslationY
|
||||
outline.offset(0, Math.round(verticalTranslation))
|
||||
}
|
||||
}
|
||||
banner.clipToOutline = true
|
||||
this.translationX = translationX
|
||||
this.splitOffsetTranslationY = translationY
|
||||
}
|
||||
|
||||
private fun updateTranslationY() {
|
||||
banner?.translationY = bannerOffsetPercentage * bannerHeight + splitOffsetTranslationY
|
||||
translationY = bannerOffsetPercentage * bannerHeight + splitOffsetTranslationY
|
||||
invalidateOutline()
|
||||
}
|
||||
|
||||
private fun updateTranslationX() {
|
||||
banner?.translationX = splitOffsetTranslationX
|
||||
}
|
||||
|
||||
fun setBannerColorTint(color: Int, amount: Float) {
|
||||
val banner = banner ?: return
|
||||
fun setColorTint(color: Int, amount: Float) {
|
||||
if (amount == 0f) {
|
||||
banner.setLayerType(View.LAYER_TYPE_NONE, null)
|
||||
setLayerType(View.LAYER_TYPE_NONE, null)
|
||||
}
|
||||
val layerPaint = Paint()
|
||||
layerPaint.setColorFilter(Utilities.makeColorTintingColorFilter(color, amount))
|
||||
banner.setLayerType(View.LAYER_TYPE_HARDWARE, layerPaint)
|
||||
banner.setLayerPaint(layerPaint)
|
||||
}
|
||||
|
||||
fun setBannerVisibility(visibility: Int) {
|
||||
banner?.visibility = visibility
|
||||
setLayerType(View.LAYER_TYPE_HARDWARE, layerPaint)
|
||||
setLayerPaint(layerPaint)
|
||||
}
|
||||
|
||||
private fun getAccessibilityActionId(): Int =
|
||||
@@ -361,9 +367,8 @@ class DigitalWellBeingToast(
|
||||
|
||||
fun getDWBAccessibilityAction(): AccessibilityNodeInfo.AccessibilityAction? {
|
||||
if (!hasLimit) return null
|
||||
val context = container.asContext()
|
||||
val label =
|
||||
if ((taskView.containsMultipleTasks()))
|
||||
if (taskView.containsMultipleTasks())
|
||||
context.getString(
|
||||
R.string.split_app_usage_settings,
|
||||
TaskUtils.getTitle(context, task)
|
||||
|
||||
@@ -115,6 +115,7 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
|
||||
R.id.snapshot,
|
||||
R.id.icon,
|
||||
R.id.show_windows,
|
||||
R.id.digital_wellbeing_toast,
|
||||
STAGE_POSITION_TOP_OR_LEFT,
|
||||
taskOverlayFactory
|
||||
),
|
||||
@@ -123,6 +124,7 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
|
||||
R.id.bottomright_snapshot,
|
||||
R.id.bottomRight_icon,
|
||||
R.id.show_windows_right,
|
||||
R.id.bottomRight_digital_wellbeing_toast,
|
||||
STAGE_POSITION_BOTTOM_OR_RIGHT,
|
||||
taskOverlayFactory
|
||||
)
|
||||
@@ -211,7 +213,7 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
|
||||
splitBoundsConfig = splitBounds
|
||||
taskContainers.forEach {
|
||||
it.digitalWellBeingToast?.splitBounds = splitBoundsConfig
|
||||
it.digitalWellBeingToast?.initialize(it.task)
|
||||
it.digitalWellBeingToast?.initialize()
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
|
||||
@@ -146,6 +146,7 @@ class TaskContainer(
|
||||
}
|
||||
|
||||
fun bind() {
|
||||
digitalWellBeingToast?.bind(task, taskView, snapshotView, stagePosition)
|
||||
if (enableRefactorTaskThumbnail()) {
|
||||
bindThumbnailView()
|
||||
} else {
|
||||
@@ -171,4 +172,18 @@ class TaskContainer(
|
||||
thumbnailViewDeprecated.setOverlayEnabled(enabled)
|
||||
}
|
||||
}
|
||||
|
||||
fun addChildForAccessibility(outChildren: ArrayList<View>) {
|
||||
addAccessibleChildToList(iconView.asView(), outChildren)
|
||||
addAccessibleChildToList(snapshotView, outChildren)
|
||||
showWindowsView?.let { addAccessibleChildToList(it, outChildren) }
|
||||
}
|
||||
|
||||
private fun addAccessibleChildToList(view: View, outChildren: ArrayList<View>) {
|
||||
if (view.includeForAccessibility()) {
|
||||
outChildren.add(view)
|
||||
} else {
|
||||
view.addChildrenForAccessibility(outChildren)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -702,6 +702,7 @@ constructor(
|
||||
R.id.snapshot,
|
||||
R.id.icon,
|
||||
R.id.show_windows,
|
||||
R.id.digital_wellbeing_toast,
|
||||
STAGE_POSITION_UNDEFINED,
|
||||
taskOverlayFactory
|
||||
)
|
||||
@@ -715,6 +716,7 @@ constructor(
|
||||
@IdRes thumbnailViewId: Int,
|
||||
@IdRes iconViewId: Int,
|
||||
@IdRes showWindowViewId: Int,
|
||||
@IdRes digitalWellbeingBannerId: Int,
|
||||
@StagePosition stagePosition: Int,
|
||||
taskOverlayFactory: TaskOverlayFactory,
|
||||
): TaskContainer {
|
||||
@@ -730,6 +732,7 @@ constructor(
|
||||
thumbnailViewDeprecated
|
||||
}
|
||||
val iconView = getOrInflateIconView(iconViewId)
|
||||
val digitalWellBeingToast = findViewById<DigitalWellBeingToast>(digitalWellbeingBannerId)!!
|
||||
return TaskContainer(
|
||||
this,
|
||||
task,
|
||||
@@ -737,7 +740,7 @@ constructor(
|
||||
iconView,
|
||||
TransformingTouchDelegate(iconView.asView()),
|
||||
stagePosition,
|
||||
DigitalWellBeingToast(container, this),
|
||||
digitalWellBeingToast,
|
||||
findViewById(showWindowViewId)!!,
|
||||
taskOverlayFactory
|
||||
)
|
||||
@@ -775,7 +778,7 @@ constructor(
|
||||
protected open fun setThumbnailOrientation(orientationState: RecentsOrientedState) {
|
||||
taskContainers.forEach {
|
||||
it.overlay.updateOrientationState(orientationState)
|
||||
it.digitalWellBeingToast?.initialize(it.task)
|
||||
it.digitalWellBeingToast?.initialize()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -827,10 +830,8 @@ constructor(
|
||||
} else {
|
||||
nonGridScale = 1f
|
||||
boxTranslationY = 0f
|
||||
expectedWidth = if (enableOverviewIconMenu()) taskWidth else LayoutParams.MATCH_PARENT
|
||||
expectedHeight =
|
||||
if (enableOverviewIconMenu()) taskHeight + thumbnailPadding
|
||||
else LayoutParams.MATCH_PARENT
|
||||
expectedWidth = taskWidth
|
||||
expectedHeight = taskHeight + thumbnailPadding
|
||||
}
|
||||
this.nonGridScale = nonGridScale
|
||||
this.boxTranslationY = boxTranslationY
|
||||
@@ -847,6 +848,7 @@ constructor(
|
||||
taskContainers[0].snapshotView.updateLayoutParams<LayoutParams> {
|
||||
topMargin = container.deviceProfile.overviewTaskThumbnailTopMarginPx
|
||||
}
|
||||
taskContainers.forEach { it.digitalWellBeingToast?.setupLayout() }
|
||||
}
|
||||
|
||||
/** Returns the thumbnail's bounds, optionally relative to the screen. */
|
||||
@@ -939,7 +941,7 @@ constructor(
|
||||
if (enableOverviewIconMenu()) {
|
||||
setText(taskContainer.iconView, taskContainer.task.title)
|
||||
}
|
||||
taskContainer.digitalWellBeingToast?.initialize(taskContainer.task)
|
||||
taskContainer.digitalWellBeingToast?.initialize()
|
||||
}
|
||||
|
||||
protected open fun onIconUnloaded(taskContainer: TaskContainer) {
|
||||
@@ -1444,7 +1446,7 @@ constructor(
|
||||
it.thumbnailViewDeprecated.dimAlpha = amount
|
||||
}
|
||||
it.iconView.setIconColorTint(tintColor, amount)
|
||||
it.digitalWellBeingToast?.setBannerColorTint(tintColor, amount)
|
||||
it.digitalWellBeingToast?.setColorTint(tintColor, amount)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1458,7 +1460,7 @@ constructor(
|
||||
taskContainers.forEach {
|
||||
if (visibility == VISIBLE || it.task.key.id == taskId) {
|
||||
it.snapshotView.visibility = visibility
|
||||
it.digitalWellBeingToast?.setBannerVisibility(visibility)
|
||||
it.digitalWellBeingToast?.visibility = visibility
|
||||
it.showWindowsView?.visibility = visibility
|
||||
it.overlay.setVisibility(visibility)
|
||||
}
|
||||
@@ -1662,6 +1664,12 @@ constructor(
|
||||
return thumbnailBounds.contains(x.toInt(), y.toInt())
|
||||
}
|
||||
|
||||
override fun addChildrenForAccessibility(outChildren: ArrayList<View>) {
|
||||
(if (isLayoutRtl) taskContainers.reversed() else taskContainers).forEach {
|
||||
it.addChildForAccessibility(outChildren)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "TaskView"
|
||||
const val FLAG_UPDATE_ICON = 1
|
||||
|
||||
@@ -15,20 +15,18 @@
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
|
||||
import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.platform.test.annotations.PlatinumTest;
|
||||
|
||||
import com.android.launcher3.tapl.Overview;
|
||||
import com.android.launcher3.tapl.OverviewTask.OverviewSplitTask;
|
||||
import com.android.launcher3.tapl.OverviewTaskMenu;
|
||||
import com.android.launcher3.ui.AbstractLauncherUiTest;
|
||||
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
import com.android.launcher3.util.rule.TestStabilityRule;
|
||||
import com.android.quickstep.util.SplitScreenTestUtils;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -70,39 +68,17 @@ public class TaplOverviewIconTest extends AbstractLauncherUiTest<QuickstepLaunch
|
||||
|
||||
@Test
|
||||
public void testSplitTaskTapBothIconMenus() {
|
||||
createAndLaunchASplitPair();
|
||||
Overview overview = SplitScreenTestUtils.createAndLaunchASplitPairInOverview(mLauncher);
|
||||
|
||||
OverviewTaskMenu taskMenu =
|
||||
mLauncher.goHome().switchToOverview().getCurrentTask().tapMenu();
|
||||
OverviewTaskMenu taskMenu = overview.getCurrentTask().tapMenu();
|
||||
assertTrue("App info item not appearing in expanded task menu.",
|
||||
taskMenu.hasMenuItem("App info"));
|
||||
taskMenu.touchOutsideTaskMenuToDismiss();
|
||||
|
||||
OverviewTaskMenu splitMenu =
|
||||
mLauncher.goHome().switchToOverview().getCurrentTask().tapMenu(
|
||||
OverviewTaskMenu splitMenu = overview.getCurrentTask().tapMenu(
|
||||
OverviewSplitTask.SPLIT_BOTTOM_OR_RIGHT);
|
||||
assertTrue("App info item not appearing in expanded split task's menu.",
|
||||
splitMenu.hasMenuItem("App info"));
|
||||
splitMenu.touchOutsideTaskMenuToDismiss();
|
||||
}
|
||||
|
||||
private void createAndLaunchASplitPair() {
|
||||
clearAllRecentTasks();
|
||||
|
||||
startTestActivity(2);
|
||||
startTestActivity(3);
|
||||
|
||||
if (mLauncher.isTablet() && !mLauncher.isGridOnlyOverviewEnabled()) {
|
||||
mLauncher.goHome().switchToOverview().getOverviewActions()
|
||||
.clickSplit()
|
||||
.getTestActivityTask(2)
|
||||
.open();
|
||||
} else {
|
||||
mLauncher.goHome().switchToOverview().getCurrentTask()
|
||||
.tapMenu()
|
||||
.tapSplitMenuItem()
|
||||
.getCurrentTask()
|
||||
.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -290,9 +290,6 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest {
|
||||
}
|
||||
}
|
||||
|
||||
// Staging; will be promoted to presubmit if stable
|
||||
@TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
|
||||
|
||||
@Test
|
||||
@NavigationModeSwitch
|
||||
@PortraitLandscape
|
||||
@@ -481,7 +478,8 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest {
|
||||
// assertTrue("Launcher internal state didn't remain in Overview",
|
||||
// isInState(() -> LauncherState.OVERVIEW));
|
||||
// overview.getCurrentTask().dismiss();
|
||||
// executeOnLauncher(launcher -> assertTrue("Grid did not rebalance after multiple dismissals",
|
||||
// executeOnLauncher(launcher -> assertTrue("Grid did not rebalance after multiple
|
||||
// dismissals",
|
||||
// (Math.abs(getTopRowTaskCountForTablet(launcher) - getBottomRowTaskCountForTablet(
|
||||
// launcher)) <= 1)));
|
||||
|
||||
@@ -592,7 +590,8 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest {
|
||||
if (overview.hasTasks()) {
|
||||
currentTask = overview.getCurrentTask();
|
||||
assertFalse("Found ExcludeFromRecentsTestActivity after entering Overview from Home",
|
||||
currentTask.containsContentDescription("ExcludeFromRecents")
|
||||
currentTask.containsContentDescription(
|
||||
"ExcludeFromRecents")
|
||||
|| currentTask.containsContentDescription(null));
|
||||
} else {
|
||||
// Presumably the test started with 0 tasks and remains that way after going home.
|
||||
|
||||
@@ -17,8 +17,6 @@ package com.android.quickstep;
|
||||
|
||||
|
||||
import static com.android.launcher3.config.FeatureFlags.enableSplitContextually;
|
||||
import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
|
||||
import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@@ -33,7 +31,7 @@ import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import com.android.launcher3.tapl.Overview;
|
||||
import com.android.launcher3.tapl.Taskbar;
|
||||
import com.android.launcher3.tapl.TaskbarAppIcon;
|
||||
import com.android.launcher3.util.rule.TestStabilityRule;
|
||||
import com.android.quickstep.util.SplitScreenTestUtils;
|
||||
import com.android.wm.shell.Flags;
|
||||
|
||||
import org.junit.After;
|
||||
@@ -110,9 +108,8 @@ public class TaplTestsSplitscreen extends AbstractQuickStepTest {
|
||||
assumeTrue("App pairs feature is currently not enabled, no test needed",
|
||||
Flags.enableAppPairs());
|
||||
|
||||
createAndLaunchASplitPair();
|
||||
Overview overview = SplitScreenTestUtils.createAndLaunchASplitPairInOverview(mLauncher);
|
||||
|
||||
Overview overview = mLauncher.goHome().switchToOverview();
|
||||
if (mLauncher.isGridOnlyOverviewEnabled() || !mLauncher.isTablet()) {
|
||||
assertTrue("Save app pair menu item is missing",
|
||||
overview.getCurrentTask()
|
||||
@@ -156,24 +153,4 @@ public class TaplTestsSplitscreen extends AbstractQuickStepTest {
|
||||
TaskbarAppIcon firstApp = taskbar.getAppIcon(firstAppName);
|
||||
firstApp.launchIntoSplitScreen();
|
||||
}
|
||||
|
||||
private void createAndLaunchASplitPair() {
|
||||
clearAllRecentTasks();
|
||||
|
||||
startTestActivity(2);
|
||||
startTestActivity(3);
|
||||
|
||||
if (mLauncher.isTablet() && !mLauncher.isGridOnlyOverviewEnabled()) {
|
||||
mLauncher.goHome().switchToOverview().getOverviewActions()
|
||||
.clickSplit()
|
||||
.getTestActivityTask(2)
|
||||
.open();
|
||||
} else {
|
||||
mLauncher.goHome().switchToOverview().getCurrentTask()
|
||||
.tapMenu()
|
||||
.tapSplitMenuItem()
|
||||
.getCurrentTask()
|
||||
.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.quickstep.util
|
||||
|
||||
import androidx.test.uiautomator.By
|
||||
import com.android.launcher3.tapl.LauncherInstrumentation
|
||||
import com.android.launcher3.tapl.Overview
|
||||
import com.android.launcher3.tapl.OverviewTask
|
||||
import com.android.launcher3.ui.AbstractLauncherUiTest
|
||||
|
||||
object SplitScreenTestUtils {
|
||||
|
||||
/** Creates 2 tasks and makes a split mode pair. Also asserts the accessibility labels. */
|
||||
@JvmStatic
|
||||
fun createAndLaunchASplitPairInOverview(launcher: LauncherInstrumentation): Overview {
|
||||
clearAllRecentTasks(launcher)
|
||||
|
||||
AbstractLauncherUiTest.startTestActivity(2)
|
||||
AbstractLauncherUiTest.startTestActivity(3)
|
||||
|
||||
val overView = launcher.goHome().switchToOverview()
|
||||
if (launcher.isTablet && !launcher.isGridOnlyOverviewEnabled) {
|
||||
overView.overviewActions.clickSplit().getTestActivityTask(2).open()
|
||||
} else {
|
||||
overView.currentTask.tapMenu().tapSplitMenuItem().currentTask.open()
|
||||
}
|
||||
|
||||
val overviewWithSplitPair = launcher.goHome().switchToOverview()
|
||||
val currentTask = overviewWithSplitPair.currentTask
|
||||
currentTask.containsContentDescription(
|
||||
By.pkg(AbstractLauncherUiTest.getAppPackageName()).text("TestActivity3").toString(),
|
||||
OverviewTask.OverviewSplitTask.SPLIT_TOP_OR_LEFT
|
||||
)
|
||||
currentTask.containsContentDescription(
|
||||
By.pkg(AbstractLauncherUiTest.getAppPackageName()).text("TestActivity2").toString(),
|
||||
OverviewTask.OverviewSplitTask.SPLIT_BOTTOM_OR_RIGHT
|
||||
)
|
||||
return overviewWithSplitPair
|
||||
}
|
||||
|
||||
private fun clearAllRecentTasks(launcher: LauncherInstrumentation) {
|
||||
if (launcher.recentTasks.isNotEmpty()) {
|
||||
launcher.goHome().switchToOverview().dismissAllTasks()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,8 +284,9 @@ public final class OverviewTask {
|
||||
*
|
||||
* TODO(b/326565120): remove Nullable support once the bug causing it to be null is fixed.
|
||||
*/
|
||||
public boolean containsContentDescription(@Nullable String expected) {
|
||||
String actual = mTask.getContentDescription();
|
||||
public boolean containsContentDescription(@Nullable String expected,
|
||||
OverviewSplitTask overviewSplitTask) {
|
||||
String actual = findObjectInTask(overviewSplitTask.snapshotRes).getContentDescription();
|
||||
if (actual == null && expected == null) {
|
||||
return true;
|
||||
}
|
||||
@@ -295,6 +296,14 @@ public final class OverviewTask {
|
||||
return actual.contains(expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given String is contained in this Task's contentDescription. Also returns
|
||||
* true if both Strings are null
|
||||
*/
|
||||
public boolean containsContentDescription(@Nullable String expected) {
|
||||
return containsContentDescription(expected, DEFAULT);
|
||||
}
|
||||
|
||||
private TaskViewType getType() {
|
||||
String resourceName = mTask.getResourceName();
|
||||
if (resourceName.endsWith("task_view_grouped")) {
|
||||
|
||||
Reference in New Issue
Block a user