Android 16系统源码_无障碍辅助(三)权限弹窗无法被无障碍服务识别

前言

无障碍服务默认情况下可以识别系统中的各种窗口内容,并允许可以实现按钮点击;不过安卓系统权限弹窗因为隐私关系,默认是无法被无障碍服务识别和点击的,本篇文章具体来分析一下为什么权限弹窗无法被无障碍服务识别。

窗口变化触发无障碍服务方法回调

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>

这就是导致权限弹窗无法被无障碍服务识别的主要原因。

相关推荐
zhangphil1 小时前
Android图形系统Graphics来源、内存占用量统计、为什么很大,如何优化
android
黄林晴1 小时前
Android Show I/O 2026:开发者该关注这几件事
android
Kapaseker1 小时前
最简单的 Compose 动画 — animateDpAsState
android·kotlin
问心无愧05131 小时前
ctf show web 入门46
android·前端·笔记
凛_Lin~~2 小时前
lifecycle源码解析 (版本2.5.1)
android·java·安卓·lifecycle
唐诺2 小时前
Android 与 iOS 核心差异
android·ios
UXbot2 小时前
Vibecoding 工具如何一次性生成 Web + iOS + Android 三端 APP?功能架构深度解读
android·前端·ui·ios·交互·软件构建·ai编程
鹏晨互联2 小时前
Jetpack Compose vs XML:fillMaxSize、fillMaxHeight、fillMaxWidth 全面对比
android·xml