Как обрабатывать в Андроиде USSD/MMI запросы

Зачем

USSD/MMI запросы- это те коды, которые можно набирать на номеронабирателе телефона, начинающиеся со звездочки («*») и заканчивающиеся решеткой («#»), чтобы получить, например баланс счета или код IMEI телефона.

Запросы еще нужны, если вы хотите вшить какие нибудь «инженерные» коды, навроде пасхальных яиц в номеронабиратель.

Проблема

Много лет висит запрос фичи разработчикам Андроида на включение API, которое бы позволило отправлять USSD запросы и затем их обрабатывать. Под запросом уже собрано несколько сот голосов разработчиков- изменений пока нет.

Не так давно появилось классное приложение для проверки баланса USSD Checker, которое периодически проверяет баланс и красиво отображает затраты на связь.

Для этого нужно сделать звонок на номер вроде этого: *100#. С этим никаких проблем нет. Но: приложение должно получить ответ на USSD запрос. С этим закавыка- Андроид сам создает диалог и показывает ответ, оставляя ваше приложениев неведении. Если хотите разобраться, как все таки получить ответ в отсутствие API, это статья для вас.

Замечание: поскольку это недокументированный способ, он может не работать на всех телефонах, если в референсную систему разработчиком телефона внесены изменения.

 

Как обрабатывать USSD запросы

В пакете package com.android.phone класс PhoneUtils, загружаемый как часть Андроида, связанного с телефонией, пытается установить связь с сервисом com.android.ussd.IExtendedNetworkService.

На тех телефонах, что я пробовал, такого сервиса не установлено. Тогда Андроид просто показывает тот самый диалог с полученным ответом. Если же сервис есть, он запускается и делаются вызовы, которые и позволят нам из сервиса увидеть, какой ответ получен.

Я расскажу, как я написал небольшое приложение call me back, которое есть в маркете, для отправки USSD запроса в сервис «Перезвони мне». Такой сервис есть у многих операторов, и это бесплатно- иначе в чем же смысл ;).

К тому же, это, пожалуй, единственный способ, как это сделать на Java без необходимости рутить телефон. Недостаток этого кроется в том, что телефон после установки приложения придется перезагрузить. Это потому, что сшивание с сервисом происходит только в момент создания экземпляра класса PhoneUtils- а именно, при включении телефона.

Ниже приведен aidl файл IExtendedNetworkService.aidl, который нужно включить в проект Eclipse.

 

package com.android.internal.telephony;

/**
 *  Interface used to interact with extended MMI/USSD network service.
 */
interface IExtendedNetworkService {
    /**
     * Set a MMI/USSD command to ExtendedNetworkService for further process.
     * This should be called when a MMI command is placed from panel.
     * @param number the dialed MMI/USSD number.
     */
    void setMmiString(String number);

    /**
     * return the specific string which is used to prompt MMI/USSD is running
     */
    CharSequence getMmiRunningText();

    /**
     * Get specific message which should be displayed on pop-up dialog.
     * @param text original MMI/USSD message response from framework
     * @return specific user message correspond to text. null stands for no pop-up dialog need to show.
     */
    CharSequence getUserMessage(CharSequence text);

    /**
     * Clear timeout message and pre-set MMI/USSD command
     * This should be called when user cancel a pre-dialed MMI command.
     */
    void clearMmiString();
}

/**
 *  Interface used to interact with extended MMI/USSD network service.
 */
interface IExtendedNetworkService {
    /**
     * Set a MMI/USSD command to ExtendedNetworkService for further process.
     * This should be called when a MMI command is placed from panel.
     * @param number the dialed MMI/USSD number.
     */
    void setMmiString(String number);

    /**
     * return the specific string which is used to prompt MMI/USSD is running
     */
    CharSequence getMmiRunningText();

    /**
     * Get specific message which should be displayed on pop-up dialog.
     * @param text original MMI/USSD message response from framework
     * @return specific user message correspond to text. null stands for no pop-up dialog need to show.
     */
    CharSequence getUserMessage(CharSequence text);

    /**
     * Clear timeout message and pre-set MMI/USSD command
     * This should be called when user cancel a pre-dialed MMI command.
     */
    void clearMmiString();
}

 

И по нему создадать свой сервис (не забудьте ему в манифесте указать искомый алиас IExtendedNetworkService):

 

package com.commandus.ussd;

import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.IBinder;
import android.os.PatternMatcher;
import android.os.RemoteException;
import android.util.Log;

import com.android.internal.telephony.IExtendedNetworkService;
import com.commandus.callmeback.R;

/**
 * Service implements IExtendedNetworkService interface.
 * USSDDumbExtendedNetworkService
 * Service must have name "com.android.ussd.IExtendedNetworkService" of the intent declared
 * in the Android manifest file so com.android.phone.PhoneUtils class bind
 * to this service after system rebooted.
 * Please note service is loaded after system reboot!
 * Your application must check is system rebooted.
 * @see Util#syslogHasLine(String, String, String, boolean)
 */
public class USSDDumbExtendedNetworkService extends Service {
	public static final String TAG = "CommandusUSSDExtNetSvc";
	public static final String LOG_STAMP = "*USSDTestExtendedNetworkService bind successfully*";
	public static final String URI_SCHEME = "ussd";
	public static final String URI_AUTHORITY = "commandus.com";
	public static final String URI_PATH = "/";
	public static final String URI_PAR = "return";
	public static final String URI_PARON = "on";
	public static final String URI_PAROFF = "off";
	public static final String MAGIC_ON = ":ON;)";
	public static final String MAGIC_OFF = ":OFF;(";
	public static final String MAGIC_RETVAL = ":RETVAL;(";

	private static boolean mActive = false;
	private static CharSequence mRetVal = null;
	private Context mContext = null;
	private String msgUssdRunning = "xxx..."; 

    final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
        	if (Intent.ACTION_INSERT.equals(intent.getAction())) {
        		mContext = context;
        		if (mContext != null) {
        			msgUssdRunning = mContext.getString(R.string.msgRunning);
        			mActive = true;
        			Log.d(TAG, "activate");
        		}
            } else if (Intent.ACTION_DELETE.equals(intent.getAction())) {
            	mContext = null;
            	mActive = false;
            	Log.d(TAG, "deactivate");
            }
        }
    };

    private final IExtendedNetworkService.Stub mBinder = new IExtendedNetworkService.Stub() {
		@Override
		public void setMmiString(String number) throws RemoteException {
			Log.d(TAG, "setMmiString: " + number);
		}

		@Override
		public CharSequence getMmiRunningText() throws RemoteException {
			Log.d(TAG, "getMmiRunningText: " + msgUssdRunning);
			return msgUssdRunning;
		}

		@Override
		public CharSequence getUserMessage(CharSequence text)
				throws RemoteException {
			if (MAGIC_ON.contentEquals(text)) {
				mActive = true;
				Log.d(TAG, "control: ON");
				return text;
			} else {
				if (MAGIC_OFF.contentEquals(text)) {
					mActive = false;
					Log.d(TAG, "control: OFF");
					return text;
				} else {
					if (MAGIC_RETVAL.contentEquals(text)) {
						mActive = false;
						Log.d(TAG, "control: return");
						return mRetVal;
					}
				}
			}

			if (!mActive) {
				Log.d(TAG, "getUserMessage deactivated: " + text);
				return text;
			}
			String s = text.toString();
			// store s to the !
			Uri uri = new Uri.Builder()
				.scheme(URI_SCHEME)
				.authority(URI_AUTHORITY)
				.path(URI_PATH)
				.appendQueryParameter(URI_PAR, text.toString())
				.build();
			sendBroadcast(new Intent(Intent.ACTION_GET_CONTENT, uri));
			mActive = false;
			mRetVal = text;
			Log.d(TAG, "getUserMessage: " + text + "=" + s);
			return null;
		}

		@Override
		public void clearMmiString() throws RemoteException {
			Log.d(TAG, "clearMmiString");
		}
    };

    /**
     * Put stamp to the system log when PhoneUtils bind to the service
     * after Android has rebooted. Application must call {@link Util#syslogHasLine(String, String, String, boolean)} to
     * check is phone rebooted or no. Without reboot phone application does not bind tom this service!
     */
	@Override
	public IBinder onBind(Intent intent) {
		// Do not localize!
		Log.i(TAG, LOG_STAMP);
		IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_INSERT);
        filter.addAction(Intent.ACTION_DELETE);
        filter.addDataScheme(URI_SCHEME);
        filter.addDataAuthority(URI_AUTHORITY, null);
        filter.addDataPath(URI_PATH, PatternMatcher.PATTERN_LITERAL);
		registerReceiver(mReceiver, filter);

		return mBinder;
	}

	public IBinder asBinder() {
		Log.d(TAG, "asBinder");
		return mBinder;
	}

}

 

Из кода видно, сервис в журнал делает отметку: «сервис запущен». Остается в GUI коде проверить, был ли сервис запущен, иначе говоря, был ли Андроид перезапущен (а иначе мы не дождемся ответа).

И последнее, так как сервис запускается сам, то из сервиса в GUI передача событий делается посылкой интентов. В активностях нужно зарегистрировать их класс- получатель.

В заключение, чтение исходников Андроида наталкивает на мысль- и все таки это работает.

 

Comments

comments

Powered by Facebook Comments

Как обрабатывать в Андроиде USSD/MMI запросы: 28 комментариев

  1. Здравствуйте!
    Уточните, пожалуйста, как это сделать «не забудьте ему в манифесте указать искомый алиас IExtendedNetworkService)»
    Не могу найти инфы о том как прописать алиас для сервиса

  2. Уведомление: How to close/cancel/dismiss a System Dialog programatically (Android) | PHP Developer Resource

  3. Hi
    tank for your work
    but when i import this code to my eclipse code i recive many errors that said to me that any @Overrided functions in USSDDumbExtendedNetworkService.java file is incorrect. also when is use eclipse to override IExtendedNetworkService.Stub functions there is no functions such setMmiString, getMmiRunningText, getUserMessage to override!!
    i use ADT20.0, elipse juno and android2.3.3 platform.
    Also i check your project call me back and see all of these errors.

    (Sorry for my bad english)

  4. Уведомление: Complile error for android USSD code sevice | Jisku.com - Developers Network

  5. Уведомление: Mobile phone check balance USSD? | Free Android Enthusiasts

  6. Уведомление: Mobile phone check balance USSD? | Jisku.com - Developers Network

  7. Уведомление: USSD service not working - feed99

  8. Уведомление: Intercepting USSD calls in Android | Code Vault

  9. Уведомление: Using IExtendedNetworkService to get USSD response in Android video

  10. Thank you for sharing.
    I tried turn off and turn on my android phone after running this sample code, but I always get msgNeedReboot alert.
    Could you let me know why this occurs? I am looking forward your help.
    Thanks again.

  11. Мне кажется, что у вас, во-первых, продублирован код интерфейса, а, во-вторых, эклипс ругается на IExtendedNetworkService.Stub.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>