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

如何使用Delphi 10.4处理Android 7/9设备上的USSD响应并管理USSD会话

Handling USSD Dialog Flow in Delphi 10.4 for Android 7 and 9

Managing USSD sessions in Delphi requires interacting with Android's native APIs, since FMX doesn't provide built-in support for USSD. The approach differs slightly between Android 7 (API 24-25) and Android 9 (API 28) due to changes in the platform's USSD handling framework. Below are step-by-step implementations for both versions.


For Android 9 (API 26+) Using TelecomManager

Android 8+ introduced the TelecomManager API, which provides official support for sending USSD requests and handling responses without relying on accessibility services.

Prerequisites

  1. Add Permissions: In your AndroidManifest.xml (via Delphi Project Options > Version Info > Custom Android Manifest), include these permissions:
    <uses-permission android:name="android.permission.CALL_PHONE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    

Step 1: Implement the UssdResponseCallback

Create a callback class to handle USSD responses and session events:

unit UssdHandler;

interface

uses
  Androidapi.JNI.Telecom, Androidapi.JNI.JavaTypes, Androidapi.Helpers,
  Androidapi.JNI.GraphicsContentViewText;

type
  TUssdResponseCallback = class(TJavaLocal, JUssdResponseCallback)
  private
    FPassword: string;
  public
    constructor Create(const Password: string);
    procedure onUssdResponseReceived(session: JUssdSession; response: JUssdResponse); cdecl;
    procedure onUssdRequestFailed(session: JUssdSession; errorCode: Integer); cdecl;
  end;

implementation

{ TUssdResponseCallback }

constructor TUssdResponseCallback.Create(const Password: string);
begin
  inherited Create;
  FPassword := Password;
end;

procedure TUssdResponseCallback.onUssdRequestFailed(session: JUssdSession; errorCode: Integer);
begin
  // Handle request failure (e.g., show error to user)
  TAndroidHelper.Activity.runOnUiThread(
    procedure
    begin
      ShowMessage('USSD request failed. Error code: ' + IntToStr(errorCode));
    end
  );
end;

procedure TUssdResponseCallback.onUssdResponseReceived(session: JUssdSession; response: JUssdResponse);
var
  ResponseText: string;
begin
  ResponseText := JStringToString(response.getMessage());
  
  // Check if the response is the password prompt (adjust text to match your carrier's message)
  if Pos('Enter password', ResponseText) > 0 then
  begin
    // Send the password to the active USSD session
    session.sendUssdResponse(StringToJString(FPassword));
  end
  else
  begin
    // Handle other responses (e.g., display balance to user)
    TAndroidHelper.Activity.runOnUiThread(
      procedure
      begin
        ShowMessage('USSD Response: ' + ResponseText);
      end
    );
  end;
end;

end.

Step 2: Send Initial USSD Request

Call this method to trigger the initial balance query:

procedure SendInitialUssd(const Password: string);
var
  TelecomManager: JTelecomManager;
  Callback: TUssdResponseCallback;
begin
  {$IFDEF ANDROID}
  TelecomManager := TJTelecomManager.Wrap(
    TAndroidHelper.Context.getSystemService(TJContext.JavaClass.TELECOM_SERVICE)
  );
  
  if TelecomManager = nil then
  begin
    ShowMessage('TelecomManager not available');
    Exit;
  end;
  
  // Check if CALL_PHONE permission is granted
  if TAndroidHelper.Context.checkSelfPermission(TJManifest_permission.JavaClass.CALL_PHONE) <>
    TJPackageManager.JavaClass.PERMISSION_GRANTED then
  begin
    // Request permission if not granted
    TAndroidHelper.Activity.requestPermissions(
      TJavaObjectArray<JString>.Create(1, StringToJString(TJManifest_permission.JavaClass.CALL_PHONE)),
      1001 // Request code for permission result
    );
    Exit;
  end;
  
  Callback := TUssdResponseCallback.Create(Password);
  try
    // Send the initial USSD code (adjust to your carrier's exact code)
    TelecomManager.sendUssdRequest(
      StringToJString('#111*1*6*1#'), // Note: Replace spaces with * if needed (USSD codes typically use *)
      Callback,
      nil // Optional handler for async operations
    );
  except
    Callback.Free;
    raise;
  end;
  {$ENDIF}
end;

For Android 7 (API 24-25) Using Accessibility Service

Before API 26, there's no official USSD API, so we use an Accessibility Service to intercept the system's USSD dialog and automate input.

Prerequisites

  1. Add Permissions & Service Declaration: Update your AndroidManifest.xml:

    <uses-permission android:name="android.permission.CALL_PHONE" />
    <uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" />
    
    <service android:name="com.embarcadero.services.MyUssdAccessibilityService"
             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>
    
  2. Create Accessibility Config: Add a ussd_accessibility_config.xml file to your project's res/xml folder:

    <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
        android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged"
        android:accessibilityFeedbackType="feedbackGeneric"
        android:canRetrieveWindowContent="true"
        android:description="@string/ussd_service_desc" />
    
  3. Add Description String: Add a string to res/values/strings.xml:

    <string name="ussd_service_desc">Automatically handles USSD password prompts for balance queries</string>
    

Step 1: Implement the Accessibility Service

Create the service class to detect and interact with USSD dialogs:

unit UssdAccessibilityService;

interface

uses
  Androidapi.JNI.AccessibilityService, Androidapi.JNI.JavaTypes, Androidapi.Helpers,
  Androidapi.JNI.GraphicsContentViewText;

type
  TMyUssdAccessibilityService = class(TAndroidAccessibilityService)
  private
    FPassword: string;
    function FindNodeWithText(Node: JAccessibilityNodeInfo; Text: string): JAccessibilityNodeInfo;
    function FindInputField(Node: JAccessibilityNodeInfo): JAccessibilityNodeInfo;
    function FindSendButton(Node: JAccessibilityNodeInfo): JAccessibilityNodeInfo;
  protected
    procedure onAccessibilityEvent(event: JAccessibilityEvent); override;
    procedure onInterrupt; override;
  public
    class procedure SetPassword(const Password: string);
    function onServiceConnected: Boolean; override;
  end;

implementation

var
  GPassword: string;

{ TMyUssdAccessibilityService }

class procedure TMyUssdAccessibilityService.SetPassword(const Password: string);
begin
  GPassword := Password;
end;

function TMyUssdAccessibilityService.FindNodeWithText(Node: JAccessibilityNodeInfo; Text: string): JAccessibilityNodeInfo;
var
  I: Integer;
  ChildNode: JAccessibilityNodeInfo;
  NodeText: string;
begin
  Result := nil;
  if Node = nil then Exit;
  
  if Node.getText <> nil then
  begin
    NodeText := JStringToString(Node.getText);
    if NodeText.Contains(Text) then
    begin
      Result := Node;
      Exit;
    end;
  end;
  
  for I := 0 to Node.getChildCount - 1 do
  begin
    ChildNode := Node.getChild(I);
    Result := FindNodeWithText(ChildNode, Text);
    if Result <> nil then Break;
  end;
end;

function TMyUssdAccessibilityService.FindInputField(Node: JAccessibilityNodeInfo): JAccessibilityNodeInfo;
var
  I: Integer;
  ChildNode: JAccessibilityNodeInfo;
begin
  Result := nil;
  if Node = nil then Exit;
  
  if Node.getClassName <> nil then
    if JStringToString(Node.getClassName) = 'android.widget.EditText' then
    begin
      Result := Node;
      Exit;
    end;
  
  for I := 0 to Node.getChildCount - 1 do
  begin
    ChildNode := Node.getChild(I);
    Result := FindInputField(ChildNode);
    if Result <> nil then Break;
  end;
end;

function TMyUssdAccessibilityService.FindSendButton(Node: JAccessibilityNodeInfo): JAccessibilityNodeInfo;
var
  I: Integer;
  ChildNode: JAccessibilityNodeInfo;
  NodeText: string;
begin
  Result := nil;
  if Node = nil then Exit;
  
  if Node.getText <> nil then
  begin
    NodeText := JStringToString(Node.getText);
    if (NodeText = 'Send') or (NodeText = 'OK') then
    begin
      Result := Node;
      Exit;
    end;
  end;
  
  for I := 0 to Node.getChildCount - 1 do
  begin
    ChildNode := Node.getChild(I);
    Result := FindSendButton(ChildNode);
    if Result <> nil then Break;
  end;
end;

procedure TMyUssdAccessibilityService.onAccessibilityEvent(event: JAccessibilityEvent);
var
  RootNode: JAccessibilityNodeInfo;
  PromptNode: JAccessibilityNodeInfo;
  InputNode: JAccessibilityNodeInfo;
  SendButton: JAccessibilityNodeInfo;
  Bundle: JBundle;
begin
  inherited;
  
  if event.getEventType <> TJAccessibilityEvent.JavaClass.TYPE_WINDOW_STATE_CHANGED then
    Exit;
  
  RootNode := getRootInActiveWindow;
  if RootNode = nil then Exit;
  
  try
    // Check if this is the USSD dialog (package name may vary by device/carrier)
    if JStringToString(RootNode.getPackageName) <> 'com.android.phone' then
      Exit;
    
    // Look for the password prompt
    PromptNode := FindNodeWithText(RootNode, 'Enter password');
    if PromptNode = nil then Exit;
    
    // Find input field and send button
    InputNode := FindInputField(RootNode);
    SendButton := FindSendButton(RootNode);
    
    if (InputNode <> nil) and (SendButton <> nil) then
    begin
      // Set password text
      Bundle := TJBundle.Create;
      Bundle.putString(
        TJAccessibilityNodeInfo.JavaClass.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,
        StringToJString(GPassword)
      );
      InputNode.performAction(TJAccessibilityNodeInfo.JavaClass.ACTION_SET_TEXT, Bundle);
      
      // Click send button
      SendButton.performAction(TJAccessibilityNodeInfo.JavaClass.ACTION_CLICK);
    end;
  finally
    RootNode.recycle;
  end;
end;

procedure TMyUssdAccessibilityService.onInterrupt;
begin
  inherited;
end;

function TMyUssdAccessibilityService.onServiceConnected: Boolean;
var
  ServiceInfo: JAccessibilityServiceInfo;
begin
  ServiceInfo := TJAccessibilityServiceInfo.JavaClass.init;
  ServiceInfo.eventTypes := TJAccessibilityEvent.JavaClass.TYPE_WINDOW_STATE_CHANGED or 
                            TJAccessibilityEvent.JavaClass.TYPE_WINDOW_CONTENT_CHANGED;
  ServiceInfo.feedbackType := TJAccessibilityServiceInfo.JavaClass.FEEDBACK_GENERIC;
  ServiceInfo.canRetrieveWindowContent := True;
  setServiceInfo(ServiceInfo);
  
  Result := True;
end;

end.

Step 2: Send Initial USSD Request & Configure Service

  1. Set the password and prompt the user to enable accessibility permissions:

    procedure PrepareUssdHandling(const Password: string);
    begin
      {$IFDEF ANDROID}
      TMyUssdAccessibilityService.SetPassword(Password);
      
      // Check if accessibility service is enabled
      if not TAndroidHelper.Context.getSystemService(TJContext.JavaClass.ACCESSIBILITY_SERVICE).
        isEnabled(TAndroidHelper.Context.getPackageName() + '/com.embarcadero.services.MyUssdAccessibilityService') then
      begin
        // Redirect user to accessibility settings
        ShowMessage('Please enable accessibility permissions for this app to handle USSD prompts.');
        TAndroidHelper.Context.startActivity(TJIntent.JavaClass.init(TJSettings.JavaClass.ACTION_ACCESSIBILITY_SETTINGS));
      end;
      {$ENDIF}
    end;
    
  2. Send the initial USSD code:

    procedure SendInitialUssdAndroid7;
    var
      Intent: JIntent;
      EncodedUssd: string;
    begin
      {$IFDEF ANDROID}
      // Encode # as %23 for the tel URI
      EncodedUssd := StringReplace('#111*1*6*1#', '#', '%23', [rfReplaceAll]);
      
      Intent := TJIntent.JavaClass.init(TJIntent.JavaClass.ACTION_CALL, 
        TJUri.JavaClass.parse('tel:' + StringToJString(EncodedUssd)));
      
      // Check CALL_PHONE permission
      if TAndroidHelper.Context.checkSelfPermission(TJManifest_permission.JavaClass.CALL_PHONE) =
        TJPackageManager.JavaClass.PERMISSION_GRANTED then
        TAndroidHelper.Context.startActivity(Intent)
      else
        TAndroidHelper.Activity.requestPermissions(
          TJavaObjectArray<JString>.Create(1, StringToJString(TJManifest_permission.JavaClass.CALL_PHONE)),
          1002
        );
      {$ENDIF}
    end;
    

Key Notes & Pitfalls

  • Carrier/Device Differences: USSD dialog package names, prompt texts, and button labels may vary by carrier or device. Adjust the text checks and package names accordingly.
  • Permission Handling: Always check and request permissions at runtime (Delphi's TAndroidHelper simplifies this).
  • Session Management: For Android 9+, ensure the TUssdResponseCallback instance isn't freed prematurely (store it in a form field or global variable if needed).
  • Accessibility Service Authorization: Users must manually enable the accessibility service for your app in system settings—your app should guide them to this screen.

内容的提问来源于stack exchange,提问作者Ranjavola Z Andrianarisaona

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 04:47:38