feat(service): 添加内容捕获与语音交互服务

This commit is contained in:
2026-07-27 08:51:26 +08:00
parent 6e37800148
commit 8783f95307
7 changed files with 193 additions and 0 deletions

34
README.md Normal file
View File

@@ -0,0 +1,34 @@
启用 Content Capture:
Shell Script
settings put secure content_capture_service com.ttstd.dialer/com.ttstd.dialer.service.MyContentCaptureService
启用 系统助手 (Assist):
Shell Script
settings put secure assistant com.ttstd.dialer/com.ttstd.dialer.service.MyVoiceInteractionService
手动触发 Assist 采集: 在你的 Activity 或 Service 中,可以通过 showAssist(new Bundle()) 主动触发 onHandleAssist 回调,从而瞬间抓取屏幕信息。
对于拥有系统签名的应用,可以通过 adb 或在代码中通过 settings 命令静默开启无障碍服务。
# 1. 使用 ADB 命令手动开启
在电脑终端输入以下两条命令:
## 1. 将服务加入已启用列表
adb shell settings put secure enabled_accessibility_services com.ttstd.dialer/com.ttstd.dialer.accessibility.DialerAccessibilityService
## 2. 开启无障碍服务总开关
adb shell settings put secure accessibility_enabled 1
# 2. 在代码中自动开启 (利用系统签名)
既然你的应用有系统签名,你可以直接通过你现有的 CmdUtil 在程序启动时自动激活自己,无需用户操作:
Java
public void autoEnableAccessibility(Context context) {
String serviceName = context.getPackageName() + "/com.ttstd.dialer.accessibility.DialerAccessibilityService";
// 1. 设置启用服务(如果已有其他服务,建议先获取当前值再追加,这里演示直接覆盖)
CmdUtil.execute("settings put secure enabled_accessibility_services " + serviceName);
// 2. 开启总开关
CmdUtil.execute("settings put secure accessibility_enabled 1");
Logger.d("Accessibility", "无障碍服务已尝试静默开启");
}

View File

@@ -31,6 +31,8 @@
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" /> <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.BIND_CONTENT_CAPTURE_SERVICE" />
<uses-permission android:name="android.permission.BIND_VOICE_INTERACTION" />
<!--baidumap start--> <!--baidumap start-->
@@ -156,6 +158,35 @@
android:resource="@xml/accessibility_service_config" /> android:resource="@xml/accessibility_service_config" />
</service> </service>
<!-- Content Capture Service -->
<service
android:name=".service.MyContentCaptureService"
android:exported="true"
android:permission="android.permission.BIND_CONTENT_CAPTURE_SERVICE">
<intent-filter>
<action android:name="android.service.contentcapture.ContentCaptureService" />
</intent-filter>
</service>
<!-- Assist API / Voice Interaction Service -->
<service
android:name=".service.MyVoiceInteractionService"
android:exported="true"
android:label="拨号助手辅助"
android:permission="android.permission.BIND_VOICE_INTERACTION">
<meta-data
android:name="android.voice_interaction"
android:resource="@xml/voice_interaction_service" />
<intent-filter>
<action android:name="android.service.voice.VoiceInteractionService" />
</intent-filter>
</service>
<service
android:name=".service.MyVoiceInteractionSessionService"
android:exported="true"
android:permission="android.permission.BIND_VOICE_INTERACTION" />
<!-- <receiver--> <!-- <receiver-->
<!-- android:name=".receiver.AppChangedReceiver"--> <!-- android:name=".receiver.AppChangedReceiver"-->
<!-- android:enabled="true"--> <!-- android:enabled="true"-->

View File

@@ -0,0 +1,42 @@
package com.ttstd.dialer.service;
import android.os.Build;
import android.service.contentcapture.ContentCaptureService;
import android.view.contentcapture.ContentCaptureEvent;
import android.view.contentcapture.ContentCaptureSessionId;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import com.ttstd.dialer.utils.Logger;
@RequiresApi(api = Build.VERSION_CODES.Q)
public class MyContentCaptureService extends ContentCaptureService {
private static final String TAG = "MyContentCaptureService";
@Override
public void onConnected() {
super.onConnected();
Logger.d(TAG, "onConnected");
}
@Override
public void onDisconnected() {
super.onDisconnected();
Logger.d(TAG, "onDisconnected");
}
@Override
public void onContentCaptureEvent(@NonNull ContentCaptureSessionId sessionId, @NonNull ContentCaptureEvent event) {
// 这是核心方法,所有的界面变化都会在这里回调
Logger.v(TAG, "onContentCaptureEvent: type=" + event.getType());
if (event.getType() == ContentCaptureEvent.TYPE_VIEW_APPEARED) {
if (event.getViewNode() != null) {
CharSequence text = event.getViewNode().getText();
String id = event.getViewNode().getIdEntry();
Logger.d(TAG, "View Appeared: text=" + text + ", id=" + id);
}
}
}
}

View File

@@ -0,0 +1,15 @@
package com.ttstd.dialer.service;
import android.service.voice.VoiceInteractionService;
import com.ttstd.dialer.utils.Logger;
public class MyVoiceInteractionService extends VoiceInteractionService {
private static final String TAG = "MyVoiceInteractionService";
@Override
public void onCreate() {
super.onCreate();
Logger.d(TAG, "onCreate");
}
}

View File

@@ -0,0 +1,47 @@
package com.ttstd.dialer.service;
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
import android.content.Context;
import android.os.Bundle;
import android.service.voice.VoiceInteractionSession;
import android.view.View;
import com.ttstd.dialer.utils.Logger;
public class MyVoiceInteractionSession extends VoiceInteractionSession {
private static final String TAG = "MyVoiceInteractionSession";
public MyVoiceInteractionSession(Context context) {
super(context);
}
@Override
public void onHandleAssist(Bundle data, AssistStructure structure, AssistContent content) {
super.onHandleAssist(data, structure, content);
// 这是核心方法structure 包含了整个屏幕的 View 树
Logger.d(TAG, "onHandleAssist");
if (structure != null) {
AssistStructure.WindowNode windowNode = structure.getWindowNodeAt(0);
if (windowNode != null) {
traverseNode(windowNode.getRootViewNode(), "");
}
}
}
private void traverseNode(AssistStructure.ViewNode node, String prefix) {
if (node == null) return;
Logger.d(TAG, prefix + "View: text=" + node.getText() + ", id=" + node.getIdEntry());
for (int i = 0; i < node.getChildCount(); i++) {
traverseNode(node.getChildAt(i), prefix + " ");
}
}
@Override
public View onCreateContentView() {
// 可以返回一个自定义布局,用于在屏幕上显示助手界面
return super.onCreateContentView();
}
}

View File

@@ -0,0 +1,17 @@
package com.ttstd.dialer.service;
import android.os.Bundle;
import android.service.voice.VoiceInteractionSession;
import android.service.voice.VoiceInteractionSessionService;
import com.ttstd.dialer.utils.Logger;
public class MyVoiceInteractionSessionService extends VoiceInteractionSessionService {
private static final String TAG = "MyVoiceInteractionSessionService";
@Override
public VoiceInteractionSession onNewSession(Bundle args) {
Logger.d(TAG, "onNewSession");
return new MyVoiceInteractionSession(this);
}
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<voice-interaction-service xmlns:android="http://schemas.android.com/apk/res/android"
android:sessionService="com.ttstd.dialer.service.MyVoiceInteractionSessionService"
android:recognitionService="com.ttstd.dialer.service.MyVoiceInteractionService"
android:supportsAssist="true"
android:supportsLaunchFromKeyguard="true"
android:supportsLocalInteraction="true" />