На разрабатываемом мною проекте под Android возникла необходимость мониторинга входящих и исходящих звонков и сообщений. Полез читать документацию и подручные книги и к сожалению понял, что задача не совсем из тривиальных, так как каждая часть одной проблемы требует различного подхода к реализации. Вообще заметил, что многие книги или же статьи всегда стараются подавать более легкие примеры, например обработка входящего сообщения, а вот про исходящее ни слова если реализация более сложна. Возможно это мое предубеждение, но уже не первый раз обращаю на это внимание
Решил собрать все воедино и поделиться с теми, кто возможно будет набивать себе оскомину пытаясь найти решение данной проблемы. На оптимальность не претендую, так как сам учусь
Мониторинг входящих сообщений
Наверное реализация этой части самая простая и легкая. В манифесте приложения даем разрешение на обработку получения сообщения
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
И регистрируем Receiver, который будет срабатывать по событию входящего сообщения
<receiver android:name="MessageReceiver" android:enabled="true">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
</receiver>
Как видно из примера кода, при получении сообщения управление будет передаваться Receiver-у MessageReceiver.
Примерная реализация:
public class MessageReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
Bundle bundle = intent.getExtras();
if (bundle != null) {
Object[] pdus = (Object[]) bundle.get("pdus");
SmsMessage[] msgs = new SmsMessage[pdus.length];
ArrayList<String> numbers = new ArrayList<String>();
ArrayList<String> messages = new ArrayList<String>();
for (int i=0; i<msgs.length; i++){ //пробегаемся по всем полученным сообщениям
msgs[i] = SmsMessage.createFromPdu((byte[])pdus[i]);
numbers.add(msgs[i].getOriginatingAddress()); //получаем номер отправителя
messages.add(msgs[i].getMessageBody().toString());//получаем текст сообщения
}
if (messages.size() > 0){
//делаем что-то с сообщениями
}
}
}
}
Мониторинг исходящих сообщений
К сожалению с исходящими сообщениями не так просто как со входящими. Telephony API не предоставляет отдельного события исходящего сообщения. Для меня это выглядит весьма странным в довольно отточенном продукте, которым является Android. Тем не менее решение все-таки есть.
В манифесте приложения даем разрешение на чтение сообщений
<uses-permission android:name="android.permission.READ_SMS"/>
А далее следует создать и зарегистрировать обработчик изменения базы сообщений. Вприцнипе тут можно объеденить обработку входящих и исходящих сообщений, но как-то со входящими сообщениями все намного проще. При обработке исходящих сообщений следует учесть тот факт, что обработчик может быть вызван несколько раз при отправке одного и того-же сообщения. Рекомендуется создать таблицу хешей всех сообщений проверять нету ли обрабатываемого сообщения в базе. В коде ниже показана примитивная проверка по идентификатору сообщения
private static final String CONTENT_SMS = "content://sms/";
private static long id = 0;
//регистрируем обработчик изменений контента сообщений
ContentResolver contentResolver = getBaseContext().getContentResolver();
contentResolver.registerContentObserver(Uri.parse(CONTENT_SMS),true, new OutgoingSmsObserver(new Handler()));
private class OutgoingSmsObserver extends ContentObserver {
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Uri uriSMSURI = Uri.parse(CONTENT_SMS);
Cursor cur = getContentResolver().query(uriSMSURI, null, null,null, null);
cur.moveToNext();
String protocol = cur.getString(cur.getColumnIndex("protocol"));
if(protocol == null){
long messageId = cur.getLong(cur.getColumnIndex("_id"));
//проверяем не обрабатывали ли мы это сообщение только-что
if (messageId != id){
id = messageId;
int threadId = cur.getInt(cur.getColumnIndex("thread_id"));
Cursor c = getContentResolver().query(Uri.parse("content://sms/outbox/" + threadId), null, null, null, null);
c.moveToNext();
//получаем адрес получателя
String address = cur.getString(cur.getColumnIndex("address"));
//получаем текст сообщения
String body= cur.getString(cur.getColumnIndex("body"));
//делаем что-то с сообщением
}
}
}
}
Мониторинг звонков
Android предоставляет возможность мониторить состояние телефона посредством действия android.intent.action.PHONE_STATE, но у меня снова таки возникла проблема получения номера абонента при исходящем звонке, поэтому мне пришлось регистрировать ресивер для двух действий.
В манифесте приложения даем разрешение на чтение состояния телефона и обработки исходящих звонков.
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
И регистрируем Receiver, который будет срабатывать по событию входящего сообщения
<receiver android:name="CallReceiver">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE"/>
<action android:name="android.intent.action.NEW_OUTGOING_CALL" />
</intent-filter>
</receiver>
Примерная реализация:
String phoneNumber = "";
public class CallReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("android.intent.action.NEW_OUTGOING_CALL")) {
//получаем исходящий номер
phoneNumber = intent.getExtras().getString("android.intent.extra.PHONE_NUMBER");
} else if (intent.getAction().equals("android.intent.action.PHONE_STATE")){
String phone_state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
if (phone_state.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
//телефон звонит, получаем входящий номер
phoneNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
} else if (phone_state.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)){
//телефон находится в режиме звонка (набор номера / разговор)
} else if (phone_state.equals(TelephonyManager.EXTRA_STATE_IDLE)){
//телефон находиться в ждущем режиме. Это событие наступает по окончанию разговора, когда мы уже знаем номер и факт звонка
}
}
}
}
Резюмируя проблемы, которые я описал в самом начале, снова таки скажу, что меня не покидало ощущение некоторой недоделанности Android API. Входящее сообщение весьма легко перехватывается, что не скажешь о исходящем. Тоже самое касается и звонков. Конечно можно использовать одно решение и для сообщений, но если для входящих так просто, то почему нету такой простоты для исходящих?
Автор: IchBinJames