From 673b88535ccaf2d512001d191c1eb4f922d1afa2 Mon Sep 17 00:00:00 2001 From: Ats Jenk Date: Fri, 30 Aug 2024 13:07:24 -0700 Subject: [PATCH 1/4] Reuse stash handle alpha animation constants Taskbar and bubble bar stash handle should use a similar animation for stash and unstash. Remove duplicate alpha animation constants and use only the ones defined in taskbar controller for taskbar and bubble bar. Bug: 345680453 Flag: EXEMPT refactor Test: m Change-Id: I9edf847fc4148bc731051de60a883011e6bfb848 --- .../android/launcher3/taskbar/TaskbarStashController.java | 4 ++-- .../taskbar/bubbles/stashing/BubbleStashController.kt | 3 --- .../bubbles/stashing/TransientBubbleStashController.kt | 8 ++++---- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java index 56f88d12f5..60e65b34a2 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java @@ -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. diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt index b2a88ac919..aad90396bc 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt @@ -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 } diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt index b8fd1cb1ed..aaa46fc9af 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt @@ -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 @@ -274,8 +274,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) From e6eca9c1957bc7a29d19f3a4ed2bf00e568cabd7 Mon Sep 17 00:00:00 2001 From: Alex Chau Date: Mon, 2 Sep 2024 19:08:59 +0100 Subject: [PATCH 2/4] Promote TaplTestsQuickstep.testBackground to presubmit - http://android-build/test_hub/search/?filter=&invfilter=unhealthyInvocations&invfilter=staleInvocations&query=test%3Acom.android.quickstep.TaplTestsQuickstep%23testBackground - Latest result is <0.5% flaky Bug: 364036214 Test: presubmit Flag: EXEMPT TEST_ONLY Change-Id: I97315a23e09c75a5f72cd3f6c959607598d9222d --- .../tests/src/com/android/quickstep/TaplTestsQuickstep.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java index f971afb4b2..84b9a869ef 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java @@ -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 From 3359fa7f326d6aa3572fa47f2e20a1f83885921f Mon Sep 17 00:00:00 2001 From: vinayjoglekar Date: Thu, 22 Aug 2024 19:51:42 +0100 Subject: [PATCH 3/4] Fix talkback in split mode 1. Announce description of both snapshot in GroupedTaskView. 2. Announce them in correct order. After setting right content description for both the apps in split mode, announcement order was reverse. Seems that order of the node tree which android sees was wrong. Also order did not change even in RTL mode. see here https://paste.googleplex.com/6492038621298688. The correct order is manually set using addChildrenForAccessibility in GroupedTaskView. (similarly being used in RecentsView since we use reverse order for overview). Also considered RTL. See order after fix here https://paste.googleplex.com/6492038621298688 https://drive.google.com/file/d/1BA1YKkcNsovEu4us9cvq5xOdXviTR1OV/view?usp=drive_link Other alternatives tried were fixing order using android:accessibilityTraversalAfter and android:accessibilityTraversalBefore, both in layout and programatically which doesn't seem to work. Test: Manual - Using Node tree debugging of talkback developer options.(Print and filter by tag TreeDebug) BUG: 330424779 Flag: EXEMPT bugfix Change-Id: If3c360277bd951d2d2b223bab7844e078e9c7d27 --- quickstep/Android.bp | 1 + .../quickstep/views/DigitalWellBeingToast.kt | 9 ++- .../android/quickstep/views/TaskContainer.kt | 14 +++++ .../com/android/quickstep/views/TaskView.kt | 6 ++ .../quickstep/TaplOverviewIconTest.java | 36 ++--------- .../android/quickstep/TaplTestsQuickstep.java | 6 +- .../quickstep/TaplTestsSplitscreen.java | 27 +-------- .../quickstep/util/SplitScreenTestUtils.kt | 60 +++++++++++++++++++ .../android/launcher3/tapl/OverviewTask.java | 13 +++- 9 files changed, 111 insertions(+), 61 deletions(-) create mode 100644 quickstep/tests/src/com/android/quickstep/util/SplitScreenTestUtils.kt diff --git a/quickstep/Android.bp b/quickstep/Android.bp index 1b9c661387..4c724dc290 100644 --- a/quickstep/Android.bp +++ b/quickstep/Android.bp @@ -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", ], } diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt index 0ab36c9d72..e340ce60ab 100644 --- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt +++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt @@ -85,7 +85,7 @@ class DigitalWellBeingToast( private fun setNoLimit() { hasLimit = false - taskView.contentDescription = task.titleDescription + setContentDescription(appUsageLimitTimeMs = -1, appRemainingTimeMs = -1) replaceBanner(null) appRemainingTimeMs = -1 } @@ -110,9 +110,14 @@ class DigitalWellBeingToast( setOnClickListener(::openAppUsageSettings) } replaceBanner(toast) + setContentDescription(appUsageLimitTimeMs, appRemainingTimeMs) + } - taskView.contentDescription = + private fun setContentDescription(appUsageLimitTimeMs: Long, appRemainingTimeMs: Long) { + val taskContainer: TaskContainer = taskView.getTaskContainerById(task.key.id) ?: return + val contentDescription = getContentDescriptionForTask(task, appUsageLimitTimeMs, appRemainingTimeMs) + taskContainer.snapshotView.contentDescription = contentDescription } fun initialize(task: Task) { diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt index af32ba2565..86f6a99be0 100644 --- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt +++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt @@ -171,4 +171,18 @@ class TaskContainer( thumbnailViewDeprecated.setOverlayEnabled(enabled) } } + + fun addChildForAccessibility(outChildren: ArrayList) { + addAccessibleChildToList(iconView.asView(), outChildren) + addAccessibleChildToList(snapshotView, outChildren) + showWindowsView?.let { addAccessibleChildToList(it, outChildren) } + } + + private fun addAccessibleChildToList(view: View, outChildren: ArrayList) { + if (view.includeForAccessibility()) { + outChildren.add(view) + } else { + view.addChildrenForAccessibility(outChildren) + } + } } diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt index d2cdfa2bef..139bc248c3 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.kt +++ b/quickstep/src/com/android/quickstep/views/TaskView.kt @@ -1662,6 +1662,12 @@ constructor( return thumbnailBounds.contains(x.toInt(), y.toInt()) } + override fun addChildrenForAccessibility(outChildren: ArrayList) { + (if (isLayoutRtl) taskContainers.reversed() else taskContainers).forEach { + it.addChildForAccessibility(outChildren) + } + } + companion object { private const val TAG = "TaskView" const val FLAG_UPDATE_ICON = 1 diff --git a/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java b/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java index 2087016388..9bc1c5981e 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java +++ b/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java @@ -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 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 +593,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. diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java index 733ea4e2cf..daa4ec327a 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java @@ -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(); - } - } } diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitScreenTestUtils.kt b/quickstep/tests/src/com/android/quickstep/util/SplitScreenTestUtils.kt new file mode 100644 index 0000000000..82361aabb8 --- /dev/null +++ b/quickstep/tests/src/com/android/quickstep/util/SplitScreenTestUtils.kt @@ -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() + } + } +} diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java index ab48a211ca..5433fa7e94 100644 --- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java +++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java @@ -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")) { From fae91d5e57225b051f2f46809e90f7cfd8afcf17 Mon Sep 17 00:00:00 2001 From: Alex Chau Date: Tue, 27 Aug 2024 16:01:15 +0100 Subject: [PATCH 4/4] Include DigitalWelllBeing banner in task XML to avoid addView during animation - Also simplified DigitalWellBeingToast to be an actual View that is a children of TaskView - Split out getDwbLayoutTranslations into 2 methods - One that update layout will be called earlier during TaskView.updateTaskSize. It now takes calcualted split snaphsot size and layoutParams sizes instead of using measured sizes so it can run before onMeasure. Also make sure updateLayoutParams is called. - One that calulate translations only can be called more frequently without causing jank during animation - TaskView.updateTaskSize now always set the exact sizes as layoutParams instead of MATCH_PARENT, so we can use the values for updateDwbBannerLayout - Simplified outline and splitOffsetTranslation calculations Fix: 345789862 Fix: 362435532 Test: Presubmit and manually testing DWB banner in variaous configuration single/split real/fake portrait/landscape/fake Flag: EXEMPT bugfix Change-Id: Ibde877c5f983689e0ef8104ba344b5947d0e3948 --- .../res/layout/digital_wellbeing_toast.xml | 2 +- quickstep/res/layout/task.xml | 4 + quickstep/res/layout/task_grouped.xml | 8 + .../orientation/LandscapePagedViewHandler.kt | 69 +++-- .../orientation/PortraitPagedViewHandler.java | 80 +++--- .../RecentsPagedOrientationHandler.kt | 17 +- .../orientation/SeascapePagedViewHandler.kt | 72 +++-- .../quickstep/views/DigitalWellBeingToast.kt | 264 +++++++++--------- .../quickstep/views/GroupedTaskView.kt | 4 +- .../android/quickstep/views/TaskContainer.kt | 1 + .../com/android/quickstep/views/TaskView.kt | 20 +- 11 files changed, 297 insertions(+), 244 deletions(-) diff --git a/quickstep/res/layout/digital_wellbeing_toast.xml b/quickstep/res/layout/digital_wellbeing_toast.xml index 6a99a3bca8..3973e56f4a 100644 --- a/quickstep/res/layout/digital_wellbeing_toast.xml +++ b/quickstep/res/layout/digital_wellbeing_toast.xml @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - + + \ No newline at end of file diff --git a/quickstep/res/layout/task_grouped.xml b/quickstep/res/layout/task_grouped.xml index cb4b98fd4c..00a990b177 100644 --- a/quickstep/res/layout/task_grouped.xml +++ b/quickstep/res/layout/task_grouped.xml @@ -73,4 +73,12 @@ android:inflatedId="@id/bottomRight_icon" android:layout_height="wrap_content" android:layout_width="wrap_content" /> + + + + \ No newline at end of file diff --git a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt index ec04cb767d..658975c4a9 100644 --- a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt +++ b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt @@ -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 { + 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 { 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( diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java index eeacee1cf6..32d9052e3a 100644 --- a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java +++ b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java @@ -243,54 +243,54 @@ public class PortraitPagedViewHandler extends DefaultPagedViewHandler implements } @Override - public Pair 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 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); diff --git a/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt b/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt index df4b03038f..06a0685d6c 100644 --- a/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt +++ b/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt @@ -199,6 +199,7 @@ interface RecentsPagedOrientationHandler : PagedOrientationHandler { parentWidth: Int, parentHeight: Int ): Pair + // 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 + // The following are only used by TaskViewTouchHandler. /** @return Either VERTICAL or HORIZONTAL. */ diff --git a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt index 333359fdd8..a972e8c1ef 100644 --- a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt +++ b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt @@ -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 { + 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 { 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( diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt index e340ce60ab..f0fdd8147e 100644 --- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt +++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt @@ -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(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,70 +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 setContentDescription(appUsageLimitTimeMs = -1, appRemainingTimeMs = -1) - replaceBanner(null) + visibility = INVISIBLE appRemainingTimeMs = -1 } private fun setLimit(appUsageLimitTimeMs: Long, appRemainingTimeMs: Long) { this.appRemainingTimeMs = appRemainingTimeMs hasLimit = true - val toast = - container.viewCache - .getView( - R.layout.digital_wellbeing_toast, - container.asContext(), - taskView - ) - .apply { - text = - Utilities.prefixTextWithIcon( - container.asContext(), - R.drawable.ic_hourglass_top, - getBannerText() - ) - setOnClickListener(::openAppUsageSettings) - } - replaceBanner(toast) + text = Utilities.prefixTextWithIcon(context, R.drawable.ic_hourglass_top, getBannerText()) + visibility = VISIBLE setContentDescription(appUsageLimitTimeMs, appRemainingTimeMs) } private fun setContentDescription(appUsageLimitTimeMs: Long, appRemainingTimeMs: Long) { - val taskContainer: TaskContainer = taskView.getTaskContainerById(task.key.id) ?: return val contentDescription = getContentDescriptionForTask(task, appUsageLimitTimeMs, appRemainingTimeMs) - taskContainer.snapshotView.contentDescription = contentDescription + snapshotView.contentDescription = contentDescription } - fun initialize(task: Task) { + 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 { @@ -149,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) @@ -198,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) @@ -213,6 +235,7 @@ class DigitalWellBeingToast( * [.SPLIT_BANNER_FULLSCREEN] */ @JvmOverloads + @VisibleForTesting fun getBannerText( remainingTime: Long = appRemainingTimeMs, forContentDesc: Boolean = false @@ -231,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 @@ -246,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) { @@ -264,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 { - 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 = @@ -366,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) diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt index ba4d78694c..4fae01e021 100644 --- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt +++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt @@ -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() } diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt index 86f6a99be0..d34a93bdf1 100644 --- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt +++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt @@ -146,6 +146,7 @@ class TaskContainer( } fun bind() { + digitalWellBeingToast?.bind(task, taskView, snapshotView, stagePosition) if (enableRefactorTaskThumbnail()) { bindThumbnailView() } else { diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt index 139bc248c3..a0e957eede 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.kt +++ b/quickstep/src/com/android/quickstep/views/TaskView.kt @@ -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(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 { 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) }