Android: Обработка СМС

0. Вместо вступления


Периодически (когда у меня выпадает свободный вечер, и наш «клуб» организует игру) я играю в регбол. «Клуб» организован таким образом, что в день игры всем участникам приходит СМС такого вида:

Регбол! Сегодня в 19-30. Двор школы №30: ул. Володарского, 20. Открытая площадка с резиновым покрытием. Тел. 8 (951) ***-**-**.

И вот я подумал — почему бы не написать небольшое приложение, которое будет отлавливать эти сообщения, и забивать их в гугл-календарь. Зачем? Да, в основном, just for fun, ибо я не настолько занятой человек, чтобы мне были жизненно необходимы автоматические секретари.

Итак, приложение будет уметь следующее:
  • Следить за входящими сообщениями. Если пришло сообщение от адресата RM FIGHT, то нужно сверить текст сообщения с шаблоном, и при совпадении создать мероприятие в гугл-календаре. Если же текст сообщения с шаблоном не совпадает (например, просто какие-то новости пришли), то сохраняем сообщение в базе, чтобы потом можно было его прочитать.
  • Показывать сообщения от этого адресата, не попадающие в категорию «Оповещение об игре» (новости, реклама и т.д.).

В рамках статьи я полагаю, что у читателя есть базовые знания — как создать проект, что такое файл Manifest, и с чего вообще начинать разработку под андроид — на этот счет есть куча разных туториалов, и здесь на этом останавливаться не будем. В то же время статья не предназначена для продвинутых андроид-девелоперов, в ней будут рассматриваться достаточно базовые вещи, вроде мониторинга и обработки смс, работы с базой данных, подключения по HTTP.

Итак, приступим. Кстати, используемая версия SDK — 14 (Android 4.0).

1. Перехватываем СМС


Для мониторинга входящих СМС первым делом нам необходимо запросить разрешение на их получение. Для этого в файл AndroidManifest.xml необходимо добавить запись вида:

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


Следующим шагом будет реализация монитора для прослушивания входящих сообщений. Для этого в манифест-файле регистрируем receiver:

<receiver android:name="SMSMonitor">
    <intent-filter android:priority="100">
        <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
    </intent-filter>
</receiver>


Здесь мы установили приоритет равным 100, чтобы наше приложение получило доступ к входящему СМС раньше стандартного обработчика СМС, которое имеет нулевой приоритет. После того, как наше приложение обработает сообщение, нет смысла отдавать его системе, и помещать в папку Входящие.

Теперь создаем класс, расширяющий BroadcastReceiver:

public class SMSMonitor extends BroadcastReceiver {
    private static final String ACTION = "android.provider.Telephony.SMS_RECEIVED";
    @Override
    public void onReceive(Context context, Intent intent) {

    }
}


В этом классе реализуется абстрактный метод onReceive(), который вызывается системой каждый раз при получении сообщения. В методе прописываем:

if (intent != null && intent.getAction() != null &&
        ACTION.compareToIgnoreCase(intent.getAction()) == 0) {
    Object[] pduArray = (Object[]) intent.getExtras().get("pdus");
    SmsMessage[] messages = new SmsMessage[pduArray.length];
    for (int i = 0; i < pduArray.length; i++) {
        messages[i] = SmsMessage.createFromPdu((byte[]) pduArray[i]);
    }
}


Здесь мы получаем сообщение с помощью метода intent.getExtras().get("pdus"), который возвращает массив объектов в формате PDU — эти объекты мы потом приводим к типу SmsMessage с помощью метода createFromPdu().

Теперь внимание. То, что мы делаем после получения сообщения, должно исполняться быстро. Broadcast receiver получает в системе высокий приоритет, но он работает в фоновом режиме и должен выполняться за короткое время, так что наши возможности ограничены. Например, мы можем сгенерировать уведомление или запустить службу, чтобы продолжить обработку в ней. Поэтому мы проверим отправителя сообщения, и если это уведомление об игре — мы вытащим текст сообщения и запустим службу, в которой уже и будем проводить обработку этого сообщения.

Дописываем в методе onReceive():

String sms_from = messages[0].getDisplayOriginatingAddress();
if (sms_from.equalsIgnoreCase("RM FIGHT")) {
    StringBuilder bodyText = new StringBuilder();
    for (int i = 0; i < messages.length; i++) {
        bodyText.append(messages[i].getMessageBody());
    }
    String body = bodyText.toString();
    Intent mIntent = new Intent(context, SmsService.class);
    mIntent.putExtra("sms_body", body);
    context.startService(mIntent);

    abortBroadcast();
}


Здесь мы составляем текст сообщения (в случае, когда сообщение было длинным и пришло в нескольких смс-ках, каждая отдельная часть хранится в messages[i]) и вызываем метод abortBroadcast(), чтобы предотвратить дальнейшую обработку сообщения другими приложениями.

2. Обрабатываем СМС


В предыдущем пункте мы остановились на том, что запускаем службу для обработки смс с помощью метода startService(). Собственно, что такое службы и с чем их едят хорошо описано на официальном сайте, поэтому здесь на этом останавливаться не будем.

Создаем класс SmsService, расширяющий класс Service:

public class SmsService extends Service {
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}


Поскольку у нас локальная служба, метод onBind() возвращает null.

Для вывода уведомлений нам понадобится вспомогательный метод showNotification():

private void showNotification(String text) {
	PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0);
	Context context = getApplicationContext();
	Notification.Builder builder = new Notification.Builder(context)
		.setContentTitle("Rugball")
		.setContentText(text)
		.setContentIntent(contentIntent)
		.setSmallIcon(R.drawable.ic_launcher)
		.setAutoCancel(true);
	NotificationManager notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
	Notification notification = builder.getNotification();
	notificationManager.notify(R.drawable.ic_launcher, notification);
}


В методе onStartCommand() прописываем:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
	String sms_body = intent.getExtras().getString("sms_body");
	showNotification(sms_body);
	return START_STICKY;
}


Осталось, собственно, реализовать метод smsProcess(), который добавит смс в базу и сформирует мероприятие в гугл-календаре. Этим и займемся в следующей части статьи.

UPDATE: выложил код на GitHub. Со второй частью статьи пока не успеваю, слишком уж загружен по работе. Надеюсь в ближайшее время с этим вопросом разберусь.
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 11

    –2
    >> Создаем класс SmsService, расширяющий класс Service…

    Что, простите?
      –2
      Я намекаю на то, что он ничего не расширяет. Он просто наследуется и получает «дары» родителя.
      0
      А почему Вы решили, что стандартный обработчик смс имеет нулевой приоретет?
        0
        Вообще, в официальной документации на это не натыкался, но вот здесь человек пишет:
        Looks like the system default SMS processing application uses priority of 0, so you could try 1 for your application to be before it.
        Ну и, как показала практика, при нулевом значении фишка не срабатывает, поставил 100 — заработало.
          0
          Просто насколько я понял, что чем больше число, тем выше приоритет. Соответственно, если у стандартного обработчика нулевой приоритет, то он срабатывает самым последним.
            0
            Ну да, просто если мы у нашего intent-filter приоритет не укажем, то он тоже будет нулевым. Соответственно, какой обработчик сработает первым — неизвестно, а нам нужно, чтобы наш обработчик сработал первым — вот и ставим число побольше.
              0
              Просто я писал свой черный список для смс и сталкивался с теми же проблемами
        0
        Исполнение сервиса происходит в основном потоке, поэтому делегирование обработчика сообщения в отдельный сервис ничего в данном случае не дает. В случае задержки более 10-ти секунд получите ANR
          0
          Выход из ситуации — запускать Runnable, я правильно понимаю?
            0
            Или использовать IntentService и все операции выполнять в onHandleIntent.
            0
            Несомненно — лучший комментарий!

          Only users with full accounts can post comments. Log in, please.