Emergency Responder SMS 例子
在这个例子里,你将创建一个SMS应用程序,它将Android手机变成紧急响应的信号站。
一旦完成这个程序,下次你不幸接近一个外来入侵或是发现你在一个机器人暴乱的场景中,你就能设置你的手机,用一条友好的消息(或是一个紧急求救的呼喊)来自动回复你的朋友和家庭成员关于你自身状况的询问。
为了让可能的拯救变得更容易,你将使用基于位置的服务来告诉你的救星找到你的精确位置。SMS网络基础设施的强壮性使SMS成为对可靠性和可访问性要求严格的程序的理想选择。
1. 创建一个新的EmergencyResponder工程,包含一个EmergencyResponder Activity。
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;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationManager;
import android.os.Bundle;
import android.telephony.gsm.SmsManager;
import android.telephony.gsm.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 icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
}
}
2. 在工程Manifest中添加查找位置和发送和接收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.ACCESS_GPS”/>
<uses-permission
android:name=”android.permission.ACCESS_LOCATION”/>
<uses-permission
android:name=”android.permission.RECEIVE_SMS”/>
<uses-permission android:name=”android.permission.SEND_SMS”/>
</manifest>
3. 修改main.xml Layout资源。包含一个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. 到这个时点,GUI就完成了,所以,启动应用程序,你应该看到如图9-3所示的画面。
图9-3
6. 在EmergencyResponder Activity中创建一个字符串数组来储存那些关心你的状态的人员号码。将其绑定到ListView上,通过在onCreate方法中使用ArrayAdapter来实现。创建一个新的ReentrantLock对象来确保处理数组时的线程安全。
获取CheckBox的引用,并为每个响应按钮添加ClickListener。每个按钮应该调用响应方法,“Setup Auto Responder”按钮应该调用startAutoResponder桩函数。
ReentrantLock lock;
CheckBox locationCheckBox;
ArrayList<String> requesters;
ArrayAdapter<String> aa;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
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. 接下来,实现一个BroadcastReceiver来监听新来的SMS消息。
7.1 创建一个静态字符串变量来储存新来的SMS消息的Intent动作。
public static final String SMS_RECEIVED =“android.provider.Telephony.SMS_RECEIVED”;
7.2 然后在EmergencyResponder Activity中创建一个BroadcastReceiver的变量。receiver应该监听外来的SMS消息,如果SMS消息中包含“are you safe”(第4步中以外部资源的方式定义)字符串,调用requestReceived方法。
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方法,来注册第7步中创建的BroadcastReceiver。
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
lock = new ReentrantLock();
requesters = new ArrayList<String>();
wireUpControls();
IntentFilter filter = new IntentFilter(SMS_RECEIVED);
registerReceiver(emergencyResponseRequestReceiver, filter);
}
9. 更新requestReceived桩函数,将每个询问状况消息的号码添加到“requesters”数组中。
public void requestReceived(String _from) {
if (!requesters.contains(_from)) {
lock.lock();
requesters.add(_from);
aa.notifyDataSetChanged();
lock.unlock();
}
}
10. EmergencyResponder Activity现在应该监听状况请求的SMS消息,并在接收到时添加到ListView中。启动应用程序,使用DDMS模拟器来模拟外来的SMS消息,如图9-4所示。
图9-4
11. 现在,更新Activity来允许用户响应这些状态请求。
现在来完成第6步中创建的respond桩方法。它应该遍历数组中所有的状态请求者,并给每个发送一个新的SMS消息。SMS消息文本基于第4步中定义的响应字符串资源。使用重载的respond方法来发送SMS,这些内容将在下一步中完成。
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的发送。
在发送SMS消息之前,从“请求者”列表中删除每个接收者。如果你想回复当前位置,在发送第二个包含当前位置的经度/纬度和地理位置信息之前,使用LocationManager得到它。
public void respond(String _to, String _response, boolean _includeLocation) {
// Remove the target from the list of people we need to respond to.
lock.lock();
requesters.remove(_to);
aa.notifyDataSetChanged();
lock.unlock();
SmsManager sms = SmsManager.getDefault();
// Send the message
sms.sendTextMessage(_to, null, _response, null, null);
StringBuilder sb = new StringBuilder();
// Find the current location and send it as SMS messages if required.
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);
}
}
此时此刻,sendTextMessage的第四个参数(sentIntent)需要一个非空的值。在这个例子中,我们会在13步中添加这个参数,所以,此时运行程序的话会抛出一个异常。
13. 在紧急情况下,能成功发送消息是非常重要的。通过增加自动重试功能来增强应用程序的强壮性。监视SMS是否发送成功,这样,在没有成功发送的情况下你可以重新发送消息。
13.1. 创建一个新的静态字符串,用于“SMS Sent”动作。
public static final String SENT_SMS = “com.paad.emergencyresponder.SMS_SENT”;
13.2. 更新respond方法,包含一个在上一步中创建的动作的PendingIntent用于监视SMS的发送。Intent中需要包含接收者的号码。
public void respond(String _to, String _response, boolean _includeLocation) {
// Remove the target from the list of people we need to respond to.
lock.lock();
requesters.remove(_to);
aa.notifyDataSetChanged();
lock.unlock();
SmsManager sms = SmsManager.getDefault();
Intent intent = new Intent(SENT_SMS);
intent.putExtra(“recipient”, _to);
PendingIntent sentIntent = PendingIntent.getBroadcast(getApplicationContext(),0,intent,0);
// Send the message
sms.sendTextMessage(_to, null, _response, sentIntent, null);
StringBuilder sb = new StringBuilder();
if (_includeLocation) {
[ … existing respond method that finds the current location … ]
ArrayList<String> locationMsgs = sms.divideMessage(sb.toString());
for (String locationMsg : locationMsgs)
sms.sendTextMessage(_to, null, locationMsg, sentIntent, null);
}
}
13.3. 然后,实现一个新的BroadcastReceiver来监听这个Intent。重写它的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. 最后,注册新定义的BroadcastReceiver。
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
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);
}
现在,你可以运行应用程序。为了测试它,你需要打开两个模拟器实例。
使用DDMS emulator control来模拟从一个模拟器往另一个模拟器发送“are you safe”消息(使用端口号作为源号码)。当你按下任一响应按钮时,你会看到一个新的SMS消息出现在模拟器上。