интерактивный сеанс ussd (многоэтапный) не работает на Android 8 (Oreo)

В настоящее время я работаю с диспетчером телефонии (USSD-ответ), доступным на уровне Android API 26 (Nexus 6P). Для одношагового сеанса ussd он работает.

ссылка: http://codedrago.com/q/140674/android-telephony-telephonymanager-ussd-android-8-0-oreo-does-android-8-0-api-26-support-sending-и-ответ-на-ussd-сообщения

пример:

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-ответ: «Х»

шаг 2.

USSD-запрос: «B» (USSD-сеанс продолжается)

USSD-ответ: "Y"

шаг 3.

USSD-запрос: "С"

USSD-ответ: «Z» (USSD-сессия завершается)


person Zoraf    schedule 02.10.2017    source источник
comment
Привет. У меня сейчас такая же проблема. Как ты решил свою? Вдобавок к этому я получаю только код ошибки -1...   -  person Jason Krs    schedule 27.05.2018
comment
Пока не нахожу решения   -  person Zoraf    schedule 01.06.2018
comment
Рад, что вы ответили, я забыл о своем комментарии к этому сообщению ... На самом деле я даже не прочитал ваш пост должным образом ... Так как я пытался использовать многошаговый USSD, это не удалось. Я прибегнул к одношаговому USSD. Теперь для многошагового, возможно, вам нужно что-то переопределить в sendUssdRequest. Я обнаружил, что там есть метод чего-то вроде onReceiveResult, который перехватывает сообщение для многоэтапного, но не знает, как продолжить обработку запроса... Просто забудьте о многошаговом режиме или интегрируйте его в свой пользовательский интерфейс. .   -  person Jason Krs    schedule 01.06.2018
comment
Я нашел альтернативное решение, используя службу доступности Android для получения ответа USSD и ввода данных.   -  person Zoraf    schedule 01.04.2019
comment
@zoraf можете ли вы поделиться ссылкой на это альтернативное решение и есть ли какое-либо решение обратной совместимости ниже, чем 26 из UssdResponseCallback   -  person vikas singh    schedule 03.04.2019
comment
@vikassingh Ты что-нибудь нашел?   -  person Jason Krs    schedule 21.05.2019
comment
@Zoraf Не могли бы вы поделиться тем, что вы нашли с доступностью?   -  person Jason Krs    schedule 21.05.2019
comment
@JasonKrs с нетерпением ждет Android API, который обрабатывает многоэтапный сеанс USSD. тем временем я использую это решение, основанное на доступности диалоговое окно программно"> stackoverflow.com/questions/35793378/   -  person Abdu    schedule 01.07.2019


Ответы (5)


Я могу получить меню, ответить на него, отправив номер, но после этого, я думаю, я нахожусь во власти телекоммуникационных компаний - они отправляют меню, которое блокирует весь экран, и если вы отмените его, весь сеанс умрет. Поработав в телекоммуникациях, я думаю, что это может отличаться от телекоммуникационной компании к телекоммуникационной, потому что у некоторых из них есть шлюзы, которые могут прервать сеанс, инициированный пользователем, и заменить его сеансом, созданным на стороне телекоммуникаций. технически 2 сеанса разделены. но вот мой код:

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

пожалуйста, не обращайте внимания на ответы на звонки - я использовал это приложение раньше, чтобы протестировать автоматический ответ на звонки на Oreo. А ниже файл Layout для дисплея:

<?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>
person Jude Mukundane    schedule 21.06.2018
comment
любое предложение по этому поводу? Я не могу получить метод handleUssdRequest! - person Abdu; 10.09.2019
comment
@Abdu, ты смог извлечь handleUssdRequest? - person real_shardul; 20.02.2020
comment
@real_shardul нет - person Abdu; 21.02.2020

Мне удалось обойти некоторые проблемы (например, неработающие многосессионные USSD-ответы) с помощью Reflection. Я сделал список GitHub здесь.

Очевидно, предполагается, что даны правильные разрешения (на данный момент только CALL_PHONE). С точки зрения контекстов, я только когда-либо запускал это в действии, но я думаю, что это будет нормально работать в большинстве, если не во всех/любых.

Следующее, что нужно сделать, это выяснить, как поддерживать сеанс, если это вообще возможно.

person pol    schedule 18.06.2018
comment
Молодец, нам нужно больше таких парней, как ты! - person Seaskyways; 22.11.2018
comment
в вашем образце, что означает l в new ResultReceiver(l){? - person Razgriz; 24.03.2019
comment
@Razgriz Это относится к некоторому объекту типа Handler, который у меня был в контексте, обычно это обработчик основного потока пользовательского интерфейса. Таким образом, чтобы создать экземпляр, вы можете сделать что-то вроде этого: Handler l = new Handler(Looper.getMainLooper()); Если вы уже выполняете в основном потоке пользовательского интерфейса, вы можете опустить ссылку на основной цикл и просто иметь new Handler();. - person pol; 25.03.2019

Эти API неправильно обрабатывают USSD на основе меню. Их предполагаемый вариант использования будет для таких вещей, как запрос простых вещей, таких как минуты или данные, оставшиеся в плане пользователя. Для систем, основанных на меню, должно быть какое-то понятие о продолжении сеанса, что не поддерживается API.

person TGunn    schedule 30.06.2018
comment
Печально. Иногда операторы не предлагают прямого доступа к вашему интернет-плану или чему-то еще, только с продолжением меню. - person Fusseldieb; 07.09.2018
comment
API на самом деле предназначен для обработки этого, поэтому он принимает обработчик в качестве аргумента. Почему он на самом деле не справляется с этим, другой вопрос... - person davkutalek; 20.04.2020
comment
Включение обработчика не означает, что API был разработан для обработки USSD на основе меню; он просто предоставляет вызывающей стороне средства для обеспечения того, чтобы операции обратного вызова отправлялись обработчику по их выбору. - person TGunn; 22.04.2020

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

}

}

если вы используете две SIM-карты, убедитесь, что вы переключились на сеть, которую собираетесь тестировать (в моем случае я использовал Airtel (* 121 #).

person Ruthvik reddy    schedule 30.09.2018

К сожалению, API, который Google добавил в Oreo, работает только для USSD-сервисов, где вы можете набрать весь USSD-код в начале и получить ответ, ничего не вводя в сеанс. Чего они, по-видимому, не понимают, так это того, что большинство телекоммуникационных компаний предотвращают это из соображений безопасности, особенно когда есть ввод PIN-кода. Дизайн API на самом деле, по-видимому, предназначен для обработки случаев дальнейших ответов, но, как отмечают различные авторы, на самом деле он не работает даже в Android 10.

Моя компания Hover разработала Android SDK, который использует специальные возможности для запуска многошаговых сеансов USSD и отображения происходить внутри вашего приложения. Вы создаете конфигурации для служб USSD, запускаете сеанс из своего приложения и передаете любые необходимые переменные времени выполнения. Пользователь никогда не видит сеанс USSD, и когда возвращается ответ, ваше приложение получает уведомление, и вы можете анализировать его по мере необходимости. Работает на Android 4.3 и выше.

SDK можно бесплатно интегрировать и использовать до тех пор, пока вы не достигнете больших масштабов. Ознакомьтесь с нашей документацией, чтобы начать работу.

(Раскрытие информации: я технический директор Hover)

person davkutalek    schedule 20.04.2020
comment
SDK можно бесплатно интегрировать и использовать, пока вы не достигнете больших масштабов. Я вижу на вашей странице цен ( usehover.com /pricing ), что в бесплатном плане не упоминается максимальное количество запросов. Не могли бы вы уточнить? - person vkammerer; 12.07.2020
comment
Привет @davkutalek Есть ли способ отключить или настроить содержимое экрана, на котором отображается название действия и данные, вставляемые в дополнительный метод? - person Eric Kaburu; 10.08.2020
comment
@vkammerer мы обновили цены на сайте. Короче говоря, вы можете совершать до 50 транзакций в месяц на одно действие бесплатно, и у вас может быть неограниченное количество действий. (действие, например, проверка баланса в одной конкретной сети) - person davkutalek; 30.11.2020
comment
@EricKaburu Этот экран предназначен для безопасности конечных пользователей, чтобы они всегда подтверждали транзакцию. Подумайте об этом, как когда вы подтверждаете платеж через Paypal. Paypal никогда не позволит вам удалить этот экран, потому что тогда вы сможете снимать деньги со счетов пользователей без их разрешения. Цвета и шрифты настраиваются. Напишите мне, если хотите больше информации - person davkutalek; 30.11.2020