Главная » Разработка для Android » Моделирование входящих SMS в эмуляторе

0

Смоделировать входящее SMS в эмуляторе можно двумя способами. Пер- вый уже описывался в этом разделе: можно отправлять сообщение из одного эмулятора в другой, используя порт в качестве телефонного номера.

Но вы также можете использовать отладочные инструменты из Android SDK, о которых шла речь в главе 2, чтобы моделировать входящие сообще- ния с произвольных номеров. Данный процесс показан на рис. 12.1.

Рис. 12.1.

Работа с бинарными SMS

Бинарные сообщения принимаются точно так же, как и обычные, и могут быть извлечены  по тому же принципу.

Чтобы извлечь данные, переданные внутри бинарного SMS, используйте метод getUserData, как показано в следующем фрагменте:

byte[] data = msg.getUserData();

Этот метод возвращает  массив байтов — данные, содержащиеся в со- общении.

Приложение Emergency Responder

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

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

Чтобы  облегчить  задачу потенциальным спасителям, воспользуйтесь геолокационными сервисами, передавая информацию о том, где именно вы находитесь.  Устойчивость сетевой инфраструктуры SMS делает этот вид передачи данных наиболее подходящим  для приложений, работа которых полностью зависит от надежности и доступности  канала связи.

1. Начните с создания нового проекта EmergencyResponder, содержащего одноименную  Активность.

package com.paad.emergencyresponder;

import java.io.IOException; import java.util.ArrayList; import java.util.Locale;

import java.util.concurrent.locks.ReentrantLock;

import java.util.List;

import android.app.Activity; import android.app.PendingIntent; import android.content.Context; import android.content.Intent;

import android.content.IntentFilter;

import android.content.BroadcastReceiver; import android.content.SharedPreferences; import android.location.Address;

1    Все фрагменты кода в этом примере — часть проекта Emergency Responder  из главы 12, их можно загрузить с сайта Wrox.com.

import android.location.Geocoder;

import android.location.Location;м

import android.location.LocationManager;

import android.os.Bundle;

import android.telephony.SmsManager; import android.telephony.SmsMessage; import android.view.View;

import android.view.View.OnClickListener;

import android.widget.ArrayAdapter;

import android.widget.Button; import android.widget.CheckBox; import android.widget.ListView;

public class EmergencyResponder extends Activity {

@Override

public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main);

}

}

2. Добавьте в манифест проекта полномочия, чтобы иметь возможность искать свое местоположение и отправлять/принимать SMS.

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.paad.emergencyresponder">

<application android:icon="@drawable/icon" android:label="@string/app_name">

<activity android:name=".EmergencyResponder" android:label="@string/app_name">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

</application>

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

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

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"

/>

</manifest>

3. Отредактируйте ресурс с разметкой  main.xml. Добавьте  элемент ListView  для отображения списка людей, которые запрашивают об- новление вашего статуса, а также несколько кнопок для отправки от- ветных SMS. Используйте ссылки на внешние ресурсы (вы создадите их в пункте 4), чтобы указать текст для этих кнопок.

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="fill_parent" android:layout_height="fill_parent">

<TextView

android:id="@+id/labelRequestList" android:layout_width="fill_parent" android:layout_height="wrap_content"

android:text="These people want to know if you’re ok" android:layout_alignParentTop="true"

/>

<LinearLayout android:id="@+id/buttonLayout" xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="wrap_content"

android:padding="5px" android:layout_alignParentBottom="true">

<CheckBox android:id="@+id/checkboxSendLocation" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Include Location in Reply"/>

<Button android:id="@+id/okButton" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/respondAllClearButtonText"/>

<Button android:id="@+id/notOkButton" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/respondMaydayButtonText"/>

<Button android:id="@+id/autoResponder" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Setup Auto Responder"/>

</LinearLayout>

<ListView android:id="@+id/myListView" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_below="@id/labelRequestList" android:layout_above="@id/buttonLayout"/>

</RelativeLayout>

4. Отредактируйте файл с внешними ресурсами strings.xml, добавив в него текст для каждой кнопки, а также сообщения  по умолчанию, которые будут использоваться при ответе (в том числе I’m safe и I’m in danger). Вам также необходимо задать текст для распознавания входящих сообщений, на которые нужно отвечать.

<?xml version="1.0" encoding="utf-8"?>

<resources>

<string name="app_name">Emergency Responder</string>

<string name="respondAllClearButtonText">I am Safe and Well

</string>

<string name="respondMaydayButtonText">MAYDAY! MAYDAY! MAYDAY!

</string>

<string name="respondAllClearText">I am safe and well. Worry not!

</string>

<string name="respondMaydayText">Tell my mother I love her.

</string>

<string name="querystring">are you ok?</string>

</resources>

5. На данный  момент графический интерфейс готов. Поэтому  при за- пуске приложения экран должен выглядеть, как на рис. 12.2.

Рис. 12.2.

6. Создайте  внутри  Активности EmergencyResponder новый  список со cтроками  для хранения  телефонных номеров, с которых  будут приходить SMS. В методе OnCreate свяжите этот список с элемен- том ListView с помощью ArrayAdapter и создайте новый объект ReentrantLock, чтобы обеспечить безопасную работу со списком в по- точном режиме.

Затем получите ссылку на элемент CheckBox и добавьте объекты OnClickListener для каждой из кнопок, с помощью которых будет от- правляться ответное сообщение. Первые две из них должны вызывать

метод respond,  тогда как последняя будет делать вызов  заглушки startAutoResponder.

ReentrantLock lock; CheckBox locationCheckBox; ArrayList<String> requesters; ArrayAdapter<String> aa;

@Override

public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.main);

lock = new ReentrantLock();

requesters = new ArrayList<String>();

wireUpControls();

}

private void wireUpControls() {

locationCheckBox = (CheckBox)findViewById(R.id.checkboxSendLocation); ListView myListView = (ListView)findViewById(R.id.myListView);

int layoutID = android.R.layout.simple_list_item_1;

aa = new ArrayAdapter<String>(this, layoutID, requesters);

myListView.setAdapter(aa);

Button okButton = (Button)findViewById(R.id.okButton);

okButton.setOnClickListener(new OnClickListener() {

public void onClick(View arg0) {

respond(true, locationCheckBox.isChecked());

}

});

Button notOkButton = (Button)findViewById(R.id.notOkButton);

notOkButton.setOnClickListener(new OnClickListener() {

public void onClick(View arg0) {

respond(false, locationCheckBox.isChecked());

}

});

Button autoResponderButton = (Button)findViewById(R.id.autoResponder);

autoResponderButton.setOnClickListener(new OnClickListener() {

public void onClick(View arg0) {

startAutoResponder();

}

});

}

public void respond(boolean _ok, boolean _includeLocation) {}

private void startAutoResponder() {}

7. Реализуйте Приемник широковещательных намерений — он будет от- слеживать входящие SMS.

7.1.Начните с воплощения нового статического строкового поля, в ко- тором будет храниться действие для Намерения, срабатывающего при входящем сообщении.

public static final String SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED";

7.2. Затем создайте новый Приемник в виде переменной внутри Актив- ности EmergencyResponder. Он должен отслеживать входящие SMS и вызывать  метод requestReceived, если обнаружит  в них строку are you safe, хранящуюся во внешнем ресурсе, который был создан на шаге 4.

BroadcastReceiver emergencyResponseRequestReceiver =

new BroadcastReceiver() {

@Override

public void onReceive(Context _context, Intent _intent) {

if (_intent.getAction().equals(SMS_RECEIVED)) {

String queryString = getString(R.string.querystring);

Bundle bundle = _intent.getExtras();

if (bundle != null) {

Object[] pdus = (Object[]) bundle.get("pdus"); SmsMessage[] messages = new SmsMessage[pdus.length]; for (int i = 0; i < pdus.length; i++)

messages[i] = SmsMessage.createFromPdu((byte[]) pdus[i]);

for (SmsMessage message : messages) {

if (message.getMessageBody().toLowerCase().contains

(queryString))

requestReceived(message.getOriginatingAddress());

}

}

}

}

};

public void requestReceived(String _from) {}

8. Добавьте в метод onCreate из Активности EmergencyResponder реги- страцию Приемника широковещательных намерений из пункта 7.

@Override

public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main);

lock = new ReentrantLock();

requesters = new ArrayList<String>();

wireUpControls();

IntentFilter filter = new IntentFilter(SMS_RECEIVED);

registerReceiver(emergencyResponseRequestReceiver, filter);

}

9. Перейдите к заглушке requestReceived и сделайте так, чтобы каждый телефонный номер, с которого приходило  SMS, содержащее строку are you safe, добавлялся в список запросов.

public void requestReceived(String _from) {

if (!requesters.contains(_from)) { lock.lock(); requesters.add(_from); aa.notifyDataSetChanged(); lock.unlock();

}

}

10. Теперь Активность EmergencyResponder должна следить за приходом SMS и по мере поступления добавлять  их в элемент ListView. Запу- стите программу  и пошлите  сообщение  устройству  или эмулятору, на котором эта программа функционирует. Когда сообщение придет, оно должно отобразиться в списке, как показано на рис. 12.3.

Рис. 12.3.

11. Отредактируйте Активность таким образом, чтобы пользователь мог отвечать на эти сообщения.

Начните с реализации метода-заглушки respond, который вы создали в пункте 6. Он должен перебрать все номера в списке и отослать по каждому из них SMS, текст которого будет основываться на строках, заданных вами в виде ресурсов в пункте 4. Сообщения  будут переда- ваться с помощью перегруженного метода respond, работу над которым вы завершите на следующем шаге.

public void respond(boolean _ok, boolean _includeLocation) { String okString = getString(R.string.respondAllClearText); String notOkString = getString(R.string.respondMaydayText);

String outString = _ok ? okString : notOkString; ArrayList<String> requestersCopy =

(ArrayList<String>)requesters.clone();

for (String to : requestersCopy)

respond(to, outString, _includeLocation);

}

private void respond(String _to, String _response, boolean _includeLocation) {}

12. Измените метод respond, который  отвечает за отправку  каждого от- ветного SMS.

Прежде чем слать сообщение, удалите всех потенциальных полу- чателей  из списка. Если ответ должен  включать  данные о текущем местоположении, получите  их с помощью объекта LocationManager, затем отправьте второе сообщение, содержащие ваши долготу/широту и адрес из геокодировщика.

public void respond(String _to, String _response, boolean _includeLocation) {

// Удалите адресата из списка людей,

// которым мы должны отвечать. lock.lock(); requesters.remove(_to); aa.notifyDataSetChanged(); lock.unlock();

SmsManager sms = SmsManager.getDefault();

// Отправьте сообщение

sms.sendTextMessage(_to, null, _response, null, null); StringBuilder sb = new StringBuilder();

// Если это необходимо, получите текущее

// местоположение и отправьте его по SMS. if (_includeLocation) {

String ls = Context.LOCATION_SERVICE;

LocationManager lm = (LocationManager)getSystemService(ls);

Location l =

lm.getLastKnownLocation(LocationManager.GPS_PROVIDER);

sb.append("I’m @:\n");

sb.append(l.toString() + "\n");

List<Address> addresses;

Geocoder g = new Geocoder(getApplicationContext(), Locale.getDefault());

try {

addresses = g.getFromLocation(l.getLatitude(), l.getLongitude(), 1);

if (addresses != null) {

Address currentAddress = addresses.get(0);

if (currentAddress.getMaxAddressLineIndex() > 0) {

for (int i = 0;

i < currentAddress.getMaxAddressLineIndex();

i++)

{

sb.append(currentAddress.getAddressLine(i));

sb.append("\n");

}

}

else {

if (currentAddress.getPostalCode() != null)

sb.append(currentAddress.getPostalCode());

}

}

} catch (IOException e) {}

ArrayList<String> locationMsgs =

sms.divideMessage(sb.toString());

for (String locationMsg : locationMsgs)

sms.sendTextMessage(_to, null, locationMsg, null, null);

}

}

13. В экстренных ситуациях чрезвычайно важно, чтобы сообщения дохо- дили до адресата. Улучшите надежность своего приложения, добавив в него возможность повторной  отправки.  Отслеживая успешность доставки SMS, вы можете отослать его второй раз, если в первый воз- никли какие-то проблемы.

13.1.Начните с создания нового статического строкового поля в Актив- ности EmergencyResponder. Оно будет использоваться для опреде- ления локального  действия  SMS_SENT.

public static final String SENT_SMS = "com.paad.emergencyresponder.SMS_SENT";

13.2. Отредактируйте метод respond,  добавив  в него новое Намере- ние PendingIntent, которое будет передавать действие, созданное в предыдущем пункте (сигнализируя о том, что отправка сообще-

ния завершена). Намерение должно  включать  номер получателя в виде дополнительного параметра.

public void respond(String _to, String _response, boolean _includeLocation) {

// Удалите адресата из списка людей,

// которым мы должны отвечать. lock.lock(); requesters.remove(_to); aa.notifyDataSetChanged(); lock.unlock();

SmsManager sms = SmsManager.getDefault();

Intent intent = new Intent(SENT_SMS);

intent.putExtra("recipient", _to);

PendingIntent sent = PendingIntent.getBroadcast(getApplicationContext(),

0, intent, 0);

// Отправьте сообщение

sms.sendTextMessage(_to, null, _response, sent, null);

StringBuilder sb = new StringBuilder();

if (_includeLocation) {

[ . . . ранее написанный код для поиска местоположения . . . ] ArrayList<String> locationMsgs =

sms.divideMessage(sb.toString());

for (String locationMsg : locationMsgs)

sms.sendTextMessage(_to, null, locationMsg, sentIntent, null);

}

}

13.3. Реализуйте новый  абстрактный класс  BroadcastReceiver для отслеживания этого Намерения. Переопределите его обработчик onReceive, чтобы подтверждать успешность доставки SMS. Если сообщение не было доставлено, адресата необходимо вернуть об- ратно в список.

private BroadcastReceiver attemptedDeliveryReceiver = new

BroadcastReceiver() {

@Override

public void onReceive(Context _context, Intent _intent) {

if (_intent.getAction().equals(SENT_SMS)) {

if (getResultCode() != Activity.RESULT_OK) {

String recipient = _intent.getStringExtra("recipient");

requestReceived(recipient);

}

}

}

};

13.4.В завершение зарегистрируйте новый Приемник широковещательных намерений в методе onCreate, принадлежащем Активности Emer- gencyResponder.

@Override

public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main);

lock = new ReentrantLock();

requesters = new ArrayList<String>();

wireUpControls();

IntentFilter filter = new IntentFilter(SMS_RECEIVED);

registerReceiver(emergencyResponseRequestReceiver, filter);

IntentFilter attemptedDeliveryfilter = new IntentFilter(SENT_SMS);

registerReceiver(attemptedDeliveryReceiver, attemptedDeliveryfilter);

}

Данный пример сознательно упрощен, чтобы сосредоточить ваше вни- мание на работе с SMS. Внимательные читатели должны были заметить по крайней мере две вещи, которые могут быть улучшены.

Регистрацию Приемника широковещательных намерений, созданного и зарегистрированного в пунктах 7 и 8, предпочтительнее проводить внутри  манифеста,  чтобы приложение могло отвечать на входящие SMS, даже если оно не запущено.

Обработка  входящих SMS, выполняющаяся внутри Приемника (пун- кты 7 и 9), должна  быть перенесена  в Сервис и работать в фоновом потоке. То же самое следует сделать с отправкой ответных сообщений, описанной  в пункте 13.

Предлагаем реализовать данные улучшения самостоятельно, пользуясь навыками,  приобретенными в главе 9.

Источник: Майер P. Android 2 : программирование приложений для планшетных компьютеров и смартфонов : [пер. с англ. ] / Рето Майер. — М. : Эксмо, 2011. — 672 с. — (Мировой компьютерный бестселлер).

По теме:

  • Комментарии