Loading core/java/android/view/AccessibilityInteractionController.java +78 −4 Original line number Original line Diff line number Diff line Loading @@ -41,12 +41,14 @@ import android.view.View.AttachInfo; import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.accessibility.AccessibilityNodeProvider; import android.view.accessibility.AccessibilityNodeProvider; import android.view.accessibility.AccessibilityRequestPreparer; import android.view.accessibility.AccessibilityRequestPreparer; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; import com.android.internal.R; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; import com.android.internal.os.SomeArgs; import java.util.ArrayList; import java.util.ArrayList; Loading @@ -64,8 +66,11 @@ import java.util.function.Predicate; * called from the interaction connection ViewAncestor gives the system to * called from the interaction connection ViewAncestor gives the system to * talk to it and a corresponding *UiThread method that is executed on the * talk to it and a corresponding *UiThread method that is executed on the * UI thread. * UI thread. * * @hide */ */ final class AccessibilityInteractionController { @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public final class AccessibilityInteractionController { private static final String LOG_TAG = "AccessibilityInteractionController"; private static final String LOG_TAG = "AccessibilityInteractionController"; Loading @@ -85,7 +90,7 @@ final class AccessibilityInteractionController { private final Object mLock = new Object(); private final Object mLock = new Object(); private final Handler mHandler; private final PrivateHandler mHandler; private final ViewRootImpl mViewRootImpl; private final ViewRootImpl mViewRootImpl; Loading Loading @@ -131,14 +136,22 @@ final class AccessibilityInteractionController { // thread in this process, set the message as a static reference so // thread in this process, set the message as a static reference so // after this call completes the same thread but in the interrogating // after this call completes the same thread but in the interrogating // client can handle the message to generate the result. // client can handle the message to generate the result. if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) { if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId && mHandler.hasAccessibilityCallback(message)) { AccessibilityInteractionClient.getInstanceForThread( AccessibilityInteractionClient.getInstanceForThread( interrogatingTid).setSameThreadMessage(message); interrogatingTid).setSameThreadMessage(message); } else { // For messages without callback of interrogating client, just handle the // message immediately if this is UI thread. if (!mHandler.hasAccessibilityCallback(message) && Thread.currentThread().getId() == mMyLooperThreadId) { mHandler.handleMessage(message); } else { } else { mHandler.sendMessage(message); mHandler.sendMessage(message); } } } } } } } private boolean isShown(View view) { private boolean isShown(View view) { // The first two checks are made also made by isShown() which // The first two checks are made also made by isShown() which Loading Loading @@ -731,6 +744,52 @@ final class AccessibilityInteractionController { } } } } /** * Finds the accessibility focused node in the root, and clears the accessibility focus. */ public void clearAccessibilityFocusClientThread() { final Message message = mHandler.obtainMessage(); message.what = PrivateHandler.MSG_CLEAR_ACCESSIBILITY_FOCUS; // Don't care about pid and tid because there's no interrogating client for this message. scheduleMessage(message, 0, 0, CONSIDER_REQUEST_PREPARERS); } private void clearAccessibilityFocusUiThread() { if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { return; } try { mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS; final View root = mViewRootImpl.mView; if (root != null && isShown(root)) { final View host = mViewRootImpl.mAccessibilityFocusedHost; // If there is no accessibility focus host or it is not a descendant // of the root from which to start the search, then the search failed. if (host == null || !ViewRootImpl.isViewDescendantOf(host, root)) { return; } final AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider(); final AccessibilityNodeInfo focusNode = mViewRootImpl.mAccessibilityFocusedVirtualView; if (provider != null && focusNode != null) { final int virtualNodeId = AccessibilityNodeInfo.getVirtualDescendantId( focusNode.getSourceNodeId()); provider.performAction(virtualNodeId, AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS.getId(), null); } else { host.performAccessibilityAction( AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS.getId(), null); } } } finally { mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; } } private View findViewByAccessibilityId(int accessibilityId) { private View findViewByAccessibilityId(int accessibilityId) { View root = mViewRootImpl.mView; View root = mViewRootImpl.mView; if (root == null) { if (root == null) { Loading Loading @@ -1294,6 +1353,12 @@ final class AccessibilityInteractionController { private static final int MSG_APP_PREPARATION_FINISHED = 8; private static final int MSG_APP_PREPARATION_FINISHED = 8; private static final int MSG_APP_PREPARATION_TIMEOUT = 9; private static final int MSG_APP_PREPARATION_TIMEOUT = 9; // Uses FIRST_NO_ACCESSIBILITY_CALLBACK_MSG for messages that don't need to call back // results to interrogating client. private static final int FIRST_NO_ACCESSIBILITY_CALLBACK_MSG = 100; private static final int MSG_CLEAR_ACCESSIBILITY_FOCUS = FIRST_NO_ACCESSIBILITY_CALLBACK_MSG + 1; public PrivateHandler(Looper looper) { public PrivateHandler(Looper looper) { super(looper); super(looper); } } Loading @@ -1320,6 +1385,8 @@ final class AccessibilityInteractionController { return "MSG_APP_PREPARATION_FINISHED"; return "MSG_APP_PREPARATION_FINISHED"; case MSG_APP_PREPARATION_TIMEOUT: case MSG_APP_PREPARATION_TIMEOUT: return "MSG_APP_PREPARATION_TIMEOUT"; return "MSG_APP_PREPARATION_TIMEOUT"; case MSG_CLEAR_ACCESSIBILITY_FOCUS: return "MSG_CLEAR_ACCESSIBILITY_FOCUS"; default: default: throw new IllegalArgumentException("Unknown message type: " + type); throw new IllegalArgumentException("Unknown message type: " + type); } } Loading Loading @@ -1356,10 +1423,17 @@ final class AccessibilityInteractionController { case MSG_APP_PREPARATION_TIMEOUT: { case MSG_APP_PREPARATION_TIMEOUT: { requestPreparerTimeoutUiThread(); requestPreparerTimeoutUiThread(); } break; } break; case MSG_CLEAR_ACCESSIBILITY_FOCUS: { clearAccessibilityFocusUiThread(); } break; default: default: throw new IllegalArgumentException("Unknown message type: " + type); throw new IllegalArgumentException("Unknown message type: " + type); } } } } boolean hasAccessibilityCallback(Message message) { return message.what < FIRST_NO_ACCESSIBILITY_CALLBACK_MSG ? true : false; } } } private final class AddNodeInfosForViewId implements Predicate<View> { private final class AddNodeInfosForViewId implements Predicate<View> { Loading core/java/android/view/ViewRootImpl.java +9 −0 Original line number Original line Diff line number Diff line Loading @@ -8718,6 +8718,15 @@ public final class ViewRootImpl implements ViewParent, } } } } } } @Override public void clearAccessibilityFocus() { ViewRootImpl viewRootImpl = mViewRootImpl.get(); if (viewRootImpl != null && viewRootImpl.mView != null) { viewRootImpl.getAccessibilityInteractionController() .clearAccessibilityFocusClientThread(); } } } } private class SendWindowContentChangedAccessibilityEvent implements Runnable { private class SendWindowContentChangedAccessibilityEvent implements Runnable { Loading core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl +2 −0 Original line number Original line Diff line number Diff line Loading @@ -55,4 +55,6 @@ oneway interface IAccessibilityInteractionConnection { void performAccessibilityAction(long accessibilityNodeId, int action, in Bundle arguments, void performAccessibilityAction(long accessibilityNodeId, int action, in Bundle arguments, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid); int interrogatingPid, long interrogatingTid); void clearAccessibilityFocus(); } } core/tests/coretests/AndroidManifest.xml +8 −0 Original line number Original line Diff line number Diff line Loading @@ -1144,6 +1144,14 @@ </intent-filter> </intent-filter> </activity> </activity> <activity android:name="android.view.accessibility.AccessibilityTestActivity" android:label="AccessibilityTestActivity" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" /> </intent-filter> </activity> <!-- Activity-level metadata --> <!-- Activity-level metadata --> <meta-data android:name="com.android.frameworks.coretests.isApp" android:value="true" /> <meta-data android:name="com.android.frameworks.coretests.isApp" android:value="true" /> <meta-data android:name="com.android.frameworks.coretests.string" android:value="foo" /> <meta-data android:name="com.android.frameworks.coretests.string" android:value="foo" /> Loading core/tests/coretests/res/layout/accessibility_test.xml 0 → 100644 +30 −0 Original line number Original line Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- ~ Copyright (C) 2018 The Android Open Source Project ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. ~ You may obtain a copy of the License at ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and ~ limitations under the License. --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/appNameBtn" android:text="@string/app_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="60dp" android:bufferType="normal"> </Button> </LinearLayout> Loading
core/java/android/view/AccessibilityInteractionController.java +78 −4 Original line number Original line Diff line number Diff line Loading @@ -41,12 +41,14 @@ import android.view.View.AttachInfo; import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.accessibility.AccessibilityNodeProvider; import android.view.accessibility.AccessibilityNodeProvider; import android.view.accessibility.AccessibilityRequestPreparer; import android.view.accessibility.AccessibilityRequestPreparer; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; import com.android.internal.R; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; import com.android.internal.os.SomeArgs; import java.util.ArrayList; import java.util.ArrayList; Loading @@ -64,8 +66,11 @@ import java.util.function.Predicate; * called from the interaction connection ViewAncestor gives the system to * called from the interaction connection ViewAncestor gives the system to * talk to it and a corresponding *UiThread method that is executed on the * talk to it and a corresponding *UiThread method that is executed on the * UI thread. * UI thread. * * @hide */ */ final class AccessibilityInteractionController { @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public final class AccessibilityInteractionController { private static final String LOG_TAG = "AccessibilityInteractionController"; private static final String LOG_TAG = "AccessibilityInteractionController"; Loading @@ -85,7 +90,7 @@ final class AccessibilityInteractionController { private final Object mLock = new Object(); private final Object mLock = new Object(); private final Handler mHandler; private final PrivateHandler mHandler; private final ViewRootImpl mViewRootImpl; private final ViewRootImpl mViewRootImpl; Loading Loading @@ -131,14 +136,22 @@ final class AccessibilityInteractionController { // thread in this process, set the message as a static reference so // thread in this process, set the message as a static reference so // after this call completes the same thread but in the interrogating // after this call completes the same thread but in the interrogating // client can handle the message to generate the result. // client can handle the message to generate the result. if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) { if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId && mHandler.hasAccessibilityCallback(message)) { AccessibilityInteractionClient.getInstanceForThread( AccessibilityInteractionClient.getInstanceForThread( interrogatingTid).setSameThreadMessage(message); interrogatingTid).setSameThreadMessage(message); } else { // For messages without callback of interrogating client, just handle the // message immediately if this is UI thread. if (!mHandler.hasAccessibilityCallback(message) && Thread.currentThread().getId() == mMyLooperThreadId) { mHandler.handleMessage(message); } else { } else { mHandler.sendMessage(message); mHandler.sendMessage(message); } } } } } } } private boolean isShown(View view) { private boolean isShown(View view) { // The first two checks are made also made by isShown() which // The first two checks are made also made by isShown() which Loading Loading @@ -731,6 +744,52 @@ final class AccessibilityInteractionController { } } } } /** * Finds the accessibility focused node in the root, and clears the accessibility focus. */ public void clearAccessibilityFocusClientThread() { final Message message = mHandler.obtainMessage(); message.what = PrivateHandler.MSG_CLEAR_ACCESSIBILITY_FOCUS; // Don't care about pid and tid because there's no interrogating client for this message. scheduleMessage(message, 0, 0, CONSIDER_REQUEST_PREPARERS); } private void clearAccessibilityFocusUiThread() { if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { return; } try { mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS; final View root = mViewRootImpl.mView; if (root != null && isShown(root)) { final View host = mViewRootImpl.mAccessibilityFocusedHost; // If there is no accessibility focus host or it is not a descendant // of the root from which to start the search, then the search failed. if (host == null || !ViewRootImpl.isViewDescendantOf(host, root)) { return; } final AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider(); final AccessibilityNodeInfo focusNode = mViewRootImpl.mAccessibilityFocusedVirtualView; if (provider != null && focusNode != null) { final int virtualNodeId = AccessibilityNodeInfo.getVirtualDescendantId( focusNode.getSourceNodeId()); provider.performAction(virtualNodeId, AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS.getId(), null); } else { host.performAccessibilityAction( AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS.getId(), null); } } } finally { mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; } } private View findViewByAccessibilityId(int accessibilityId) { private View findViewByAccessibilityId(int accessibilityId) { View root = mViewRootImpl.mView; View root = mViewRootImpl.mView; if (root == null) { if (root == null) { Loading Loading @@ -1294,6 +1353,12 @@ final class AccessibilityInteractionController { private static final int MSG_APP_PREPARATION_FINISHED = 8; private static final int MSG_APP_PREPARATION_FINISHED = 8; private static final int MSG_APP_PREPARATION_TIMEOUT = 9; private static final int MSG_APP_PREPARATION_TIMEOUT = 9; // Uses FIRST_NO_ACCESSIBILITY_CALLBACK_MSG for messages that don't need to call back // results to interrogating client. private static final int FIRST_NO_ACCESSIBILITY_CALLBACK_MSG = 100; private static final int MSG_CLEAR_ACCESSIBILITY_FOCUS = FIRST_NO_ACCESSIBILITY_CALLBACK_MSG + 1; public PrivateHandler(Looper looper) { public PrivateHandler(Looper looper) { super(looper); super(looper); } } Loading @@ -1320,6 +1385,8 @@ final class AccessibilityInteractionController { return "MSG_APP_PREPARATION_FINISHED"; return "MSG_APP_PREPARATION_FINISHED"; case MSG_APP_PREPARATION_TIMEOUT: case MSG_APP_PREPARATION_TIMEOUT: return "MSG_APP_PREPARATION_TIMEOUT"; return "MSG_APP_PREPARATION_TIMEOUT"; case MSG_CLEAR_ACCESSIBILITY_FOCUS: return "MSG_CLEAR_ACCESSIBILITY_FOCUS"; default: default: throw new IllegalArgumentException("Unknown message type: " + type); throw new IllegalArgumentException("Unknown message type: " + type); } } Loading Loading @@ -1356,10 +1423,17 @@ final class AccessibilityInteractionController { case MSG_APP_PREPARATION_TIMEOUT: { case MSG_APP_PREPARATION_TIMEOUT: { requestPreparerTimeoutUiThread(); requestPreparerTimeoutUiThread(); } break; } break; case MSG_CLEAR_ACCESSIBILITY_FOCUS: { clearAccessibilityFocusUiThread(); } break; default: default: throw new IllegalArgumentException("Unknown message type: " + type); throw new IllegalArgumentException("Unknown message type: " + type); } } } } boolean hasAccessibilityCallback(Message message) { return message.what < FIRST_NO_ACCESSIBILITY_CALLBACK_MSG ? true : false; } } } private final class AddNodeInfosForViewId implements Predicate<View> { private final class AddNodeInfosForViewId implements Predicate<View> { Loading
core/java/android/view/ViewRootImpl.java +9 −0 Original line number Original line Diff line number Diff line Loading @@ -8718,6 +8718,15 @@ public final class ViewRootImpl implements ViewParent, } } } } } } @Override public void clearAccessibilityFocus() { ViewRootImpl viewRootImpl = mViewRootImpl.get(); if (viewRootImpl != null && viewRootImpl.mView != null) { viewRootImpl.getAccessibilityInteractionController() .clearAccessibilityFocusClientThread(); } } } } private class SendWindowContentChangedAccessibilityEvent implements Runnable { private class SendWindowContentChangedAccessibilityEvent implements Runnable { Loading
core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl +2 −0 Original line number Original line Diff line number Diff line Loading @@ -55,4 +55,6 @@ oneway interface IAccessibilityInteractionConnection { void performAccessibilityAction(long accessibilityNodeId, int action, in Bundle arguments, void performAccessibilityAction(long accessibilityNodeId, int action, in Bundle arguments, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid); int interrogatingPid, long interrogatingTid); void clearAccessibilityFocus(); } }
core/tests/coretests/AndroidManifest.xml +8 −0 Original line number Original line Diff line number Diff line Loading @@ -1144,6 +1144,14 @@ </intent-filter> </intent-filter> </activity> </activity> <activity android:name="android.view.accessibility.AccessibilityTestActivity" android:label="AccessibilityTestActivity" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" /> </intent-filter> </activity> <!-- Activity-level metadata --> <!-- Activity-level metadata --> <meta-data android:name="com.android.frameworks.coretests.isApp" android:value="true" /> <meta-data android:name="com.android.frameworks.coretests.isApp" android:value="true" /> <meta-data android:name="com.android.frameworks.coretests.string" android:value="foo" /> <meta-data android:name="com.android.frameworks.coretests.string" android:value="foo" /> Loading
core/tests/coretests/res/layout/accessibility_test.xml 0 → 100644 +30 −0 Original line number Original line Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- ~ Copyright (C) 2018 The Android Open Source Project ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. ~ You may obtain a copy of the License at ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and ~ limitations under the License. --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/appNameBtn" android:text="@string/app_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="60dp" android:bufferType="normal"> </Button> </LinearLayout>