Пользователь
0,0
рейтинг
25 августа 2014 в 07:21

Разработка → Простой USSD-запрос в Android 4.0+ tutorial

В Android до сих пор нет API для USSD-запросов. Баг висит уже 6 лет!
Я находил разные способы создания и получения информации из USSD запросов, но в итоге ни один не устроил.
Затем я нашел упоминания о том, что с помощью обновленных в Android 4.0 служб спец. возможностей можно легко получать содержимое окон и так получить текст из окна и результатом USSD запроса. Попробовал — получается отлично! Без перезагрузок и надежно.

Отправка запроса

Для начала, надо отослать сам USSD-запрос. Это достаточно просто:

String encodedHash = Uri.encode("#");
String ussd = "*100" + encodedHash;
startActivityForResult(new Intent("android.intent.action.CALL",
Uri.parse("tel:" + ussd)), 1);

Для получения разрешение на работу с телефоном, надо получить разрешение, для этого прописываем в Android.Manifest.xml:

<uses-permission android:name="android.permission.CALL_PHONE" />

OK, из программы запрос отправляется, теперь придется потрудиться, чтобы получить его текст.

Accessibility service

Для работы со спец. возможностями надо экспортировать наш сервис. Для этого добавляем в Android.Manifest.xml:

<service 
      android:name=".USSDService"
      android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" >
	<intent-filter>
   	<action android:name="android.accessibilityservice.AccessibilityService" />
	</intent-filter>
</service>

Вместо USSDService напишите название вашего класса.

Сам класс я брал почти без изменений отсюда.

Для того, чтобы не получать лишнего, меняем метод onServiceConnected:

protected void onServiceConnected() {
    super.onServiceConnected();
    Log.v(TAG, "onServiceConnected");
    AccessibilityServiceInfo info = new AccessibilityServiceInfo();
    info.flags = AccessibilityServiceInfo.DEFAULT;
    info.packageNames = new String[]
            {"com.android.phone"};
    info.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
    info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
    setServiceInfo(info);
}

Еще нужен будет дополнительный фильтр в методе onAccessibilityEvent:

public void onAccessibilityEvent(AccessibilityEvent event) {
    String text = getEventText(event);
    Log.v(TAG, String.format(
            "onAccessibilityEvent: [type] %s [class] %s [package] %s [time] %s [text] %s",
            getEventType(event), event.getClassName(), event.getPackageName(),
            event.getEventTime(), getEventText(event)));
    if (event.getClassName().equals("android.app.AlertDialog")) {
		performGlobalAction(GLOBAL_ACTION_BACK);
    		Log.i(TAG, text);
		Intent intent = new Intent("REFRESH");
		intent.putExtra("message", text);
		sendBroadcast(intent);
    }
}

Метод performGlobalAction(GLOBAL_ACTION_BACK) требует Android 4.1+, если его не использовать, можно уложиться в 4.0. Он закрывает окошко сразу же после появления AlertDialog, поэтому окно не останется висеть.
Для простоты я уже добавил метод sendBroadcast, чтобы отослать полученное сообщение дальше.
Чтобы получить сообщение, добавляем в нужный класс такие методы:

private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
  @Override
  public void onReceive(Context context, Intent intent) {
    String message = intent.getStringExtra("message");
    Log.i("receiver", "Got message: " + message);
    showText(message);
  }
};

showText — ваша процедура, которая что-то делает с полученным текcтом.

Для работы BroadcastReceiver'а надо добавить немного кода в onCreate() или подобный метод вашего класса:

IntentFilter mFilter = new IntentFilter("REFRESH");
mContext.registerReceiver(mMessageReceiver, mFilter);
isRegistered = true;

И это — в onPause() или подобный:

try
{
    if (isRegistered) {
        mContext.unregisterReceiver(mMessageReceiver);
        isRegistered = false;
    }
}
catch (Exception e) {
    e.printStackTrace();
}

Вот и все! Как видите, просто и надежно.

Подводные камни

Могут быть проблемы с работой в фоне: например, если экран заблокирован, то AccessibilityService вообще не будет работать — надо будить устройство. В разблокированном же состоянии запрос всегда будет вылазить на первый план, что то же не удобно.

AccessibilityService будет слушать постоянно, даже когда пользователь сам набирает USSD код или, еще хуже, если com.android.phone кидает AlertDialog. Так что надо либо усиливать фильтры (например парсить только если в сообщении есть определенная последовательность), либо использовать флаг, чтобы обрабатывать события только если ваше приложение сделало USSD-запрос.

И не забудьте в настройках спец. возможностей активировать ваше приложение, кстати для этого тоже неплохо было бы добавить проверку %)

Альтернативы:
habrahabr.ru/post/130717 — берет ответ из логов. Неудобно тем, что для подавления окна надо прибегать к хакам, а сделать это через AccessibilityService чище и проще.
github.com/alaasalman/ussdinterceptor — еще старый способ. Использует недокументированный класс. По отзывам часто отваливается и требуется перезагрузка.
@Gordon01
карма
–14,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (11)

  • 0
    Спасибо, интересно
  • +3
    Да, именно таким образом работает поддержка ussd в piBalance. Но подводных камней очень много, правда. Начиная от того, как парсить несколько таких диалогов если устройство заблокировано (на разных устройствах это работает по разному) до того что на разных аппаратах как класс диалога так и layout внутри диалога у всех разные. А еще на разные ussd запросы может приходить ответ ожидающий ввода от пользователя. И так далее. Особенно большая чехарда на китайских аппаратах.
    • +2
      И еще сюрпрайз сюрпрайз, при включении хотя бы одного accessibility services пропадает переключатель языка на самсунговской стандартной клавиатуре! Казалось бы как это связано? Чинится только установкой другой клавиатуры. Но сколько гневных отзывов было оставлено — не счесть.
      • +1
        Явный баг от самсунга, как и вобщем-то локальная их проблема.
  • 0
    Черт, а как же делать это для заблокированного телефона? именно работа в фоне — может быть критична. Какие там еще есть методы?
    • 0
      Я так понимаю, будить AlarmManager'ом, вешать wakelock, выполнять действия и снова засыпать. Но пока еще не реализовал в коде, так что я без пруфов :)
      • +1
        Только после этого пользователи жалуются что у них самопроизвольно включается экран. Проверено :) Без рута никуда. А с рутом уже и другие методы запроса могут быть — через АТ команды модема.
        • 0
          ИМХО, рут на андроиде отмирает и не стоит на него надеяться. Сейчас все меньше народу с рутом, все же не 2.3 на дворе, все, для чего нужен был рут, и так работает.
          Я и сам последний раз 6 марта юзал…
          • 0
            А как насчет идиотского запрета записи на SD в 4.4?
            • 0
              Не так уж и страшно, фактически же оно надо только файл-эксплорерам.
              Зато при удалении приложений будет удаляться и его папка на SD карте.
  • +2
    Теперь мне хочется взять код системной звонилки и посмотреть, как USSD-запросы реализованы там. Думаю, API таки есть, но недокументированное и доступное только системным приложениям.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.