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>