Introducing SMS and MMS
❘
417
<item>Next 8 hours</item>
</string-array>
<array name="respondForValues">
<item>0</item>
<item>5</item>
<item>15</item>
<item>30</item>
<item>60</item>
<item>120</item>
<item>480</item>
</array>
</resources>
4. Now create a new
AutoResponder
Activity, populating it with the layout you created in
Step 1.
package com.paad.emergencyresponder;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.res.Resources;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.BroadcastReceiver;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Bundle;
418
❘
CHAPTER 12 TELEPHONY AND SMS
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.autoresponder);
5.1. Start by getting references to each View.
respondForSpinner = (Spinner)findViewById(R.id.spinnerRespondFor);
locationCheckbox = (CheckBox)findViewById(R.id.checkboxLocation);
responseTextBox = (EditText)findViewById(R.id.responseText);
5.2. Populate the Spinner to let users select the auto-responder expiry time.
ArrayAdapter<CharSequence> adapter =
ArrayAdapter.createFromResource(this,
R.array.respondForDisplayItems,
android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(
android.R.layout.simple_spinner_dropdown_item);
respondForSpinner.setAdapter(adapter);
5.3. Now wire up the OK and Cancel buttons to let users save or cancel setting changes.
Button okButton = (Button) findViewById(R.id.okButton);
okButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
savePreferences();
setResult(RESULT_OK, null);
finish();
}
});
Button cancelButton = (Button) findViewById(R.id.cancelButton);
cancelButton.setOnClickListener(new View.OnClickListener() {
private void updateUIFromPreferences() {
// Get the saves settings
String preferenceName = getString(R.string.user_preferences);
SharedPreferences sp = getSharedPreferences(preferenceName, 0);
String autoResponsePref = getString(R.string.autoRespondPref);
String responseTextPref = getString(R.string.responseTextPref);
String autoLocPref = getString(R.string.includeLocationPref);
String respondForPref = getString(R.string.respondForPref);
boolean autoRespond = sp.getBoolean(autoResponsePref, false);
String respondText = sp.getString(responseTextPref, "");
boolean includeLoc = sp.getBoolean(includeLocPref, false);
int respondForIndex = sp.getInt(respondForPref, 0);
// Apply the saved settings to the UI
if (autoRespond)
respondForSpinner.setSelection(respondForIndex);
else
respondForSpinner.setSelection(0);
locationCheckbox.setChecked(includeLoc);
responseTextBox.setText(respondText);
}
7. Complete the
savePreferences
stub to save the current UI settings to a Shared Preferences
file.
private void savePreferences() {
// Get the current settings from the UI
boolean autoRespond =
respondForSpinner.getSelectedItemPosition() > 0;
int respondForIndex = respondForSpinner.getSelectedItemPosition();
boolean includeLoc = locationCheckbox.isChecked();
8.1. Start by creating the action String that will represent the Alarm Intent.
public static final String alarmAction =
"com.paad.emergencyresponder.AUTO_RESPONSE_EXPIRED";
8.2. Then create a new Broadcast Receiver instance that listens for an Intent that
includes the action specified in Step 8.1. When this Intent is received, it should
modify the auto-responder settings to disable the automatic response.
private BroadcastReceiver stopAutoResponderReceiver = new
BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(alarmAction)) {
String preferenceName = getString(R.string.user_preferences);
SharedPreferences sp = getSharedPreferences(preferenceName,0);
Editor editor = sp.edit();
editor.putBoolean(getString(R.string.autoRespondPref), false);
editor.commit();
}
}
};
8.3. Finally, complete the
setAlarm
method. It should cancel the existing alarm if the
auto-responder is turned off; otherwise, it should update the alarm with the latest
expiry time.
PendingIntent intentToFire;
private void setAlarm(int respondForIndex) {
// Create the alarm and register the alarm intent receiver.
AlarmManager alarms =
(AlarmManager)getSystemService(ALARM_SERVICE);
if (intentToFire == null) {
application manifest.
<?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>
<activity
android:name=".AutoResponder"
android:label="Auto Responder Setup"/>
</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>
10. To enable the auto-responder, return to the Emergency Responder Activity and update the
startAutoResponder
method stub that you created in the previous example. It should open
the
String preferenceName = getString(R.string.user_preferences);
SharedPreferences prefs = getSharedPreferences(preferenceName,
0);
Summary
❘
423
String autoRespondPref = getString(R.string.autoRespondPref)
boolean autoRespond = prefs.getBoolean(autoRespondPref, false);
if (autoRespond) {
String responseTextPref =
getString(R.string.responseTextPref);
String includeLocationPref =
getString(R.string.includeLocationPref);
String respondText = prefs.getString(responseTextPref, "");
boolean includeLoc = prefs.getBoolean(includeLocationPref,
false);
respond(_from, respondText, includeLoc);
}
}
}
All code snippets in this example are part of the Chapter 12 Emergency Responder 2 project, available for download at Wrox.com.
You should now have a fully functional interactive and automated emergency responder.
SUMMARY
The telephony stack is one of the fundamental technologies available on mobile phones. While not
all Android devices will necessarily provide telephony APIs, those that do are particularly versatile
platforms for person-to-person communication.
Using the telephony APIs you learned how to initiate calls directly and through the dialer. You also
discovered how to read and monitor phone, network, data, and SIM states.
Android lets you use SMS to create applications that exchange data between devices and send and
receive text messages for your users.
CHAPTER 13 BLUETOOTH, NETWORKS, AND WI-FI
Using Bluetooth you can search for, and connect to, other devices within range. By initiating a com-
munications link using Bluetooth Sockets you can then transmit and receive streams of data between
devices from within your applications.
The Bluetooth libraries have been available in Android only since Android version
2.0 (SDK API level 5). It’s also important to remember that not all Android devices
will necessarily include Bluetooth hardware.
Bluetooth is a communications protocol designed for short-range, low-bandwidth peer-to-peer com-
munications. As of Android 2.1, only encrypted communication is supported, meaning you can only
form connections between paired devices. In Android, Bluetooth devices and connections are handled
by the following classes:
➤
BluetoothAdapter
The Bluetooth Adapter represents the local Bluetooth device — that is,
the Android device on which your application is running.
➤
BluetoothDevice
Each remote device with which you wish to communicate is represented
as a
BluetoothDevice
.
➤
BluetoothSocket
Call
createRfcommSocketToServiceRecord
on a remote Bluetooth Device
object to create a Bluetooth Socket that will let you make a connection request to the remote
device, and then initiate communications.
➤
BluetoothServerSocket
The Bluetooth Adapter properties can be read and changed only if the Bluetooth
adapter is currently turned on (that is, if its device state is enabled). If the device is
off, these methods will return
null
.
If the Bluetooth Adapter is turned on, and you have included the
BLUETOOTH
permission in your man-
ifest, you can access the Bluetooth Adapter’s friendly name (an arbitrary string that users can set and
then use to identify a particular device) and hardware address, as shown in Listing 13-2.
Use the
isEnabled
method, as shown in Listing 13-2, to confirm the device is enabled before accessing
these properties.
LISTING 13-2: Reading Bluetooth Adapter properties
BluetoothAdapter bluetooth = BluetoothAdapter.getDefaultAdapter();
String toastText;
if (bluetooth.isEnabled()) {
String address = bluetooth.getAddress();
String name = bluetooth.getName();
toastText = name + " : " + address;
}
else
toastText = "Bluetooth is not enabled";
Toast.makeText(this, toastText, Toast.LENGTH_LONG).show();
If you also have the
BLUETOOTH_ADMIN
permission you can change the friendly name of the Bluetooth
Adapter using the
setName
Bluetooth Adapter has turned on (or has encountered an error). If the user selects no, the sub-Activity
will close and return immediately. Use the result code parameter returned in the
onActivityResult
handler to determine the success of this operation.
FIGURE 13-1
It is also possible to turn the Bluetooth Adapter on and off directly, using the
enable
and
disable
methods, if you include the
BLUETOOTH_ADMIN
permission in
your manifest.
Note that this should be done only when absolutely necessary and that the user
should always be notified if you are manually changing the Bluetooth Adapter
status on the user’s behalf. In most cases you should use the Intent mechanism
described earlier.
Using Bluetooth
❘
429
Enabling and disabling the Bluetooth Adapter are somewhat time-consuming, asynchronous opera-
tions. Rather than polling the Bluetooth Adapter, your application should register a Broadcast Receiver
that listens for
ACTION_STATE_CHANGED
. The broadcast Intent will include two extras,
EXTRA_STATE
and
EXTRA_PREVIOUS_STATE
, which indicate the current and previous Bluetooth Adapter states,
respectively.
}
default: break;
}
Toast.makeText(this, tt, Toast.LENGTH_LONG).show();
}
};
if (!bluetooth.isEnabled()) {
String actionStateChanged = BluetoothAdapter.ACTION_STATE_CHANGED;
String actionRequestEnable = BluetoothAdapter.ACTION_REQUEST_ENABLE;
registerReceiver(bluetoothState,
new IntentFilter(actionStateChanged));
startActivityForResult(new Intent(actionRequestEnable), 0);
}
430
❘
CHAPTER 13 BLUETOOTH, NETWORKS, AND WI-FI
Being Discoverable and Remote Device Discovery
The process of two devices finding each other in order to connect is called discovery. Before you can
establish a Bluetooth Socket for communications, the local Bluetooth Adapter must bond with the
remote device. Before two devices can bond and connect, they first need to discover each other.
While the Bluetooth protocol supports ad-hoc connections for data transfer, this
mechanism is not currently available in Android. Android Bluetooth
communication is currently supported only between bonded devices.
Managing Device Discoverability
FIGURE 13-2
In order for remote Android Devices to find your local Blue-
tooth Adapter during a discovery scan, you need to ensure
that it is discoverable.
The Bluetooth Adapter’s discoverability is indicated by its
scan mode. You can find the adapter’s scan mode by calling
String aDiscoverable = BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
startActivityForResult(new Intent(aDiscoverable),
DISCOVERY_REQUEST);
By default discoverability will be enabled for two minutes. You can modify this setting by adding an
EXTRA_DISCOVERABLE_DURATION
extra to the launch Intent, specifying the number of seconds you want
discoverability to last.
When the Intent is broadcast the user will be prompted by the dialog shown in Figure 13-2 to turn
discoverability on for the specified duration.
Using Bluetooth
❘
431
To learn if the user has allowed or rejected your discovery request, override the
onActivityResult
handler, as shown in Listing 13-4. The returned
resultCode
parameter indicates the duration of dis-
coverability, or a negative number if the user has rejected your request.
LISTING 13-4: Monitoring discoverability modes
@Override
protected void onActivityResult(int requestCode,
int resultCode, Intent data) {
if (requestCode == DISCOVERY_REQUEST) {
boolean isDiscoverable = resultCode > 0;
int discoverableDuration = resultCode;
}
}
Alternatively you can monitor changes in discoverability by receiving the
ACTION_SCAN_MODE_CHANGED
broadcast action, as shown in Listing 13-5. The broadcast Intent includes the current and previous scan
CHAPTER 13 BLUETOOTH, NETWORKS, AND WI-FI
To initiate the discovery process call
startDiscovery
on the Bluetooth Adapter. To cancel a discovery
in progress call
cancelDiscovery
.
bluetooth.startDiscovery();
bluetooth.cancelDiscovery();
The discovery process is asynchronous. Android uses broadcast Intents to notify you of the start and
end of discovery as well as remote devices discovered during the scan.
You can monitor changes in the discovery process by creating Broadcast Receivers to listen for the
ACTION_DISCOVERY_STARTED
and
ACTION_DISCOVERY_FINISHED
broadcast Intents, as shown in
Listing 13-6.
LISTING 13-6: Monitoring discovery
BroadcastReceiver discoveryMonitor = new BroadcastReceiver() {
String dStarted = BluetoothAdapter.ACTION_DISCOVERY_STARTED;
String dFinished = BluetoothAdapter.ACTION_DISCOVERY_FINISHED;
@Override
public void onReceive(Context context, Intent intent) {
if (dStarted.equals(intent.getAction())) {
// Discovery has started.
Toast.makeText(getApplicationContext(),
"Discovery Started
", Toast.LENGTH_SHORT).show();
}
String remoteDeviceName =
intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
Using Bluetooth
❘
433
BluetoothDevice remoteDevice;
remoteDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Toast.makeText(getApplicationContext(),
"Discovered: " + remoteDeviceName,
Toast.LENGTH_SHORT).show();
// TODO Do something with the remote Bluetooth Device.
}
};
registerReceiver(discoveryResult,
new IntentFilter(BluetoothDevice.ACTION_FOUND));
if (!bluetooth.isDiscovering())
bluetooth.startDiscovery();
The
BluetoothDevice
object returned through the discovery broadcast represents the remote Bluetooth
Device discovered. In the following sections it will be used to create a connection, bond, and ultimately
transfer data between the local Bluetooth Adapter and the remote Bluetooth Device.
Bluetooth Communications
The Bluetooth communications APIs are wrappers around RFCOMM, the Bluetooth radio frequency
communications protocol. RFCOMM supports RS232 serial communication over the Logical Link
Control and Adaptation Protocol (L2CAP) layer.
In practice, this alphabet soup provides a mechanism for opening communication sockets between two
paired Bluetooth devices.
Before your application can communicate between devices they must be paired
(bonded). At the time of writing (Android API level 7) there is no way to manually
A Bluetooth Server Socket is used to listen for incoming
Bluetooth Socket connection requests from remote Bluetooth
Devices. In order for two Bluetooth devices to be connected,
one must act as a server (listening for and accepting incoming
requests) and the other as a client (initiating the request to
connect to the server).
Once the two are connected, the communications between
the server and host device are handled through a Bluetooth
Socket at both ends.
To listen for incoming connection requests call the
listenUsingRfcommWithServiceRecord
method on your
Bluetooth Adapter, passing in both a string ‘‘name’’ to iden-
tify your server and a UUID (universally unique identifier).
This will return a
BluetoothServerSocket
object. Note that
the client Bluetooth Socket that connects to this listener will
need to know the UUID in order to connect.
To start listening for connections call
accept
on this Server
Socket, optionally passing in a timeout duration. The Server
Socket will now block until a remote Bluetooth Socket client
with a matching UUID attempts to connect. If a connection
request is made from a remote device that is not yet paired
with the local adapter, the user will be prompted to accept a
pairing request before the accept call returns. This prompt is
made via a notification, as shown in Figure 13-3.
If an incoming connection request is successful,
bluetooth.listenUsingRfcommWithServiceRecord(name, uuid);
Thread acceptThread = new Thread(new Runnable() {
public void run() {
try {
// Block until client connection established.
BluetoothSocket serverSocket = btserver.accept();
// TODO Transfer data using the server socket
} catch (IOException e) {
Log.d("BLUETOOTH", e.getMessage());
}
}
});
acceptThread.start();
}
}
}
Selecting Remote Bluetooth Devices for Communications
The
BluetoothSocket
class is used on the client device to initiate a communications channel from
within your application to a listening Bluetooth Server Socket.
You create client-side Bluetooth Sockets by calling
createRfcommSocketToServiceRecord
on a
BluetoothDevice
object. That object represents the target remote server device. It should have a
Bluetooth Server Socket listening for connection requests (as described in the previous section).
There are a number of ways to obtain a reference to a remote Bluetooth Device, and some important
caveats regarding the devices with which you can create a communications link.
Bluetooth Device Connection Requirements
ware address of the remote Bluetooth Device you want to connect to.
BluetoothDevice device = bluetooth.getRemoteDevice("01:23:77:35:2F:AA");
To find the set of currently paired devices call
getBondedDevices
on the local Bluetooth Adapter. You
can query the returned set to find out if a target Bluetooth Device is paired with the local adapter.
Set<BluetoothDevice> bondedDevices = bluetooth.getBondedDevices();
if (bondedDevices.contains(remoteDevice))
// TODO Target device is bonded / paired with the local device.
Listing 13-9 shows a typical implementation pattern that checks a given Bluetooth Device for discover-
ability and pairing.
LISTING 13-9: Checking remote devices for discoverability and pairing
final BluetoothDevice device =
bluetooth.getRemoteDevice("01:23:77:35:2F:AA");
final Set<BluetoothDevice> bondedDevices = bluetooth.getBondedDevices();
BroadcastReceiver discoveryResult = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
BluetoothDevice remoteDevice =
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if ((remoteDevice.equals(device) &&
(bondedDevices.contains(remoteDevice)) {
// TODO Target device is paired and discoverable
}
};
registerReceiver(discoveryResult,
new IntentFilter(BluetoothDevice.ACTION_FOUND));
Using Bluetooth
❘
437
connect
is a blocking operation, so it’s best practice to initiate
connection requests on a background thread rather than block the UI thread until a
connection has been made.
LISTING 13-10: Connecting to a remote Bluetooth server
Try{
BluetoothDevice device = bluetooth.getRemoteDevice("00:23:76:35:2F:AA");
BluetoothSocket clientSocket =
device.createRfcommSocketToServiceRecord(uuid);
clientSocket.connect();
// TODO Transfer data using the Bluetooth Socket
} catch (IOException e) {
Log.d("BLUETOOTH", e.getMessage());
}
438
❘
CHAPTER 13 BLUETOOTH, NETWORKS, AND WI-FI
Transmitting Data Using Bluetooth Sockets
Once a connection has been established, you will have a Bluetooth Socket on both the client and the
server devices. From this point onward there is no significant distinction between them: you can send
and receive data using the Bluetooth Socket on both devices.
Data transfer across Bluetooth Sockets is handled via standard Java
InputStream
and
OutputStream
objects, which you can obtain from a Bluetooth Socket using the appropriately named
getInputStream
and
getOutputStream
methods, respectively.
while ((bytesRead == bufferSize) && (buffer[bufferSize-1] != 0)){
message = message + new String(buffer, 0, bytesRead);
bytesRead = instream.read(buffer);
}
message = message + new String(buffer, 0, bytesRead
−
1);
return result;
}
}
Using Bluetooth
❘
439
} catch (IOException e) {}
return result;
}
Bluetooth Data Transfer Example
The following example uses the Android Bluetooth APIs to construct a simple peer-to-peer messaging
system that works between two paired Bluetooth devices.
Unfortunately the Android emulator can’t currently be used to test Bluetooth functionality. In order to
test this application you will need to have two physical devices.
1. Start by creating a new
BluetoothTexting
project featuring a
BluetoothTexting
Activity.
Modify the manifest to include
BLUETOOTH
and
BLUETOOTH_ADMIN
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
440
❘
CHAPTER 13 BLUETOOTH, NETWORKS, AND WI-FI
<EditText
android:id="@+id/text_message"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:enabled="false"
/>
<Button
android:id="@+id/button_search"
android:text="Search for listener"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_above="@id/text_message"
/>
<Button
android:id="@+id/button_listen"
android:text="Listen for connection"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_above="@id/button_search"
/>
<ListView
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
Using Bluetooth
❘
441
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;
public class BluetoothTexting extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Get the Bluetooth Adapter
configureBluetooth();