Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +16 −0 Original line number Diff line number Diff line Loading @@ -1181,6 +1181,22 @@ class DesktopTasksController( } } /** * Returns the task that will be focused next after the current task (the given [taskInfo]) is * removed, due to being minimized or closed. * * @param taskInfo the task that is being removed. * @return the taskId of the next focused task, or [INVALID_TASK_ID] if no task is found. */ fun getNextFocusedTask(taskInfo: RunningTaskInfo): Int { val deskId = getOrCreateDefaultDeskId(taskInfo.displayId) ?: return INVALID_TASK_ID return taskRepository .getExpandedTasksIdsInDeskOrdered(deskId) // exclude current task since maximize/restore transition has not taken place yet. .filterNot { it == taskInfo.taskId } .firstOrNull { !taskRepository.isClosingTask(it) } ?: INVALID_TASK_ID } fun minimizeTask(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) { val wct = WindowContainerTransaction() val taskId = taskInfo.taskId Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +15 −0 Original line number Diff line number Diff line Loading @@ -1099,6 +1099,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, mSplitScreenController.moveTaskToFullscreen(getOtherSplitTask(mTaskId).taskId, SplitScreenController.EXIT_REASON_DESKTOP_MODE); } else { final int nextFocusedTaskId = mDesktopTasksController.getNextFocusedTask(decoration.mTaskInfo); if (nextFocusedTaskId != INVALID_TASK_ID) { mWindowDecorByTaskId.get(nextFocusedTaskId).a11yAnnounceNewFocusedWindow(); } WindowContainerTransaction wct = new WindowContainerTransaction(); final Function1<IBinder, Unit> runOnTransitionStart = mDesktopTasksController.onDesktopWindowClose( Loading Loading @@ -1141,6 +1146,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, getInputMethod(mMotionEvent)); } } else if (id == R.id.minimize_window) { final int nextFocusedTaskId = mDesktopTasksController .getNextFocusedTask(decoration.mTaskInfo); if (nextFocusedTaskId != INVALID_TASK_ID) { mWindowDecorByTaskId.get(nextFocusedTaskId).a11yAnnounceNewFocusedWindow(); } mDesktopTasksController.minimizeTask( decoration.mTaskInfo, MinimizeReason.MINIMIZE_BUTTON); } Loading Loading @@ -2191,6 +2201,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, @Override public void onMinimize(@NonNull RunningTaskInfo taskInfo) { final int nextFocusedTaskId = mDesktopTasksController.getNextFocusedTask(taskInfo); if (nextFocusedTaskId != INVALID_TASK_ID) { mViewModel.mWindowDecorByTaskId.get(nextFocusedTaskId) .a11yAnnounceNewFocusedWindow(); } mDesktopTasksController.minimizeTask(taskInfo, MinimizeReason.MINIMIZE_BUTTON); } Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +9 −0 Original line number Diff line number Diff line Loading @@ -1950,6 +1950,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin isCaptionVisible())); } /** * Announces that the app window is now being focused for accessibility. This is used after a * window is minimized/closed, and a new app window gains focus. */ void a11yAnnounceNewFocusedWindow() { if (!isAppHeader(mWindowDecorViewHolder)) return; asAppHeader(mWindowDecorViewHolder).a11yAnnounceFocused(); } /** * Declares whether a Recents transition is currently active. * Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt +9 −50 Original line number Diff line number Diff line Loading @@ -25,7 +25,6 @@ import android.graphics.Bitmap import android.graphics.Color import android.graphics.Rect import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.View.OnLongClickListener Loading Loading @@ -73,12 +72,6 @@ import com.android.wm.shell.windowdecor.common.Theme import com.android.wm.shell.windowdecor.common.createBackgroundDrawable import com.android.wm.shell.windowdecor.extension.isLightCaptionBarAppearance import com.android.wm.shell.windowdecor.extension.isTransparentCaptionBarAppearance import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder.A11yState.CLOSING import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder.A11yState.FOCUSED import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder.A11yState.MINIMIZING import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder.A11yState.NOT_FOCUSED import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder.A11yState.OPENING import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder.A11yState.UNKNOWN /** * A desktop mode window decoration used when the window is floating (i.e. freeform). It hosts Loading Loading @@ -209,8 +202,6 @@ class AppHeaderViewHolder( private lateinit var a11yTextRestore: String private lateinit var currentTaskInfo: RunningTaskInfo private var a11yState = UNKNOWN init { captionView.setOnTouchListener(onCaptionTouchListener) captionHandle.setOnTouchListener(onCaptionTouchListener) Loading Loading @@ -240,7 +231,6 @@ class AppHeaderViewHolder( context.getString(R.string.desktop_mode_a11y_action_maximize_restore) ) captionHandle.accessibilityLiveRegion = View.ACCESSIBILITY_LIVE_REGION_ASSERTIVE captionHandle.accessibilityDelegate = object : View.AccessibilityDelegate() { override fun onInitializeAccessibilityNodeInfo( host: View, Loading @@ -250,6 +240,8 @@ class AppHeaderViewHolder( info.addAction(a11yActionSnapLeft) info.addAction(a11yActionSnapRight) info.addAction(a11yActionMaximizeRestore) info.liveRegion = View.ACCESSIBILITY_LIVE_REGION_POLITE info.isScreenReaderFocusable = false } override fun performAccessibilityAction( Loading Loading @@ -344,7 +336,7 @@ class AppHeaderViewHolder( ): Boolean { when (action) { AccessibilityAction.ACTION_CLICK.id -> { setA11yStateTo(CLOSING) captionHandle.stateDescription = a11yAnnounceTextClosing desktopModeUiEventLogger.log(currentTaskInfo, A11Y_APP_WINDOW_CLOSE_BUTTON) } } Loading @@ -361,7 +353,7 @@ class AppHeaderViewHolder( ): Boolean { when (action) { AccessibilityAction.ACTION_CLICK.id -> { setA11yStateTo(MINIMIZING) captionHandle.stateDescription = a11yAnnounceTextMinimizing desktopModeUiEventLogger.log( currentTaskInfo, A11Y_APP_WINDOW_MINIMIZE_BUTTON ) Loading Loading @@ -408,13 +400,16 @@ class AppHeaderViewHolder( ) } /** Announces app window name as "focused" via Talkback */ fun a11yAnnounceFocused() { captionHandle.stateDescription = a11yAnnounceTextFocused } /** Sets the app's name in the header. */ fun setAppName(name: CharSequence) { appNameTextView.text = name populateA11yStrings(name) if (a11yState == OPENING) setA11yStateTo(FOCUSED) updateMaximizeButtonContentDescription() } Loading Loading @@ -472,20 +467,6 @@ class AppHeaderViewHolder( } else { bindDataLegacy(taskInfo, hasGlobalFocus, isCaptionVisible) } if (hasGlobalFocus) { // app window is gaining focus if (a11yState == UNKNOWN) { // app window is opening from close or minimize setA11yStateTo(OPENING) } else if (a11yState == NOT_FOCUSED) { // app window is being re-focused after being in background setA11yStateTo(FOCUSED) } } else if (!hasGlobalFocus && a11yState == FOCUSED) { // app window is losing focus and moving to background as another window gains focus setA11yStateTo(NOT_FOCUSED) } } private fun bindDataLegacy( Loading Loading @@ -916,28 +897,6 @@ class AppHeaderViewHolder( maximizeWindowButton.cancelLongPress() } private enum class A11yState { UNKNOWN, OPENING, MINIMIZING, CLOSING, NOT_FOCUSED, FOCUSED } private fun setA11yStateTo(newState: A11yState) { if (!DesktopExperienceFlags.ENABLE_DESKTOP_APP_HEADER_STATE_CHANGE_ANNOUNCEMENTS.isTrue) { Log.i(TAG, "no a11y state change due to missing " + "enable_desktop_windowing_app_header_state_change_announcements") return } val newStateDesc = when (newState) { OPENING -> a11yAnnounceTextOpening MINIMIZING -> a11yAnnounceTextMinimizing CLOSING -> a11yAnnounceTextClosing NOT_FOCUSED -> a11yAnnounceTextNotFocused FOCUSED -> a11yAnnounceTextFocused else -> null } captionHandle.stateDescription = newStateDesc a11yState = newState } companion object { private const val TAG = "DesktopModeAppControlsWindowDecorationViewHolder" Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +25 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.wm.shell.desktopmode import android.app.ActivityManager.RecentTaskInfo import android.app.ActivityManager.RunningTaskInfo import android.app.ActivityOptions import android.app.ActivityTaskManager.INVALID_TASK_ID import android.app.AppOpsManager import android.app.KeyguardManager import android.app.PendingIntent Loading Loading @@ -587,6 +588,30 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() assertThat(controller.doesAnyTaskRequireTaskbarRounding(DEFAULT_DISPLAY)).isTrue() } @Test fun getNextFocusedTask_onlyClosingTask_returnInvalidId() { val closingTask = setUpFreeformTask() assertThat(controller.getNextFocusedTask(closingTask)).isEqualTo(INVALID_TASK_ID) } @Test fun getNextFocusedTask_oneNonClosingTask_returnNextFocusedTask() { val otherTask = setUpFreeformTask() val closingTask = setUpFreeformTask() assertThat(controller.getNextFocusedTask(closingTask)).isEqualTo(otherTask.taskId) } @Test fun getNextFocusedTask_multipleNonClosingTask_returnNextFocusedTask() { val otherTask = setUpFreeformTask() val otherTask2 = setUpFreeformTask() val otherTask3 = setUpFreeformTask() val closingTask = setUpFreeformTask() assertThat(controller.getNextFocusedTask(closingTask)).isNotEqualTo(otherTask.taskId) assertThat(controller.getNextFocusedTask(closingTask)).isNotEqualTo(otherTask2.taskId) assertThat(controller.getNextFocusedTask(closingTask)).isEqualTo(otherTask3.taskId) } @Test fun instantiate_cannotEnterDesktopMode_doNotAddInitCallback() { desktopState.canEnterDesktopMode = false Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +16 −0 Original line number Diff line number Diff line Loading @@ -1181,6 +1181,22 @@ class DesktopTasksController( } } /** * Returns the task that will be focused next after the current task (the given [taskInfo]) is * removed, due to being minimized or closed. * * @param taskInfo the task that is being removed. * @return the taskId of the next focused task, or [INVALID_TASK_ID] if no task is found. */ fun getNextFocusedTask(taskInfo: RunningTaskInfo): Int { val deskId = getOrCreateDefaultDeskId(taskInfo.displayId) ?: return INVALID_TASK_ID return taskRepository .getExpandedTasksIdsInDeskOrdered(deskId) // exclude current task since maximize/restore transition has not taken place yet. .filterNot { it == taskInfo.taskId } .firstOrNull { !taskRepository.isClosingTask(it) } ?: INVALID_TASK_ID } fun minimizeTask(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) { val wct = WindowContainerTransaction() val taskId = taskInfo.taskId Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +15 −0 Original line number Diff line number Diff line Loading @@ -1099,6 +1099,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, mSplitScreenController.moveTaskToFullscreen(getOtherSplitTask(mTaskId).taskId, SplitScreenController.EXIT_REASON_DESKTOP_MODE); } else { final int nextFocusedTaskId = mDesktopTasksController.getNextFocusedTask(decoration.mTaskInfo); if (nextFocusedTaskId != INVALID_TASK_ID) { mWindowDecorByTaskId.get(nextFocusedTaskId).a11yAnnounceNewFocusedWindow(); } WindowContainerTransaction wct = new WindowContainerTransaction(); final Function1<IBinder, Unit> runOnTransitionStart = mDesktopTasksController.onDesktopWindowClose( Loading Loading @@ -1141,6 +1146,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, getInputMethod(mMotionEvent)); } } else if (id == R.id.minimize_window) { final int nextFocusedTaskId = mDesktopTasksController .getNextFocusedTask(decoration.mTaskInfo); if (nextFocusedTaskId != INVALID_TASK_ID) { mWindowDecorByTaskId.get(nextFocusedTaskId).a11yAnnounceNewFocusedWindow(); } mDesktopTasksController.minimizeTask( decoration.mTaskInfo, MinimizeReason.MINIMIZE_BUTTON); } Loading Loading @@ -2191,6 +2201,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, @Override public void onMinimize(@NonNull RunningTaskInfo taskInfo) { final int nextFocusedTaskId = mDesktopTasksController.getNextFocusedTask(taskInfo); if (nextFocusedTaskId != INVALID_TASK_ID) { mViewModel.mWindowDecorByTaskId.get(nextFocusedTaskId) .a11yAnnounceNewFocusedWindow(); } mDesktopTasksController.minimizeTask(taskInfo, MinimizeReason.MINIMIZE_BUTTON); } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +9 −0 Original line number Diff line number Diff line Loading @@ -1950,6 +1950,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin isCaptionVisible())); } /** * Announces that the app window is now being focused for accessibility. This is used after a * window is minimized/closed, and a new app window gains focus. */ void a11yAnnounceNewFocusedWindow() { if (!isAppHeader(mWindowDecorViewHolder)) return; asAppHeader(mWindowDecorViewHolder).a11yAnnounceFocused(); } /** * Declares whether a Recents transition is currently active. * Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt +9 −50 Original line number Diff line number Diff line Loading @@ -25,7 +25,6 @@ import android.graphics.Bitmap import android.graphics.Color import android.graphics.Rect import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.View.OnLongClickListener Loading Loading @@ -73,12 +72,6 @@ import com.android.wm.shell.windowdecor.common.Theme import com.android.wm.shell.windowdecor.common.createBackgroundDrawable import com.android.wm.shell.windowdecor.extension.isLightCaptionBarAppearance import com.android.wm.shell.windowdecor.extension.isTransparentCaptionBarAppearance import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder.A11yState.CLOSING import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder.A11yState.FOCUSED import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder.A11yState.MINIMIZING import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder.A11yState.NOT_FOCUSED import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder.A11yState.OPENING import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder.A11yState.UNKNOWN /** * A desktop mode window decoration used when the window is floating (i.e. freeform). It hosts Loading Loading @@ -209,8 +202,6 @@ class AppHeaderViewHolder( private lateinit var a11yTextRestore: String private lateinit var currentTaskInfo: RunningTaskInfo private var a11yState = UNKNOWN init { captionView.setOnTouchListener(onCaptionTouchListener) captionHandle.setOnTouchListener(onCaptionTouchListener) Loading Loading @@ -240,7 +231,6 @@ class AppHeaderViewHolder( context.getString(R.string.desktop_mode_a11y_action_maximize_restore) ) captionHandle.accessibilityLiveRegion = View.ACCESSIBILITY_LIVE_REGION_ASSERTIVE captionHandle.accessibilityDelegate = object : View.AccessibilityDelegate() { override fun onInitializeAccessibilityNodeInfo( host: View, Loading @@ -250,6 +240,8 @@ class AppHeaderViewHolder( info.addAction(a11yActionSnapLeft) info.addAction(a11yActionSnapRight) info.addAction(a11yActionMaximizeRestore) info.liveRegion = View.ACCESSIBILITY_LIVE_REGION_POLITE info.isScreenReaderFocusable = false } override fun performAccessibilityAction( Loading Loading @@ -344,7 +336,7 @@ class AppHeaderViewHolder( ): Boolean { when (action) { AccessibilityAction.ACTION_CLICK.id -> { setA11yStateTo(CLOSING) captionHandle.stateDescription = a11yAnnounceTextClosing desktopModeUiEventLogger.log(currentTaskInfo, A11Y_APP_WINDOW_CLOSE_BUTTON) } } Loading @@ -361,7 +353,7 @@ class AppHeaderViewHolder( ): Boolean { when (action) { AccessibilityAction.ACTION_CLICK.id -> { setA11yStateTo(MINIMIZING) captionHandle.stateDescription = a11yAnnounceTextMinimizing desktopModeUiEventLogger.log( currentTaskInfo, A11Y_APP_WINDOW_MINIMIZE_BUTTON ) Loading Loading @@ -408,13 +400,16 @@ class AppHeaderViewHolder( ) } /** Announces app window name as "focused" via Talkback */ fun a11yAnnounceFocused() { captionHandle.stateDescription = a11yAnnounceTextFocused } /** Sets the app's name in the header. */ fun setAppName(name: CharSequence) { appNameTextView.text = name populateA11yStrings(name) if (a11yState == OPENING) setA11yStateTo(FOCUSED) updateMaximizeButtonContentDescription() } Loading Loading @@ -472,20 +467,6 @@ class AppHeaderViewHolder( } else { bindDataLegacy(taskInfo, hasGlobalFocus, isCaptionVisible) } if (hasGlobalFocus) { // app window is gaining focus if (a11yState == UNKNOWN) { // app window is opening from close or minimize setA11yStateTo(OPENING) } else if (a11yState == NOT_FOCUSED) { // app window is being re-focused after being in background setA11yStateTo(FOCUSED) } } else if (!hasGlobalFocus && a11yState == FOCUSED) { // app window is losing focus and moving to background as another window gains focus setA11yStateTo(NOT_FOCUSED) } } private fun bindDataLegacy( Loading Loading @@ -916,28 +897,6 @@ class AppHeaderViewHolder( maximizeWindowButton.cancelLongPress() } private enum class A11yState { UNKNOWN, OPENING, MINIMIZING, CLOSING, NOT_FOCUSED, FOCUSED } private fun setA11yStateTo(newState: A11yState) { if (!DesktopExperienceFlags.ENABLE_DESKTOP_APP_HEADER_STATE_CHANGE_ANNOUNCEMENTS.isTrue) { Log.i(TAG, "no a11y state change due to missing " + "enable_desktop_windowing_app_header_state_change_announcements") return } val newStateDesc = when (newState) { OPENING -> a11yAnnounceTextOpening MINIMIZING -> a11yAnnounceTextMinimizing CLOSING -> a11yAnnounceTextClosing NOT_FOCUSED -> a11yAnnounceTextNotFocused FOCUSED -> a11yAnnounceTextFocused else -> null } captionHandle.stateDescription = newStateDesc a11yState = newState } companion object { private const val TAG = "DesktopModeAppControlsWindowDecorationViewHolder" Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +25 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.wm.shell.desktopmode import android.app.ActivityManager.RecentTaskInfo import android.app.ActivityManager.RunningTaskInfo import android.app.ActivityOptions import android.app.ActivityTaskManager.INVALID_TASK_ID import android.app.AppOpsManager import android.app.KeyguardManager import android.app.PendingIntent Loading Loading @@ -587,6 +588,30 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() assertThat(controller.doesAnyTaskRequireTaskbarRounding(DEFAULT_DISPLAY)).isTrue() } @Test fun getNextFocusedTask_onlyClosingTask_returnInvalidId() { val closingTask = setUpFreeformTask() assertThat(controller.getNextFocusedTask(closingTask)).isEqualTo(INVALID_TASK_ID) } @Test fun getNextFocusedTask_oneNonClosingTask_returnNextFocusedTask() { val otherTask = setUpFreeformTask() val closingTask = setUpFreeformTask() assertThat(controller.getNextFocusedTask(closingTask)).isEqualTo(otherTask.taskId) } @Test fun getNextFocusedTask_multipleNonClosingTask_returnNextFocusedTask() { val otherTask = setUpFreeformTask() val otherTask2 = setUpFreeformTask() val otherTask3 = setUpFreeformTask() val closingTask = setUpFreeformTask() assertThat(controller.getNextFocusedTask(closingTask)).isNotEqualTo(otherTask.taskId) assertThat(controller.getNextFocusedTask(closingTask)).isNotEqualTo(otherTask2.taskId) assertThat(controller.getNextFocusedTask(closingTask)).isEqualTo(otherTask3.taskId) } @Test fun instantiate_cannotEnterDesktopMode_doNotAddInitCallback() { desktopState.canEnterDesktopMode = false Loading