interactive ussd session(multi step) does not work on android 8(Oreo)
Asked Answered
N

5

18

I am currently working with Telephony Manager(USSD response) available in android api level 26(Nexus 6P). For single step ussd session, it's working.

reference: 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

example:

USSD request : "A" (ussd session initiates)

USSD response : "X" (ussd session terminates)

    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();
        }

but for interactive ussd request-response(multi-step), it's not working. Multi step scenario is as follows:

step # 1.

USSD request : "A" (ussd session initiates)

USSD response : "X"

step # 2.

USSD request : "B" (ussd session continues)

USSD response : "Y"

step # 3.

USSD request : "C"

USSD response : "Z" (ussd session terminates)

Nairobi answered 2/10, 2017 at 5:26 Comment(8)
Hello. I'm having the same issue right now. How did you solve yours ? Added to that I only get error code -1 ...Topmast
Till now I don't find any solutionNairobi
Glad you answered I forgot about my comment on this post... Actually I did not even read your post properly...Since I was trying multi-step USSD, it failed. I resorted to stick to single-step USSD. Now for multistep, maybe you'll need to override something in sendUssdRequest. I found that there is a method there of something like onReceiveResult that catches the message for multi step but it does not know how to continue processing the request... Just forget about multi-step for now or o integrate it in you UI...Topmast
I have found an alternative solution by using android accessibility service for receiving USSD response and give inputNairobi
@zoraf can u share the the link of that alternate solution and is there any solution of backward compatibility below than 26 of UssdResponseCallbackExclaim
@vikassingh Did you find anything ?Topmast
@Nairobi Could you share what you've found with accessibility ?Topmast
@JasonKrs looking forward the android API that handle multi-step USSD session. in the meantime i use this accessibility-based solution #35793878Chrismatory
M
6

I am able to get a menu, respond to it by sending a number but past that, I guess am at the mercy of the telco - they send a menu that blocks the entire screen and if you cancel it the entire session dies. Having worked in telecom, I think that this might be different from telco to telco because some of them have gateways that can kill a user initiated session and replace it with a telecom side originated session. technically, the 2 sessions are detached. but here is my code:

    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();
        }
    }
}

please ignore the call answering stuff - I was using this app before to test automatic call answering on Oreo. And below is the Layout file for the display:

<?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>
Magnoliamagnoliaceous answered 21/6, 2018 at 20:42 Comment(3)
any proposition on this one? I cannot get the handleUssdRequest method!Chrismatory
@Chrismatory were you able to extract handleUssdRequest?Godunov
@real_shardul noChrismatory
C
2

I managed to bypass some of the problems (like the multisession USSD responses not working) by using Reflection. I've made a GitHub gist here.

Obviously the assumption is that, the correct permissions (only CALL_PHONE at this point) are given - In terms of Contexts, I've only ever run this in an Activity but I think it would work fine in most if not all/any.

The next thing is figuring out how to sustain the session, if at all possible.

Camisado answered 18/6, 2018 at 16:29 Comment(5)
Good job, we need more guys like you around here !Corroboree
in your sample, what does the l refer to in new ResultReceiver(l){?Sollows
@Sollows It refers to some Handler type object I had in context, typically the main UI thread handler. So to instantiate one, you could do something like this: Handler l = new Handler(Looper.getMainLooper()); If you're executing in the main UI thread already, then you can omit the reference to the main looper and just have new Handler();.Camisado
The link is dead. Can u please update...??Tonsillotomy
I think this repo is removed.Valvulitis
V
0

These APIs don't properly handle menu-based USSDs. Their intended use case would be for things like querying simple things like minutes or data left in the user's plan. For menu-based systems there would need to be some notion of a continued session, which is not something the APIs support.

Vacillating answered 30/6, 2018 at 16:28 Comment(3)
That's sad. Sometimes the carriers doesn't offer a direct way to access your internet plan or something else, only with a continued menu.Toadflax
The API is actually designed to handle this - that is why it takes a Handler as an argument. Why it doesn't actually handle it is another question...Puga
The inclusion of a Handler does not mean that API was designed to handle menu-based USSDs; it simply provides a means for the caller to ensure callback operations are posted to the handler of their choice.Vacillating
C
0
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();
            }
        }

}

}

if your using dual sim make sure change to the network that your going to test(In my i used Airtel(*121#).

Casarez answered 30/9, 2018 at 10:44 Comment(0)
P
0

Unfortunately the API that Google added in Oreo only works for USSD services where you can dial the entire USSD code at the start and get back the response without entering anything into the session. What they apparently don't realize is that most telcos prevent this for security reasons, especially when there is a PIN entry. The design of the API actually appears to be meant to handle the case of further responses, but as various posters have noted doesn't actually work even as of Android 10.

My company Hover has developed an Android SDK which uses accessibility services to run multi-step USSD sessions and have it appear to happen inside your app. You create configurations for USSD services, trigger the session to run from your app and pass in any runtime variables you need. The user never sees the USSD session and when the response is returned your app is notified and you can parse it as you need. It works on Android 4.3 and above.

The SDK is free to integrate and use until you hit large scale. Please see our docs to get started.

(Disclosure: I am the CTO of Hover)

Puga answered 20/4, 2020 at 18:24 Comment(8)
"The SDK is free to integrate and use until you hit large scale.". I can see on your pricing page ( usehover.com/pricing ) that there is no mention of a maximum of request in the free plan. Could you clarify?Succursal
Hey @Puga Is there a way to turn off of customize contents of the screen that shows the name of an action and the data being inserted into the method extra?Aberdeen
@Succursal we've updated pricing on the site. In short, you can do up to 50 transactions per month per action for free and you can have unlimited actions. (an action is eg check balance on one particular network)Puga
@EricKaburu That screen is for the security of end users so that they always confirm the transaction. Think of it like when you confirm a payment with Paypal. Paypal would never let you remove that screen, cause then you could remove money from users' accounts without their permission. The colors and fonts are customizable. DM me if you want more infoPuga
@Puga I got error when creating app on your website. Your SDK use the calling Intent, accessibility services and the scenario setup on the web to automate the process right? If you have a demo that allow menu interaction then input a text, it will be greatDurarte
@Puga can you SDK hide the USSD multiple-step dialog?Durarte
@TrungNguyen yep. We use the system overlay permission to show a progress screen on top. You can find a demo at github.com/UseHover/HoverStarter. We also have a free app on the Play store: play.google.com/store/apps/details?id=com.hover.staxPuga
@Puga i created an account on Hover. I got error when creating app. I sent email to support channel but no response yet. Really want to check the demoDurarte

© 2022 - 2024 — McMap. All rights reserved.