Простой USSD-запрос в Android 4.0+

в 3:21, , рубрики: accessibility service, android, ussd

В 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 — ваша процедура, которая что-то делает с полученным тектом.

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

IntentFilter mFilter = new IntentFilter("SMARTWATCH_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-запрос.

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

Автор: Gordon01

Источник



https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js