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

Commit ac2b62e9 authored by Jackal Guo's avatar Jackal Guo
Browse files

Accessibility actions should trigger ACTION_OUTSIDE

Some accessibility actions (e.g., click) aren't counted as touch
currently. Hence, windows that are monitoring touches outside of
themselves wouldn't be notified when an accessibility action
takes place on another window.

WindowsInfo doesn't record if a window monitoring touches outside
nowadays. In order to find out this kind of window, add the field
for it.

Bug: 76228634
Bug: 62725890
Test: check if dialogs (e.g., volume panel or a11y menu) dismiss
      after performing a11y action click.
Test: atest CtsAccessibilityServiceTestCases
Test: atest CtsAccessibilityTestCases
Test: atest WindowStateTests
Change-Id: I4fd84848b8a445e6df22251d68449ae8b502c601
parent 0a2348f8
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import android.os.Message;
import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.text.style.AccessibilityClickableSpan;
import android.text.style.ClickableSpan;
import android.util.LongSparseArray;
@@ -702,6 +703,14 @@ final class AccessibilityInteractionController {
                    // Handle this hidden action separately
                    succeeded = handleClickableSpanActionUiThread(
                            target, virtualDescendantId, arguments);
                } else if (action == R.id.accessibilityActionOutsideTouch) {
                    // trigger ACTION_OUTSIDE to notify windows
                    final long now = SystemClock.uptimeMillis();
                    MotionEvent event = MotionEvent.obtain(now, now, MotionEvent.ACTION_OUTSIDE,
                            0, 0, 0);
                    event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
                    mViewRootImpl.dispatchInputEvent(event);
                    succeeded = true;
                } else {
                    AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
                    if (provider != null) {
+7 −0
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@ public class WindowInfo implements Parcelable {
    public CharSequence title;
    public long accessibilityIdOfAnchor = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
    public boolean inPictureInPicture;
    public boolean hasFlagWatchOutsideTouch;

    private WindowInfo() {
        /* do nothing - hide constructor */
@@ -74,6 +75,7 @@ public class WindowInfo implements Parcelable {
        window.title = other.title;
        window.accessibilityIdOfAnchor = other.accessibilityIdOfAnchor;
        window.inPictureInPicture = other.inPictureInPicture;
        window.hasFlagWatchOutsideTouch = other.hasFlagWatchOutsideTouch;

        if (other.childTokens != null && !other.childTokens.isEmpty()) {
            if (window.childTokens == null) {
@@ -108,6 +110,7 @@ public class WindowInfo implements Parcelable {
        parcel.writeCharSequence(title);
        parcel.writeLong(accessibilityIdOfAnchor);
        parcel.writeInt(inPictureInPicture ? 1 : 0);
        parcel.writeInt(hasFlagWatchOutsideTouch ? 1 : 0);

        if (childTokens != null && !childTokens.isEmpty()) {
            parcel.writeInt(1);
@@ -130,6 +133,8 @@ public class WindowInfo implements Parcelable {
        builder.append(", focused=").append(focused);
        builder.append(", children=").append(childTokens);
        builder.append(", accessibility anchor=").append(accessibilityIdOfAnchor);
        builder.append(", pictureInPicture=").append(inPictureInPicture);
        builder.append(", watchOutsideTouch=").append(hasFlagWatchOutsideTouch);
        builder.append(']');
        return builder.toString();
    }
@@ -145,6 +150,7 @@ public class WindowInfo implements Parcelable {
        title = parcel.readCharSequence();
        accessibilityIdOfAnchor = parcel.readLong();
        inPictureInPicture = (parcel.readInt() == 1);
        hasFlagWatchOutsideTouch = (parcel.readInt() == 1);

        final boolean hasChildren = (parcel.readInt() == 1);
        if (hasChildren) {
@@ -167,6 +173,7 @@ public class WindowInfo implements Parcelable {
            childTokens.clear();
        }
        inPictureInPicture = false;
        hasFlagWatchOutsideTouch = false;
    }

    public static final Parcelable.Creator<WindowInfo> CREATOR =
+3 −0
Original line number Diff line number Diff line
@@ -172,4 +172,7 @@

    <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_HIDE_TOOLTIP}. -->
    <item type="id" name="accessibilityActionHideTooltip" />

  <!-- Accessibility action to notify a window there is an outside touch. -->
  <item type="id" name="accessibilityActionOutsideTouch" />
</resources>
+1 −0
Original line number Diff line number Diff line
@@ -218,6 +218,7 @@
  <java-symbol type="id" name="selection_end_handle" />
  <java-symbol type="id" name="insertion_handle" />
  <java-symbol type="id" name="accessibilityActionClickOnClickableSpan" />
  <java-symbol type="id" name="accessibilityActionOutsideTouch" />
  <java-symbol type="id" name="camera" />
  <java-symbol type="id" name="mic" />
  <java-symbol type="id" name="overlay" />
+57 −0
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@ import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY
import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import static android.accessibilityservice.AccessibilityService.SHOW_MODE_AUTO;
@@ -2549,6 +2551,38 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
        return -1;
    }

    private void notifyOutsideTouchIfNeeded(int targetWindowId, int action, Bundle arguments,
            int interactionId, IAccessibilityInteractionConnectionCallback callback, int fetchFlags,
            int interrogatingPid, long interrogatingTid) {
        if (action != ACTION_CLICK && action != ACTION_LONG_CLICK) {
            return;
        }

        final List<Integer> outsideWindowsIds;
        final List<RemoteAccessibilityConnection> connectionList = new ArrayList<>();
        synchronized (mLock) {
            outsideWindowsIds = mSecurityPolicy.getWatchOutsideTouchWindowId(targetWindowId);
            for (int i = 0; i < outsideWindowsIds.size(); i++) {
                connectionList.add(getConnectionLocked(outsideWindowsIds.get(i)));
            }
        }
        for (int i = 0; i < connectionList.size(); i++) {
            final RemoteAccessibilityConnection connection = connectionList.get(i);
            if (connection != null) {
                try {
                    connection.mConnection.performAccessibilityAction(
                            AccessibilityNodeInfo.ROOT_ITEM_ID,
                            R.id.accessibilityActionOutsideTouch, arguments, interactionId,
                            callback, fetchFlags, interrogatingPid, interrogatingTid);
                } catch (RemoteException re) {
                    if (DEBUG) {
                        Slog.e(LOG_TAG, "Error calling performAccessibilityAction: " + re);
                    }
                }
            }
        }
    }

    @Override
    public void ensureWindowsAvailableTimed() {
        synchronized (mLock) {
@@ -2628,6 +2662,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
            mPowerManager.userActivity(SystemClock.uptimeMillis(),
                    PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY, 0);

            notifyOutsideTouchIfNeeded(resolvedWindowId, action, arguments, interactionId, callback,
                    fetchFlags, interrogatingPid, interrogatingTid);
            if (activityToken != null) {
                LocalServices.getService(ActivityTaskManagerInternal.class)
                        .setFocusedActivity(activityToken);
@@ -2955,6 +2991,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
        public long mAccessibilityFocusNodeId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;

        private boolean mTouchInteractionInProgress;
        private boolean mHasWatchOutsideTouchWindow;

        private boolean canDispatchAccessibilityEventLocked(AccessibilityEvent event) {
            final int eventType = event.getEventType();
@@ -3112,6 +3149,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                mWindowInfoById.valueAt(i).recycle();
            }
            mWindowInfoById.clear();
            mHasWatchOutsideTouchWindow = false;

            mFocusedWindowId = INVALID_WINDOW_ID;
            if (!mTouchInteractionInProgress) {
@@ -3156,6 +3194,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                                activeWindowGone = false;
                            }
                        }
                        if (!mHasWatchOutsideTouchWindow && windowInfo.hasFlagWatchOutsideTouch) {
                            mHasWatchOutsideTouchWindow = true;
                        }
                        mWindows.add(window);
                        mA11yWindowInfoById.put(windowId, window);
                        mWindowInfoById.put(windowId, WindowInfo.obtain(windowInfo));
@@ -3574,6 +3615,22 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
            return mWindowInfoById.get(windowId);
        }

        private List<Integer> getWatchOutsideTouchWindowId(int targetWindowId) {
            if (mWindowInfoById != null && mHasWatchOutsideTouchWindow) {
                final List<Integer> outsideWindowsId = new ArrayList<>();
                final WindowInfo targetWindow = mWindowInfoById.get(targetWindowId);
                for (int i = 0; i < mWindowInfoById.size(); i++) {
                    WindowInfo window = mWindowInfoById.valueAt(i);
                    if (window.layer < targetWindow.layer
                            && window.hasFlagWatchOutsideTouch) {
                        outsideWindowsId.add(mWindowInfoById.keyAt(i));
                    }
                }
                return outsideWindowsId;
            }
            return Collections.emptyList();
        }

        private AccessibilityWindowInfo getPictureInPictureWindow() {
            if (mWindows != null) {
                final int windowCount = mWindows.size();
Loading