diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8a92b45..bf376cf 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -195,6 +195,9 @@ + diff --git a/app/src/main/java/com/ttstd/dialer/service/MyVoiceInteractionService.java b/app/src/main/java/com/ttstd/dialer/service/MyVoiceInteractionService.java index b467813..e9930e9 100644 --- a/app/src/main/java/com/ttstd/dialer/service/MyVoiceInteractionService.java +++ b/app/src/main/java/com/ttstd/dialer/service/MyVoiceInteractionService.java @@ -1,15 +1,61 @@ package com.ttstd.dialer.service; +import android.content.ComponentName; +import android.content.Intent; +import android.os.Bundle; import android.service.voice.VoiceInteractionService; +import android.service.voice.VoiceInteractionSession; import com.ttstd.dialer.utils.Logger; +/** + * 语音交互服务类,作为语音助手的入口。 + */ public class MyVoiceInteractionService extends VoiceInteractionService { private static final String TAG = "MyVoiceInteractionService"; + /** + * 自定义 Action,用于从外部启动语音交互会话 + */ + public static final String ACTION_SHOW_SESSION = "com.ttstd.dialer.ACTION_SHOW_SESSION"; + @Override public void onCreate() { super.onCreate(); Logger.d(TAG, "onCreate"); } + + @Override + public void onReady() { + super.onReady(); + Logger.d(TAG, "onReady"); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Logger.d(TAG, "onStartCommand: " + intent); + if (intent != null) { + String action = intent.getAction(); + if (ACTION_SHOW_SESSION.equals(action)) { + // 确保服务是当前活跃的语音交互服务 + if (isActiveService(this, new ComponentName(this, MyVoiceInteractionService.class))) { + Logger.d(TAG, "Showing session with assist and screenshot flags"); + // 核心:调用 showSession 并请求辅助数据(AssistStructure)和截图 + Bundle args = new Bundle(); + showSession(args, + VoiceInteractionSession.SHOW_WITH_ASSIST | + VoiceInteractionSession.SHOW_WITH_SCREENSHOT); + } else { + Logger.w(TAG, "Service is not the active voice interaction service"); + } + } + } + return START_STICKY; + } + + @Override + public void onShutdown() { + super.onShutdown(); + Logger.d(TAG, "onShutdown"); + } } diff --git a/app/src/main/java/com/ttstd/dialer/service/MyVoiceInteractionSession.java b/app/src/main/java/com/ttstd/dialer/service/MyVoiceInteractionSession.java index 841f85b..27a06d2 100644 --- a/app/src/main/java/com/ttstd/dialer/service/MyVoiceInteractionSession.java +++ b/app/src/main/java/com/ttstd/dialer/service/MyVoiceInteractionSession.java @@ -3,10 +3,15 @@ package com.ttstd.dialer.service; import android.app.assist.AssistContent; import android.app.assist.AssistStructure; import android.content.Context; +import android.graphics.Bitmap; +import android.os.Build; import android.os.Bundle; import android.service.voice.VoiceInteractionSession; import android.view.View; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.ttstd.dialer.utils.Logger; public class MyVoiceInteractionSession extends VoiceInteractionSession { @@ -16,24 +21,69 @@ public class MyVoiceInteractionSession extends VoiceInteractionSession { super(context); } + @Override + public void onShow(Bundle args, int showFlags) { + super.onShow(args, showFlags); + Logger.d(TAG, "onShow: flags=" + showFlags); + } + + @Override + public void onHide() { + super.onHide(); + Logger.d(TAG, "onHide"); + } + @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); + Logger.d(TAG, "onHandleAssist: data=" + data); + processAssistStructure(structure); + } + + // Android 12 (API 31) 及以上建议使用此方法 + @Override + public void onHandleAssist(@NonNull AssistState state) { + super.onHandleAssist(state); + Logger.d(TAG, "onHandleAssist(AssistState)"); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + processAssistStructure(state.getAssistStructure()); + } + } + + @Override + public void onHandleScreenshot(@Nullable Bitmap screenshot) { + super.onHandleScreenshot(screenshot); + Logger.d(TAG, "onHandleScreenshot: " + (screenshot != null ? "has screenshot" : "null")); + } + + private void processAssistStructure(AssistStructure structure) { + if (structure == null) { + Logger.w(TAG, "AssistStructure is null"); + return; + } + + int windowCount = structure.getWindowNodeCount(); + Logger.d(TAG, "Window count: " + windowCount); + for (int i = 0; i < windowCount; i++) { + AssistStructure.WindowNode windowNode = structure.getWindowNodeAt(i); if (windowNode != null) { - traverseNode(windowNode.getRootViewNode(), ""); + Logger.d(TAG, "Window[" + i + "]: title=" + windowNode.getTitle()); + traverseNode(windowNode.getRootViewNode(), " "); } } } private void traverseNode(AssistStructure.ViewNode node, String prefix) { if (node == null) return; + + CharSequence text = node.getText(); + String id = node.getIdEntry(); + String className = node.getClassName(); - Logger.d(TAG, prefix + "View: text=" + node.getText() + ", id=" + node.getIdEntry()); - + if (text != null || id != null) { + Logger.d(TAG, prefix + "View: class=" + className + ", text=" + text + ", id=" + id); + } + for (int i = 0; i < node.getChildCount(); i++) { traverseNode(node.getChildAt(i), prefix + " "); } @@ -41,7 +91,7 @@ public class MyVoiceInteractionSession extends VoiceInteractionSession { @Override public View onCreateContentView() { - // 可以返回一个自定义布局,用于在屏幕上显示助手界面 - return super.onCreateContentView(); + // 返回一个空的 View,以确保 Session 能够正确显示 + return new View(getContext()); } } diff --git a/app/src/main/res/xml/my_recognition_service.xml b/app/src/main/res/xml/my_recognition_service.xml new file mode 100644 index 0000000..e5891d6 --- /dev/null +++ b/app/src/main/res/xml/my_recognition_service.xml @@ -0,0 +1,3 @@ + +