You are on page 1of 34

Androidfp_17_enhaddr.

fm Page 1 Tuesday, June 5, 2012 11:55 AM

17
Enhanced Address Book App
Connecting Devices Via Bluetooth, Using JSONObject to Create and Parse JSON-Formatted Data
Objectives
In this chapter youll:

Use Bluetooth to make a peer-to-peer connection between two devices and transfer information from one to the other. Use class BluetoothAdapter to check whether Bluetooth is enabled on a device. Start an Intent that asks the users permission to enable Bluetooth if its not already enabled. Start an Intent that asks the users permission to make a device discoverable via Bluetooth so the device can receive a connection. Use class BluetoothServerSocket to wait for a connection from another device. Use class BluetoothDevice to make a connection to another device. Use class BluetoothSocket to pass data between devices. Use JSONObjects to create and parse JSON-formatted data.

Androidfp_17_enhaddr.fm Page 2 Tuesday, June 5, 2012 11:55 AM

Chapter 17 Enhanced Address Book App

Outline

17.1 Introduction 17.2 Test-Driving the Enhanced Address Book App 17.3 Technologies Overview 17.4 Building the GUI and Resource Files
17.4.1 Creating the Project 17.4.2 AndroidManifest.xml 17.4.3 addressbook_menu.xml: Menu for the AddressBook Activity 17.4.4 view_contact_menu.xml: Menu for the ViewContact Activity

17.4.5 device_chooser.xml: Layout for the DeviceChooser


ListActivity

17.4.6 device_layout.xml: Layout for the ListView Items in the


DeviceChooser ListActivitys ListView

17.5 Building the App


17.5.1 Updated AddressBook Activity 17.5.2 Updated ViewContact Activity 17.5.3 DeviceChooser Activity

17.6 Wrap-Up

17.1 Introduction
The Enhanced Address Book app adds to Chapter 10s version of the app the ability for users to transfer contacts between devices using Bluetooth networking. Youll use various classes from the android.bluetooth package to determine whether Bluetooth is enabled and establish a connection between two devices. Once a connection is established, youll obtain an OutputStream on the sending device to send the data and an InputStream on the receiving device to read the data. The data will be transferred in JSON formatyoull format the data for sending and parse the received data using JSONObjects. Youll also use Intents to launch built-in Android activities that ask the user for permission to enable a devices Bluetooth adapter and to make a device discoverable by other devices so that a connection can be established. For additional details on Bluetooth development in Android visit:
http://developer.android.com/guide/topics/wireless/bluetooth.html

17.2 Test-Driving the Enhanced Address Book App


[Note: You must test this app with two devices because the Android emulator does not support Bluetooth at this time.]

Opening and Running the App Open Eclipse and import the Enhanced Address Book app project. To import the project: 1. Select File > Import to display the Import dialog. 2. Expand the General node and select Existing Projects into Workspace, then click Next >. 3. To the right of the Select root directory: textfield, click Browse, then locate and select the Enhanced Address Book folder. 4. Click Finish to import the project. 5. Launch the Enhanced Address Book app. In Eclipse, right click the EnhancedAddressBook project in the Package Explorer window, then select Run As > Android Application from the menu that appears. Youll need to repeat this step for each device on which youll run this app for testing.

Androidfp_17_enhaddr.fm Page 3 Tuesday, June 5, 2012 11:55 AM

17.2 Test-Driving the Enhanced Address Book App

Preparing a Device to Receive a Contact To receive a contact from another device thats running the Enhanced Address Book app, open the menu while viewing the contact list, then touch the devices Receive Contact menu item (Fig. 17.1(a)). A dialog asks your permission to make the device discoverable. Touch Yes to make it discoverable for 120 seconds (Fig. 17.1(b)). Another device can now connect to this device and send a contact. A Toast will appear on the receiving device when a contact is received and the new contact will be displayed in the contact list.
a) Touch Receive Contact in the menu to receive a contact from another device b) Touch Yes in the dialog to make the device discoverable and allow device to receive a contact

Fig. 17.1 | Selecting the Receive Contact menu item on the receiving device causes
Android to ask whether the device should be discoverable for 120 seconds.

Sending a Contact To send a contact to another device thats running the Enhanced Address Book app, first select the contact you wish to send to view its details. From the contacts details screen, open the menu and touch Transfer Contact (Fig. 17.2). This displays the DeviceChooser Activity (Fig. 17.3), which immediately searches for nearby Bluetooth devicesif Bluetooth is enabled on the device that launched the DeviceChooser. As devices are discovered, theyre displayed in the DeviceChooser Activitys ListView. You can also restart the search for other devices by touching the Scan for compatible devices Button at the bottom of the screen. When you see the other device thats running the Enhanced Address Book app, touch the devices name to send the contact to that device. Both devices must be running the app for the transfer to occur. Figure 17.4 shows the receiving devices contact list before and after the contact is received.

Androidfp_17_enhaddr.fm Page 4 Tuesday, June 5, 2012 11:55 AM

Chapter 17 Enhanced Address Book App

Touch Transfer Contact to send this contacts information to another device

Fig. 17.2 | Selecting the Transfer Contact menu item on the sending device.

Touch a devices name to transfer a contact to that device

Touch this button to re-scan for nearby devices

Fig. 17.3 | List of nearby Bluetooth devices displayed on the sending device.

Androidfp_17_enhaddr.fm Page 5 Tuesday, June 5, 2012 11:55 AM

17.3 Technologies Overview

a) Contacts list before new contact received

b) Contacts list after new contact received

Newly received contact

Fig. 17.4 | Receiving devices contacts before and after the new contact is received.

17.3 Technologies Overview


This section presents the new technologies that we use in the Enhanced Address Book app. The Bluetooth classes are located in the package android.bluetooth.

Getting a Reference to the Bluetooth Adapter In this app, we use BluetoothAdapters static method getDefaultAdapter to obtain an object that represents the devices Bluetooth adapter. Youll use this object to determine whether Bluetooth is enabled, to listen for connections from other devices and to locate other Bluetooth devices. Detecting Other Bluetooth Devices To search for nearby Bluetooth devices, youll use the BluetoothAdapters startDiscovery method. Android will perform the discovery task for you and notify you when devices are found and when the discovery task is complete. Receiving Broadcast Intents for Found Devices and Discovery Completion To get the discovery results, youll register to receive two broadcast Intents: The Intent for the action BluetoothAdapter.ACTION_FOUND is broadcast for each Bluetooth device thats discovered. The Intent includes as an extra a BluetoothDevice object that represents a Bluetooth-capable device. The Intent for the action BluetoothAdapter.ACTION_DISCOVERY_FINISHED is broadcast when the scan for devices has completed.

Androidfp_17_enhaddr.fm Page 6 Tuesday, June 5, 2012 11:55 AM

Chapter 17 Enhanced Address Book App

Listening For Connections Youll use a separate thread to listen for incoming connection requests from other devices running the Enhanced Address Book app. In that thread, youll call BluetoothAdapters listenUsingRfcommWithServiceRecord method, which returns a BluetoothServerSocket that can be used to start listening for connections. The BluetoothServerSockets accept method blocks the thread from which its called and listens for incoming Bluetooth connection requests. When a connection is received, a BluetoothSocket is returned. This object contains an InputStream and an OutputStream for communicating with the other device. As youll see, method listenUsingRfcommWithServiceRecord receives as an argument a unique identifier that other devices must use to connect to this app on the receiving device. Connecting to Another Device To connect to another device, youll use class BluetoothDevices createRfcommSocketToServiceRecord method, which returns a BluetoothSocket for communicating with the remote device. This method requires as an argument a globally unique identifier that matches the one passed to BluetoothAdapters listenUsingRfcommWithServiceRecord methodthis helps ensure that the connection is being made only to another device thats running the Enhanced Address Book app. If the BluetoothSocket is retrieved, we call its connect method to open a connection to the remote device. Transferring a Contact Once we have a connection, we use the BluetoothSocket to get an InputStream on the receiving device for reading the contact and an OutputStream on the sending device for sending the contact. The InputStreams read method is used to obtain the incoming contact from the remote device. To send a contact, we pass a byte buffer representing that contacts information to the OutputStreams send method. Using JSONObjects to Transmit and Receive Data We use a JSONObject (package org.json) to format a contacts data as a String for sending and to extract the contacts data on the receiving device.

17.4 Building the GUI and Resource Files


In this section, youll create the Enhanced Address Book apps GUI layout files and modify some of those that you created in Chapter 10s Address Book app. We show only the new and changed files in the subsections that follow.

17.4.1 Creating the Project


Begin by deleting from your Eclipse workspace the EnhancedAddressBook project that you test-drove in Section 17.2. To do so, right click the project in the Package Explorer and select Delete. In the dialog that appears, ensure that Delete project contents on disk is not checked, then click OK. Next, in your computers file system, copy the AddressBook folder for the Address Book app, rename the copy as EnhancedAddressBook, then import the EnhancedAddressBook project into your workspace using the steps in Section 17.2.

Androidfp_17_enhaddr.fm Page 7 Tuesday, June 5, 2012 11:55 AM

17.4 Building the GUI and Resource Files

17.4.2 AndroidManifest.xml
Figure 17.5 shows this apps AndroidManifest.xml file. There are three new items in this file. Lines 67 specify two <uses-permission> elements:
android.permission.BLUETOOTH (line 6) allows the app to connect to other Bluetooth-enabled devices that are currently paired with this device. android.permission.BLUETOOTH_ADMIN (line 7) allows the app to discover other Bluetooth-enabled devices and pair with them.

Lines 2223 specify an <activity> element for the new DeviceChooser Activity.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.deitel.addressbook" android:versionCode="1" android:versionName="1.0"> <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="10"/> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".AddressBook" 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=".AddEditContact" android:label="@string/app_name"></activity> <activity android:name=".ViewContact" android:label="@string/app_name"></activity> <activity android:name=".DeviceChooser" android:label="@string/app_name"></activity> </application> </manifest> AndroidManifest.xml.

Fig. 17.5 |

17.4.3 addressbook_menu.xml: Menu for the AddressBook Activity


Figure 17.6 shows the updated AddressBook menu. The receiveContactItem (lines 8 12) allows the user to tell the app to receive a contact from another device. Selecting this item causes the app to ask the user whether its OK to make this device discoverable. If the user says yes, then the app listens for a connection from another device. If it receives a connection, it then reads the contact from the other device.
1 2 3
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/addContactItem"

Fig. 17.6 | Menu for the AddressBook Activity. (Part 1 of 2.)

Androidfp_17_enhaddr.fm Page 8 Tuesday, June 5, 2012 11:55 AM

Chapter 17 Enhanced Address Book App

4 5 6 7 8 9 10 11 12 13

android:title="@string/menuitem_add_contact" android:icon="@drawable/ic_menu_add" android:titleCondensed="@string/menuitem_add_contact" android:alphabeticShortcut="a"></item> <item android:id="@+id/receiveContactItem" android:title="@string/menuitem_receive_contact" android:icon="@drawable/ic_volume_bluetooth_in_call" android:titleCondensed="@string/menuitem_receive_contact" android:alphabeticShortcut="e"></item> </menu>

Fig. 17.6 | Menu for the AddressBook Activity. (Part 2 of 2.)

17.4.4 view_contact_menu.xml: Menu for the ViewContact


Activity Figure 17.7 shows the updated menu for the ViewContact Activity. The transferItem menu item (lines 1317) allows the user to send a contact to another device. Selecting this menu item causes the app to display the DeviceChooser Activity (Section 17.5.3), which scans for and displays a list of nearby Bluetooth devices. The user can then choose the device to which to transfer the contact, which causes the ViewContact Activity to send the contact in a separate thread.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/editItem" android:title="@string/menuitem_edit_contact" android:orderInCategory="1" android:alphabeticShortcut="e" android:titleCondensed="@string/menuitem_edit_contact" android:icon="@android:drawable/ic_menu_edit"></item> <item android:id="@+id/deleteItem" android:title="@string/menuitem_delete_contact" android:orderInCategory="2" android:alphabeticShortcut="d" android:titleCondensed="@string/menuitem_delete_contact" android:icon="@android:drawable/ic_delete"></item> <item android:id="@+id/transferItem" android:title="@string/menuitem_transfer_contact" android:orderInCategory="3" android:alphabeticShortcut="t" android:titleCondensed="@string/menuitem_transfer_contact" android:icon="@android:drawable/stat_sys_data_bluetooth"></item> </menu>

Fig. 17.7 | Menu for the ViewContact Activity.

17.4.5 device_chooser_layout.xml: Layout for the


DeviceChooser ListActivity Figure 17.8 shows the customized layout for the DeviceChooser subclass of ListActivity. As always, when creating a custom layout for a ListActivity, be sure to include a ListView and set its android:id attribute set to "@android:id/list".

Androidfp_17_enhaddr.fm Page 9 Tuesday, June 5, 2012 11:55 AM

17.5 Building the App

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/title_other_devices" android:background="#666666" android:textColor="#FFFFFF" android:paddingLeft="4dp" /> <ListView android:id="@android:id/list" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <Button android:id="@+id/scanButton" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/button_scan" /> </LinearLayout>

Fig. 17.8 | Layout for DeviceChooser subclass of ListActivity.

17.4.6 device_layout.xml: Layout for the ListView Items in the DeviceChooser ListActivitys ListView
Figure 17.9 shows the customized TextView thats used to display each item in the DeviceChooser Activitys ListView.
1 2 3 4 5
<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="16sp" android:padding="4dp" /> ListView

Fig. 17.9 |

item layout for DeviceChooser Activitys ListView.

17.5 Building the App


In this section, we discuss only the Enhanced Address Book apps modified classes and new class. The classes AddEditContact and DatabaseConnector have not changed from Chapter 10, so theyre not shown here. For the classes AddressBook (Section 17.5.1) and ViewContact (Section 17.5.2), we show only the parts of these classes that have changed the complete class definitions can be viewed by opening the source code files from the project. We show the complete DeviceChooser class (Section 17.5.3), which is launched by the ViewContact ListActivity when the user chooses to send a contact to another device.

17.5.1 Updated AddressBook Activity


Class AddressBook (Figs. 17.1017.15) contains the updates that allow the app to enable the devices Bluetooth adapter if its not currently enabled and to receive a new contact from another device.

Androidfp_17_enhaddr.fm Page 10 Tuesday, June 5, 2012 11:55 AM

10

Chapter 17 Enhanced Address Book App

Statement, import Statements and Fields Figure 17.10 contains the classs package statement, import statements and fields. We discuss the new classes and interfaces as we encounter them throughout the class. Lines 39 40 declare a constant of type UUID (universally unique identifier). We use a peer-to-peer Bluetooth connection between two devices running this app in close proximity to one another (typically 30 to 300 feet, depending on the device). As youll see, this UUID helps ensure that the connection between the devices is coming from this app running on another device. There are many UUID generators on the webwe used the one at
package
www.guidgenerator.com

Line 43 declares a constant String name thats associated with the UUID. When the user selects this Activitys Receive Contact menu item, the Activity will register the name/UUID pair with the devices Service Discovery Protocol (SDP) server, which will then assign a communication channel thats used by remote devices to connect. A remote device can then use the UUID to query the SDP server on the device thats waiting to receive a contact. If the SDP server finds the UUID, the server returns the proper communication channel so the remote device can initiate a peer-to-peer connection. The constants at lines 4647 are passed to startActivityForResult when starting Intents that enable the devices Bluetooth adapter and allow the device to be discovered from another Bluetooth device, respectively. Line 50 declares the BluetoothAdapter variable thats used to interact with the devices Bluetooth adapter. The Handler (line 52) is used from non-GUI threads in this Activity to ensure that Toasts are displayed in the GUI thread. The remaining instance variables are from class AddressBook in Chapter 10.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
// AddressBook.java // Main activity for the Address Book app. package com.deitel.addressbook; import java.io.IOException; import java.io.InputStream; import java.util.UUID; import org.json.JSONException; import org.json.JSONObject; import import import import import import import import import import import import import import android.app.ListActivity; android.bluetooth.BluetoothAdapter; android.bluetooth.BluetoothServerSocket; android.bluetooth.BluetoothSocket; android.content.Context; android.content.Intent; android.database.Cursor; android.os.AsyncTask; android.os.Bundle; android.os.Handler; android.util.Log; android.view.Menu; android.view.MenuInflater; android.view.MenuItem; AddressBooks package

Fig. 17.10 |

statement, import statements and fields. (Part 1 of 2.)

Androidfp_17_enhaddr.fm Page 11 Tuesday, June 5, 2012 11:55 AM

17.5 Building the App

11

26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57

import import import import import import import

android.view.View; android.widget.AdapterView; android.widget.AdapterView.OnItemClickListener; android.widget.CursorAdapter; android.widget.ListView; android.widget.SimpleCursorAdapter; android.widget.Toast;

public class AddressBook extends ListActivity { private static String TAG = AddressBook.class.getName(); // unique app UUID generated at http://www.guidgenerator.com/ public static final UUID MY_UUID = UUID.fromString("6acc0a73-afc3-4483-a3a8-94be2c0dfc52"); // name of this service for service discovery private static final String NAME = "AddressBookBluetooth"; // constants passed to startActivityForResult private static final int ENABLE_BLUETOOTH = 1; private static final int REQUEST_DISCOVERABILITY = 2; // BluetoothAdapter provides access to Bluetooth capabilities private BluetoothAdapter bluetoothAdapter = null; private boolean userAllowedBluetooth = true; private Handler handler; // for displaying Toasts from non-GUI threads public static final String ROW_ID = "row_id"; // Intent extra key private ListView contactListView; // the ListActivity's ListView private CursorAdapter contactAdapter; // adapter for ListView

Fig. 17.10 |

AddressBooks package

statement, import statements and fields. (Part 2 of 2.)

Updated Activity Methods onCreate and onResume Lines 74 and 76 in method onCreate (Fig. 17.11, lines 5977) are new in this app. Line 74 calls class BluetoothAdapters static getDefaultAdapter method to get a reference to the devicess default Bluetooth adapter. Line 76 creates the Handler for displaying Toasts in the GUI thread.
58 59 60 61 62 63 64 65 66 67
// called when the activity is first created @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // call super's onCreate contactListView = getListView(); // get the built-in ListView contactListView.setOnItemClickListener(viewContactListener); // map each contact's name to a TextView in the ListView layout String[] from = new String[] { "name" };

Fig. 17.11 | Updated Activity methods onCreate and onResume. (Part 1 of 2.)

Androidfp_17_enhaddr.fm Page 12 Tuesday, June 5, 2012 11:55 AM

12

Chapter 17 Enhanced Address Book App

68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97

int[] to = new int[] { R.id.contactTextView }; contactAdapter = new SimpleCursorAdapter( AddressBook.this, R.layout.contact_list_item, null, from, to); setListAdapter(contactAdapter); // set contactView's adapter // get the default Bluetooth adapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); handler = new Handler(); // for displaying Toasts in GUI thread } // end method onCreate // called when this Activity returns from the background @Override protected void onResume() { super.onResume(); // call super's onResume method // request that Bluetooth be enabled if it isn't already if (!bluetoothAdapter.isEnabled()) { // create and start Intent to ask user to enable Bluetooth Intent enableBluetoothIntent = new Intent( BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBluetoothIntent, ENABLE_BLUETOOTH); } // end if // create new GetContactsTask and execute it new GetContactsTask().execute((Object[]) null); } // end method onResume

Fig. 17.11 | Updated Activity methods onCreate and onResume. (Part 2 of 2.)
Method onResume (lines 8097) calls BluetoothAdapters isEnabled method (line 86) to determine whether Bluetooth is enabled. If not, the Intent for BluetoothAdapter.ACTION_REQUEST_ENABLE (lines 8990) is passed to startActivityForResult to ask the system to launch the Activity thats used to enable Bluetooth communications. Method onActivityResult (Fig. 17.13) will receive the result of this Activity.

Updated Activity Method onOptionsItemSelected Weve updated the onOptionsItemSelected method with a new case for the Receive Contact menu item (Fig. 17.12, lines 159174). If the BluetoothAdapter is enabled, lines 163 166 ask the user whether its OK to make this device discoverable to other devices. The Intent for BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE (lines 163164) is passed to startActivityForResult to ask the system to launch the Activity thats used to make the device discoverable by other devices. Method onActivityResult (Fig. 17.13) will receive the result of this Activity. If the user allows the app to make the device discoverable, it will be discoverable for 120 seconds by default. This can be customized by adding an Intent extra for the key BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION with a value in seconds (0 means the device is always discoverable). If the BluetoothAdapter is not enabled, we simply display a Toast indicating that Bluetooth is not enabled.

Androidfp_17_enhaddr.fm Page 13 Tuesday, June 5, 2012 11:55 AM

17.5 Building the App

13

147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178

// handle choice from options menu @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.addContactItem: // create a new Intent to launch the AddEditContact Activity Intent addNewContact = new Intent(AddressBook.this, AddEditContact.class); startActivity(addNewContact); // start AddEditContact Activity break; case R.id.receiveContactItem: if (bluetoothAdapter.isEnabled()) { // launch Intent to request discoverability for 120 seconds Intent requestDiscoverabilityIntent = new Intent( BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); startActivityForResult(requestDiscoverabilityIntent, REQUEST_DISCOVERABILITY); } // end if else // user did not allow Bluetooth adapter to be enabled { Toast.makeText(this, R.string.no_bluetooth, Toast.LENGTH_LONG).show(); } // end else break; } // end switch return super.onOptionsItemSelected(item); // call super's method } // end method onOptionsItemSelected

Fig. 17.12 | Updated Activity method onOptionsItemSelected. Overriding Activity Method onActivityResult When method onActivityResult (Fig. 17.13) is called in response to the BluetoothAdapter.ACTION_REQUEST_ENABLE Intent that was launched in onResume (Fig. 17.11), lines 208 219 display a Toast indicating whether Bluetooth was enabled. When the method is called in response to the BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE Intent in onOptionsItemSelected (Fig. 17.12), if the user allowed the device to become discoverable, line 225 calls listenForContact (Fig. 17.14) to listen in a separate thread for a connection from another device; otherwise, we display a Toast indicating that the device is not discoverable.
198 199 200 201 202 203
// called with result of startActivityForResult @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data);

Fig. 17.13 | Overriding Activity method onActivityResult. (Part 1 of 2.)

Androidfp_17_enhaddr.fm Page 14 Tuesday, June 5, 2012 11:55 AM

14

Chapter 17 Enhanced Address Book App

204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236

switch (requestCode) // process result based on the requestCode { case ENABLE_BLUETOOTH: // attempted to enable Bluetooth if (resultCode == RESULT_OK) // Bluetooth was enabled { Toast.makeText(this, R.string.bluetooth_enabled, Toast.LENGTH_LONG).show(); } // end if else // Bluetooth was not enabled { userAllowedBluetooth = false; Toast.makeText(this, R.string.no_bluetooth, Toast.LENGTH_LONG).show(); } // end else break; // attempted to make the device discoverable case REQUEST_DISCOVERABILITY: if (resultCode != RESULT_CANCELED) // user gave permission { listenForContact(); // start listening for a connection } // end if else // user did not allow discoverability { Toast.makeText(this, R.string.no_discoverability, Toast.LENGTH_LONG).show(); } // end else break; } // end switch } // end method onActivityResult

Fig. 17.13 | Overriding Activity method onActivityResult. (Part 2 of 2.) Method listenForContact and Nested Class ReceiveContactTask Method listenForContact and nested class ReceiveContactTask (Fig. 17.14) are both new in this app. Method listenForContact (lines 238244) simply launches the ReceiveContactTask AsyncTask (lines 247347), which begins waiting for a connection from another device running the Enhanced Address Book app, and if it receives one, reads the new contact information from that other device.
237 238 239 240 241 242 243 244
// start listening for a contact sent from another device private void listenForContact() { // start background task to wait for connection // and receive a contact ReceiveContactTask task = new ReceiveContactTask(); task.execute((Object[]) null); } // end method listenForContact

Fig. 17.14 | Method listenForContact and nested class ReceiveContactTask. (Part 1 of 3.)

Androidfp_17_enhaddr.fm Page 15 Tuesday, June 5, 2012 11:55 AM

17.5 Building the App

15

245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297

// thread that listens for incoming connection requests private class ReceiveContactTask extends AsyncTask<Object, Object, Object> { private BluetoothServerSocket serverSocket; // awaits connection private BluetoothSocket socket; // used to process connection // await connection, receive contact and update contacts list @Override protected Object doInBackground(Object... params) { try { // get BluetoothServerSocket from bluetoothAdapter serverSocket = bluetoothAdapter.listenUsingRfcommWithServiceRecord( NAME, MY_UUID); displayToastViaHandler(AddressBook.this, handler, R.string.waiting_for_contact); // wait for connection BluetoothSocket socket = serverSocket.accept(); // get InputStream for receiving contact InputStream inputStream = socket.getInputStream(); // create a byte array to hold incoming contact information byte[] buffer = new byte[1024]; int bytes; // number of bytes read // read from the InputStream and store data in buffer bytes = inputStream.read(buffer); if (bytes != -1) // a contact was received { DatabaseConnector databaseConnector = null; // convert readMessage to JSONObject try { // create JSONObject from read bytes JSONObject contact = new JSONObject(new String(buffer, 0, buffer.length)); // create new DatabaseConnector databaseConnector = new DatabaseConnector(getBaseContext()); // open the database and add the contact to the database databaseConnector.open(); // connect to the database

Fig. 17.14 | Method listenForContact and nested class ReceiveContactTask. (Part 2 of 3.)

Androidfp_17_enhaddr.fm Page 16 Tuesday, June 5, 2012 11:55 AM

16

Chapter 17 Enhanced Address Book App

298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348

databaseConnector.insertContact( // add the contact contact.getString("name"), contact.getString("email"), contact.getString("phone"), contact.getString("street"), contact.getString("city")); // update the contacts list new GetContactsTask().execute((Object[]) null); displayToastViaHandler(AddressBook.this, handler, R.string.contact_received); } // end try catch (JSONException e) // problem with the JSON formatting { displayToastViaHandler(AddressBook.this, handler, R.string.contact_not_received); Log.e(TAG, e.toString()); } // end catch finally // ensure that the database connection is closed { if (databaseConnector != null) databaseConnector.close(); // close connection } // end finally } // end if } // end try catch (IOException e) { Log.e(TAG, e.toString()); } // end catch finally // ensure BluetoothServerSocket & BluetoothSocket closed { try { // if the BluetoothServerSocket is not null, close it if (serverSocket != null) serverSocket.close(); // if the BluetoothSocket is not null, close it if (socket != null) socket.close(); } // end try catch (IOException e) // problem closing a socket { Log.e(TAG, e.toString()); } // end catch } // end finally return null; } // end method doInBackround } // end nested class ReceiveContactTask

Fig. 17.14 | Method listenForContact and nested class ReceiveContactTask. (Part 3 of 3.)

Androidfp_17_enhaddr.fm Page 17 Tuesday, June 5, 2012 11:55 AM

17.5 Building the App

17

Networking with Bluetooth is similar to standard socket-based networking. Line 250 declares a BluetoothServerSocket, which will be used to wait for a connection from another device. Line 251 declares a BluetoothSocketif we receive a connection, we can then use BluetoothSockets methods to get references to the InputStream for reading from another device and the OutputStream for writing to another device. The AsyncTasks doInBackground method (lines 254346) first calls BluetoothAdapter method listenUsingRfcommWithServiceRecord with the constants declared in lines 3940 and 43 (Fig. 17.10) to register the app with Androids Service Discovery Protocol (SDP) server and obtain a BluetoothServerSocket object. Next, line 268 calls method accept on the BluetoothServerSocket to begin listening for a connection from another device. When a connection is received, this method returns a BluetoothSocket that can be used to communicate with the other device. In this Activity, we simply need to read a contact from the other device, so line 271 obtains the InputStream from the BluetoothSocket. Line 278 reads the bytes of the contact from the sending device. In this app, we use JSON-formatted data (introduced in Chapter 14) to transfer the contact between devices. Lines 288289 convert the bytes of the contact into a String, then create a JSONObject from that String. We use the DatabaseConnector class from Chapter 10 to insert the new contact in the database. Lines 298303 use the JSONObjects getString method to extract the contacts data and pass it to the DatabaseConnectors insertContact method. Line 306 then starts an AsyncTask to update the contact list. The finally block in lines 327343 ensures that the BluetoothServerSocket and BluetoothSocket are closed after the transfer is complete (or if a problem occurs during the transfer). [Note: Its possible that line 278 will read only a portion of the datathough unlikely in this app. To ensure that you read all the data, read the bytes in a loop until method read returns -1 to indicate that there are no more bytes. Each iteration would convert the bytes to a String and concatenate the characters to qthe end of what had been read previously.]

Utility Method displayToastViaHandler Method displayToastViaHandler (Fig. 17.15) is called from other threads in this Activity to display Toasts in the GUI thread by using a Handler.
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
// use handler to display a Toast in GUI thread with specified message public static void displayToastViaHandler(final Context context, Handler handler, final int stringID) { handler.post( new Runnable() { public void run() { Toast.makeText(context, stringID, Toast.LENGTH_SHORT).show(); } // end method run } // end Runnable ); // end call to handler's post method } // end method displayToastViaHandler

Fig. 17.15 | Utility method displayToastViaHandler.

Androidfp_17_enhaddr.fm Page 18 Tuesday, June 5, 2012 11:55 AM

18

Chapter 17 Enhanced Address Book App

17.5.2 Updated ViewContact Activity


Class ViewContact (Figs. 17.1617.20) contains the updates that allow the apps user to send a contact to another device.

Statement, import Statements and Fields Figure 17.16 contains the classs package statement, import statements and fields. We discuss the new classes and interfaces as we encounter them throughout the class. The constant at line 34 is passed to startActivityForResult when launching the DeviceChooser Activity so the user can choose the device to which to send a contact. The BluetoothAdapter (line 36) is used to determine whether Bluetooth is enabled and to get a BluetoothDevice object for the device the user selects in the DeviceChooser Activity. The Handler (line 37) is used from non-GUI threads in this Activity to ensure that Toasts are displayed in the GUI thread.
package 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
// ViewContact.java // Activity for viewing a single contact. package com.deitel.addressbook; import java.io.IOException; import java.io.OutputStream; import org.json.JSONException; import org.json.JSONObject; import import import import import import import import import import import import import import import import import android.app.Activity; android.app.AlertDialog; android.bluetooth.BluetoothAdapter; android.bluetooth.BluetoothDevice; android.bluetooth.BluetoothSocket; android.content.DialogInterface; android.content.Intent; android.database.Cursor; android.os.AsyncTask; android.os.Bundle; android.os.Handler; android.util.Log; android.view.Menu; android.view.MenuInflater; android.view.MenuItem; android.widget.TextView; android.widget.Toast;

public class ViewContact extends Activity { private static final String TAG = ViewContact.class.getName(); // Intent request code used to start an Activity that returns a result private static final int REQUEST_CONNECT_DEVICE = 1;

Fig. 17.16 |

ViewContacts package

statement, import statements and fields. (Part 1 of 2.)

Androidfp_17_enhaddr.fm Page 19 Tuesday, June 5, 2012 11:55 AM

17.5 Building the App

19

36 37 38 39 40 41 42 43 44 45

private BluetoothAdapter bluetoothAdapter = null; // Bluetooth adapter private Handler handler; // for displaying Toasts in the GUI thread private private private private private private long rowID; // selected contact's row ID in the database TextView nameTextView; // displays contact's name TextView phoneTextView; // displays contact's phone TextView emailTextView; // displays contact's e-mail TextView streetTextView; // displays contact's street TextView cityTextView; // displays contact's city/state/zip

Fig. 17.16 |

ViewContacts package

statement, import statements and fields. (Part 2 of 2.)

Updated Activity Method onCreate The only new features in method onCreate (Fig. 17.17) are at lines 65 and 67, which get the default BluetoothAdapter and create a Handler, respectively.
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
// called when the Activity is first created @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.view_contact); // inflate GUI // get the EditTexts nameTextView = (TextView) findViewById(R.id.nameTextView); phoneTextView = (TextView) findViewById(R.id.phoneTextView); emailTextView = (TextView) findViewById(R.id.emailTextView); streetTextView = (TextView) findViewById(R.id.streetTextView); cityTextView = (TextView) findViewById(R.id.cityTextView); // get the selected contact's row ID Bundle extras = getIntent().getExtras(); rowID = extras.getLong(AddressBook.ROW_ID); // get the default Bluetooth adapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); handler = new Handler(); // create the Handler } // end method onCreate

Fig. 17.17 | Updated Activity method onCreate. Updated Activity Method onOptionsItemSelected Weve updated the onOptionsItemSelected method with a new case for the Transfer Contact menu item (Fig. 17.18, lines 156171). If the BluetoothAdapter is enabled, lines 161164 launch the DeviceChooser Activity so the user can select the device to which the contact should be sent. The result of this Activity is the address of the selected device, which is returned to the onActivityResult method (Fig. 17.19).

Androidfp_17_enhaddr.fm Page 20 Tuesday, June 5, 2012 11:55 AM

20

Chapter 17 Enhanced Address Book App

133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175

// handle choice from options menu @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) // switch based on selected MenuItem's ID { case R.id.editItem: // selected Edit Contact menu item // create an Intent to launch the AddEditContact Activity Intent addEditContact = new Intent(this, AddEditContact.class); // pass the selected contact's data as extras with the Intent addEditContact.putExtra(AddressBook.ROW_ID, rowID); addEditContact.putExtra("name", nameTextView.getText()); addEditContact.putExtra("phone", phoneTextView.getText()); addEditContact.putExtra("email", emailTextView.getText()); addEditContact.putExtra("street", streetTextView.getText()); addEditContact.putExtra("city", cityTextView.getText()); startActivity(addEditContact); // start the Activity break; case R.id.deleteItem: // selected Delete Contact menu item deleteContact(); // delete the displayed contact break; case R.id.transferItem: // selected Transfer Contact menu item // if we are not already connected if (bluetoothAdapter.isEnabled()) { // launch DeviceChooser so user can pick a nearby device Intent serverIntent = new Intent(this, DeviceChooser.class); startActivityForResult( serverIntent, REQUEST_CONNECT_DEVICE); } // end if else // indicate that Bluetooth is not enabled { Toast.makeText(this, R.string.no_bluetooth, Toast.LENGTH_LONG).show(); } // end else break; } // end switch return super.onOptionsItemSelected(item); } // end method onOptionsItemSelected

Fig. 17.18 | Updated Activity method onOptionsItemSelected. Overriding Activity Method onActivityResult Method onActivityResult (Fig. 17.19) is called when the DeviceChooser Activity returns. If the Activity returns a device, lines 236237 start a SendContactTask to send the contact to another device. Method onActivityResults data parameter contains an extra representing the address of the device to which the contact should be sent. This address is passed as the only element of a String array to the AsyncTasks execute method, which launches the AsyncTask and passes the String array to the tasks doInBackground method.

Androidfp_17_enhaddr.fm Page 21 Tuesday, June 5, 2012 11:55 AM

17.5 Building the App

21

226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246

// called when an Activity launched from this one using // startActivityForResult finishes public void onActivityResult(int requestCode, int resultCode, Intent data) { // if the connection was established if (resultCode == Activity.RESULT_OK) { // get the remote device's MAC address and pass it to // SendContactTask's execute method new SendContactTask().execute(new String[] { data.getExtras().getString(DeviceChooser.DEVICE_ADDRESS)}); } // end if else // there was a connection error { // display connection error Toast Toast.makeText(this, R.string.connection_error, Toast.LENGTH_LONG); } // end else } // end method onActivityResult

Fig. 17.19 | Overriding Activity method onActivityResult. Nested Class SendContactTask Class SendContactTask (Fig. 17.20) sends a contact to another device using an AsyncTask. When the doInBackground method executes, lines 256257 use the first element of the params arraywhich contains a String representing a device addressto the BluetoothAdapters getRemoteDevice method. This returns a BluetoothDevice representing the device at that address. Next, lines 268269 call the BluetoothDevices createRfcommSocketToServiceRecord method with the AddressBook.UUID as an argument. This call communicates with the remote device to create a secure connection, assuming that there is an app with the same UUID listening on the remote device. If so, the method returns a BluetoothSocket that can be used to communicate with the app on the remote device. Then, line 270 calls the BluetoothSockets connect method to open the connection. In this Activity, we send a contact to the remote device, so we get only the OutputStream from the BluetoothSocket (line 273). Lines 276281 create a JSONObject containing the contacts information as key/value pairs. Line 284 gets the JSONObjects String representation then gets the bytes from the String and writes those bytes to the OutputStream. This sends the contact to the remote device. The finally block in lines 301313 ensures that the BluetoothSocket is closed after the transfer is complete (or if a problem occurs during the transfer).
247 248 249
// Task for sending a contact in a background thread private class SendContactTask extends AsyncTask<String, Object, Object> {

Fig. 17.20 | Nested class SendContactTask sends a contact to a remote device using a background thread. (Part 1 of 3.)

Androidfp_17_enhaddr.fm Page 22 Tuesday, June 5, 2012 11:55 AM

22

Chapter 17 Enhanced Address Book App

250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300

// get the BluetoothDevice for the specified address, // connect to the device and send the contact @Override protected Object doInBackground(String... params) { // get a BluetoothDevice object representing the remote device BluetoothDevice device = bluetoothAdapter.getRemoteDevice(params[0]); BluetoothSocket bluetoothSocket = null; // for sending contact // make connection to remote device and send contact try { AddressBook.displayToastViaHandler(ViewContact.this, handler, R.string.sending_contact); // get BluetoothSocket, then connect to the other device bluetoothSocket = device.createRfcommSocketToServiceRecord( AddressBook.MY_UUID); bluetoothSocket.connect(); // establish connection // get streams for communicating via BluetoothSocket OutputStream outputStream = bluetoothSocket.getOutputStream(); // create JSONObject representing the contact final JSONObject contact = new JSONObject(); contact.put("name", nameTextView.getText().toString()); contact.put("phone", phoneTextView.getText().toString()); contact.put("email", emailTextView.getText().toString()); contact.put("street", streetTextView.getText().toString()); contact.put("city", cityTextView.getText().toString()); // send a byte array containing the contact's information outputStream.write(contact.toString().getBytes()); outputStream.flush(); AddressBook.displayToastViaHandler(ViewContact.this, handler, R.string.contact_sent); } // end try catch (IOException e) // problem sending contact { AddressBook.displayToastViaHandler(ViewContact.this, handler, R.string.transfer_failed); Log.e(TAG, e.toString()); } // end catch catch (JSONException e) // problem with JSON data formatting { AddressBook.displayToastViaHandler(ViewContact.this, handler, R.string.transfer_failed); Log.e(TAG, e.toString()); } // end catch

Fig. 17.20 | Nested class SendContactTask sends a contact to a remote device using a background thread. (Part 2 of 3.)

Androidfp_17_enhaddr.fm Page 23 Tuesday, June 5, 2012 11:55 AM

17.5 Building the App

23

301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317

finally // ensure that BluetoothSocket is closed { try { bluetoothSocket.close(); // closes BluetoothSocket } // end try catch (IOException e) // problem closing BluetoothSocket { Log.e(TAG, e.toString()); } // end catch bluetoothSocket = null; } // end finally return null; } // end method doInBackground } // end class SendContactTask

Fig. 17.20 | Nested class SendContactTask sends a contact to a remote device using a background thread. (Part 3 of 3.)

17.5.3 DeviceChooser Activity


The
DeviceChooser ViewContact Activity

subclass of ListActivity (Fig. 17.2117.26) is launched by the when the user attempts to send a contact to a remote device.

Statement, import Statements and Fields Figure 17.21 contains the classs package statement, import statements and fields. Line 30 defines a constant representing the number of characters in a Media Access Control (MAC) address. A MAC address is a unique network identifier for a given device. We use the MAC address of a remote device to connect to that device in the ViewContact Activity. Line 33 defines a constant thats used as the key when we store the selected devices MAC address in an Intent thats returned to the ViewContact Activity. The ArrayAdapter (line 36) will be used to populate this Activitys ListView with a list of nearby Bluetooth devices.
package 1 2 3 4 5 6 7 8 9 10 11 12 13
// DeviceChooser.java // Activity for choosing a connecting device. package com.deitel.addressbook; import java.util.Set; import import import import import import import android.app.Activity; android.app.ListActivity; android.bluetooth.BluetoothAdapter; android.bluetooth.BluetoothDevice; android.content.BroadcastReceiver; android.content.Context; android.content.Intent; DeviceChoosers package Statement, import Statements and Fields. (Part 1 of 2.)

Fig. 17.21 |

Androidfp_17_enhaddr.fm Page 24 Tuesday, June 5, 2012 11:55 AM

24

Chapter 17 Enhanced Address Book App

14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38

import import import import import import import import import import import import

android.content.IntentFilter; android.os.Bundle; android.view.View; android.view.View.OnClickListener; android.view.Window; android.widget.AdapterView; android.widget.ArrayAdapter; android.widget.Button; android.widget.ListView; android.widget.TextView; android.widget.AdapterView.OnItemClickListener; android.widget.Toast;

public class DeviceChooser extends ListActivity { // length of a MAC address in characters private static final int MAC_ADDRESS_LENGTH = 17; // key for storing selected device's MAC address as an Intent extra public static final String DEVICE_ADDRESS = "device_address"; private BluetoothAdapter bluetoothAdapter; // the Bluetooth Adapter private ArrayAdapter<String> foundDevicesAdapter; // ListView data private ListView newDevicesListView; // ListView that shows devices

Fig. 17.21 |

DeviceChoosers package Statement, import Statements and Fields. (Part 2 of 2.)

Overriding Activity Method onCreate Method onCreate (Fig. 17.22) sets up this Activity. Line 46 calls Activity method requestWindowFeature (which must occur before the call to setContentView) to indicate that this Activitys window should show an indeterminate progress iconthe device discovery process could take some time and there could be an arbitrary number of devices nearby (and this number is likely to grow as more and more Bluetooth devices appear), so we cannot show an exact progress status.
39 40 41 42 43 44 45 46 47 48 49 50 51 52
// called when this Activity is first created @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // shows a progess bar while activity loads requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); // set the Activity's layout setContentView(R.layout.device_chooser_layout); // set the result code to return to the previous Activity // if the user touches the 'Cancel' Button

Fig. 17.22 | Overriding Activity Method onCreate. (Part 1 of 2.)

Androidfp_17_enhaddr.fm Page 25 Tuesday, June 5, 2012 11:55 AM

17.5 Building the App

25

53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104

setResult(Activity.RESULT_CANCELED); // create Button to start the discovery Button scanButton = (Button) findViewById(R.id.scanButton); scanButton.setOnClickListener( new OnClickListener() { // called when the scanButton is clicked public void onClick(View v) { startDiscovery(); // begin searching for devices } // end method onClick } // end OnClickListener ); // end call to setOnClickListener // initialize the list adapter for list of found devices foundDevicesAdapter = new ArrayAdapter<String>(this, R.layout.device_layout); // initialize the ListView that will display newly found devices newDevicesListView = getListView(); newDevicesListView.setAdapter(foundDevicesAdapter); newDevicesListView.setOnItemClickListener( deviceListItemClickListener); // listen for broadcast Intents alerting that a Bluetooth device // was found nearby IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); registerReceiver(deviceChooserReceiver, filter); // listen for broadcast Intents alerting that the search for // nearby devices is completed filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); registerReceiver(deviceChooserReceiver, filter); // get the local BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); // get a Set of all devices we are already connected to Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices(); // add each connected device's name to our ListView for (BluetoothDevice device : pairedDevices) { foundDevicesAdapter.add(device.getName() + "\n" + device.getAddress()); } // end for } // end method onCreate

Fig. 17.22 | Overriding Activity Method onCreate. (Part 2 of 2.)

Androidfp_17_enhaddr.fm Page 26 Tuesday, June 5, 2012 11:55 AM

26

Chapter 17 Enhanced Address Book App

Line 53 calls Activitys setResult method using the RESULT_CANCELED constant. If the user exits this Activity by pressing the back button before we get a chance to return real data, this result will let the calling Activity know there was an error. Lines 5666 get the scanButton and set its OnClickListener, which calls method startDiscovery (Fig. 17.24) when the user touches the Button. Lines 6970 create the ArrayAdapter thats used to populate this Activitys ListView. Lines 7376 set up the ListView for displaying the detected devices. Lines 8082 create an IntentFilter using the BluetoothDevice.ACTION_FOUND constant, then call Activitys registerReceiver method with the BroadcastReceiver deviceChooserReceiver (defined in Fig. 17.26) and the new IntentFilter. Each time a Bluetooth device is found, a BluetoothDevice.ACTION_FOUND Intent will be broadcast, and our deviceChooserReceivers onReceive method will be called to add the new device to the list of devices. Lines 8688 create another IntentFilter using the BluetoothDevice.ACTION_DISCOVERY_FINISHED constant, then register deviceChooserReceiver for that IntentFilter. When discovery of Bluetooth devices completes, a BluetoothAdapter.ACTION_DISCOVERY_FINISHED Intent will be broadcast, and our deviceChooserReceivers onReceive method will be called to check whether any devices were found. Line 91 gets the default BluetoothAdapter, then lines 9495 use the BluetoothAdapters getBondedDevices method to get a Set<BluetoothDevice> containing the currently paired devices. We then add the name and address of each device (if any) to the ListViews ArrayAdapter (lines 98102).

Overriding Activity Method onDestroy When the Activity completes or when Android destroys the Activity, method onDestroy (Fig. 17.23) calls the BluetoothAdapters cancelDiscovery method to stop scanning for nearby devices (line 114). We also unregister deviceChooserReceiver.
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
// called before this Activity is destroyed @Override protected void onDestroy() { super.onDestroy(); // end Bluetooth discovery if (bluetoothAdapter != null) { bluetoothAdapter.cancelDiscovery(); } // end if // unregister the deviceChooserReceiver BroadcastReceiver unregisterReceiver(deviceChooserReceiver); } // end method onDestroy

Fig. 17.23 | Overriding Activity Method onDestroy. Method startDiscovery The startDiscovery method (Fig. 17.24) searches for nearby Bluetooth-enabled devices. We call BluetoothAdapters isDiscovering method (line 132) to determine whether

Androidfp_17_enhaddr.fm Page 27 Tuesday, June 5, 2012 11:55 AM

17.5 Building the App

27

were already searching for devices. If so, we stop the current search by calling Bluetoothmethod (line 134). Line 138 displays the indeterminate progress bar, then line 141 calls BluetoothAdapters startDiscovery method to begin device discovery from scratch.
Adapters cancelDiscovery

121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143

// start the discovery private void startDiscovery() { // check if Bluetooth is still enabled if (!bluetoothAdapter.isEnabled()) { Toast.makeText(this, R.string.no_bluetooth, Toast.LENGTH_LONG); return; } // end if // end existing discovery if needed if (bluetoothAdapter.isDiscovering()) { bluetoothAdapter.cancelDiscovery(); } // end if // show the progress bar setProgressBarIndeterminateVisibility(true); // begin searching for other devices bluetoothAdapter.startDiscovery(); } // end method startDiscovery

Fig. 17.24 | Method startDiscovery.


OnItemClickListener deviceListItemClickListener

When the user touches a device in the Activitys ListView, the deviceListItemClickListeners onItemClick method (Fig. 17.25, lines 148168) is called. The user has selected a device, so we immediately call BluetoothAdapters cancelDiscovery method (line 152) to stop scanning for other devices. Scanning decreases a devices battery life, so you should terminate the scanning process when the Activity is no longer needed. Lines 155157 extract the selected devices MAC address, then lines 160166 create a new Intent with the MAC address included as an extra and set the Intent as the Activitys result. We call finish to terminate this Activity and return the Intent to the ViewContact Activity.
144 145 146 147 148 149 150
// listens for events generated when the user clicks ListView item private OnItemClickListener deviceListItemClickListener = new OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View view, int position, long id) { OnItemClickListener deviceListItemClickListener.

Fig. 17.25 |

(Part 1 of 2.)

Androidfp_17_enhaddr.fm Page 28 Tuesday, June 5, 2012 11:55 AM

28

Chapter 17 Enhanced Address Book App

151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170

// cancel the discovery before attempting to connect bluetoothAdapter.cancelDiscovery(); // get the MAC address of the selected device String info = ((TextView) view).getText().toString(); String address = info.substring(info.length() MAC_ADDRESS_LENGTH); // create an Intent to return to the calling Activity Intent intent = new Intent(); // include the device's MAC address in the return Intent intent.putExtra(DEVICE_ADDRESS, address); // set our Intent as the successful return value and finish setResult(Activity.RESULT_OK, intent); finish(); } // end method onItemClick }; // end OnItemClickListener

Fig. 17.25 |

OnItemClickListener deviceListItemClickListener.

(Part 2 of 2.)

BroadcastReceiver deviceChooserReceiver

Figure 17.26 defines the BroadcastReceiver that responds to broadcast Intents indicating when a device is discovered and when device discovery is completed. The onReceive method is called each time an Intent is broadcast that matches any of the IntentFilters for which this receiver is registered to respond. First, we get the incoming Intents action using its getAction method (line 180). If the action matches BluetoothDevices ACTION_FOUND constant, we know that the given Intent includes as an extra the selected BluetoothDevice. Lines 186187 get the BluetoothDevice using Intents getParcelableExtra method. Lines 191195 check whether the returned device is already paired with the current device (in which case, its already displayed the list of devices). If not, we add it to the ListViews ArrayAdapter. If the action matches BluetoothAdapters ACTION_DISCOVERY_FINISHED constant, scanning has finished. Line 202 hides the indeterminate progress bar. If no devices have been found, we add an item to the ListViews ArrayAdapter explaining that fact.
171 172 173 174 175 176 177 178
// listens for broadcast Intents announcing when a // discovery finishes and when new devices are detected private final BroadcastReceiver deviceChooserReceiver = new BroadcastReceiver() { // called when a broadcast is received public void onReceive(Context context, Intent intent) { BroadcastReceiver deviceChooserReceiver listens for broadcast Intents that

Fig. 17.26 |

indicate when a device is found and when discovery of devices has finished. (Part 1 of 2.)

Androidfp_17_enhaddr.fm Page 29 Tuesday, June 5, 2012 11:55 AM

17.6 Wrap-Up

29

179 // get the calling Intent's action 180 String action = intent.getAction(); 181 182 // a new device was detected 183 if (BluetoothDevice.ACTION_FOUND.equals(action)) 184 { 185 // get the BluetoothDevice from the broadcast Intent BluetoothDevice device = 186 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 187 188 189 // if the device is not already connected, add its name 190 // to the ListView 191 if (device.getBondState() != BluetoothDevice.BOND_BONDED) 192 { 193 foundDevicesAdapter.add(device.getName() + "\n" + 194 device.getAddress()); 195 } // end if 196 } // end if 197 // a search for new devices has ended 198 else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals( 199 action)) 200 { 201 // hide the progress bar setProgressBarIndeterminateVisibility(false); 202 203 204 // set the title of the Activity 205 setTitle(getResources().getString(R.string.choose_device)); 206 207 // if there were no devices in range, display a message 208 if (foundDevicesAdapter.getCount() == 0) 209 { 210 // disable list item clicks 211 newDevicesListView.setOnItemClickListener(null); 212 foundDevicesAdapter.add(getResources().getString( 213 R.string.no_devices)); 214 } // end if 215 } // end else if 216 } // end method onReceive 217 }; // end BroadcastReceiver 218 } // end class DeviceChooser

Fig. 17.26 |

BroadcastReceiver deviceChooserReceiver listens for broadcast Intents that

indicate when a device is found and when discovery of devices has finished. (Part 2 of 2.)

17.6 Wrap-Up
The Enhanced Address Book allows users to transfer contacts between devices using Bluetooth networking. You used various classes from the android.bluetooth package to perform Bluetooth-related tasks. You used BluetoothAdapters static method getDefaultAdapter to obtain an object that represents the devices Bluetooth adapter. To search for nearby Bluetooth devices, you used the BluetoothAdapters startDiscovery method to tell Android to perform the discovery task, notify you when devices were found and notify you when the discovery task completed.

Androidfp_17_enhaddr.fm Page 30 Tuesday, June 5, 2012 11:55 AM

30

Chapter 17 Enhanced Address Book App

To receive the discovery results, you registered to receive broadcast Intents that notified the app when each Bluetooth device was discovered (supplying a BluetoothDevice object for each) and notified the app when the scan for devices completed. You used a separate thread to listen for incoming connection requests from other devices running the Enhanced Address Book app. In that thread, you called BluetoothAdapters listenUsingRfcommWithServiceRecord method to get a BluetoothServerSocket, then used that objects accept method to listen for incoming Bluetooth connection requests. When a connection was received, a BluetoothSocket was returned containing an InputStream and an OutputStream for communicating with the other device. To connect to another device, you used class BluetoothDevices createRfcommSocketToServiceRecord method to get a BluetoothSocket for communicating with the remote device. You then called that objects connect method to open a connection to the device. Finally, you transmitted and received the data in JSON format. To create the JSONformatted data you created a JSONObject and obtained its String representation. To receive the JSON data, you created a JSONObject and passed the JSON-formatted String to the objects constructor. In Chapter 18, we introduce OpenGL ES for creating 3D graphics and animations. Youll create an app that displays a cube, pyramid or rectangular prism. The user will be able to select which shape to display, to scale the shape and to select the currently displayed shapes rotation speed along the x-, y- and z-axis.

Androidfp_17_enhaddr.fm Page 31 Tuesday, June 5, 2012 11:55 AM

[*** Notes to Reviewers ***]

31

[*** Notes to Reviewers ***]


Please fill out the chapter assessment page including the comment box at the bottom of the page. This is a crucial part of our process. Please be constructive. If you find something thats incorrect, please show precisely how youd like us to correct it. Please mark your comments in place on a PDF copy of the chapter. All chapters are set up so that you can mark comments using Adobe Reader 8+ if you dont have the complete Adobe Acrobat. Please contact mark.taub@pearson.com for information on the proper way to mark up PDFs so that we can work with them efficiently. Please return ONLY the marked pages to us within seven days of the date you receive the chapter. Please write your name on each page that you return to us. Please run every program. The source code is located in the directory from which you downloaded the PDF of this chapter. Please feel free to send any lengthy additional comments by e-mail to both paul.deitel@deitel.com and harvey.deitel@deitel.com. Please check that there are no inconsistencies, errors or significant omissions in the chapter discussions. Please do not rewrite the manuscript. Were concerned primarily with the relevance of examples, technical correctness, completeness and correct use of idiom. Please review the index to ensure weve covered (and indexed) the topics you feel are important. We provide a separate index for each chapter we send. The book will contain a only conventional full-book index.

Thanks! Paul and Harvey

Androidfp_17_enhaddr.fm Page 32 Tuesday, June 5, 2012 11:55 AM

32

Chapter 17 Enhanced Address Book App

[*** Chapter Assessment Page ***]


Dear Reviewer, Thank you for reviewing this chapter. Please assess the following items. Please rate the quality of the writing (1 is lowest, 10 is highest):
1 2 3 4 5 6 7 8 9 10

Please rate the quality of the code examples (1 is lowest, 10 is highest):


1 2 3 4 5 6 7 8 9 10

Please rate your overall assessment of the chapter (1 is lowest, 10 is highest):


1 2 3 4 5 6 7 8 9 10

Please rate the chapters breadth of coverage:


Too little Just right Too much

Please rate the chapters depth of coverage:


Too little Just right Too much

If too little content, what should we add? If too much, what should we remove?

REQUIRED
Please provide a general assessment of the chapter in the space below. Please do not simply outline the chapter featuresplease include positives and negatives. This information is crucial to the review process. Thanks!
[Note: We may use your comments, name and affiliation in our marketing materials.]

Androidfp_17_enhaddr.fm Page 33 Tuesday, June 5, 2012 11:55 AM

[***DUMP FILE***]

33

[***DUMP FILE***]

[***Bookwide***]
On lifecycle methods we typically lead with a comment that says something about when its called, not what occurs in the method body.
Activity

should be capitalized and CDT in the writearounds.

Androidfp_17_enhaddr.fm Page 34 Tuesday, June 5, 2012 11:55 AM

34

Chapter 17 Enhanced Address Book App

[***HISTORY***]
5/25/2012P applied H edits and sent to review/CE. 5/25/2012H read the chapter to prepare it for review. 5/24/2012H read the draft. 5/24/2012Paul completed draft of the chapter, minus Wrap-Up, key terms, indexing and updated screen captures. Michaels implementation of class BluetoothService was convoluted and needed to be redone. Paul redid Michaels code to make it work better, make it more understandable and to eliminate a couple of important bugs such as not being able to connect back to a device to send additional contacts, program crashes, loss of connectivity after 120 seconds, etc. Michael reworked the code from Chapter 10 to include the Bluetooth transfer capability 4/2/2010Abbey prepared the chaplet. Waiting tor Michaels content.

You might also like