Loading core/java/android/view/AccessibilityInteractionController.java +78 −4 Original line number Diff line number Diff line Loading @@ -41,12 +41,14 @@ import android.view.View.AttachInfo; import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.accessibility.AccessibilityNodeProvider; import android.view.accessibility.AccessibilityRequestPreparer; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; import java.util.ArrayList; Loading @@ -64,8 +66,11 @@ import java.util.function.Predicate; * called from the interaction connection ViewAncestor gives the system to * talk to it and a corresponding *UiThread method that is executed on the * UI thread. * * @hide */ final class AccessibilityInteractionController { @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public final class AccessibilityInteractionController { private static final String LOG_TAG = "AccessibilityInteractionController"; Loading @@ -85,7 +90,7 @@ final class AccessibilityInteractionController { private final Object mLock = new Object(); private final Handler mHandler; private final PrivateHandler mHandler; 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 // after this call completes the same thread but in the interrogating // client can handle the message to generate the result. if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) { if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId && mHandler.hasAccessibilityCallback(message)) { AccessibilityInteractionClient.getInstanceForThread( 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 { mHandler.sendMessage(message); } } } } private boolean isShown(View view) { // 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) { View root = mViewRootImpl.mView; 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_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) { super(looper); } Loading @@ -1320,6 +1385,8 @@ final class AccessibilityInteractionController { return "MSG_APP_PREPARATION_FINISHED"; case MSG_APP_PREPARATION_TIMEOUT: return "MSG_APP_PREPARATION_TIMEOUT"; case MSG_CLEAR_ACCESSIBILITY_FOCUS: return "MSG_CLEAR_ACCESSIBILITY_FOCUS"; default: throw new IllegalArgumentException("Unknown message type: " + type); } Loading Loading @@ -1356,10 +1423,17 @@ final class AccessibilityInteractionController { case MSG_APP_PREPARATION_TIMEOUT: { requestPreparerTimeoutUiThread(); } break; case MSG_CLEAR_ACCESSIBILITY_FOCUS: { clearAccessibilityFocusUiThread(); } break; default: 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> { Loading core/java/android/view/ViewRootImpl.java +9 −0 Original line number 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 { Loading core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -55,4 +55,6 @@ oneway interface IAccessibilityInteractionConnection { void performAccessibilityAction(long accessibilityNodeId, int action, in Bundle arguments, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid); void clearAccessibilityFocus(); } core/tests/coretests/AndroidManifest.xml +8 −0 Original line number Diff line number Diff line Loading @@ -1144,6 +1144,14 @@ </intent-filter> </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 --> <meta-data android:name="com.android.frameworks.coretests.isApp" android:value="true" /> <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 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 Diff line number Diff line Loading @@ -41,12 +41,14 @@ import android.view.View.AttachInfo; import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.accessibility.AccessibilityNodeProvider; import android.view.accessibility.AccessibilityRequestPreparer; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; import java.util.ArrayList; Loading @@ -64,8 +66,11 @@ import java.util.function.Predicate; * called from the interaction connection ViewAncestor gives the system to * talk to it and a corresponding *UiThread method that is executed on the * UI thread. * * @hide */ final class AccessibilityInteractionController { @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public final class AccessibilityInteractionController { private static final String LOG_TAG = "AccessibilityInteractionController"; Loading @@ -85,7 +90,7 @@ final class AccessibilityInteractionController { private final Object mLock = new Object(); private final Handler mHandler; private final PrivateHandler mHandler; 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 // after this call completes the same thread but in the interrogating // client can handle the message to generate the result. if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) { if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId && mHandler.hasAccessibilityCallback(message)) { AccessibilityInteractionClient.getInstanceForThread( 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 { mHandler.sendMessage(message); } } } } private boolean isShown(View view) { // 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) { View root = mViewRootImpl.mView; 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_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) { super(looper); } Loading @@ -1320,6 +1385,8 @@ final class AccessibilityInteractionController { return "MSG_APP_PREPARATION_FINISHED"; case MSG_APP_PREPARATION_TIMEOUT: return "MSG_APP_PREPARATION_TIMEOUT"; case MSG_CLEAR_ACCESSIBILITY_FOCUS: return "MSG_CLEAR_ACCESSIBILITY_FOCUS"; default: throw new IllegalArgumentException("Unknown message type: " + type); } Loading Loading @@ -1356,10 +1423,17 @@ final class AccessibilityInteractionController { case MSG_APP_PREPARATION_TIMEOUT: { requestPreparerTimeoutUiThread(); } break; case MSG_CLEAR_ACCESSIBILITY_FOCUS: { clearAccessibilityFocusUiThread(); } break; default: 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> { Loading
core/java/android/view/ViewRootImpl.java +9 −0 Original line number 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 { Loading
core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -55,4 +55,6 @@ oneway interface IAccessibilityInteractionConnection { void performAccessibilityAction(long accessibilityNodeId, int action, in Bundle arguments, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid); void clearAccessibilityFocus(); }
core/tests/coretests/AndroidManifest.xml +8 −0 Original line number Diff line number Diff line Loading @@ -1144,6 +1144,14 @@ </intent-filter> </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 --> <meta-data android:name="com.android.frameworks.coretests.isApp" android:value="true" /> <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 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>