Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fa0e75c3 authored by Daniel Akinola's avatar Daniel Akinola
Browse files

Update state announcements for app header to read app chip name

Small tweak to so now the app chip description will be read out when a
window is opened, minimized or closed

Bug: 398732993
Test: manual testing
Flag: com.android.window.flags.enable_desktop_app_header_state_change_announcements
Change-Id: Ia14c6070729b8612cdb24152ca62b213c6b28050
parent 2070d402
Loading
Loading
Loading
Loading
+16 −0
Original line number Diff line number Diff line
@@ -1180,6 +1180,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
+15 −0
Original line number Diff line number Diff line
@@ -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(
@@ -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);
            }
@@ -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);
        }

+9 −0
Original line number Diff line number Diff line
@@ -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.
     *
+9 −50
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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)
@@ -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,
@@ -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(
@@ -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)
                    }
                }
@@ -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
                        )
@@ -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()
    }

@@ -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(
@@ -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"

+25 −0
Original line number Diff line number Diff line
@@ -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
@@ -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