Skip to main content

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

· 5 min read

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. Со второй частью статьи пока не успеваю, слишком уж загружен по работе. Надеюсь в ближайшее время с этим вопросом разберусь.