前言
无障碍服务默认情况下可以识别系统中的各种窗口内容,并允许可以实现按钮点击;不过安卓系统权限弹窗因为隐私关系,默认是无法被无障碍服务识别和点击的,本篇文章具体来分析一下为什么权限弹窗无法被无障碍服务识别。
窗口变化触发无障碍服务方法回调
在Android 16系统源码_无障碍辅助(一)认识无障碍服务我们有定制一个ClickAccessibilityService的无障碍服务。在有新页面或者窗口的时候,会触发onAccessibilityEvent方法,我们在该方法中将页面或者窗口中的文字打印出来。
java
public class ClickAccessibilityService extends AccessibilityService {
private static final String TAG = "ClickAccessibilityService";
private static ClickAccessibilityService mService = null;
public static boolean isConnected() {
return mService != null;
}
@Override
protected void onServiceConnected() {
super.onServiceConnected();
Log.i(TAG, "onServiceConnected");
mService = this;
}
@Override
public void onInterrupt() {
Log.i(TAG, "onInterrupt");
mService = null;
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy");
mService = null;
}
@Override
public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
// 打印无障碍服务的事件类型
int eventType = accessibilityEvent.getEventType();
Log.i(TAG, "onAccessibilityEvent: eventType =" + eventType, new Throwable());
Log.i(TAG, "onAccessibilityEvent: accessibilityEvent = " + accessibilityEvent);
getTestWindow();
}
private void getTestWindow() {
// 获取所有显示器上的窗口信息,返回一个 SparseArray
SparseArray<List<AccessibilityWindowInfo>> windowsOnAllDisplays = getWindowsOnAllDisplays();
// 1. 检查返回值是否为 null
if (windowsOnAllDisplays == null) {
return;
}
List<AccessibilityWindowInfo> listWindow = windowsOnAllDisplays.valueAt(0);
// Log.i("TextAccessibilityService", "getTestWindow: listWindow = " + listWindow);
Log.i(TAG, "getTestWindow: accessibilityNodeInfo --------------------------------------------------------------------------------------------------------------------------------------");
for (int i = 0; i < listWindow.size(); i++) {
AccessibilityWindowInfo accessibilityWindowInfo = listWindow.get(i);
AccessibilityNodeInfo accessibilityNodeInfo = accessibilityWindowInfo.getRoot();
Log.i(TAG, "getTestWindow: accessibilityNodeInfo[+" + i + "] = " + accessibilityNodeInfo);
}
}
}
ClickAccessibilityService的onAccessibilityEvent方法的调用堆栈如下所示。
java
java.lang.Throwable
at com.example.empty.ClickAccessibilityService.onAccessibilityEvent(ClickAccessibilityService.java:46)
at android.accessibilityservice.AccessibilityService$2.onAccessibilityEvent(AccessibilityService.java:2812)
at android.accessibilityservice.AccessibilityService$IAccessibilityServiceClientWrapper.lambda$onAccessibilityEvent$2(AccessibilityService.java:2998)
at android.accessibilityservice.AccessibilityService$IAccessibilityServiceClientWrapper.$r8$lambda$BK9U4O9uTvtOrxe1k6nU74w4nWg(Unknown Source:0)
at android.accessibilityservice.AccessibilityService$IAccessibilityServiceClientWrapper$$ExternalSyntheticLambda8.run(D8$$SyntheticClass:0)
at android.os.Handler.handleCallback(Handler.java:995)
at android.os.Handler.dispatchMessage(Handler.java:103)
at android.os.Looper.loopOnce(Looper.java:248)
at android.os.Looper.loop(Looper.java:338)
at android.app.ActivityThread.main(ActivityThread.java:9067)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:593)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:932)
初步定位到AccessibilityService类的内部类IAccessibilityServiceClientWrapper的onAccessibilityEvent方法。
java
//frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java
public abstract class AccessibilityService extends Service {
public static class IAccessibilityServiceClientWrapper extends
IAccessibilityServiceClient.Stub {
public void onAccessibilityEvent(AccessibilityEvent event, boolean serviceWantsEvent) {
mExecutor.execute(() -> {
if (event != null) {
// Send the event to AccessibilityCache via AccessibilityInteractionClient
AccessibilityInteractionClient.getInstance(mContext).onAccessibilityEvent(
event, mConnectionId);
if (serviceWantsEvent
&& (mConnectionId != AccessibilityInteractionClient.NO_ID)) {
// Send the event to AccessibilityService
mCallback.onAccessibilityEvent(event);
}
}
return;
});
}
}
}
在AOSP16源码中搜索所有调用onAccessibilityEvent方法,找到关键类AbstractAccessibilityServiceConnection 的notifyAccessibilityEventInternal方法,确认是该方法触发了IAccessibilityServiceClient的onAccessibilityEvent方法。
java
//frameworks/base/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServiceConnection.Stub
implements ServiceConnection, IBinder.DeathRecipient, KeyEventDispatcher.KeyEventFilter,
FingerprintGestureDispatcher.FingerprintGestureClient {
private void notifyAccessibilityEventInternal(
int eventType, AccessibilityEvent event, boolean clientWantsEvent) {IAccessibilityServiceClient client;
...代码省略...
client.onAccessibilityEvent(event, clientWantsEvent);
...代码省略...
}
public Handler mEventDispatchHandler;
public AbstractAccessibilityServiceConnection(Context context, ComponentName componentName,
AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler,
Object lock, AccessibilitySecurityPolicy securityPolicy, SystemSupport systemSupport,
AccessibilityTrace trace, WindowManagerInternal windowManagerInternal,
SystemActionPerformer systemActionPerfomer,
AccessibilityWindowManager a11yWindowManager) {
super(PermissionEnforcer.fromContext(context));
...代码省略...
mEventDispatchHandler =
new Handler(mainHandler.getLooper()) {
@Override
public void handleMessage(Message message) {
final int eventType = message.what;
AccessibilityEvent event = (AccessibilityEvent) message.obj;
boolean clientWantsEvent = message.arg1 != 0;
//调用notifyAccessibilityEventInternal方法
notifyAccessibilityEventInternal(eventType, event, clientWantsEvent);
}
};
...代码省略...
}
public void notifyAccessibilityEvent(AccessibilityEvent event) {
//调用clientWantsEventLocked过滤该事件知否需要发给对应的无障碍服务
final boolean clientWantsEvent = clientWantsEventLocked(event);
...代码省略...
AccessibilityEvent newEvent = AccessibilityEvent.obtain(event);
Message message;
if ((mNotificationTimeout > 0) && (eventType != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED)) {
// Allow at most one pending event
final AccessibilityEvent oldEvent = mPendingEvents.get(eventType);
mPendingEvents.put(eventType, newEvent);
if (oldEvent != null) {
mEventDispatchHandler.removeMessages(eventType);
oldEvent.recycle();
}
message = mEventDispatchHandler.obtainMessage(eventType);
} else {
// Send all messages, bypassing mPendingEvents
message = mEventDispatchHandler.obtainMessage(eventType, newEvent);
}
message.arg1 = clientWantsEvent ? 1 : 0;
//触发mEventDispatchHandler的handleMessage方法
mEventDispatchHandler.sendMessageDelayed(message, mNotificationTimeout);
...代码省略...
}
private boolean clientWantsEventLocked(AccessibilityEvent event) {
//检测服务状态
if (!canReceiveEventsLocked()) {
return false;
}
final boolean includeNotImportantViews = (mFetchFlags
& AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
//视图重要性检查
if ((event.getWindowId() != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID)
&& !event.isImportantForAccessibility()
&& !includeNotImportantViews) {
return false;
}
String packageName = (event.getPackageName() != null)
? event.getPackageName().toString() : null;
//关键点
//数据敏感性检查(隐私保护),如果事件标记为数据敏感(应用通过 setAccessibilityDataSensitive() 设置)
//且服务不是系统级的无障碍工具(如旁白/TalkBack),则过滤掉(防止普通无障碍服务读取敏感信息如密码输入框)
if (event.isAccessibilityDataSensitive() && (mFetchFlags & AccessibilityNodeInfo.FLAG_SERVICE_IS_ACCESSIBILITY_TOOL) == 0)
{
return false;
}
int eventType = event.getEventType();
//检查服务注册的事件类型是否匹配(如 TYPE_VIEW_CLICKED、TYPE_WINDOW_STATE_CHANGED 等)
if ((mEventTypes & eventType) != eventType) {
return false;
}
Set<String> packageNames = mPackageNames;
StringBuilder sb = new StringBuilder();
sb.append("packageName = ").append(packageName).append("; mPackageNames = ");
for (String name : mPackageNames) {
sb.append(",").append(name);
}
//包名过滤(最终检查);如果服务指定了要监听的包名列表,只有事件来源包名在列表中才返回 true;空列表表示监听所有应用
return (packageNames.isEmpty() || packageNames.contains(packageName));
}
}
经过验证发现,在Aosp16系统上,包名为com.android.permissioncontroller的权限弹窗打开的时候,clientWantsEventLocked方法的AccessibilityEvent参数的isAccessibilityDataSensitive方法返回值为true,就是这里导致无障碍服务无法收到该弹窗的回调通知。
java
public final class AccessibilityEvent extends AccessibilityRecord implements Parcelable {
@Override
public boolean isAccessibilityDataSensitive() {
return super.isAccessibilityDataSensitive();
}
}
public class AccessibilityRecord {
boolean isAccessibilityDataSensitive() {
return getBooleanProperty(PROPERTY_ACCESSIBILITY_DATA_SENSITIVE);
}
void setAccessibilityDataSensitive(boolean accessibilityDataSensitive) {
enforceNotSealed();
setBooleanProperty(PROPERTY_ACCESSIBILITY_DATA_SENSITIVE, accessibilityDataSensitive);
}
private void setBooleanProperty(int property, boolean value) {
if(property == PROPERTY_ACCESSIBILITY_DATA_SENSITIVE){
Slog.i("AccessibilityRecord", "LPAccess.setBooleanProperty value = "+value, new Throwable());
}
if (value) {
mBooleanProperties |= property;
} else {
mBooleanProperties &= ~property;
}
}
}
AccessibilityRecord方法日志打印堆栈如下所示。
java
AccessibilityRecord com.android.permissioncontroller I LPAccess.setBooleanProperty value = true
java.lang.Throwable
at android.view.accessibility.AccessibilityRecord.setBooleanProperty(AccessibilityRecord.java:843)
at android.view.accessibility.AccessibilityRecord.setSource(AccessibilityRecord.java:164)
at android.view.accessibility.AccessibilityRecord.setSource(AccessibilityRecord.java:138)
at android.view.View.onInitializeAccessibilityEventInternal(View.java:9300)
at android.view.View.onInitializeAccessibilityEvent(View.java:9287)
at android.view.View.sendAccessibilityEventUncheckedInternal(View.java:9068)
at android.view.View.sendAccessibilityEventUnchecked(View.java:9047)
at android.view.ViewRootImpl$SendWindowContentChangedAccessibilityEvent.run(ViewRootImpl.java:12456)
at android.view.ViewRootImpl$SendWindowContentChangedAccessibilityEvent.removeCallbacksAndRun(ViewRootImpl.java:12541)
at android.view.ViewRootImpl$SendWindowContentChangedAccessibilityEvent.runOrPost(ViewRootImpl.java:12533)
at android.view.ViewRootImpl.postSendWindowContentChangedCallback(ViewRootImpl.java:11067)
at android.view.ViewRootImpl.notifySubtreeAccessibilityStateChanged(ViewRootImpl.java:11270)
at android.view.View.notifySubtreeAccessibilityStateChangedIfNeeded(View.java:15997)
at android.view.ViewGroup.notifySubtreeAccessibilityStateChangedIfNeeded(ViewGroup.java:3903)
at android.view.ViewGroup.addViewInner(ViewGroup.java:5357)
at android.view.ViewGroup.addView(ViewGroup.java:5121)
at android.view.ViewGroup.addView(ViewGroup.java:5093)
at com.android.internal.policy.DecorView.updateColorViewInt(DecorView.java:1515)
at com.android.internal.policy.DecorView.updateColorViews(DecorView.java:1150)
at com.android.internal.policy.DecorView.onApplyWindowInsets(DecorView.java:1044)
at android.view.View.dispatchApplyWindowInsets(View.java:12774)
at android.view.ViewGroup.dispatchApplyWindowInsets(ViewGroup.java:7514)
at android.view.ViewRootImpl.dispatchApplyInsets(ViewRootImpl.java:3478)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3619)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:3076)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:10643)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1570)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1579)
at android.view.Choreographer.doCallbacks(Choreographer.java:1179)
at android.view.Choreographer.doFrame(Choreographer.java:1108)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1553)
at android.os.Handler.handleCallback(Handler.java:995)
at android.os.Handler.dispatchMessage(Handler.java:103)
at android.os.Looper.loopOnce(Looper.java:248)
at android.os.Looper.loop(Looper.java:338)
at android.app.ActivityThread.main(ActivityThread.java:9067)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:593)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:932)
可以发现权限管理弹窗是在View这个类中进行设置窗口的隐私性的,调用的是AccessibilityRecord的setSource方法。
java
public class AccessibilityRecord {
public void setSource(@Nullable View root, int virtualDescendantId) {
...代码省略...
//可以发现窗的敏感数据来源于Viiew的isAccessibilityDataSensitive方法。
setBooleanProperty(PROPERTY_ACCESSIBILITY_DATA_SENSITIVE,
root.isAccessibilityDataSensitive());
...代码省略...
}
}
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
public boolean isAccessibilityDataSensitive() {
if (mInferredAccessibilityDataSensitive == ACCESSIBILITY_DATA_SENSITIVE_AUTO) {
calculateAccessibilityDataSensitive();
}
return mInferredAccessibilityDataSensitive == ACCESSIBILITY_DATA_SENSITIVE_YES;
}
void calculateAccessibilityDataSensitive() {
// Use the explicit value if set.
if (mExplicitAccessibilityDataSensitive != ACCESSIBILITY_DATA_SENSITIVE_AUTO) {
mInferredAccessibilityDataSensitive = mExplicitAccessibilityDataSensitive;
} else if (getFilterTouchesWhenObscured()) {
//经过派发现权限申请弹窗主要是在这里设置了窗口为敏感数据窗口
// Views that set filterTouchesWhenObscured default to accessibilityDataSensitive.
mInferredAccessibilityDataSensitive = ACCESSIBILITY_DATA_SENSITIVE_YES;
} else if (mParent instanceof View && ((View) mParent).isAccessibilityDataSensitive()) {
// Descendants of accessibilityDataSensitive Views are also accessibilityDataSensitive.
mInferredAccessibilityDataSensitive = ACCESSIBILITY_DATA_SENSITIVE_YES;
} else {
// Otherwise, default to not accessibilityDataSensitive.
mInferredAccessibilityDataSensitive = ACCESSIBILITY_DATA_SENSITIVE_NO;
}
}
public boolean getFilterTouchesWhenObscured() {
// 窗口属性中只要包含FILTER_TOUCHES_WHEN_OBSCURED属性,则窗口为隐私权限窗口
return (mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0;
}
}
可以发现关键点在于View的getFilterTouchesWhenObscured方法,该方法主要是判断窗口是否有FILTER_TOUCHES_WHEN_OBSCURED属性,该属性一般是通过样式
xml
<item name="android:filterTouchesWhenObscured">true</item>
经过搜索发现PermissionController这个权限应用有大量配置该属性。
xml
packages/modules/Permission/PermissionController/res/values/themes.xml:121: <item name="android:filterTouchesWhenObscured">true</item>
packages/modules/Permission/PermissionController/res/values/themes.xml:125: <item name="android:filterTouchesWhenObscured">true</item>
packages/modules/Permission/PermissionController/res/values/themes.xml:129: <item name="android:filterTouchesWhenObscured">true</item>
packages/modules/Permission/PermissionController/res/values/themes.xml:133: <item name="android:filterTouchesWhenObscured">true</item>
packages/modules/Permission/PermissionController/res/values/themes.xml:138: <item name="android:filterTouchesWhenObscured">true</item>
packages/modules/Permission/PermissionController/res/values/themes.xml:143: <item name="android:filterTouchesWhenObscured">true</item>
packages/modules/Permission/PermissionController/res/values/themes.xml:147: <item name="android:filterTouchesWhenObscured">true</item>
packages/modules/Permission/PermissionController/res/values/themes.xml:151: <item name="android:filterTouchesWhenObscured">true</item>
packages/modules/Permission/PermissionController/res/layout-television/permissions_frame.xml:21: android:filterTouchesWhenObscured="true">
packages/modules/Permission/PermissionController/res/values-v33/themes.xml:20: <item name="android:filterTouchesWhenObscured">true</item>
这就是导致权限弹窗无法被无障碍服务识别的主要原因。