You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

KitKat系统下后台拨打USSD并交互选选项的技术求助

针对你在Android KitKat系统中遇到的USSD后台调用、响应解析及选项交互问题,我整理了一套适配该版本的可行方案——由于KitKat没有提供直接获取USSD响应的公开API,我们需要通过辅助功能服务(AccessibilityService)+ 系统弹窗监听的组合方式来实现需求,具体步骤如下:

解决Android KitKat下USSD全流程交互问题

一、核心实现思路

  1. 通过系统Intent发起USSD号码呼叫
  2. 利用AccessibilityService监听系统弹出的USSD响应弹窗,提取响应内容
  3. 解析响应内容中的选项并展示给用户
  4. 根据用户选择,再次通过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输入框就绪后的回调
    }
}

三、常见问题排查

  1. 权限未授予:确保已申请CALL_PHONE权限,且辅助功能已在系统设置中开启
  2. 弹窗识别失败:可调整onAccessibilityEvent中的判断逻辑,比如根据运营商弹窗的标题或内容关键词识别
  3. 指令发送失败:不同ROM的输入框ViewId可能不同,可尝试通过遍历节点的方式查找输入框

内容的提问来源于stack exchange,提问作者Hassan Nomani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:48:16