feat: 被控端增加无障碍检查,flutter固定获取Android_Id

This commit is contained in:
2026-07-16 21:56:12 +08:00
parent 68a863c66a
commit 45ee9ef60c
13 changed files with 506 additions and 72 deletions

View File

@@ -35,6 +35,19 @@
android:exported="false"
android:foregroundServiceType="mediaProjection" />
<!-- 键盘/输入模拟无障碍服务(无需系统签名/root用于模拟全局按键与手势 -->
<service
android:name=".accessibility.KeyboardAccessibilityService"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
android:exported="false">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config" />
</service>
</application>
</manifest>

View File

@@ -0,0 +1,82 @@
package com.ttstd.controlled.accessibility;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.view.accessibility.AccessibilityManager;
import java.util.List;
public class AccessibilityServiceHelper {
private static final String TAG = "AccessibilityHelper";
/** 检查本应用在系统设置中是否已开启该无障碍服务。 */
public static boolean isAccessibilityEnabled(Context context) {
String enabled = Settings.Secure.getString(
context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (enabled == null || enabled.isEmpty()) {
return false;
}
String component = context.getPackageName() + "/" + KeyboardAccessibilityService.class.getName();
return enabled.contains(component);
}
/**
* 检查特定的无障碍服务是否已启用
*
* @param context 上下文对象
* @param serviceClass 你的无障碍服务类(例如 MyAccessibilityService.class
* @return true 已开启false 未开启
*/
public static boolean isAccessibilityServiceEnabled(Context context, Class<?> serviceClass) {
// 获取 AccessibilityManager 系统服务
AccessibilityManager am = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
if (am == null) {
return false;
}
// 获取所有已启用的无障碍服务列表
List<AccessibilityServiceInfo> enabledServices = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
// 构建你服务的完整组件标识符
String expectedServiceId = new ComponentName(context, serviceClass).flattenToShortString();
Log.e(TAG, "isAccessibilityServiceEnabled: expectedServiceId = " + expectedServiceId);
for (AccessibilityServiceInfo serviceInfo : enabledServices) {
Log.e(TAG, "isAccessibilityServiceEnabled: serviceInfo.getId() = " + serviceInfo.getId());
if (expectedServiceId.equals(serviceInfo.getId())) {
return true;
}
}
return false;
}
/**
* 跳转到系统的无障碍服务设置页面
*
* @param context 上下文对象
*/
public static void jumpToAccessibilitySettings(Context context) {
try {
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // 通常从应用上下文启动时需要此标志
String str = context.getPackageName() + "/" + KeyboardAccessibilityService.class.getName();
Bundle bundle = new Bundle();
bundle.putString(":settings:fragment_args_key", str);
intent.putExtra(":settings:fragment_args_key", str);
intent.putExtra(":settings:show_fragment_args", bundle);
context.startActivity(intent);
} catch (Exception e) {
// 处理跳转失败的情况,例如某些设备可能没有标准的设置界面
e.printStackTrace();
// 可以尝试跳转到系统通用设置作为备选方案
Intent fallbackIntent = new Intent(Settings.ACTION_SETTINGS);
fallbackIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(fallbackIntent);
}
}
}

View File

@@ -0,0 +1,112 @@
package com.ttstd.controlled.accessibility;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.Context;
import android.os.Build;
import android.provider.Settings;
import android.util.Log;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityEvent;
/**
* 键盘无障碍服务。
* <p>
* 用于在应用没有系统签名、也没有 root 权限时,通过无障碍能力模拟系统级全局按键。
* 具体能力:
* - {@link #pressKey(int)}:将部分 KeyEvent 按键码映射为 AccessibilityService 的全局动作
* (返回 / 主页 / 多任务 / 通知 / 音量等),通过 {@link #performGlobalAction(int)} 触发。
* - {@link #dispatchGesture(GestureDescription, ...)}(继承自父类):供
* {@code AccessibilityInputUtils} 模拟触摸/滑动/长按等手势。
* <p>
* 注意:无障碍服务无法直接模拟任意字符按键(如字母、数字),只能触发上述全局动作。
*/
public class KeyboardAccessibilityService extends AccessibilityService {
private static final String TAG = "KeyboardA11yService";
private static KeyboardAccessibilityService sInstance;
/** 获取当前正在运行的实例(未启用时返回 null。 */
public static KeyboardAccessibilityService getInstance() {
return sInstance;
}
/** 无障碍服务是否已经启用并运行。 */
public static boolean isRunning() {
return sInstance != null;
}
@Override
protected void onServiceConnected() {
super.onServiceConnected();
sInstance = this;
// 也可以通过 res/xml/accessibility_service_config.xml 配置,这里做一次兜底设置。
AccessibilityServiceInfo info = new AccessibilityServiceInfo();
info.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
| AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
info.notificationTimeout = 100;
setServiceInfo(info);
Log.i(TAG, "Accessibility service connected");
}
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
// 本服务仅用于模拟输入,无需处理具体无障碍事件。
}
@Override
public void onInterrupt() {
}
@Override
public void onDestroy() {
super.onDestroy();
if (sInstance == this) {
sInstance = null;
}
Log.i(TAG, "Accessibility service destroyed");
}
/**
* 通过无障碍服务的全局动作模拟按键。
* 仅支持系统级全局按键,对于无障碍无法直接模拟的按键码返回 false。
*
* @param keyCode Android {@link KeyEvent} 按键码
* @return 是否成功模拟true 表示已触发对应全局动作)
*/
public boolean pressKey(int keyCode) {
int globalAction = mapKeyToGlobalAction(keyCode);
if (globalAction >= 0) {
return performGlobalAction(globalAction);
}
Log.w(TAG, "Unsupported key code for accessibility: " + keyCode);
return false;
}
private static int mapKeyToGlobalAction(int keyCode) {
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
return GLOBAL_ACTION_BACK;
case KeyEvent.KEYCODE_HOME:
return GLOBAL_ACTION_HOME;
case KeyEvent.KEYCODE_APP_SWITCH:
return GLOBAL_ACTION_RECENTS;
case KeyEvent.KEYCODE_NOTIFICATION:
return GLOBAL_ACTION_NOTIFICATIONS;
case KeyEvent.KEYCODE_VOLUME_UP:
// GLOBAL_ACTION_VOLUME_UP = 11 (API 29)
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ? 11 : -1;
case KeyEvent.KEYCODE_VOLUME_DOWN:
// GLOBAL_ACTION_VOLUME_DOWN = 12 (API 29)
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ? 12 : -1;
case KeyEvent.KEYCODE_VOLUME_MUTE:
// GLOBAL_ACTION_VOLUME_MUTE = 13 (API 29)
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ? 13 : -1;
default:
return -1;
}
}
}

View File

@@ -4,11 +4,13 @@ import android.Manifest;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.app.AlertDialog;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.media.projection.MediaProjectionManager;
import android.os.Build;
import android.os.IBinder;
import android.provider.Settings;
import android.util.Log;
import android.widget.Toast;
@@ -18,9 +20,11 @@ import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.ttstd.controlled.R;
import com.ttstd.controlled.accessibility.AccessibilityServiceHelper;
import com.ttstd.controlled.base.BaseMvvmActivity;
import com.ttstd.controlled.databinding.ActivityMainBinding;
import com.ttstd.controlled.service.ScreenCaptureService;
import com.ttstd.controlled.accessibility.KeyboardAccessibilityService;
import com.ttstd.controlled.utils.DeviceUtils;
import com.ttstd.controlled.utils.SignatureUtils;
import com.ttstd.controlled.webrtc.WebRtcClient;
@@ -39,6 +43,10 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
private boolean isBound = false;
private boolean isLocalViewInitialized = false;
/** 是否为特权应用(系统签名或无 root 权限),缓存后避免重复在主线程执行 su 检测。 */
private boolean isPrivileged = false;
private AlertDialog accessibilityDialog;
private final ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
@@ -67,7 +75,7 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
// 默认服务器地址
binding.etServerUrl.setText("ws://175.178.213.60:8088/ws/signal");
// 生成设备ID
binding.etDeviceId.setText(DeviceUtils.getSystemSerialNumber());
// binding.etDeviceId.setText(DeviceUtils.getSystemSerialNumber());
binding.btnStart.setOnClickListener(v -> startScreenSharing());
binding.btnStop.setOnClickListener(v -> stopScreenSharing());
@@ -84,9 +92,45 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
Log.e(TAG, "initData: isSystemUid = " + SignatureUtils.isSystemUid());
Log.e(TAG, "initData: isDeviceRooted = " + SignatureUtils.isDeviceRooted());
Log.e(TAG, "initData: isAppHasRootPermission = " + SignatureUtils.isAppHasRootPermission());
// 特权应用判定:系统签名(含共享系统 UID / 系统 UID或已获取 Root 权限。
// 特权应用无需依赖无障碍服务即可模拟输入,缓存结果避免每次 onResume 在主线程检测 su。
boolean systemSigned = SignatureUtils.isSystemSignature(this)
|| SignatureUtils.isSharedSystemUid(this)
|| SignatureUtils.isSystemUid();
boolean rooted = SignatureUtils.isDeviceRooted() && SignatureUtils.isAppHasRootPermission();
isPrivileged = systemSigned || rooted;
Log.e(TAG, "initData: isPrivileged = " + isPrivileged);
}
@Override
protected void onResume() {
super.onResume();
}
/** 弹窗提示开启无障碍服务;同一运行周期内仅提示一次。 */
private void showAccessibilityPermissionDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.accessibility_dialog_title)
.setMessage(R.string.accessibility_dialog_message)
.setCancelable(false)
.setPositiveButton(R.string.accessibility_dialog_go, (dialog, which) -> {
AccessibilityServiceHelper.jumpToAccessibilitySettings(this);
})
.setNegativeButton(R.string.accessibility_dialog_cancel, (dialog, which) -> {
dialog.dismiss();
});
accessibilityDialog = builder.create();
accessibilityDialog.show();
}
private void startScreenSharing() {
// 非特权应用且未开启无障碍服务时,弹窗提示用户前往系统设置开启。
if (!isPrivileged && !AccessibilityServiceHelper.isAccessibilityEnabled(this)) {
showAccessibilityPermissionDialog();
return;
}
// 检查通知权限 (Android 13+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
@@ -185,6 +229,10 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
@Override
protected void onDestroy() {
super.onDestroy();
if (accessibilityDialog != null && accessibilityDialog.isShowing()) {
accessibilityDialog.dismiss();
accessibilityDialog = null;
}
if (isBound) {
if (screenCaptureService != null) {
screenCaptureService.setStateListener(null);

View File

@@ -0,0 +1,106 @@
package com.ttstd.controlled.input;
import android.accessibilityservice.GestureDescription;
import android.graphics.Path;
import android.os.SystemClock;
import android.util.Log;
import android.view.MotionEvent;
import com.ttstd.controlled.accessibility.KeyboardAccessibilityService;
/**
* 基于无障碍服务AccessibilityService的输入执行器。
* <p>
* 适用于没有系统签名、也没有 root 权限的普通应用:
* - 触摸 / 滑动 / 长按 / 连续手势:通过 AccessibilityService.dispatchGesture 模拟;
* - 按键:通过 KeyboardAccessibilityService.pressKey 模拟系统级全局按键。
* <p>
* 注意:无障碍无法模拟任意字符按键,仅支持返回 / 主页 / 多任务 / 通知 / 音量等全局动作。
*/
public class AccessibilityInputUtils implements InputExecutor {
private static final String TAG = "AccessibilityInputUtils";
// 连续手势MOTION_EVENT的累积路径
private Path motionPath;
private long motionStartTime;
private KeyboardAccessibilityService getService() {
return KeyboardAccessibilityService.getInstance();
}
@Override
public void injectTap(int x, int y) {
Path path = new Path();
path.moveTo(x, y);
// 同一点、短时长即模拟“按下-抬起”的点击
dispatch(path, 1, 50);
}
@Override
public void injectLongPress(int x, int y) {
Path path = new Path();
path.moveTo(x, y);
dispatch(path, 1, 1000);
}
@Override
public void injectSwipe(int x1, int y1, int x2, int y2, long duration) {
Path path = new Path();
path.moveTo(x1, y1);
path.lineTo(x2, y2);
dispatch(path, 0, Math.max(50, duration));
}
@Override
public void injectKeyEvent(int keyCode) {
KeyboardAccessibilityService svc = getService();
if (svc == null) {
Log.w(TAG, "Accessibility service not running, cannot inject key");
return;
}
boolean handled = svc.pressKey(keyCode);
if (!handled) {
Log.w(TAG, "Key code " + keyCode + " is not supported via accessibility");
}
}
@Override
public void injectMotionEvent(int action, int x, int y) {
switch (action) {
case MotionEvent.ACTION_DOWN:
motionPath = new Path();
motionPath.moveTo(x, y);
motionStartTime = SystemClock.uptimeMillis();
break;
case MotionEvent.ACTION_MOVE:
if (motionPath != null) {
motionPath.lineTo(x, y);
}
break;
case MotionEvent.ACTION_UP:
if (motionPath != null) {
long duration = Math.max(1, SystemClock.uptimeMillis() - motionStartTime);
dispatch(motionPath, 0, duration);
motionPath = null;
}
break;
default:
Log.w(TAG, "Unknown motion action: " + action);
}
}
private void dispatch(Path path, long startOffset, long duration) {
KeyboardAccessibilityService svc = getService();
if (svc == null) {
Log.w(TAG, "Accessibility service not running, cannot dispatch gesture");
return;
}
GestureDescription.Builder builder = new GestureDescription.Builder();
builder.addStroke(new GestureDescription.StrokeDescription(path, startOffset, duration));
boolean result = svc.dispatchGesture(builder.build(), null, null);
if (!result) {
Log.w(TAG, "dispatchGesture failed");
}
}
}

View File

@@ -19,10 +19,20 @@ public class InputCommandHandler {
private final InputExecutor inputExecutor;
public InputCommandHandler(int screenWidth, int screenHeight) {
this(screenWidth, screenHeight, new SystemInputUtils());
}
/**
* 允许调用方根据权限/环境选择具体的输入执行器。
*
* @param screenWidth 真实屏幕宽度(用于坐标映射)
* @param screenHeight 真实屏幕高度(用于坐标映射)
* @param inputExecutor 输入执行器(系统注入 / Shell / 无障碍 等)
*/
public InputCommandHandler(int screenWidth, int screenHeight, InputExecutor inputExecutor) {
this.screenWidth = screenWidth;
this.screenHeight = screenHeight;
// 使用系统签名权限下的 SystemInputUtils 进行事件注入
this.inputExecutor = new SystemInputUtils();
this.inputExecutor = inputExecutor;
}
public void handleCommand(byte[] data) {

View File

@@ -28,7 +28,13 @@ import androidx.core.app.NotificationCompat;
import com.ttstd.controlled.R;
import com.ttstd.controlled.activity.connection.ConnectionRequestActivity;
import com.ttstd.controlled.activity.main.MainActivity;
import com.ttstd.controlled.accessibility.KeyboardAccessibilityService;
import com.ttstd.controlled.input.AccessibilityInputUtils;
import com.ttstd.controlled.input.InputCommandHandler;
import com.ttstd.controlled.input.InputExecutor;
import com.ttstd.controlled.input.ShellInputUtils;
import com.ttstd.controlled.input.SystemInputUtils;
import com.ttstd.controlled.utils.SignatureUtils;
import com.ttstd.controlled.signaling.SignalMessage;
import com.ttstd.controlled.signaling.WebSocketClient;
import com.ttstd.controlled.webrtc.WebRtcClient;
@@ -153,13 +159,32 @@ public class ScreenCaptureService extends Service {
Log.i(TAG, "Capture resolution: " + captureWidth + "x" + captureHeight + " @ " + fps + "fps");
// 输入处理器必须使用真实分辨率进行坐标映射
inputHandler = new InputCommandHandler(realWidth, realHeight);
inputHandler = new InputCommandHandler(realWidth, realHeight, createInputExecutor());
// 开启屏幕捕获,使用计算出的 capture 分辨率
startScreenCapture(resultCode, resultData, serverUrl, deviceId, captureWidth, captureHeight, fps);
return START_NOT_STICKY;
}
/**
* 根据当前应用权限/环境选择最合适的输入执行器:
* 1. 系统签名应用优先使用隐藏 API 注入(支持精确触摸与任意按键);
* 2. 已开启无障碍服务时使用 AccessibilityInputUtils无需 root/系统签名);
* 3. 兜底使用 root shell 方式。
*/
private InputExecutor createInputExecutor() {
if (SignatureUtils.isSystemSignature(this) || SignatureUtils.isSharedSystemUid(this)) {
Log.i(TAG, "Input executor: SystemInputUtils (system signature)");
return new SystemInputUtils();
}
if (KeyboardAccessibilityService.getInstance() != null) {
Log.i(TAG, "Input executor: AccessibilityInputUtils (accessibility enabled)");
return new AccessibilityInputUtils();
}
Log.i(TAG, "Input executor: ShellInputUtils (fallback)");
return new ShellInputUtils();
}
private final IBinder binder = new LocalBinder();
public class LocalBinder extends Binder {

View File

@@ -4,78 +4,84 @@
xmlns:tools="http://schemas.android.com/tools"
tools:context=".activity.main.MainActivity">
<LinearLayout
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp">
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:text="WebRTC 被控端"
android:textSize="24sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="信令服务器地址:"
android:textSize="14sp" />
<EditText
android:id="@+id/et_server_url"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:hint="ws://175.178.213.60:8088/ws/signal"
android:inputType="textUri" />
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="设备ID:"
android:textSize="14sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:text="WebRTC 被控端"
android:textSize="24sp"
android:textStyle="bold" />
<EditText
android:id="@+id/et_device_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:hint="设备ID"
android:inputType="text"
android:text="981964879" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="信令服务器地址:"
android:textSize="14sp" />
<TextView
android:id="@+id/tv_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:text="状态: 已停止"
android:textSize="16sp"
android:textStyle="bold" />
<EditText
android:id="@+id/et_server_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:hint="ws://175.178.213.60:8088/ws/signal"
android:inputType="textUri" />
<Button
android:id="@+id/btn_start"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="开始屏幕共享" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="设备ID:"
android:textSize="14sp" />
<Button
android:id="@+id/btn_stop"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:enabled="false"
android:text="停止屏幕共享" />
<EditText
android:id="@+id/et_device_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:hint="设备ID"
android:inputType="text"
android:text="981964879" />
<org.webrtc.SurfaceViewRenderer
android:id="@+id/local_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:id="@+id/tv_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:text="状态: 已停止"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
<Button
android:id="@+id/btn_start"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="开始屏幕共享" />
<Button
android:id="@+id/btn_stop"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:enabled="false"
android:text="停止屏幕共享" />
<org.webrtc.SurfaceViewRenderer
android:id="@+id/local_view"
android:layout_width="match_parent"
android:layout_height="500dp"
android:layout_weight="1" />
</LinearLayout>
</ScrollView>
</layout>

View File

@@ -7,4 +7,10 @@
<string name="connection_reject">拒绝</string>
<string name="signal_disconnected_message">与信令服务器断开,屏幕共享已停止</string>
<string name="controller_disconnected_message">控制端(%s已断开远程控制</string>
<string name="accessibility_service_description">用于远程控制时模拟键盘与触摸输入。开启后,控制端可通过本服务向本机发送返回、主页、音量等系统按键及触摸手势。</string>
<string name="accessibility_hint">未使用系统签名:如需在本机模拟键盘/触摸,请在系统设置中开启本应用的无障碍服务。</string>
<string name="accessibility_dialog_title">需要开启无障碍服务</string>
<string name="accessibility_dialog_message">当前应用未使用系统签名,也未获取 Root 权限。如需在远程控制时模拟键盘与触摸输入,请前往系统设置开启本应用的无障碍服务。</string>
<string name="accessibility_dialog_go">去开启</string>
<string name="accessibility_dialog_cancel">取消</string>
</resources>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/accessibility_service_description"
android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged"
android:accessibilityFeedbackType="feedbackGeneric"
android:notificationTimeout="100"
android:canPerformGestures="true"
android:canRetrieveWindowContent="false" />