KitKat系统下后台拨打USSD并交互选选项的技术求助
针对你在Android KitKat系统中遇到的USSD后台调用、响应解析及选项交互问题,我整理了一套适配该版本的可行方案——由于KitKat没有提供直接获取USSD响应的公开API,我们需要通过辅助功能服务(AccessibilityService)+ 系统弹窗监听的组合方式来实现需求,具体步骤如下:
解决Android KitKat下USSD全流程交互问题
一、核心实现思路
- 通过系统Intent发起USSD号码呼叫
- 利用AccessibilityService监听系统弹出的USSD响应弹窗,提取响应内容
- 解析响应内容中的选项并展示给用户
- 根据用户选择,再次通过AccessibilityService模拟输入选项指令并提交
二、具体代码实现
1. 发起USSD呼叫(MainActivity.java)
首先实现调用USSD号码的方法,注意转义特殊字符#:
package io.github.hassannomani....; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 示例:拨打*XYZ# dialUSSD("*XYZ#"); } private void dialUSSD(String ussdCode) { try { // 转义USSD中的#号为%23,避免Intent解析错误 String encodedUssd = Uri.encode(ussdCode); Intent callIntent = new Intent(Intent.ACTION_CALL); callIntent.setData(Uri.parse("tel:" + encodedUssd)); startActivity(callIntent); } catch (SecurityException e) { Toast.makeText(this, "请授予CALL_PHONE权限", Toast.LENGTH_SHORT).show(); e.printStackTrace(); } } }
记得在AndroidManifest.xml中添加权限:
<uses-permission android:name="android.permission.CALL_PHONE" />
2. 监听并捕获USSD响应(AccessibilityService实现)
KitKat中USSD响应以系统Dialog形式弹出,我们需要创建AccessibilityService来监听并提取弹窗内容:
创建UssdAccessibilityService.java:
package io.github.hassannomani....; import android.accessibilityservice.AccessibilityService; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import java.util.List; public class UssdAccessibilityService extends AccessibilityService { private UssdCallback callback; // 定义回调接口,将响应传递给MainActivity public interface UssdCallback { void onUssdResponseReceived(String response); void onUssdInputReady(); } public void setCallback(UssdCallback callback) { this.callback = callback; } @Override public void onAccessibilityEvent(AccessibilityEvent event) { if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { AccessibilityNodeInfo rootNode = getRootInActiveWindow(); if (rootNode == null) return; // 识别USSD弹窗(可根据运营商弹窗特征调整判断逻辑) List<AccessibilityNodeInfo> dialogNodes = rootNode.findAccessibilityNodeInfosByText("USSD"); if (!dialogNodes.isEmpty()) { // 提取弹窗中的响应内容 String ussdResponse = dialogNodes.get(0).getParent().getText().toString(); if (callback != null) { callback.onUssdResponseReceived(ussdResponse); callback.onUssdInputReady(); } } } } @Override public void onInterrupt() {} // 模拟输入选项指令并点击确认 public void sendSelectedOption(String option) { AccessibilityNodeInfo rootNode = getRootInActiveWindow(); if (rootNode == null) return; // 查找输入框(部分ROM可能需要调整ViewId或查找逻辑) List<AccessibilityNodeInfo> inputNodes = rootNode.findAccessibilityNodeInfosByViewId("android:id/input"); if (!inputNodes.isEmpty()) { AccessibilityNodeInfo inputNode = inputNodes.get(0); Bundle inputArgs = new Bundle(); inputArgs.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, option); inputNode.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, inputArgs); } // 查找确认按钮 List<AccessibilityNodeInfo> confirmNodes = rootNode.findAccessibilityNodeInfosByText("确认"); if (!confirmNodes.isEmpty()) { confirmNodes.get(0).performAction(AccessibilityNodeInfo.ACTION_CLICK); } } }
在AndroidManifest.xml中注册该服务:
<service android:name=".UssdAccessibilityService" 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/ussd_accessibility_config" /> </service>
创建res/xml/ussd_accessibility_config.xml配置文件:
<?xml version="1.0" encoding="utf-8"?> <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:accessibilityEventTypes="typeWindowStateChanged" android:accessibilityFeedbackType="feedbackGeneric" android:accessibilityFlags="flagDefault" android:canRetrieveWindowContent="true" android:description="@string/ussd_accessibility_desc" />
注意:首次启动时需要引导用户前往系统设置开启该辅助功能,否则无法监听弹窗。
3. 解析响应并处理用户选择(MainActivity完善)
在MainActivity中实现回调,解析选项并展示给用户:
// 实现UssdCallback接口 public class MainActivity extends AppCompatActivity implements UssdAccessibilityService.UssdCallback { private UssdAccessibilityService ussdService; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化辅助服务回调 ussdService = new UssdAccessibilityService(); ussdService.setCallback(this); } @Override public void onUssdResponseReceived(String response) { // 解析响应中的选项(示例:响应格式为"A. 充值\nB. 查询\nC. 退出") List<String> options = new ArrayList<>(); String[] lines = response.split("\n"); for (String line : lines) { if (line.matches("[A-Z]\\. .+")) { options.add(line); } } // 弹窗展示选项给用户 new AlertDialog.Builder(this) .setTitle("请选择操作") .setItems(options.toArray(new String[0]), (dialog, which) -> { // 提取选项指令(比如选B就取字符"B") String selectedCmd = options.get(which).substring(0, 1); ussdService.sendSelectedOption(selectedCmd); }) .show(); } @Override public void onUssdInputReady() { // 可选:USSD输入框就绪后的回调 } }
三、常见问题排查
- 权限未授予:确保已申请
CALL_PHONE权限,且辅助功能已在系统设置中开启 - 弹窗识别失败:可调整
onAccessibilityEvent中的判断逻辑,比如根据运营商弹窗的标题或内容关键词识别 - 指令发送失败:不同ROM的输入框ViewId可能不同,可尝试通过遍历节点的方式查找输入框
内容的提问来源于stack exchange,提问作者Hassan Nomani
相关产品推荐
相关产品推荐

