【问题标题】:interactive ussd session(multi step) does not work on android 8(Oreo)交互式 ussd 会话(多步骤)在 android 8(奥利奥)上不起作用
【发布时间】:2020-04-21 02:24:41
【问题描述】:

我目前正在使用 android api level 26(Nexus 6P) 中提供的 Telephony Manager(USSD 响应)。对于单步 ussd 会话,它正在工作。

参考: http://codedrago.com/q/140674/android-telephony-telephonymanager-ussd-android-8-0-oreo-does-android-8-0-api-26-support-sending-and-repying-to-ussd-messages

示例:

USSD 请求:“A”(ussd 会话启动)

USSD 响应:“X”(ussd 会话终止)

    TelephonyManager =  telephonyManager(TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
    Handler handler = new Handler();
    TelephonyManager.UssdResponseCallback callback = new TelephonyManager.UssdResponseCallback() {
        @Override
        public void onReceiveUssdResponse(TelephonyManager telephonyManager, String request, CharSequence response) {
            super.onReceiveUssdResponse(telephonyManager, request, response);
            Log.e("ussd",response.toString());

        }

        @Override
        public void onReceiveUssdResponseFailed(TelephonyManager telephonyManager, String request, int failureCode) {
            super.onReceiveUssdResponseFailed(telephonyManager, request, failureCode);
            Log.e("ussd","failed with code " + Integer.toString(failureCode));
        }
    };

    try {
           Log.e("ussd","trying to send ussd request");
           telephonyManager.sendUssdRequest("*123#",
                    callback,
                    handler);
        }catch (Exception e){


            String msg= e.getMessage();
            Log.e("DEBUG",e.toString());
            e.printStackTrace();
        }

但对于交互式 ussd 请求响应(多步),它不起作用。 多步场景如下:

第 1 步。

USSD 请求:“A”(ussd 会话启动)

USSD 响应:“X”

第 2 步。

USSD 请求:“B”(ussd 会话继续)

USSD 响应:“是”

第 3 步。

USSD 请求:“C”

USSD 响应:“Z”(ussd 会话终止)

【问题讨论】:

  • 你好。我现在也有同样的问题。你是怎么解决你的?除此之外,我只得到错误代码 -1 ...
  • 到现在还没有找到解决办法
  • 很高兴你回答我忘记了我对这篇文章的评论......实际上我什至没有正确阅读你的帖子......因为我尝试了多步 USSD,它失败了。我求助于单步 USSD。现在对于多步,也许您需要覆盖sendUssdRequest 中的某些内容。我发现有一种类似onReceiveResult 的方法可以捕获多步骤的消息,但它不知道如何继续处理请求......暂时忘记多步骤或者将它集成到你的 UI 中...
  • 我找到了一种替代解决方案,即使用 android 无障碍服务来接收 USSD 响应并提供输入
  • @zoraf 可以分享该替代解决方案的链接吗?是否有任何低于 UssdResponseCallback 26 的向后兼容性解决方案

标签: android ussd android-8.0-oreo


【解决方案1】:

我可以得到一个菜单,通过发送一个号码来回应它,但过去,我猜是受电信公司的摆布 - 他们发送的菜单会阻塞整个屏幕,如果你取消它,整个会话就会死掉.在电信工作过,我认为这可能因电信公司而异,因为其中一些公司的网关可以杀死用户发起的会话并将其替换为电信端发起的会话。从技术上讲,这两个会话是分开的。但这是我的代码:

    package org.rootio.test.telephony.telephonyautorespond;

import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
import android.telecom.TelecomManager;
import android.telephony.PhoneStateListener;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.Switch;
import android.widget.Toast;

import com.google.android.material.snackbar.Snackbar;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import androidx.annotation.RequiresApi;

import static android.content.ContentValues.TAG;

interface UssdResultNotifiable {
    void notifyUssdResult(String request, String returnMessage, int resultCode);
}

public class HomeActivity extends Activity implements UssdResultNotifiable {

    USSDSessionHandler hdl;
    private TelephonyManager telephonyManager;
    private PhoneCallListener listener;
    private TelecomManager telecomManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);
    }

    public void onUssdSend(View view) {
        //USSDHandler callback = new USSDHandler(view);
        /* if (checkSelfPermission(Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
         *//*if(ActivityCompat.shouldShowRequestPermissionRationale(HomeActivity.this, Manifest.permission.CALL_PHONE))
                    {

                    }
                    else
                    {*//*
                        //ActivityCompat.requestPermissions(HomeActivity.this, new String[]{Manifest.permission.CALL_PHONE}, 0);
                   // }
                    Snackbar.make(view, "permissions were missing", Snackbar.LENGTH_LONG)
                            .setAction("Response", null).show();
                    return;
                }*/
        //HomeActivity.this.telephonyManager.sendUssdRequest("*#123*99#", callback, new Handler());



        hdl = new USSDSessionHandler(HomeActivity.this, HomeActivity.this);

        hdl.doSession(((EditText)this.findViewById(R.id.ussdText)).getText().toString());

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_home, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    public void toggleListener(View v) {
        if (((Switch) v).isChecked()) {
            this.listenForTelephony();
            Toast.makeText(this, "Listening for calls", Toast.LENGTH_LONG).show();
        } else {
            this.stopListeningForTelephony();
        }
    }

    private void listenForTelephony() {
        this.telephonyManager = (TelephonyManager) this.getSystemService(this.TELEPHONY_SERVICE);
        this.telecomManager = (TelecomManager) this.getSystemService(this.TELECOM_SERVICE);
        this.listener = new PhoneCallListener();
        telephonyManager.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
    }

    private void stopListeningForTelephony() {
        this.telephonyManager = null;
        this.telecomManager = null;
    }

    @Override
    public void notifyUssdResult(final String request, final String returnMessage, final int resultCode) {
        this.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(HomeActivity.this, "Request was " + request + "\n response is " + returnMessage + "\n result code is " + resultCode, Toast.LENGTH_LONG).show();

            }
        });

    }


    class PhoneCallListener extends PhoneStateListener {
        @RequiresApi(api = Build.VERSION_CODES.M)
        @Override
        public void onCallStateChanged(int state, String incomingNumber) {

            switch (state) {
                case TelephonyManager.CALL_STATE_RINGING:
                    HomeActivity.this.telecomManager.acceptRingingCall();
                    break;
                case TelephonyManager.CALL_STATE_IDLE:
                    Toast.makeText(HomeActivity.this, "Call is no longer active...", Toast.LENGTH_LONG);
                    break;
            }
        }
    }

    @TargetApi(Build.VERSION_CODES.O)
    class USSDHandler extends TelephonyManager.UssdResponseCallback {

        View parent;

        USSDHandler(View v) {
            this.parent = v;
        }

        @Override
        public void onReceiveUssdResponse(TelephonyManager telephonyManager, String request, CharSequence response) {
            super.onReceiveUssdResponse(telephonyManager, request, response);
            Snackbar.make(this.parent, response, Snackbar.LENGTH_LONG)
                    .setAction("Response", null).show();
        }

        @Override
        public void onReceiveUssdResponseFailed(TelephonyManager telephonyManager, String request, int failureCode) {
            super.onReceiveUssdResponseFailed(telephonyManager, request, failureCode);
            Snackbar.make(this.parent, "error is " + failureCode + " for req " + request, Snackbar.LENGTH_LONG)
                    .setAction("Response", null).show();
        }
    }


}

class USSDSessionHandler {

    TelephonyManager tm;
    private UssdResultNotifiable client;
    private Method handleUssdRequest;
    private Object iTelephony;

    USSDSessionHandler(Context parent, UssdResultNotifiable client) {
        this.client = client;
        this.tm = (TelephonyManager) parent.getSystemService(Context.TELEPHONY_SERVICE);
        try {
            this.getUssdRequestMethod();
        } catch (Exception ex) {
            //log
        }

    }

    private void getUssdRequestMethod() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        if (tm != null) {
            Class telephonyManagerClass = Class.forName(tm.getClass().getName());
            if (telephonyManagerClass != null) {
                Method getITelephony = telephonyManagerClass.getDeclaredMethod("getITelephony");
                getITelephony.setAccessible(true);
                this.iTelephony = getITelephony.invoke(tm); // Get the internal ITelephony object
                Method[] methodList = iTelephony.getClass().getMethods();
                this.handleUssdRequest = null;
                /*
                 *  Somehow, the method wouldn't come up if I simply used:
                 *  iTelephony.getClass().getMethod('handleUssdRequest')
                 */

                for (Method _m : methodList)
                    if (_m.getName().equals("handleUssdRequest")) {
                        handleUssdRequest = _m;
                        break;
                    }
            }
        }
    }

    public void doSession(String ussdRequest) {
        try {

            if (handleUssdRequest != null) {
                handleUssdRequest.setAccessible(true);
                handleUssdRequest.invoke(iTelephony, SubscriptionManager.getDefaultSubscriptionId(), ussdRequest, new ResultReceiver(new Handler()) {

                    @Override
                    protected void onReceiveResult(int resultCode, Bundle ussdResponse) {
                        /*
                         * Usually you should the getParcelable() response to some Parcel
                         * child class but that's not possible here, since the "UssdResponse"
                         * class isn't in the SDK so we need to
                         * reflect again to get the result of getReturnMessage() and
                         * finally return that!
                         */

                        Object p = ussdResponse.getParcelable("USSD_RESPONSE");

                        if (p != null) {

                            Method[] methodList = p.getClass().getMethods();
                            for(Method m : methodList)
                            {
                                Log.i(TAG, "onReceiveResult: " + m.getName());
                            }
                            try {
                                CharSequence returnMessage = (CharSequence) p.getClass().getMethod("getReturnMessage").invoke(p);
                                CharSequence request = (CharSequence) p.getClass().getMethod("getUssdRequest").invoke(p);
                                USSDSessionHandler.this.client.notifyUssdResult("" + request, "" + returnMessage, resultCode); //they could be null
                            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                                e.printStackTrace();
                            }
                        }
                    }

                });
            }
        } catch (IllegalAccessException | InvocationTargetException e1) {
            e1.printStackTrace();
        }
    }
}

请忽略接听电话的东西 - 我之前使用这个应用程序测试奥利奥的自动接听电话。下面是显示的布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".HomeActivity">


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="USSD Input"
            android:textSize="18sp" />

        <EditText
            android:id="@+id/ussdText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:ems="10"
            android:inputType="textPersonName" />
    </LinearLayout>

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="onUssdSend"
        android:text="send" />
</LinearLayout>

【讨论】:

  • 对这个有什么建议吗?我无法获取 handleUssdRequest 方法!
  • @Abdu 你能提取handleUssdRequest吗?
  • @real_shardul no
【解决方案2】:

我通过使用 Reflection 设法绕过了一些问题(例如多会话 USSD 响应不起作用)。我已经创建了一个 GitHub gist here

显然假设是,正确的权限(此时仅CALL_PHONE)被给予 - 就上下文而言,我只在 Activity 中运行过它,但我认为它在大多数情况下都可以正常工作,如果不是全部/任何。

如果可能的话,接下来要弄清楚如何维持会话。

【讨论】:

  • 干得好,我们需要更多像你这样的人!
  • 在您的示例中,lnew ResultReceiver(l){ 中指的是什么?
  • @Razgriz 它指的是我在上下文中拥有的一些 Handler 类型对象,通常是主 UI 线程处理程序。所以要实例化一个,你可以这样做:Handler l = new Handler(Looper.getMainLooper()); 如果你已经在主 UI 线程中执行,那么你可以省略对主循环器的引用,只使用new Handler();
【解决方案3】:

这些 API 无法正确处理基于菜单的 USSD。他们的预期用例将用于查询诸如分钟或用户计划中剩余的数据之类的简单事物。对于基于菜单的系统,需要有一些持续会话的概念,这不是 API 支持的。

【讨论】:

  • 这很可悲。有时,运营商不提供直接访问您的互联网计划或其他内容的方式,仅提供连续菜单。
  • API 实际上是为了处理这个问题而设计的——这就是为什么它需要一个 Handler 作为参数。为什么它实际上不处理它是另一个问题......
  • 包含 Handler 并不意味着 API 旨在处理基于菜单的 USSD;它只是为调用者提供了一种方法,以确保将回调操作发布到他们选择的处理程序。
【解决方案4】:
public class MainActivity extends AppCompatActivity {
private Button dail;
private String number;
private TelephonyManager telephonyManager;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);


    telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);

    dail = (Button) findViewById(R.id.dail);
    dail.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
                    runtimepermissions();
                    return;
                }else{
                    telephonyManager.sendUssdRequest("*121#", new TelephonyManager.UssdResponseCallback() {
                        @Override
                        public void onReceiveUssdResponse(TelephonyManager telephonyManager, String request, CharSequence response) {
                            super.onReceiveUssdResponse(telephonyManager, request, response);

                            Log.d("Received response","okay");
                            ((TextView)findViewById(R.id.response)).setText(response);
                        }

                        @Override
                        public void onReceiveUssdResponseFailed(TelephonyManager telephonyManager, String request, int failureCode) {
                            super.onReceiveUssdResponseFailed(telephonyManager, request, failureCode);
                            Log.e("ERROR ","can't receive response"+failureCode);
                        }
                    },new Handler(Looper.getMainLooper()){
                        @Override
                        public void handleMessage(Message msg) {
                            Log.e("ERROR","error");
                        }
                    });
                }

        }
    });

    }
    public boolean runtimepermissions() {
        if (Build.VERSION.SDK_INT >= 23 && ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED
                && ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(new String[]{Manifest.permission.READ_PHONE_STATE, Manifest.permission.CALL_PHONE}, 100);
            return true;
        }
        return false;
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == 100) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
                Log.d("PERMISSIONS","granted");
               // doJob();
            } else {
                runtimepermissions();
            }
        }

}

}

如果您使用双卡,请确保更改您要测试的网络(在我使用 Airtel(*121#)时。

【讨论】:

    【解决方案5】:

    不幸的是,Google 在 Oreo 中添加的 API 仅适用于 USSD 服务,您可以在开始时拨打整个 USSD 代码并获取响应,而无需在会话中输入任何内容。他们显然没有意识到大多数电信公司出于安全原因会阻止这种情况,尤其是在输入 PIN 码时。 API 的设计实际上似乎是为了处理进一步响应的情况,但正如各种海报所指出的那样,即使在 Android 10 中实际上也不起作用。

    我的公司 Hover 开发了一个 Android SDK,它使用无障碍服务来运行多步 USSD 会话,并让它看起来发生在您的应用程序中。您为 USSD 服务创建配置,触发会话从您的应用程序运行并传入您需要的任何运行时变量。用户永远不会看到 USSD 会话,当返回响应时,您的应用程序会收到通知,您可以根据需要对其进行解析。它适用于 Android 4.3 及更高版本。

    SDK 可以免费集成和使用,直到您实现大规模。请查看我们的docs 以开始使用。

    (披露:我是 Hover 的 CTO)

    【讨论】:

    • "SDK 可以免费集成和使用,直到您实现大规模。"。我可以在您的定价页面 (usehover.com/pricing) 上看到,免费计划中没有提到最大请求。你能澄清一下吗?
    • 嘿@davkutalek 有没有办法关闭显示动作名称和额外插入方法中的数据的屏幕自定义内容?
    • @vkammerer 我们已经更新了网站上的定价。简而言之,您每个操作每月最多可以免费进行 50 次交易,并且您可以进行无限制的操作。 (例如,检查某个特定网络上的余额)
    • @EricKaburu 该屏幕是为了确保最终用户的安全,以便他们始终确认交易。想象一下,当您使用 Paypal 确认付款时。 Paypal 永远不会让您删除该屏幕,因为您可以在未经用户许可的情况下从用户帐户中删除资金。颜色和字体是可定制的。如果您想了解更多信息,请私信我
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多