Мониторинг сообщений и звонков в Android

в 10:27, , рубрики: android, call, sms, Разработка под android, метки: , , ,

На разрабатываемом мною проекте под 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js