Chapter 6

Using Your Accessory Library

WHAT’S IN THIS CHAPTER?

  • Using custom Android libraries
  • Foreground versus background processes
  • Custom Android UI widgets
  • Building the mini projects

WROX.COM CODE DOWNLOADS FOR THIS CHAPTER

The wrox.com code downloads for this chapter are found at www.wrox.com/remtitle.cgi?isbn=1118454766 on the Download Code tab. The code is in the Chapter 6 download and individually named according to the names throughout the chapter.

In this chapter you familiarize yourself in more detail with the Android Open Accessory (AOA) framework and what it means to use accessories in your apps; in particular, all the different steps you need to take to use accessories in your app and the different things you need to think about.

In this chapter you also use the WroxAccessories library that you developed in Chapter 5 to build four accessory-enabled apps for the mini projects introduced in Chapters 7 and 8. Using your library implies understanding how to add custom libraries to your application, so you’ll start off by exploring what a library is from the point of view of the Android application.

USING CUSTOM ANDROID LIBRARIES

You’ve already used Android libraries in your project; in fact, the Android SDK itself is a library! You can find the Android library inside any Android project. If you’re using the latest SDK (at the time of writing this was Android SDK 16, or 4.1), you can see it in the subfolder called “Android 4.1” in your Android project. The Android library file is called android.jar and contains all the compiled classes available in that particular SDK version. You can even browse the constants and methods available for each class from the Package Explorer.

The Android Support Libraries have been available since Android 3.1. You’ll often see them in projects developed to be compatible with Android versions earlier than 3.1. The Android Support Libraries enable you to use new components in old projects, without having to do much magic in your code; the most obvious example that is (or at least should be) used a lot by many developers is the Fragment. You can find the support library for your project (if it has one) under the Android Dependencies folder, and the file is called android-support-v?.jar.

You don’t need to worry about either of these libraries as they’re both added automatically to your build path in Eclipse. Adding custom libraries is slightly trickier, depending on your approach. In general, you can add custom libraries to your projects in two ways: either as compiled collections of class files (as in the example of android.jar and android-support-v?.jar) or by using the new Android-specific way, which is adding a special Android library project to your workspace. In this approach, Eclipse automatically builds the referenced libraries at the same time when building your application.


ANT IS SMART, SOMETIMES TOO SMART
Usually, Ant (or rather Apache Ant, the tool used to compile Android projects) manages to rebuild and compile only the files that changed in your projects. Ant is great — it does the heavy lifting of organizing your compilation and does it in a smart way by compiling only the files that need to be compiled.
However, sometimes Ant is so smart it gets lazy and seems to ignore some changes, or fails to update the compiled version of your library in your Android project. A general tip when working with Android libraries, then, is to clean both the Android application project and the Android library project when changes have been made; cleaning your projects like this forces Eclipse to do a complete rebuild of your projects.
1. To clean a project, open the Project menu and select Clean.
In the dialog box that pops up, you can either select to clean your entire workspace (not recommended if you have a lot of projects open) or to clean only the selected projects in the list.
2. Choose “Clean projects selected below” and then select the project you’re currently working on.

The WroxAccessories Library

The WroxAccessories library handles the communication with your accessory using the well-known MQTT protocol standard. However, it doesn’t define exactly how, or where, you should use it. The most obvious way to use your WroxAccessories library is within an activity that is closely linked to the communication, such as a control UI for a robot or a chat view; however, for the communication to work in these circumstances your activity needs to be visible in the foreground.

At times you want your accessory to work even if the accessory application isn’t in the foreground, or you want to share the accessory connection between multiple activities. In these circumstances it makes sense to push the communication to a service running in the background.

The Activity Approach

Because your accessory library already handles the communication in a thread different from the UI thread, the heavy lifting is done. The activity approach, then, means that you’ll instead focus your efforts on quickly building a working UI prototype without putting much consideration into creating services in which to run the communication.

This approach is suitable mostly for accessories that require only one user interface to work.

The Service Approach

In the service approach, you would first create an Android service running in either the local process or a process of its own, and then hook your Android user interface to that service using a Binder. This allows the accessory to run continuously regardless of the state of the user interface. It is a fair bit more complex than the previous approach, in that it requires at least a basic understanding of the service component in the Android system; it’s also intended for use in a completely different scenario.

As an example, consider a clock application that includes an alarm function. The clock must manage at least two basic functions: show the time when the application is in the foreground and at any time be able to alert the user of an alarm event. The second function needs to run both when the clock application is active in the foreground and, even more importantly, when the clock application isn’t in the foreground.


CONNECTING TO AN ANDROID ACCESSORY
Establishing an accessory connection can happen in two ways: either by detecting a system-wide broadcast or by manually requesting permission from the user to use to a connected accessory. Both ways require the user to manually select your application to handle the accessory.
The first option is the most intriguing because it automatically attempts to launch the appropriate application for the connected accessory, and it’s also the option used in the examples in this book. What’s important to note, though, is that the only component that can receive this system-wide broadcast is the activity. As you can imagine, this limitation causes some headaches when you want to use the accessory connection in a service because the activity needs to act as a proxy in setting up the connection.

Unfortunately, it’s not enough to just rely on the underlying mechanics of the WroxAccessories library you’ve developed; you still need to add some special components when you want to use Android accessories in your app.

The Manifest

The first thing you need to add to your manifest of any accessory-enabled application is the <uses-feature> element. This element declares what software or hardware features your app relies on. The Google Play store uses this element to filter available applications for your device. Some of the more common uses-feature declarations include hardware features like the camera or sensors such as the gyroscope.

You should add the highlighted code in the following snippet to the AndroidManifest.xml of any application that uses accessories; it’s a direct child of the <manifest> tag:

<manifest . . .>
  <uses-feature
    android:name="android.hardware.usb.accessory"
    android:required="true" />
</manifest>

Moving on, to make your launcher activity (often called MainActivity.java) attempt to start when the system fires the USB_ACCESSORY_ATTACHED broadcast, you need to modify the <intent-filter> element of that activity. See the following code snippet for a typical accessory-enabled filter.

<intent-filter>
  <action android:name="android.intent.action.MAIN" />
  <category android:name="android.intent.category.LAUNCHER" />
  <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
</intent-filter>

However, just making your application react to the USB_ACCESSORY_ATTACHED broadcast isn’t enough; each accessory is uniquely identified by the manufacturer, the model, and its version. This information is sent by the accessory when it connects to the Android device and you need to know this identification to let your app react properly to the accessory.

Create a new XML file with the root element <resources> containing only one child element, <usb-accessory>. See the following snippet for a typical declaration of this filter:

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <usb-accessory model="Android Application Name" manufacturer="You" version="1.0"/>
</resources>

The model name is the most important part; that is the name you have to use inside your accessory firmware (the Arduino). You learn more about this in Chapters 7 and 8.

To make your activity use this filter you have to add a <meta-data> element to your activity; this lets the activity know that there’s extra data it should take into consideration. You define the data, and what action the meta-data is and what action the meta-data belongs to. See the following snippet for a typical declaration:

<meta-data
  android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
  android:resource="@xml/accessory_filter" />

Note that it defines a resource file; this needs to correspond to the aforementioned XML filter where you defined which accessory you wanted to connect to.

BUILDING THE MINI PROJECTS

In this section you build the four mini projects: the Large SMS Display (LSMSD), the Sampler, the Parking Assistant, and the Basic Robot. These projects are meant as simple illustrations on how to build basic accessories using common sensors, actuators, and Arduino. In this chapter you focus on building the Android applications; you build the physical accessories in Chapters 7 and 8.

The LSMSD

The Large Short Message Service Display (LSMSD) is a large LED display connected to an Android accessory that displays incoming SMS messages. Because this application has no real user interface — it just prints a received SMS message on the LED display — you’ll build a service that runs in the background listening for new SMS messages. You can see the finished accessory in Figure 6-1.

FIGURE 6-1: The finished LED display

image

Create the Project

First, create the Eclipse project by following these steps:

1. From the File menu, select New ⇒ Android Application Project.
2. Enter LSMSD as the Application Name.
3. For the Package Name, enter com.wiley.aoa.lsmsd.
4. Select Android SDK 12 or above; if you want to use the earlier version of the USB Accessory you should select Google SDK 10 or above.
5. Click Next.
6. Style the launcher icon to your preference, using either an image or the supplied clipart resources.
7. Let Eclipse create the BlankActivity; this will only be used as an interface to start or stop the service.
8. Click Next.
9. Change the title of your activity to LSMSD, and leave everything else as is. Make sure Hierarchical Parent is empty.
10. Click Finish to create the project.

Eclipse should automatically load the layout for your MainActivity with the text “Hello world!” printed in the center. Go ahead and delete the text and replace it with a button instead, as shown in Figure 6-2.

FIGURE 6-2: The LSMSD user interface

image

Usually you’d change the ID of all UI widgets in your application, but because your entire interface consists of just this one button, you can skip that if you want.

Fix the Manifest

Before going further, make sure you add the <uses-feature> declaration to your manifest so that devices without support can’t install and run the app. See Listing 6-1.

LISTING 6-1: Add the uses-feature declaration

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.wiley.aoa.lsmsd"
  android:versionCode="1"
  android:versionName="1.0" >
  <uses-feature android:name="android.hardware.usb.accessory" android:required="true" />
  <uses-sdk
    android:minSdkVersion="12"
    android:targetSdkVersion="15" />
  <application
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >
    <activity
      android:name=".MainActivity"
      android:label="@string/title_activity_main" >
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
        <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
      </intent-filter>
      <meta-data
        android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
        android:resource="@xml/accessory_filter" />    </activity>
  </application>
</manifest>

Create the accessory_filter.xml

The accessory_filter defines what accessory your application can connect to. It’s a resource file often located in the /res/xml folder. Follow these steps to create a new Android XML Resources file:

1. From the File menu, select New ⇒ Other.
2. Expand the Android category in the dialog box.
3. Select Android XML Values File and click Next.
4. In the File box, enter accessory_filter.
5. Select resources as the Root Element and click Next.
6. Change the folder name to /res/xml and click Finish. If this fails, change it back to /res/values and simply move the file later by dragging it to the /res/xml folder.

Open your new XML file, located inside the /res/xml folder, and add the <usb-accessory> element as shown in Listing 6-2.

LISTING 6-2: Add the <usb-accessory> element

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <usb-accessory manufacturer="Wiley" model="Large SMS Display" version="1.0" />
</resources>

Create the Service

Before you make any changes to the MainActivity class, you should create the service; after all, the MainActivity just starts and stops the service.

1. In the Package Explorer, expand the src folder and select the package called com.wiley.aoa.lsmsd.
2. From the File menu, select New ⇒ Class.
3. Call the service AoaService.
4. As the superclass, enter android.app.Service.
5. Uncheck the public static void main checkbox to make sure you do not create the Main method.
6. Click Finish to create the class.

You should end up with something similar to Listing 6-3.

LISTING 6-3: Create the AoaService

package com.wiley.aoa.lsmsd;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class AoaService extends Service {
  @Override
  public IBinder onBind(Intent intent) {
    return null;
  }
}

Opening the Service for Connections

The way services work is that they run in a process; either the same process as the user interface (called the local service) or a process of its own (called a remote service). Though slightly different, the two service types share the idea of allowing other Android application components to bind to them. Add the local binder for the service as shown in Listing 6-4.

LISTING 6-4: Add the binder interface

package com.wiley.aoa.lsmsd;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
public class AoaService extends Service {
  private final IBinder mBinder = new AoaBinder();
  public class AoaBinder extends Binder {
    AoaService getService() {
      return AoaService.this;
    }
  }
  @Override
  public IBinder onBind(Intent intent) {
    return mBinder;
  }
}

Reading Incoming SMS Messages

When the Android device receives an SMS message, it automatically publishes a device-wide broadcast with all the details for that SMS. The only thing your service needs to do to get messages is register a receiver for those broadcasts. Add the BroadcastReciever as shown in Listing 6-5.

LISTING 6-5: Add the SMS receiver

package com.wiley.aoa.lsmsd;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.telephony.SmsMessage;
import android.util.Log;
import android.widget.Toast;
public class AoaService extends Service {
  private final IBinder mBinder = new AoaBinder();
  public class AoaBinder extends Binder {
    AoaService getService() {
      return AoaService.this;
    }
  }
  @Override
  public void onCreate() {
    super.onCreate();
    IntentFilter filter = new IntentFilter();
    filter.addAction("android.provider.Telephony.SMS_RECEIVED");
    registerReceiver(smsReceiver, filter);
  }
  @Override
  public void onDestroy() {
    super.onDestroy();
    unregisterReceiver(smsReceiver);
  }
  @Override
  public IBinder onBind(Intent intent) {
    return mBinder;
  }
  private BroadcastReceiver smsReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
      Bundle pudsBundle = intent.getExtras();
      Object[] pdus = (Object[]) pudsBundle.get("pdus");
      SmsMessage messages = SmsMessage.createFromPdu((byte[]) pdus[0]);
      String sms = messages.getMessageBody();
    }
  };
}

Add the WroxAccessory Instance

Having created the core background service, all that’s left is to hook it up to the WroxAccessory and send the SMS message as it arrives. Add the WroxAccessory instance as shown in Listing 6-6.

LISTING 6-6: Add the WroxAccessory instance

package com.wiley.aoa.lsmsd;import java.io.IOException;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.telephony.SmsMessage;
import android.widget.Toast;
import android.hardware.usb.UsbManager;
import com.wiley.wroxaccessories.UsbConnection12;
import com.wiley.wroxaccessories.WroxAccessory;
public class AoaService extends Service {
  private final IBinder mBinder = new AoaBinder();
  private WroxAccessory mAccessory;
  private UsbConnection12 mConnection;
    public class AoaBinder extends Binder {
    AoaService getService() {
      return AoaService.this;
    }
  }
  @Override
  public void onCreate() {
    super.onCreate();
    IntentFilter filter = new IntentFilter();
    filter.addAction("android.provider.Telephony.SMS_RECEIVED");
    registerReceiver(mReceiver, filter);
    if (mAccessory == null)
      mAccessory = new WroxAccessory(this);
    UsbManager manager = (UsbManager) getSystemService(USB_SERVICE);
    if (mConnection == null)
      mConnection = new UsbConnection12(this, manager);
    try {
      mAccessory.connect(WroxAccessory.USB_ACCESSORY_12, mConnection);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
  @Override
  public boolean onUnbind(Intent intent) {
    return super.onUnbind(intent);
  }
  @Override
  public void onDestroy() {
    super.onDestroy();
    unregisterReceiver(mReceiver);
    try {
      mAccessory.disconnect();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
  @Override
  public IBinder onBind(Intent intent) {
    return mBinder;
  }
  private BroadcastReceiver mReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
      Bundle pudsBundle = intent.getExtras();
      Object[] pdus = (Object[]) pudsBundle.get("pdus");
      SmsMessage messages = SmsMessage.createFromPdu((byte[]) pdus[0]);
      String sms = messages.getMessageBody();
    }
  };
}

Pass the SMS to Your Accessory

The final thing to do is to send the MQTT message to your accessory. Publish the message as shown in Listing 6-7.

LISTING 6-7: Publishing an SMS

package com.wiley.aoa.lsmsd;
import java.io.IOException;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.telephony.SmsMessage;
import android.hardware.usb.UsbManager;
import com.wiley.wroxaccessories.UsbConnection12;
import com.wiley.wroxaccessories.WroxAccessory;
public class AoaService extends Service {
  private final IBinder mBinder = new AoaBinder();
  private WroxAccessory mAccessory;
  private UsbConnection12 mConnection;
  public class AoaBinder extends Binder {
    AoaService getService() {
      return AoaService.this;
    }
  }
  @Override
  public void onCreate() {
    super.onCreate();
    IntentFilter filter = new IntentFilter();
    filter.addAction("android.provider.Telephony.SMS_RECEIVED");
    registerReceiver(mReceiver, filter);
    if (mAccessory == null)
      mAccessory = new WroxAccessory(this);
    UsbManager manager =(UsbManager) getSystemService(USB_SERVICE);
    if (mConnection == null)
      mConnection = new UsbConnection12(this, manager);
    try {
      mAccessory.connect(WroxAccessory.USB_ACCESSORY_12, mConnection);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }  @Override
  public boolean onUnbind(Intent intent) {
    return super.onUnbind(intent);
  }  @Override
  public void onDestroy() {
    super.onDestroy();
    unregisterReceiver(mReceiver);
    try {
      mAccessory.disconnect();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
  @Override
  public IBinder onBind(Intent intent) {
    return mBinder;
  }
  private BroadcastReceiver mReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
      Bundle pudsBundle = intent.getExtras();
      Object[] pdus = (Object[]) pudsBundle.get("pdus");
      SmsMessage messages = SmsMessage.createFromPdu((byte[]) pdus[0]);
      String sms = messages.getMessageBody();
      try {
        mAccessory.publish("sms", sms.getBytes());
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  };
}

You’re now ready to build the Arduino accessory in Chapter 7, and then you’ll have your very own LSMSD to publicize all the fancy SMS messages you receive.

Improving the Prototype

Some SMS messages just aren’t meant to be displayed publicly, so the first possible improvement to the LSMSD would be a filter of some sort. Perhaps only displaying messages sent from contacts that the user manually selects in a list is a wise improvement.

You could also add extra information to the display, such as time since you received a message or showing multiple messages after each other.

Another improvement would be letting the user interact with messages by pressing buttons or pulling levers on the accessory, so that he or she doesn’t need to open the phone to mark a message as read.

The Parking Assistant

The Parking Assistant is intended to help you avoid hitting things as you reverse your car. Most modern cars have this already built in — they emit sound beeps, and the interval between them defines the distance to an object behind your car.

In this mini project, however, you build a visual aid to parking your car; obviously not the best idea if you consider the project from an interaction design perspective. But you’ll find that out as you try the prototype yourself on your car. For clarity, do not use this aid in a “live” setting without having tested it in controlled environments first.

Figure 6-3 shows the finished, mounted prototype.

FIGURE 6-3: The finished Parking Assistant

image

Create the Project

Follow these steps to create the Eclipse project:

1. From the File menu in Eclipse, select New ⇒ Android Application Project.
2. Enter Parking Assistant as the Application Name.
3. For the Package Name, enter com.wiley.aoa.parking_assistant.
4. Select Android SDK 12 or above. If you want to work with the older USB Accessory, select Google SDK 10.
5. Click Next.
6. Style the launcher icon to your preference, using either an image or the supplied clipart resources.
7. Let Eclipse create the BlankActivity.
8. Click Next.
9. Change the title of your activity to Parking Assistant, and leave everything else as is.
10. Click finish to create the project.

Because you’re building the Parking Assistant as a USB accessory, remember to add the required elements to the manifest: the <uses-feature> declaration, the extra action to your <intent-filter>, and the <meta-data> element defining which accessory you’ll connect to. See Listing 6-8 for details.

LISTING 6-8: Add the needed manifest changes

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.wiley.aoa.parking_assistant"
  android:versionCode="1"
  android:versionName="1.0" >
  <uses-feature android:name="android.hardware.usb.accessory" android:required="true" />
  <uses-sdk
    android:minSdkVersion="12"
    android:targetSdkVersion="15" />
  <application
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >
    <activity
      android:name=".MainActivity"
      android:label="@string/title_activity_main" >
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
        <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
      </intent-filter>
      <meta-data
        android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
        android:resource="@xml/accessory_filter" />
    </activity>
  </application>
</manifest>

Create the accessory_filter.xml

The accessory_filter defines what accessory your application can connect to. It’s a resource file often located in the /res/xml folder. Follow these steps to create a new Android XML Resources file:

1. From the File menu, select New ⇒ Other.
2. Expand the Android category in the dialog box.
3. Select Android XML Values File and click Next.
4. In the File box, enter accessory_filter.
5. Select resources as the Root Element and click Next.
6. Change the folder name to /res/xml and click Finish. If this fails, change it back to /res/values and simply move the file later by dragging it to the /res/xml folder.

Open your new XML file, located inside the /res/xml folder, and add the <usb-accessory> element as shown in Listing 6-9.

LISTING 6-9: Add the <usb-accessory> element

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <usb-accessory manufacturer="Wiley" model="Parking Assistant" version="1.0" />
</resources>

Link Your WroxAccessories Library

To connect to the WroxAccessory you need to link the library to your build path. You do this by using the project context menu:

1. Select your project in the Package Explorer, and in the Eclipse File menu select Properties.
2. Select Android on the left side.
3. Click the Add button on the right side within the Library panel.
4. Select WroxAccessories in the dialog box that pops up.
5. Click Apply and close the dialog box by clicking OK.

Build the User Interface

Eclipse should automatically load the layout for your MainActivity with the text “Hello world!” printed in the center. Go ahead and delete the text and replace it with a ProgressBar that expands over the whole screen. This will visualize the distance to the objects behind the vehicle. Over the ProgressBar you should place a TextView, centered in the screen, that will display the distance with more detail. See Listing 6-10.

LISTING 6-10: The Parking Assistant user interface

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent" >
  <ProgressBar
    android:id="@+id/progressBar1"
    style="?android:attr/progressBarStyleHorizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_centerHorizontal="true"
    android:layout_centerVertical="true"
    android:layout_margin="5dp" />
  <TextView
    android:id="@+id/textView1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:layout_centerVertical="true"
    android:text="Large Text"
    android:textAppearance="?android:attr/textAppearanceLarge" />
</RelativeLayout>

The standard ProgressBar is horizontal, however, and that’s just not very suitable for this application. To make the ProgressBar vertical instead, you should create a custom drawable:

1. From the File menu, select New ⇒ Other.
2. Select Android XML File.
3. Set the Resource Type to Drawable.
4. Enter myprogress as the File name.
5. Select layer-list as the Root Element.

Add the items from Listing 6-11 to your new myprogress.xml file. The names of the <item> elements are important in this file because the ProgressBar expects them to match a certain pattern.

LISTING 6-11: Create the vertical progress bar

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
  <item android:id="@android:id/background">
    <shape>
      <solid android:color="#FFFFFF" />
    </shape>
  </item>
  <item android:id="@android:id/secondaryProgress">
    <clip
      android:clipOrientation="vertical"
      android:gravity="bottom" >
      <shape>
        <corners android:radius="5dip" />
        <solid android:color="#00FF00" />
      </shape>
    </clip>
  </item>
  <item android:id="@android:id/progress">
    <clip
      android:clipOrientation="vertical"
      android:gravity="bottom" >
      <shape>
        <corners android:radius="5dip" />
        <solid android:color="#00FF00" />
      </shape>
    </clip>
  </item>
</layer-list>

To make your ProgressBar use this new drawable, add the android:progressDrawable attribute to the <ProgressBar> element. See the following code snippet for details:

<ProgressBar
  android:id="@+id/progressBar1"
  style="?android:attr/progressBarStyleHorizontal"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:layout_centerHorizontal="true"
  android:layout_centerVertical="true"
  android:layout_margin="5dp"
  android:progressDrawable="@drawable/myprogress" />

The finished layout should look something like Figure 6-4.

FIGURE 6-4: The user interface for the Parking Assistant

image

Load the User Interface

Now, because you want to update this UI you need some references to it. Declare the ProgressBar object and the TextView object in your MainActivity class. See Listing 6-12.

LISTING 6-12: Load the user interface

package com.wiley.aoa.parking_assistant;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ProgressBar;
import android.widget.TextView;
public class MainActivity extends Activity {
  private ProgressBar mProgressBar;
  private TextView mTextView;
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mProgressBar = (ProgressBar) findViewById(R.id.progressBar1);
    mProgressBar.setMax(6);
    mProgressBar.setProgress(0);
    mTextView = (TextView) findViewById(R.id.textView1);
  }
}

Create the WroxAccessory Instance

You need an instance of the library interface for handling the connection to your accessory. Add it to your MainActivity.java class as shown in Listing 6-13, passing the current context as an argument.

LISTING 6-13: Add the WroxAccessory instance

package com.wiley.aoa.parking_assistant;
import com.wiley.wroxaccessories.WroxAccessory;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ProgressBar;
import android.widget.TextView;public class MainActivity extends Activity {
  private ProgressBar mProgressBar;
  private TextView mTextView;
  private WroxAccessory mWroxAccessory;
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mProgressBar = (ProgressBar) findViewById(R.id.progressBar1);
    mProgressBar.setMax(6);
    mProgressBar.setProgress(0);
    mTextView = (TextView) findViewById(R.id.textView1);
    mWroxAccessory = new WroxAccessory(this);
  }
}

Connecting

The WroxAccessory constructor initiates all the necessary framework for establishing a connection, but it doesn’t create the connection itself. For that you’ll need another object, the Connection object. Depending on the type of accessory you’re building the Parking Assistant to be — USB or Bluetooth — instantiate a Connection object as shown in Listing 6-14.

LISTING 6-14: Create the connection

package com.wiley.aoa.parking_assistant;
import com.wiley.wroxaccessories.UsbConnection12;
import com.wiley.wroxaccessories.WroxAccessory;
import android.app.Activity;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.widget.ProgressBar;
import android.widget.TextView;public class MainActivity extends Activity {
  private ProgressBar mProgressBar;
  private TextView mTextView;
  private WroxAccessory mWroxAccessory;
  private UsbManager mUsbManager;
  private UsbConnection12 mUsbConnection12;
    @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mProgressBar = (ProgressBar) findViewById(R.id.progressBar1);
    mProgressBar.setMax(6);
    mProgressBar.setProgress(0);
    mTextView = (TextView) findViewById(R.id.textView1);
    mWroxAccessory = new WroxAccessory(this);
    mUsbManager = (UsbManager) getSystemService(USB_SERVICE);
    mUsbConnection12 = new UsbConnection12(this, mUsbManager);
    try {
      mAccessory.connect(WroxAccessory.USB_ACCESSORY_12, connection);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

Make sure to disconnect as well. See Listing 6-15.

LISTING 6-15: Disconnect from the accessory

package com.wiley.aoa.parking_assistant;
import com.wiley.wroxaccessories.UsbConnection12;
import com.wiley.wroxaccessories.WroxAccessory;
import android.app.Activity;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.widget.ProgressBar;
import android.widget.TextView;
public class MainActivity extends Activity {
  private ProgressBar mProgressBar;
  private TextView mTextView;
  private WroxAccessory mWroxAccessory;
  private UsbManager mUsbManager;
  private UsbConnection12 mUsbConnection12;
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mProgressBar = (ProgressBar) findViewById(R.id.progressBar1);
    mProgressBar.setMax(6);
    mProgressBar.setProgress(0);
    mTextView = (TextView) findViewById(R.id.textView1);
    mWroxAccessory = new WroxAccessory(this);
    mUsbManager = (UsbManager) getSystemService(USB_SERVICE);
    mUsbConnection12 = new UsbConnection12(this, mUsbManager);
    mWroxAccessory.connect(WroxAccessory.USB_ACCESSORY_12, mUsbConnection12);
  }
  @Override
  protected void onDestroy() {
    super.onDestroy();
    try {
      mAccessory.disconnect();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

Interacting with the WroxAccessory

You’re building an application that will listen for information from the accessory, not the other way around. This means you need to subscribe to a topic on the Android side; otherwise, you won’t receive any data from the accessory. Add the subscription as shown in Listing 6-16.

LISTING 6-16: Subscribe to the “us” topic

package com.wiley.aoa.parking_assistant;
import com.wiley.wroxaccessories.UsbConnection12;
import com.wiley.wroxaccessories.WroxAccessory;
import android.app.Activity;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.widget.ProgressBar;
import android.widget.TextView;
public class MainActivity extends Activity {
  private ProgressBar mProgressBar;
  private TextView mTextView;
  private WroxAccessory mWroxAccessory;
  private UsbManager mUsbManager;
  private UsbConnection12 mUsbConnection12;
  private int id = 0;
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mProgressBar = (ProgressBar) findViewById(R.id.progressBar1);
    mProgressBar.setMax(6);
    mProgressBar.setProgress(0);
    mTextView = (TextView) findViewById(R.id.textView1);
    mWroxAccessory = new WroxAccessory(this);
    mUsbManager = (UsbManager) getSystemService(USB_SERVICE);
    mUsbConnection12 = new UsbConnection12(this, mUsbManager);
    mWroxAccessory.connect(WroxAccessory.USB_ACCESSORY_12, mUsbConnection12);
  }
  @Override
  protected void onResume() {
    super.onResume();
    try {
      subscription = mAccessory.subscribe(receiver, "us", id++);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
  @Override
  protected void onDestroy() {
    super.onDestroy();
    try {
      mAccessory.disconnect();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
  private BroadcastReceiver receiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
      if (intent.getAction().equals(subscription)) {
        byte[] payload = intent.getByteArrayExtra(subscription + ".payload");
        mProgressBar.setProgress(payload[0]);
        mTextView.setText(payload[0] + " m");
      }
    }
  };
}

Possible Improvements

The most obvious improvement to this mini project is to fix the dangerous interaction; you should never talk on your phone as you drive your car, and you should definitely not look on the phone’s screen as you try to reverse your car! So, instead of using a visual feedback you should add sound to the application, and change a variable in the sound playback (interval, pitch, volume, or other) depending on the distance.

The Basic Robot

Building a robot isn’t as hard as it may seem. Of course, if you want your robot to be as brilliant as Data, or as fierce as the Terminator, you’ll find yourself taking on quite a challenge. The little robot you build for this application won’t be as impressive as those sci-fi alternatives; it will have no intelligence of its own, and will only be able to take simple movement commands from you. You can see the final prototype in Figure 6-5.

FIGURE 6-5: The Basic Robot

image

Create the Project

To create the Basic Robot project, follow these steps:

1. In the Eclipse menu, select File ⇒ New ⇒ Android Application Project.
2. Enter Basic Robot as the Application Name.
3. For the Package Name, enter com.wiley.aoa.basic_robot.
4. Select Android SDK 12 or above. If you want to use the first version of the Accessory library, select Google SDK 10 or above instead.
5. Click Next.
6. Style the launcher icon to your preference, using either an image or the supplied clipart resources.
7. Let Eclipse create the BlankActivity; this will only be used as an interface to start or stop the service.
8. Click Next.
9. Change the title of your activity to Basic Robot, and leave everything else as is.
10. Click Finish to create the project.

Like with all accessory-enabled projects, you need to add some attributes and elements to the manifest. See Listing 6-17.

LISTING 6-17: Add the needed manifest changes

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.wiley.aoa.basic_robot"
  android:versionCode="1"
  android:versionName="1.0" >
  <uses-feature android:name="android.hardware.usb.accessory" />
  <uses-sdk
    android:minSdkVersion="12"
    android:targetSdkVersion="15" />
  <application
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >
    <activity
      android:name=".MainActivity"
      android:label="@string/title_activity_main" >
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
        <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
      </intent-filter>
      <meta-data
        android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
        android:resource="@xml/accessory_filter" />
    </activity>
  </application>
</manifest>

Create the accessory_filter.xml

The accessory_filter defines what accessory your application can connect to. It’s a resource file often located in the /res/xml folder. Follow these steps to create a new Android XML Resources file:

1. From the File menu, select New ⇒ Other.
2. Expand the Android category in the dialog box.
3. Select Android XML Values File and click Next.
4. In the File box, enter accessory_filter.
5. Select resources as the Root Element and click Next.
6. Change the folder name to /res/xml and click Finish. If this fails, change it back to /res/values and simply move the file later by dragging it to the /res/xml folder.

Open your new XML file, located inside the /res/xml folder, and add the <usb-accessory> element as shown in Listing 6-18.

LISTING 6-18 Add the <usb-accessory> element

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <usb-accessory manufacturer="Wiley" model="Basic Robot" version="1.0" />
</resources>

Link Your WroxAccessories Library

To connect to the WroxAccessory you need to link the library to your build path. You do this by using the project context menu:

1. Select your project in the Package Explorer and in the File menu, select Properties.
2. Select Android on the left side.
3. Click the Add button on the right side within the Library panel.
4. Select WroxAccessories in the dialog box that pops up.
5. Click Apply and exit by clicking OK.

Build the User Interface

The user interface to control Basic Robot is quite simple; it contains five different buttons placed in a cross, as you can see in Figure 6-6.

FIGURE 6-6: The Basic Robot interface

image

The central button tells the robot to stop everything it is currently doing; in essence, it tells the robot to stop moving because it can’t do anything else. The button to the north tells Basic Robot to move forward, the button to the south tells Basic Robot to move backward, and the two buttons on the side tell him to move to the left or right.


NESTED WEIGHTS
You can achieve this layout in a number of ways; one easy way is using LinearLayouts with nested weights. Although this will work, it’s also going to be costly for your app, so in general it’s not recommended to use the weight attribute. But because it’s such a simple app with no real other performance issues, you can safely use it in this example.
You can, of course, try other alternatives. GridLayout (requires API 14 or above) or TableLayout are the two primary alternatives to nested weights.

Build the user interface as shown in Listing 6-19.

LISTING 6-19: Basic Robot user interface

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent" >
  <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_alignParentLeft="true"
    android:layout_alignParentTop="true"
    android:orientation="vertical" >
    <LinearLayout
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:layout_weight="1" >
      <View
        android:id="@+id/view1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1" />
      <Button
        android:id="@+id/button_north"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:text="Button" />
      <View
        android:id="@+id/view2"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1" />
    </LinearLayout>
    <LinearLayout
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:layout_weight="1" >
      <Button
        android:id="@+id/button_west"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:text="Button" />
      <Button
        android:id="@+id/button_stop"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:text="Button" />
      <Button
        android:id="@+id/button_east"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:text="Button" />
    </LinearLayout>
    <LinearLayout
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:layout_weight="1" >
      <View
        android:id="@+id/view3"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1" />
      <Button
        android:id="@+id/button_south"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:text="Button" />
      <View
        android:id="@+id/view4"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1" />
    </LinearLayout>
  </LinearLayout>
</RelativeLayout>

When you have the layout you want, it’s time to apply a little bit of style. Much like before, you use a selector drawable for all your buttons. Create the selector as shown in Listing 6-20.

LISTING 6-20: The Basic Robot selector

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
  <item android:state_pressed="true">
    <shape android:shape="rectangle">
      <corners android:radius="5dp" />
      <solid android:color="#043C6B"/>
      <stroke android:width="2dp" android:color="#25547B"/>
      <padding 
        android:left="5dp" android:top="5dp" 
        android:right="5dp" android:bottom="5dp" />
    </shape>
  </item>
  <item android:state_focused="true">
    <shape android:shape="rectangle">
      <corners android:radius="5dp" />
      <solid android:color="#043C6B"/>
      <stroke android:width="2dp" android:color="#25547B"/>
      <padding 
       android:left="5dp" android:top="5dp" 
       android:right="5dp" android:bottom="5dp" />
    </shape>
  </item>
  <item>
    <shape android:shape="rectangle">
      <corners android:radius="5dp" />
      <solid android:color="#0B5FA5"/>
      <stroke android:width="2dp" android:color="#25547B"/>
      <padding 
        android:left="5dp" android:top="5dp" 
        android:right="5dp" android:bottom="5dp" />
    </shape>
  </item>
</selector>

You also want to add some text to the buttons to make it even more clear what each button does. Open the strings.xml file in your project; it’s located inside the /res/values folder. Add the strings as shown in Listing 6-21.

LISTING 6-21: Create the button labels

<resources>
  <string name="app_name">Basic Robot</string>
  <string name="hello_world">Hello world!</string>
  <string name="menu_settings">Settings</string>
  <string name="title_activity_main">Basic Robot</string>
  <string name="north">forward</string>
  <string name="east">right</string>
  <string name="stop">stop</string>
  <string name="west">left</string>
  <string name="south">back</string>
</resources>

With all the UI styles complete, you can go ahead and apply them to the buttons, as shown in Listing 6-22.

LISTING 6-22: Apply the styles

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent" >
  <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_alignParentLeft="true"
    android:layout_alignParentTop="true"
    android:orientation="vertical" >
    <LinearLayout
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:layout_weight="1" >
      <View
        android:id="@+id/view1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1" />
      <Button
        android:id="@+id/button_north"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:background="@drawable/mybutton"
        android:text="@string/north" />
      <View
        android:id="@+id/view2"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1" />
    </LinearLayout>
    <LinearLayout
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:layout_weight="1" >
      <Button
        android:id="@+id/button_west"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:background="@drawable/mybutton"
        android:text="@string/west" />
      <Button
        android:id="@+id/button_stop"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:background="@drawable/mybutton"
        android:text="@string/stop" />
      <Button
        android:id="@+id/button_east"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:background="@drawable/mybutton"
        android:text="@string/east" />
    </LinearLayout>
    <LinearLayout
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:layout_weight="1" >
      <View
        android:id="@+id/view3"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1" />
      <Button
        android:id="@+id/button_south"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:background="@drawable/mybutton"
        android:text="@string/south" />
      <View
        android:id="@+id/view4"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1" />
    </LinearLayout>
  </LinearLayout>
</RelativeLayout>

Connect to the WroxAccessory

Create the connection to your WroxAccessory as shown in Listing 6-23.

LISTING 6-23: The WroxAccessory connection

package com.wiley.aoa.basic_robot;
import java.io.IOException;
import android.app.Activity;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import com.wiley.wroxaccessories.UsbConnection12;
import com.wiley.wroxaccessories.WroxAccessory;
public class MainActivity extends Activity {
  private WroxAccessory mWroxAccessory;
  private UsbManager mUsbManager;
  private UsbConnection12 mUsbConnection12;
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mWroxAccessory = new WroxAccessory(this);
    mUsbManager = (UsbManager) getSystemService(USB_SERVICE);
    mUsbConnection12 = new UsbConnection12(this, mUsbManager);
    try {
      mWroxAccessory.connect(WroxAccessory.USB_ACCESSORY_12, mUsbConnection12);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
  
    @Override
  protected void onPause() {
    try {
      mWroxAccessory.disconnect();
    } catch (IOException e) {
      e.printStackTrace();
    }
    super.onPause();
  }
}

Hook Up the User Interface

You need to create an OnClickListener for your buttons and attach it. The listener will then determine which button it received an event from and publish the appropriate message to the accessory. See Listing 6-24.

LISTING 6-24: The OnClickListener

package com.wiley.aoa.basic_robot;
import java.io.IOException;
import android.app.Activity;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import com.wiley.wroxaccessories.UsbConnection12;
import com.wiley.wroxaccessories.WroxAccessory;
public class MainActivity extends Activity {
  private WroxAccessory mWroxAccessory;
  private UsbManager mUsbManager;
  private UsbConnection12 mUsbConnection12;
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mWroxAccessory = new WroxAccessory(this);
    mUsbManager = (UsbManager) getSystemService(USB_SERVICE);
    mUsbConnection12 = new UsbConnection12(this, mUsbManager);
    Button button = (Button) findViewById(R.id.button_north);
    button.setOnClickListener(buttonListener);
    button = (Button) findViewById(R.id.button_east);
    button.setOnClickListener(buttonListener);
    button = (Button) findViewById(R.id.button_south);
    button.setOnClickListener(buttonListener);
    button = (Button) findViewById(R.id.button_west);
    button.setOnClickListener(buttonListener);
    button = (Button) findViewById(R.id.button_stop);
    button.setOnClickListener(buttonListener);
  }
  @Override
  protected void onResume() {
    try {
      mWroxAccessory.connect(WroxAccessory.USB_ACCESSORY_12, mUsbConnection12);
    } catch (IOException e) {
      e.printStackTrace();
    }
    super.onResume();
  }
  @Override
  protected void onPause() {
    try {
      mWroxAccessory.disconnect();
    } catch (IOException e) {
      e.printStackTrace();
    }
    super.onPause();
  }
 private OnClickListener buttonListener = new OnClickListener() {
    public void onClick(View v) {
      byte[] message = new byte[1];      
      switch (v.getId()) {
      case R.id.button_north:
        message[0] = 1;
        break;
      case R.id.button_east:
        message[0] = 2;
        break;
      case R.id.button_south:
        message[0] = 3;
        break;
      case R.id.button_west:
        message[0] = 4;
        break;
      case R.id.button_stop:
        message[0] = 0;
      }
      try {
        mWroxAccessory.publish("mv", message);
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  };
}

Possible Improvements

The first thing that comes to mind when thinking of improvements for the Basic Robot is more actuators on the accessory. You could, for example, add some LEDs in the front and back of the robot that would act as headlights and brakelights, and then turn them on or off depending on how you steer.

Another improvement is in the way you interact with the robot. Instead of using buttons you could build a software joystick, like the ones you find in many popular games for handheld devices.

The Sampler

The Sampler is a project that turns the phone into a music machine, giving you a way to play sounds on the device by pressing physical buttons — turning it into a sampler or keyboard, if you will. This is by far the easiest of all the mini projects, using built-in sounds effectively through the RingtoneManager.

Figure 6-7 shows the finished Sampler prototype.

FIGURE 6-7: The Sampler

image

Create the Project

Start by creating a new Eclipse project. Follow these steps:

1. From the File menu, select New ⇒ Android Application Project.
2. Enter The Sampler as the Application Name.
3. For the Package Name, enter com.wiley.aoa.the_sampler.
4. Select Android SDK 12 or above. If you want to use the first version of the Accessory library, select Google SDK 10 or above instead.
5. Click Next.
6. Style the launcher icon to your preference, using either an image or the supplied clipart resources.
7. Let Eclipse create the BlankActivity; this will only be used as an interface to start or stop the service.
8. Click Next.
9. Change the title of your activity to The Sampler, and leave everything else as is.
10. Click Finish to create the project.

Don’t forget to make the required changes to the manifest. See Listing 6-25.

LISTING 6-25: Add the needed manifest changes

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.wiley.aoa.basic_robot"
  android:versionCode="1"
  android:versionName="1.0" >
  <uses-feature android:name="android.hardware.usb.accessory" />
  <uses-sdk
    android:minSdkVersion="12"
    android:targetSdkVersion="15" />
  <application
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >
    <activity
      android:name=".MainActivity"
      android:label="@string/title_activity_main" >
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
        <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
      </intent-filter>
      <meta-data
        android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
        android:resource="@xml/accessory_filter" />
    </activity>
  </application>
</manifest>

Create the accessory_filter.xml

The accessory_filter defines what accessory your application can connect to. It’s a resource file often located in the /res/xml folder. Follow these steps to create a new Android XML Resources file:

1. From the File menu, select New ⇒ Other.
2. Expand the Android category in the dialog box.
3. Select Android XML Values File and click Next.
4. In the File box, enter accessory_filter.
5. Select resources as the Root Element and click Next.
6. Change the folder name to /res/xml and click Finish. If this fails, change it back to /res/values and simply move the file later by dragging it to the /res/xml folder.

Open your new XML file, located inside the /res/xml folder, and add the <usb-accessory> element as shown in Listing 6-26.

LISTING 6-26 Add the <usb-accessory> element

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <usb-accessory manufacturer="Wiley" model="The Sampler" version="1.0" />
</resources>

Add the WroxAccessory object, the Connection object, and the UsbManager to your MainActivity.java class. You’ll use these to interact with the accessory. See Listing 6-27.

LISTING 6-27: Add the WroxAccessory variables

package com.wiley.aoa.the_sampler;
import android.app.Activity;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import com.wiley.wroxaccessories.UsbConnection12;
import com.wiley.wroxaccessories.WroxAccessory;
public class MainActivity extends Activity {
  private WroxAccessory mAccessory;
  private UsbManager mUsbManager;
  private UsbConnection12 connection;
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
  }
}

Perform the connection inside the onCreate life-cycle method, and disconnect inside onDestroy. The connect method sets up the communication and starts the threads, and the disconnect method effectively kills the communication, so you don’t want to do that before you know that your app is dying. See Listing 6-28.

LISTING 6-28: Let the accessory connect and disconnect

package com.wiley.aoa.the_sampler;
import android.app.Activity;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import com.wiley.wroxaccessories.UsbConnection12;
import com.wiley.wroxaccessories.WroxAccessory;
public class MainActivity extends Activity {
  private WroxAccessory mAccessory;
  private UsbManager mUsbManager;
  private UsbConnection12 connection;
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mUsbManager = (UsbManager) getSystemService(USB_SERVICE);
    connection = new UsbConnection12(this, mUsbManager);
    mAccessory = new WroxAccessory(this);
    mRingtoneManager = new RingtoneManager(this);
    try {
      mAccessory.connect(WroxAccessory.USB_ACCESSORY_12, connection);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
  @Override
  protected void onDestroy() {
    super.onDestroy();
  }    try {
      mAccessory.disconnect();
    } catch (IOException e) {
      e.printStackTrace();
    }
}

In this mini project you use the RingtoneManager to play short sounds when the pushbuttons on the prototype are pressed. It might not be the best choice in performance and options, but it’s a quick way of utilizing already available audio on every device. Notice that in onPause you stop the previously played RingTone; if you didn’t it would just keep playing. Listing 6-29 shows you how to get a reference to the RingtoneManager.

LISTING 6-29: Add the RingtoneManager

package com.wiley.aoa.the_sampler;
import java.io.IOException;
import android.app.Activity;
import android.hardware.usb.UsbManager;
import android.media.RingtoneManager;
import android.os.Bundle;
import com.wiley.wroxaccessories.UsbConnection12;
import com.wiley.wroxaccessories.WroxAccessory;
public class MainActivity extends Activity {
  RingtoneManager mRingtoneManager;
  private WroxAccessory mAccessory;
  private UsbManager mUsbManager;
  private UsbConnection12 connection;
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mUsbManager = (UsbManager) getSystemService(USB_SERVICE);
    connection = new UsbConnection12(this, mUsbManager);
    mAccessory = new WroxAccessory(this);
    try {
      mAccessory.connect(WroxAccessory.USB_ACCESSORY_12, connection);
    } catch (IOException e) {
      e.printStackTrace();
    }
    mRingtoneManager = new RingtoneManager(this);
  }
  @Override
  protected void onPause() {
    super.onPause();
    mRingtoneManager.stopPreviousRingtone();
  }
  @Override
  protected void onDestroy() {
    super.onDestroy();
    try {
      mAccessory.disconnect();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

Finally, subscribe to messages from the accessory on the topic “ts,” as shown in Listing 6-30.

LISTING 6-30: Subscribe to messages on the topic “ts”

package com.wiley.aoa.the_sampler;
 
import java.io.IOException;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.hardware.usb.UsbManager;
import android.media.RingtoneManager;
import android.os.Bundle;
import com.wiley.wroxaccessories.UsbConnection12;
import com.wiley.wroxaccessories.WroxAccessory;
public class MainActivity extends Activity {
  RingtoneManager mRingtoneManager;
  private WroxAccessory mAccessory;
  private UsbManager mUsbManager;
  private UsbConnection12 connection;
  private String subscription;
  private int id = 0;
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mUsbManager = (UsbManager) getSystemService(USB_SERVICE);
    connection = new UsbConnection12(this, mUsbManager);
    mAccessory = new WroxAccessory(this);
    try {
      mAccessory.connect(WroxAccessory.USB_ACCESSORY_12, connection);
    } catch (IOException e) {
      e.printStackTrace();
    }
    mRingtoneManager = new RingtoneManager(this);
  }
  @Override
  protected void onResume() {
    super.onResume();
    try {
      subscription = mAccessory.subscribe(mReceiver, "ts", id++);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
  @Override
  protected void onPause() {
    super.onPause();
    mRingtoneManager.stopPreviousRingtone();
  }
  @Override
  protected void onDestroy() {
    super.onDestroy();
    try {
      mAccessory.disconnect();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
  private BroadcastReceiver mReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
      if (intent.getAction().equals(subscription)) {
        byte[] payload = intent.getByteArrayExtra(subscription + ".payload");
        mRingtoneManager.stopPreviousRingtone();
        mRingtoneManager.getRingtone(payload[0]).play();
      }
    }
  };
}

Possible Improvements

You can easily enhance The Sampler, both visually and technically. First of all is the obvious: Instead of using RingtoneManager, use the SoundPool or JetPlayer because those are much more suited for playing multiple sounds concurrently.

Secondly, you could create a visual feedback for the user giving certain sounds a certain visual feel — turn your phone into a disco!

SUMMARY

Although the WroxAccessories library helps you in dealing with the communication, it can’t determine what accessory you want to use currently. You need to choose the correct Connection object to instantiate for your setup; some phones may not even support the USB accessory connection.

Because of this, make sure to add the <uses-feature> declaration in your manifest if the USB accessory is required; this will help the device determine if it should even try to install this application. If you’re building accessories for Bluetooth, you should instead use the feature android.hardware.bluetooth.

Currently, you have three types of connections to choose from:

  • UsbConnection10 is the first version, sometimes called the backport ADK. This uses an add-on library from Google and therefore requires the <uses-library> declaration in your manifest as well as the <uses-feature>.
  • UsbConnection12 is the real accessory version available from the standard Android libraries. It was introduced in SDK 12 Honeycomb, and because it’s part of the core libraries, it doesn’t require the extra <uses-library> declaration. You should, however, use the <uses-feature> declaration to filter out any device not capable of USB accessory connections.
  • BluetoothConnection was introduced as an accessory during Google IO 2012. However, the BluetoothAccessory is built on top of the common Bluetooth library in Android, available from SDK 5 and forward. You don’t need any extra library declarations to use Bluetooth, but you do need the <uses-feature>.

Another key feature is the way you use the WroxAccessories library. It will create an extra thread for the communication to happen in, but it won’t push it to another process or automatically create a service that runs in the background. Consider the scenario for your accessory before you start building it — will the user interact with the accessory actively all the time, or will she only use it on certain occasions while it’s still running in the background?

Finally, remember to disconnect the accessory when you don’t need it anymore by calling WroxAccessory.disconnect(). This will close the streams used, and it will also unregister any BroadcastReceiver used for subscriptions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset