Merge "Add Accessibility Menu support for multiple DWB banners" into main
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 4b5c826..fd141c3 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -21,6 +21,7 @@
 import static android.view.Surface.ROTATION_0;
 
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.window.flags.Flags.enableDesktopWindowingMode;
 
 import android.app.ActivityOptions;
@@ -107,10 +108,14 @@
         public List<SystemShortcut> getShortcuts(RecentsViewContainer container,
                 TaskContainer taskContainer) {
             TaskView taskView = taskContainer.getTaskView();
+            int actionId = taskContainer.getStagePosition() == STAGE_POSITION_BOTTOM_OR_RIGHT
+                    ? R.id.action_app_info_bottom_right
+                    : R.id.action_app_info_top_left;
+
             AppInfo.SplitAccessibilityInfo accessibilityInfo =
                     new AppInfo.SplitAccessibilityInfo(taskView.containsMultipleTasks(),
                             TaskUtils.getTitle(taskView.getContext(), taskContainer.getTask()),
-                            taskContainer.getA11yNodeId()
+                            actionId
                     );
             return Collections.singletonList(new AppInfo(container, taskContainer.getItemInfo(),
                     taskView, accessibilityInfo));
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index cb1ee0c..6377bf4 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -23,6 +23,7 @@
 
 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;
@@ -38,6 +39,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
@@ -49,6 +51,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
+import com.android.quickstep.TaskUtils;
 import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
 import com.android.systemui.shared.recents.model.Task;
 
@@ -68,13 +71,15 @@
     private static final int SPLIT_GRID_BANNER_LARGE = 1;
     /** Used for grid task view, only showing icon */
     private static final int SPLIT_GRID_BANNER_SMALL = 2;
+
     @IntDef(value = {
             SPLIT_BANNER_FULLSCREEN,
             SPLIT_GRID_BANNER_LARGE,
             SPLIT_GRID_BANNER_SMALL,
     })
     @Retention(RetentionPolicy.SOURCE)
-    @interface SplitBannerConfig{}
+    @interface SplitBannerConfig {
+    }
 
     static final Intent OPEN_APP_USAGE_SETTINGS_TEMPLATE = new Intent(ACTION_APP_USAGE_SETTINGS);
     static final int MINUTE_MS = 60000;
@@ -398,4 +403,36 @@
 
         mBanner.setVisibility(visibility);
     }
+
+    private int getAccessibilityActionId() {
+        return (mSplitBounds != null
+                && mSplitBounds.rightBottomTaskId == mTask.key.id)
+                ? R.id.action_digital_wellbeing_bottom_right
+                : R.id.action_digital_wellbeing_top_left;
+    }
+
+    @Nullable
+    public AccessibilityNodeInfo.AccessibilityAction getDWBAccessibilityAction() {
+        if (!hasLimit()) {
+            return null;
+        }
+
+        Context context = mContainer.asContext();
+        String label =
+                (mTaskView.containsMultipleTasks())
+                        ? context.getString(
+                        R.string.split_app_usage_settings,
+                        TaskUtils.getTitle(context, mTask)
+                ) : context.getString(R.string.accessibility_app_usage_settings);
+        return new AccessibilityNodeInfo.AccessibilityAction(getAccessibilityActionId(), label);
+    }
+
+    public boolean handleAccessibilityAction(int action) {
+        if (getAccessibilityActionId() == action) {
+            openAppUsageSettings(mTaskView);
+            return true;
+        } else {
+            return false;
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 71093af..cae23a1 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -38,6 +38,7 @@
 import android.view.ViewGroup
 import android.view.ViewStub
 import android.view.accessibility.AccessibilityNodeInfo
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
 import android.widget.FrameLayout
 import android.widget.Toast
 import androidx.annotation.IntDef
@@ -67,7 +68,6 @@
 import com.android.launcher3.util.RunnableList
 import com.android.launcher3.util.SafeCloseable
 import com.android.launcher3.util.SplitConfigurationOptions
-import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
 import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption
 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition
@@ -133,19 +133,26 @@
     val taskIds: IntArray
         /** Returns a copy of integer array containing taskIds of all tasks in the TaskView. */
         get() = taskContainers.map { it.task.key.id }.toIntArray()
+
     val thumbnailViews: Array<TaskThumbnailViewDeprecated>
         get() = taskContainers.map { it.thumbnailViewDeprecated }.toTypedArray()
+
     val isGridTask: Boolean
         /** Returns whether the task is part of overview grid and not being focused. */
         get() = container.deviceProfile.isTablet && !isFocusedTask
+
     val isRunningTask: Boolean
         get() = this === recentsView?.runningTaskView
+
     val isFocusedTask: Boolean
         get() = this === recentsView?.focusedTaskView
+
     val taskCornerRadius: Float
         get() = currentFullscreenParams.cornerRadius
+
     val recentsView: RecentsView<*, *>?
         get() = parent as? RecentsView<*, *>
+
     val pagedOrientationHandler: RecentsPagedOrientationHandler
         get() = orientedState.orientationHandler
 
@@ -153,10 +160,12 @@
     val firstTask: Task
         /** Returns the first task bound to this TaskView. */
         get() = taskContainers[0].task
+
     @get:Deprecated("Use [taskContainers] instead.")
     val firstThumbnailViewDeprecated: TaskThumbnailViewDeprecated
         /** Returns the first thumbnailView of the TaskView. */
         get() = taskContainers[0].thumbnailViewDeprecated
+
     @get:Deprecated("Use [taskContainers] instead.")
     val firstItemInfo: ItemInfo
         get() = taskContainers[0].itemInfo
@@ -173,6 +182,7 @@
          * not change according to a temporary state.
          */
         get() = Utilities.mapRange(gridProgress, nonGridScale, 1f)
+
     protected val persistentTranslationX: Float
         /**
          * Returns addition of translationX that is persistent (e.g. fullscreen and grid), and does
@@ -182,42 +192,50 @@
             (getNonGridTrans(nonGridTranslationX) +
                 getGridTrans(this.gridTranslationX) +
                 getNonGridTrans(nonGridPivotTranslationX))
+
     protected val persistentTranslationY: Float
         /**
          * Returns addition of translationY that is persistent (e.g. fullscreen and grid), and does
          * not change according to a temporary state (e.g. task offset).
          */
         get() = boxTranslationY + getGridTrans(gridTranslationY)
+
     protected val primarySplitTranslationProperty: FloatProperty<TaskView>
         get() =
             pagedOrientationHandler.getPrimaryValue(
                 SPLIT_SELECT_TRANSLATION_X,
                 SPLIT_SELECT_TRANSLATION_Y
             )
+
     protected val secondarySplitTranslationProperty: FloatProperty<TaskView>
         get() =
             pagedOrientationHandler.getSecondaryValue(
                 SPLIT_SELECT_TRANSLATION_X,
                 SPLIT_SELECT_TRANSLATION_Y
             )
+
     protected val primaryDismissTranslationProperty: FloatProperty<TaskView>
         get() =
             pagedOrientationHandler.getPrimaryValue(DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y)
+
     protected val secondaryDismissTranslationProperty: FloatProperty<TaskView>
         get() =
             pagedOrientationHandler.getSecondaryValue(DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y)
+
     protected val primaryTaskOffsetTranslationProperty: FloatProperty<TaskView>
         get() =
             pagedOrientationHandler.getPrimaryValue(
                 TASK_OFFSET_TRANSLATION_X,
                 TASK_OFFSET_TRANSLATION_Y
             )
+
     protected val secondaryTaskOffsetTranslationProperty: FloatProperty<TaskView>
         get() =
             pagedOrientationHandler.getSecondaryValue(
                 TASK_OFFSET_TRANSLATION_X,
                 TASK_OFFSET_TRANSLATION_Y
             )
+
     protected val taskResistanceTranslationProperty: FloatProperty<TaskView>
         get() =
             pagedOrientationHandler.getSecondaryValue(
@@ -234,6 +252,7 @@
     /** Returns a list of all TaskContainers in the TaskView. */
     lateinit var taskContainers: List<TaskContainer>
         protected set
+
     lateinit var orientedState: RecentsOrientedState
 
     var taskViewId = UNBOUND_TASK_VIEW_ID
@@ -264,46 +283,55 @@
             field = value
             onModalnessUpdated(field)
         }
+
     protected var taskThumbnailSplashAlpha = 0f
         set(value) {
             field = value
             applyThumbnailSplashAlpha()
         }
+
     protected var nonGridScale = 1f
         set(value) {
             field = value
             applyScale()
         }
+
     private var dismissScale = 1f
         set(value) {
             field = value
             applyScale()
         }
+
     private var dismissTranslationX = 0f
         set(value) {
             field = value
             applyTranslationX()
         }
+
     private var dismissTranslationY = 0f
         set(value) {
             field = value
             applyTranslationY()
         }
+
     private var taskOffsetTranslationX = 0f
         set(value) {
             field = value
             applyTranslationX()
         }
+
     private var taskOffsetTranslationY = 0f
         set(value) {
             field = value
             applyTranslationY()
         }
+
     private var taskResistanceTranslationX = 0f
         set(value) {
             field = value
             applyTranslationX()
         }
+
     private var taskResistanceTranslationY = 0f
         set(value) {
             field = value
@@ -321,6 +349,7 @@
             field = value
             applyTranslationX()
         }
+
     var gridTranslationY = 0f
         protected set(value) {
             field = value
@@ -339,6 +368,7 @@
             field = value
             applyTranslationX()
         }
+
     protected var nonGridPivotTranslationX = 0f
         set(value) {
             field = value
@@ -350,16 +380,19 @@
             field = value
             applyTranslationY()
         }
+
     private var splitSelectTranslationX = 0f
         set(value) {
             field = value
             applyTranslationX()
         }
+
     protected var stableAlpha = 1f
         set(value) {
             field = value
             alpha = stableAlpha
         }
+
     protected var shouldShowScreenshot = false
         get() = !isRunningTask || field
     /** Enable or disable showing border on hover and focus change */
@@ -375,6 +408,7 @@
             hoverBorderAnimator?.setBorderVisibility(visible = field && isHovered, animated = true)
             focusBorderAnimator?.setBorderVisibility(visible = field && isFocused, animated = true)
         }
+
     protected var iconScaleAnimStartProgress = 0f
     private var focusTransitionProgress = 1f
 
@@ -522,11 +556,12 @@
         super.onInitializeAccessibilityNodeInfo(info)
         with(info) {
             addAction(
-                AccessibilityNodeInfo.AccessibilityAction(
-                    R.string.accessibility_close,
+                AccessibilityAction(
+                    R.id.action_close,
                     context.getText(R.string.accessibility_close)
                 )
             )
+
             taskContainers.forEach {
                 TraceHelper.allowIpcs("TV.a11yInfo") {
                     TaskOverlayFactory.getEnabledShortcuts(this@TaskView, it).forEach { shortcut ->
@@ -534,15 +569,12 @@
                     }
                 }
             }
-            // TODO(b/341672022): handle multiple digitalWellBeingToast accessibility actions
-            if (taskContainers[0].digitalWellBeingToast?.hasLimit() == true) {
-                addAction(
-                    AccessibilityNodeInfo.AccessibilityAction(
-                        R.string.accessibility_app_usage_settings,
-                        context.getText(R.string.accessibility_app_usage_settings)
-                    )
-                )
+
+            // Add DWB accessibility action at the end of the list
+            taskContainers.forEach {
+                it.digitalWellBeingToast?.getDWBAccessibilityAction()?.let(::addAction)
             }
+
             recentsView?.let {
                 collectionItemInfo =
                     AccessibilityNodeInfo.CollectionItemInfo.obtain(
@@ -557,16 +589,17 @@
     }
 
     override fun performAccessibilityAction(action: Int, arguments: Bundle?): Boolean {
-        if (action == R.string.accessibility_close) {
+        // TODO(b/343708271): Add support for multiple tasks per action.
+        if (action == R.id.action_close) {
             recentsView?.dismissTask(this, true /*animateTaskView*/, true /*removeTask*/)
             return true
         }
-        if (action == R.string.accessibility_app_usage_settings) {
-            // TODO(b/341672022): handle multiple digitalWellBeingToast accessibility actions
-            taskContainers[0].digitalWellBeingToast?.openAppUsageSettings(this)
-            return true
-        }
+
         taskContainers.forEach {
+            if (it.digitalWellBeingToast?.handleAccessibilityAction(action) == true) {
+                return true
+            }
+
             TaskOverlayFactory.getEnabledShortcuts(this, it).forEach { shortcut ->
                 if (shortcut.hasHandlerForAction(action)) {
                     shortcut.onClick(this)
@@ -574,6 +607,7 @@
                 }
             }
         }
+
         return super.performAccessibilityAction(action, arguments)
     }
 
@@ -1555,11 +1589,6 @@
     ) {
         val overlay: TaskOverlay<*> = taskOverlayFactory.createOverlay(this)
 
-        @IdRes
-        val a11yNodeId: Int =
-            if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) R.id.split_bottomRight_appInfo
-            else R.id.split_topLeft_appInfo
-
         val snapshotView: View
             get() = thumbnailView ?: thumbnailViewDeprecated
 
diff --git a/res/values/id.xml b/res/values/id.xml
index 7bb9396..28496b5 100644
--- a/res/values/id.xml
+++ b/res/values/id.xml
@@ -19,9 +19,6 @@
     <item type="id" name="view_type_widgets_space" />
     <item type="id" name="view_type_widgets_list" />
     <item type="id" name="view_type_widgets_header" />
-    <!--  Used for A11y actions in staged split to identify each task uniquely  -->
-    <item type="id" name="split_topLeft_appInfo" />
-    <item type="id" name="split_bottomRight_appInfo" />
 
     <!-- Accessibility actions -->
     <item type="id" name="action_remove" />
@@ -37,6 +34,12 @@
     <item type="id" name="action_remote_action_shortcut" />
     <item type="id" name="action_dismiss_prediction" />
     <item type="id" name="action_pin_prediction"/>
+    <item type="id" name="action_close"/>
+    <!--  Used for A11y actions in staged split to identify each task uniquely  -->
+    <item type="id" name="action_app_info_top_left" />
+    <item type="id" name="action_app_info_bottom_right" />
+    <item type="id" name="action_digital_wellbeing_top_left" />
+    <item type="id" name="action_digital_wellbeing_bottom_right" />
 
     <!-- QSB IDs. DO not change -->
     <item type="id" name="search_container_workspace" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index ef0f0d8..207d246 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -41,6 +41,7 @@
     <!-- Title for an option to enter split screen mode for a given app -->
     <string name="recent_task_option_split_screen">Split screen</string>
     <string name="split_app_info_accessibility">App info for %1$s</string>
+    <string name="split_app_usage_settings">Usage settings for %1$s</string>
 
     <!-- App pairs -->
     <string name="save_app_pair">Save app pair</string>