6

背景

SAW(系统警报窗口)权限可用于在其他应用程序之上绘制内容。

很久以前有人告诉我无障碍服务也可以做到这一点,但我从来没有找到任何教程、示例、文档甚至是可以做到这一点的应用程序......直到最近:

https://play.google.com/store/apps/details?id=com.github.ericytsang.screenfilter.app.android

事实上,这个应用程序似乎可以在任何地方绘制,而不是 SAW 许可。它甚至在设置应用程序和系统对话框的顶部绘制,而不允许 SAW 权限。

问题

可悲的是,由于可访问性是一种非常独特且很少使用的东西,就像我写的那样,我找不到这样的东西如何在其他应用程序之上绘图。

我发现了什么

我唯一知道的是这个应用程序以某种方式做到了,这就是它要求授予它时显示的内容:

在此处输入图像描述

但这还不够。我知道对于我使用无障碍服务所做的一些旧 POC,它显示相同,并且检查它,我看不出是什么触发了它。可以肯定的是,对于任何类型的可访问性服务,用户都会看到最少的东西,所以这无济于事。

问题

  1. AccessibilityService 如何利用其他应用程序?

  2. 它与 SAW 许可的工作方式相同吗?例如,你能处理它绘制的触摸事件吗?

  3. 使用它有什么限制,如果有的话?

4

2 回答 2

6

在从无障碍服务中添加视图时使用TYPE_ACCESSIBILITY_OVERLAY似乎可以解决问题。我做了一个快速测试,甚至在设置菜单中也显示了覆盖窗口。覆盖窗口也接收到触摸事件。这在没有清单中的许可的情况下也有效,也没有用户交互地设置“在其他应用程序上显示”权限。我使用目标 SDK 29 进行了测试。typeWindowManager.LayoutParamsSYSTEM_ALERT_WINDOW

抱歉,我无法回答您关于适用哪些具体限制的第三个问题。


编辑:通过查看 Google here的旧教程,这里有一个简短的示例:

GlobalActionBarService.java

public class GlobalActionBarService extends AccessibilityService {
    FrameLayout mLayout;

    @Override
    protected void onServiceConnected() {
        // Create an overlay and display the action bar
        WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
        mLayout = new FrameLayout(this);
        WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
        lp.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
        lp.format = PixelFormat.TRANSLUCENT;
        lp.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
        lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
        lp.gravity = Gravity.TOP;
        LayoutInflater inflater = LayoutInflater.from(this);
        inflater.inflate(R.layout.action_bar, mLayout);
        wm.addView(mLayout, lp);
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
    }

    @Override
    public void onInterrupt() {
    }
}

显现

<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
    package="com.lb.myapplication">

    <application
        android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true"
        android:theme="@style/Theme.MyApplication" tools:ignore="AllowBackup">
        <activity
            android:name=".MainActivity" android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".GlobalActionBarService" android:exported="false"
            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
            <intent-filter>
                <action android:name="android.accessibilityservice.AccessibilityService" />
            </intent-filter>
            <meta-data
                android:name="android.accessibilityservice" android:resource="@xml/global_action_bar_service" />
        </service>
    </application>

</manifest>

global_action_bar_service.xml

<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityFeedbackType="feedbackGeneric" android:accessibilityFlags="flagDefault"
    android:canPerformGestures="true" android:canRetrieveWindowContent="true" />

action_bar.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
    android:layout_height="wrap_content" android:orientation="horizontal">

    <Button
        android:id="@+id/power" android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:text="@string/power" />

    <Button
        android:id="@+id/volume_up" android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:text="@string/volume" />

    <Button
        android:id="@+id/scroll" android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:text="@string/scroll" />

    <Button
        android:id="@+id/swipe" android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:text="@string/swipe" />
</LinearLayout>
于 2021-04-18T16:28:08.847 回答
1

答案是:您必须使用 WindowManager 将视图放置到服务中的窗口中,以将其绘制在其他应用程序之上。

    val typeApplication =
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
            WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
        else
            WindowManager.LayoutParams.TYPE_PHONE

    val layoutParams = WindowManager.LayoutParams(
        windowWidth,
        ViewGroup.LayoutParams.WRAP_CONTENT,
        typeApplication,
        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
        PixelFormat.TRANSLUCENT
    )
    // inflater view with above layoutParams variable

并且应用程序授予了覆盖权限并确保在设备设置中启用了可访问性(设置-> 辅助功能-> 启用您的应用程序)。或者使用这个意图去它

Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)

. 您可以通过以下代码检查 Overlay 权限

  // check
  Settings.canDrawOverlays(applicationContext)
  // Use this intent to enable permision
  Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
         Uri.parse("package:$packageName"))

它与 SAW 许可的工作方式相同吗?例如,你能处理它绘制的触摸事件吗? 我以前从未使用过 SAW,所以我不确定这个问题。

希望能帮到你!

于 2021-04-21T01:57:43.093 回答