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