serialization to describe how data is represented in its stored state. Without persistence, data would be able to

keep its state only in RAM and would be lost after the relevant process finishes. Achieving persistence often

involves compromises among performance, latency, size, and complexity. For instance, if data must be read fast,

you’ll usually go with a slower than usual write operation. Serialization is all about how the data is structured,

both in its persisted state as well as in-memory.

In this chapter, I cover the two most common techniques used in Android applications for persistence and

serialization,—namely, SQLite and SharedPreferences—as well as two alternative methods that also allow

you to transport data over a network or between two devices.

Storing data on a cloud-storage service such as Google Drive or Dropbox is beyond the scope of this

chapter, but you can learn more about storing data on Google Drive in Chapter 19.

Persistence Options for Android

When you store persistent data on an Android device, the standard APIs provide two readymade methods for

storing structured data, preference files and SQLite databases. Preference files are stored in an XML format and

managed by the SharedPreferences class. SQLite databases have a more complex API and are usually

wrapped in a ContentProvider component.

The names of these methods indicate what they should be used for. You use SharedPreferences for

settings, options, user preferences, and other simple values. In preference files, you don’t store arrays and tables

of values or any binary data. Instead, data represented in Java as lists or arrays is more likely to go into an SQLite

database through a ContentProvider. There are, of course, exceptions to these rules, so always consider the

best choice for your application.

Binary data, which is usually media such as image, video, or audio files, should not be stored directly in

an SQLite database or a preference file. It’s usually better to store binary data as regular files, either in an

app’s internal storage or on external public storage. However, in many cases, it may be a good idea to use a

ContentProvider to handle the persistence of binary files as well; doing so provides convenient ways to

deal with files and keep them in sync with the records in your database.

166 Part II: Getting the Most Out of Components

Storing Data in Preference Files

The files backing your SharedPreferences objects are regular XML files stored in the app’s data directory.

The structure is quite simple because it allows only key/value pairs to be stored, but the Android APIs also

provide a very convenient abstraction that allows you to read and write data in a type-safe way.

The easiest way to create a SharedPreferences object is to use the PreferenceManager.

getDefaultSharedPreferences() method, which gives you the default preference object for your

application. Using this approach as your main storage for preferences is convenient because the framework

manages the name of the file. However, if you have multiple preference files in your application, you’re

better off using the Context.getSharedPreference() method, which allows you to freely name the

file. If you want to create a preference file that is relevant to only one activity, you can use the Activity.

getPreference() method, which gets its name from the Activity calling the method.

The name for preference files created by PreferenceManager.

getDefaultSharedPreferences() is formed by the package name with the suffix _

preferences—for instance, com.aaptl.code_preferences. Although you rarely need this

name, it becomes important when you want to implement a backup agent for this file, as I describe in

the section “Application Data Backup” near the end of this chapter.

The types of values you can store in a preference file using the SharedPreferences class are int, float,

long, boolean, String, and a Set of String objects (string arrays). The name of the key must always be a

valid String, and the common practice is to use a dot notation for the keys in order to structure multiple keys

into groups.

For instance, if your preference file contains values that will be used to configure networking as well as user-

interface settings, you can group these values by prefixing each key with the term network or ui. In this

way, you can easily manage and avoid conflicting names. In the following example, you see how to structure

preferences this way by using a prefix and defining the keys in a separate Java interface:

public interface Constants {

public static final String NETWORK_PREFIX = “network.”;

public static final String UI_PREFIX = “ui.”;

public static final String NETWORK_RETRY_COUNT

= NETWORK_PREFIX + “retryCount”;

public static final String NETWORK_CONNECTION_TIMEOUT

= NETWORK_PREFIX + “connectionTimeout”;

public static final String NETWORK_WIFI_ONLY

= NETWORK_PREFIX + “wifiOnly”;

public static final String UI_BACKGROUND_COLOR

= UI_PREFIX + “backgroundColor”;

public static final String UI_FOREGROUND_COLOR

= UI_PREFIX + “foregroundColor”;

public static final String UI_SORT_ORDER

= UI_PREFIX + “sortOrder”;

Chapter 9: Data Storage and Serialization Techniques 167

public static final int SORT_ORDER_NAME = 10;

public static final int SORT_ORDER_AGE = 20;

public static final int SORT_ORDER_CITY = 30;

}

The preceding method is the preferred way for accessing the preference values instead of hardcoding the key

names for each access. Doing so removes the chance of misspelling the names of the keys, a common source of

bugs.

The following code is an example of using preferences together with a Constants class:

public class MainActivity extends Activity {

private void readUiPreferences() {

SharedPreferences preferences

= PreferenceManager.getDefaultSharedPreferences(this);

int defaultBackgroundColor = getResources().

getColor(R.color.default_background);

int backgroundColor = preferences.getInt(

Constants.UI_BACKGROUND_COLOR,

defaultBackgroundColor);

View view = findViewById(R.id.background_view);

view.setBackgroundColor(backgroundColor);

}

}

To change the values in a preference, you first retrieve an Editor instance, which provides the appropriate

PUT method as well as methods for committing your changes. Prior to Android version 2.3 (API level 9), you

committed changes using the commit() method, which did the writing to disk synchronously. However, in

version 2.3, the Editor class provides an apply() method that performs the actual disk write operation

asynchronously. Because you always want to avoid blocking operations in your main thread as much as

possible, the apply() method is preferable to the old commit() method. This makes it safe to update

SharedPreference on the main thread directly from a UI operation (for instance, in an onClick()

method, as shown in the following example).

public class MainActivity extends Activity {

public void doToggleWifiOnlyPreference(View view) {

SharedPreferences preferences = PreferenceManager.

getDefaultSharedPreferences(this);

boolean currentValue = preferences.

getBoolean(Constants.NETWORK_WIFI_ONLY, false);

preferences.edit()

.putBoolean(Constants.NETWORK_WIFI_ONLY, !currentValue)

.apply();

}

}

The preceding code shows where you use a click listener to toggle the preference value stored in Constants.

NETWORK_WIFI_ONLY. If you were to use the old commit() method, the main thread could have been

blocked, causing a degraded user experience. With apply(), you no longer have to worry about that issue.

168 Part II: Getting the Most Out of Components

Every preference file has a single instance within the same process. So even though you retrieve a

SharedPreference object from two different components (but in the same application) with the two

objects having the same name, they share the same backing instance, so every change will immediately be

reflected in both objects.

In order to get a notification when a value is updated, you can register a callback listener that is triggered

whenever apply() or commit() is called. The most common use for this is when you change a value in

a preference from an Activity that should affect the behavior of a background service, as shown in this

example:

public class NetworkService extends IntentService

implements SharedPreferences.OnSharedPreferenceChangeListener {

public static final String TAG = “NetworkService”;

private boolean mWifiOnly;

@Override

public void onCreate() {

super.onCreate();

SharedPreferences preferences = PreferenceManager

.getDefaultSharedPreferences(this);

preferences.registerOnSharedPreferenceChangeListener(this);

mWifiOnly = preferences.getBoolean(Constants.NETWORK_WIFI_ONLY,

false);

}

@Override

public void onDestroy() {

super.onDestroy();

SharedPreferences preferences = PreferenceManager

.getDefaultSharedPreferences(this);

preferences.unregisterOnSharedPreferenceChangeListener(this);

}

@Override

public void onSharedPreferenceChanged(SharedPreferences preferences,

String key) {

if (Constants.NETWORK_WIFI_ONLY.equals(key)) {

mWifiOnly = preferences

.getBoolean(Constants.NETWORK_WIFI_ONLY, false);

if(mWifiOnly) cancelNetworkOperationIfNecessary();

}

}

@Override

protected void onHandleIntent(Intent intent) {

ConnectivityManager connectivityManager

= (ConnectivityManager)

getSystemService(CONNECTIVITY_SERVICE);

NetworkInfo networkInfo

= connectivityManager.getActiveNetworkInfo();

Chapter 9: Data Storage and Serialization Techniques 169

int type = networkInfo.getType();

if (mWifiOnly && type != ConnectivityManager.TYPE_WIFI) {

Log.d(TAG, “We should only perform network I/O over WiFi.”);

return;

}

performNetworkOperation(intent);

}

}

User Options and Settings UI

In many applications, it’s common to provide a separate UI that allows users to change the options and settings

of the application. Android provides a set of readymade Activity and Fragment classes that make creating

such a UI easy—PreferenceActivity and PreferenceFragment.

Start by creating an XML file, placed in the XML resources directory, which follows the PreferenceScreen

syntax. This is a simple XML structure that specifies all the preferences you’ll allow users to change and also

how they interact with the preferences. You can provide simple text fields for entering text strings, check

boxes, and lists of choices. For each option, you can specify a title and description, and you can group a

number of preferences together in different categories. You don’t need to deal with saving changed values

because that’s managed by the PreferenceFragment. The instance of the SharedPreferences

that a PreferenceFragment is working against is the same as you get from PreferenceManaget.

getDefaultSharedPreferences().

The following code shows a PreferenceScreen XML for two user-configurable options relating to the

previous examples.

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

<PreferenceScreen xmlns:android=”http://schemas.android.com/apk/res/

android”>

<PreferenceCategory android:title=”@string/network_preferences_title”>

<CheckBoxPreference

android:title=”@string/network_wifi_only_title”

android:summaryOn=”@string/network_wifi_only_summary_on”

android:summaryOff=”@string/network_wifi_only_summary_off”

android:key=”network.wifiOnly”

android:defaultValue=”false”/>

<ListPreference

android:title=”@string/network_retry_count_title”

android:summary=”@string/network_retry_count_summary”

android:key=”network.retryCount”

android:defaultValue=”3”

android:entryValues=”@array/network_retry_count_option_values”

android:entries=”@array/network_retry_count_options” />

</PreferenceCategory>

</PreferenceScreen>

170 Part II: Getting the Most Out of Components

Next, you implement a PreferenceActivity, add a PreferenceFragment as its UI, and then call

PreferenceFragment.addPreferencesFromResource() to specify the XML used for displaying the

settings UI. The Android framework will generate a UI that follows the style and theme of your application (see

Figure 9-1).

Figure 9-1 The result of a simple PreferenceScreen

In the following code, you specify which XML file to use from the resources. You also call

PreferenceManager.setDefaultValues() so that the preference file is populated with the default

values specified in the XML file.

public class SettingsFragment extends PreferenceFragment {

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

PreferenceManager.setDefaultValues(getActivity(),

R.xml.preferences, false);

addPreferencesFromResource(R.xml.preferences);

}

}

Chapter 9: Data Storage and Serialization Techniques 171

The most common way to start this Activity is through an Intent where you specify the ComponentName,

instead of using an action string. Also make sure that you set the android:exported flag to false in the

manifest so that it can be started only within your application.

High-Performance ContentProviders

When you choose to store data in an SQLite database, I always recommend that you create a ContentProvider,

even if you store data only for internal use. My reason is that Android provides several utility and UI-related classes

that work on top of ContentProviders and make things much easier. Also, these classes provide an easy

mechanism for notifying clients whenever an update occurs to the data, which makes it easier for developers to

keep the list in the UI in sync with the actual content.

When you create the tables for your database, be sure to consider its main purpose. Will the database mostly

be read and displayed in a UI (for instance, a ListView), or are you designing a database that will have more

write than read operations and that will happen in the background (for instance, an activity logger or a sports

application tracking the user’s position during training)? Depending on the use, read performance may be more

important than fast write operations, or vice versa.

Android Database Design

Design of a relational database is usually done through a process called database normalization. This process

tries to minimize dependency and redundancy in a database using a number rule called normal form. There

are a number of normal forms, but in most situations, only the first three are relevant. When a database design

fulfills the first three normal forms, it’s considered normalized.

In many cases, you may want to disregard the traditional database design and normalization rules. Unless you’ll

be storing hundreds of thousands of records in your database, each containing a huge amount of text and

maybe even binary data, you can generally use a much simpler database design. Consider an application that

keeps track of tasks. Each task has a name, a date when it was created, a priority, a status, and an owner. If you

were to make an optimal database design, it probably would look something like the one shown in Figure 9-2.

Figure 9-2 A database with three tables where the task table has two foreign keys

172 Part II: Getting the Most Out of Components

This design requires two foreign keys in the task table, which is perfectly okay, but it will make your application

code more complex. The choice of statuses isn’t likely to change for this application, so the better option would

be to remove the extra table and just interpret the column as a reference to a set of constants. The Owner field

is at this moment a more complex matter. It may be okay just to store the name—in fact, doing so is okay for

most occasions. If you need additional data for each person, you can rely on the ContactsProvider and

store a reference to a contact in your table instead. The result is a much simpler table, as illustrated in Figure 9-3.

Figure 9-3 A simplified version of the database for storing tasks

Creating and Upgrading Databases

I recommend that you always wrap your SQLite databases in Android using the ContentProvider

component. In this way, you can manage all calls to the database from one place and also use several

readymade utilities for working with databases.

In this section, I give an example of a provider that stores tasks that can be given different priorities, status, and

owners. I’ll start with the basics of creating the actual database.

The following code shows the provider without the query methods (query(), insert(), update(), and

delete()). I show some of these in the section “Database Transactions,” later in this chapter. The important

thing here is the class MyDatabaseHelper that extends SQLiteOpenHelper. This class helps in opening

SQLiteDatabase objects and in managing database upgrades. The onCreate() method for this class is

called once when the application starts and the provider is accessed the first time—more specifically, the first

time getReadableDatabase() or getWritableDatabase() is called.

public class TaskProvider extends ContentProvider {

public static final String AUTHORITY = “com.aptl.code.provider”;

public static final int ALL_TASKS = 10;

public static final int SINGLE_TASK = 20;

public static final String TASK_TABLE = “task”;

public static final String[] ALL_COLUMNS =

new String[]{TaskColumns._ID, TaskColumns.NAME,

TaskColumns.CREATED, TaskColumns.PRIORITY,

TaskColumns.STATUS, TaskColumns.OWNER};

public static final String DATABASE_NAME = “TaskProvider”;

public static final int DATABASE_VERSION = 2;

public static final String TAG = “TaskProvider”;

public static final String CREATE_SQL = “CREATE TABLE “

+ TASK_TABLE + “ (“

+ TaskColumns._ID + “ INTEGER PRIMARY KEY AUTOINCREMENT, “

+ TaskColumns.NAME + “ TEXT NOT NULL, “

Chapter 9: Data Storage and Serialization Techniques 173

+ TaskColumns.CREATED + “ INTEGER DEFAULT NOW, “

+ TaskColumns.PRIORITY + “ INTEGER DEFAULT 0, “

+ TaskColumns.STATUS + “ INTEGER DEFAULT 0, “

+ TaskColumns.OWNER + “ TEXT);”;

public static final String CREATED_INDEX_SQL = “CREATE INDEX “

+ TaskColumns.CREATED + “_idx ON “ + TASK_TABLE + “ (“

+ TaskColumns.CREATED + “ ASC);”;

public static final String OWNER_INDEX_SQL = “CREATE INDEX “

+ TaskColumns.OWNER + “_idx ON “ + TASK_TABLE + “ (“

+ TaskColumns.CREATED + “ ASC);”;

public static UriMatcher mUriMatcher

= new UriMatcher(UriMatcher.NO_MATCH);

public MyDatabaseHelper mOpenHelper;

static {

mUriMatcher.addURI(AUTHORITY, “task”, ALL_TASKS);

mUriMatcher.addURI(AUTHORITY, “task/#”, SINGLE_TASK);

}

@Override

public boolean onCreate() {

mOpenHelper = new MyDatabaseHelper(getContext());

return true;

}

// Query methods omitted for brevity...

public interface TaskColumns extends BaseColumns {

public static final String NAME = “name”;

public static final String CREATED = “created”;

public static final String PRIORITY = “priority”;

public static final String STATUS = “status”;

public static final String OWNER = “owner”;

}

private class MyDatabaseHelper extends SQLiteOpenHelper {

public MyDatabaseHelper(Context context) {

super(context, DATABASE_NAME, null, DATABASE_VERSION);

}

@Override

public void onCreate(SQLiteDatabase database) {

Log.d(TAG, “Create SQL : “ + CREATE_SQL);

database.execSQL(CREATE_SQL);

database.execSQL(CREATED_INDEX_SQL);

}

@Override

public void onUpgrade(SQLiteDatabase db,

int oldVersion, int newVersion) {

174 Part II: Getting the Most Out of Components

if (oldVersion < 2) {

db.execSQL(“ALTER TABLE “ + TASK_TABLE

+ “ ADD COLUMN “ + TaskColumns.OWNER + “ TEXT” );

db.execSQL(OWNER_INDEX_SQL);

}

}

}

}

Every time the MyDatabaseHelper class is created, it compares the internal version number that the SQLite

database currently has (stored in the internal table called android_metadata) with the one provided to

the constructor of SQLiteOpenHelper. Thus you can perform database upgrades and add new columns to

existing tables or perform any other SQL commands. This capability is efficient because it doesn’t require you to

drop all tables and their content when you need to change the database.

In the preceding example, the Owner column is part of the CREATE_SQL as well as the ALTER TABLE

statement called in onUpgrade(). This is because there are two possible scenarios. In the first scenario,

a new installation of the application will first call onCreate(), followed by onUpgrade(). Because the

if statement in onUpgrade() is false, the ALTER TABLE statement won’t be called. However, for an

upgrade of the application from an older version, onCreate() won’t be called, and the if statement in

onUpgrade() is true, the new column will be created. By incrementally increasing the version number

for your database and adding a new if statement for each alteration to the database, you have a simple yet

powerful method for upgrading the database.

In the code examples in this chapter I use String constants for declaring the SQL statements to

make the examples more clear. A better alternative is to store them in text files in the raw application

resources. This makes them easier to work with as well as simplifies testing.

Implementing Query Methods

When your database is queried (generally using ContentResolver.query()), the method

ContentProvider.query() is called. Your implementation must take care of interpreting the incoming

Uri to decide which query should be executed and check that all incoming parameters are safe.

The following code shows the implementation of the query() method as well as two utility methods used for

modifying the selection and selectionArgs parameters. Also, here you make a simple check to see if the

incoming projection parameter is null, and if so you set it to the default value. The same check is made on

the sortOrder parameter where you use the column named priority as the default for sorting.

public static String[] fixSelectionArgs(String[] selectionArgs,

String taskId) {

if (selectionArgs == null) {

selectionArgs = new String[]{taskId};

} else {

String[] newSelectionArg =

new String[selectionArgs.length + 1];

Chapter 9: Data Storage and Serialization Techniques 175

newSelectionArg[0] = taskId;

System.arraycopy(selectionArgs, 0,

newSelectionArg, 1, selectionArgs.length);

}

return selectionArgs;

}

public static String fixSelectionString(String selection) {

selection = selection == null ? TaskColumns._ID + “ = ?” :

TaskColumns._ID + “ = ? AND (“ + selection + “)”;

return selection;

}

@Override

public Cursor query(Uri uri, String[] projection,

String selection, String[] selectionArgs,

String sortOrder) {

projection = projection == null ? ALL_COLUMNS : projection;

sortOrder = sortOrder == null ? TaskColumns.PRIORITY : sortOrder;

SQLiteDatabase database = mOpenHelper.getReadableDatabase();

switch (mUriMatcher.match(uri)) {

case ALL_TASKS:

return database.query(TASK_TABLE, projection,

selection, selectionArgs,

null, null, sortOrder);

case SINGLE_TASK:

String taskId = uri.getLastPathSegment();

selection = fixSelectionString(selection);

selectionArgs = fixSelectionArgs(selectionArgs, taskId);

return database.query(TASK_TABLE, projection,

selection, selectionArgs,

null, null, sortOrder);

default:

throw new IllegalArgumentException(“Invalid Uri: “ + uri);

}

}

The two utility methods for modifying the selection and selectionArgs parameters are used only when

addressing a specific record in the database using an Uri. Note: You prepend (in the fixSelectionString()

and fixSelectionArgs() methods) the ID column to the existing selection. This makes the query faster

because comparison on the primary key column is always very fast and thus speeds up the entire query.

When writing database queries, keep the simplest comparisons in the WHERE clause before the

more complex ones. Doing so makes the query execute faster because it can determine much earlier

whether the record should be included.

176 Part II: Getting the Most Out of Components

Database Transactions

When you execute a piece of SQL on an SQLite database, you always perform a transaction. Unless you

specifically manage the transaction yourself, as I show next, it will be created for you automatically for that

statement. Because most calls to a ContentProvider result in only one SQL statement, there’s’ little need

for handling transactions manually in these cases. However, if your application will execute multiple SQL

statements, such as inserting many new records in one batch, always manage your own transactions.

The ContentProvider class provides two methods for transaction management, ContentProvider.

bulkInsert() and ContentProvider.applyBatch(). In the following code, you see how to implement

the bulkInsert() methods, which will insert multiple records in one single transaction. This approach is

significantly faster than calling ContentProvider.insert() for every new piece of data you want to store.

private Uri doInsert(Uri uri, ContentValues values, SQLiteDatabase

database) {

Uri result = null;

switch (mUriMatcher.match(uri)) {

case ALL_TASKS:

long id = database.insert(TASK_TABLE, “”, values);

if (id == -1) throw new SQLException(“Error inserting data!”);

result = Uri.withAppendedPath(uri, String.valueOf(id));

}

return result;

}

@Override

public Uri insert(Uri uri, ContentValues values) {

SQLiteDatabase database = mOpenHelper.getWritableDatabase();

Uri result = doInsert(uri, values, database);

return result;

}

@Override

public int bulkInsert(Uri uri, ContentValues[] contentValueses) {

SQLiteDatabase database = mOpenHelper.getWritableDatabase();

int count = 0;

try {

database.beginTransaction();

for (ContentValues values : contentValueses) {

Uri resultUri = doInsert(uri, values, database);

if (resultUri != null) {

count++;

} else {

count = 0;

throw new SQLException(“Error in bulk insert”);

}

}

database.setTransactionSuccessful();

} finally {

database.endTransaction();

}

return count;

}

Chapter 9: Data Storage and Serialization Techniques 177

The semantics for transactions are simple. You begin a new transaction by calling SQLiteDatabase.

beginTransaction(). After you insert all the records successfully, you call SQLiteDatabase.

setTransactionSuccessful() and then end the transaction with SQLiteException.

endTransaction(). If something goes wrong in one of the insertions, you throw a SQLException,

and all the previous insertions will be rolled back because the call to SQLiteDatabase.

setTransactionSuccessful() was never called.

I highly recommend that you implement this method for your own providers because it will increase the

performance on insertions significantly. However, because this method works only for insert operations, you

may need to implement another method for handling more complex operations.

If you need to perform multiple update() or delete() statements within a single transaction, you must

implement your own version of ContentProvider.applyBatch().

@Override

public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperat

ion>

operations)

throws OperationApplicationException {

SQLiteDatabase database = mOpenHelper.getWritableDatabase();

ContentProviderResult[] result

= new ContentProviderResult[operations.size()];

try {

database.beginTransaction();

for (int i = 0; i < operations.size(); i++) {

ContentProviderOperation operation = operations.get(i);

result[i] = operation.apply(this, result, i);

}

database.setTransactionSuccessful();

} finally {

database.endTransaction();

}

return result;

}

As with the buildInsert() method shown earlier, you begin a transaction, apply the operations, set the

transaction successfully, and finally end the transaction. This API is designed for complex providers like the

ContactsProvider, where there are many connected tables, each with its own Uri. Also, if you need to

perform batch inserts to multiple tables, this API can still be useful in your own application.

Storing Binary Data in ContentProvider

Binary data includes anything that cannot be represented using the simple data types in Java, usually an image

or some other media file, though it can be any type of file of a proprietary format. Working with such content

can be tricky, but luckily the ContentProvider class provides a number of methods for dealing with this

issue. Say that you want to store a JPG photo with each task in your database. First, you need to add another

column to your task table with the name _data and the type TEXT, as shown here:

db.execSQL(“ALTER TABLE “ + TASK_TABLE + “ ADD COLUMN _data TEXT”);

178 Part II: Getting the Most Out of Components

The ContentProvider.openFileHelper() method uses this internally. You just store the path to the

future file belonging to each record at each insertion. To do so, you modify the doInsert() method shown

earlier.

private Uri doInsert(Uri uri, ContentValues values,

SQLiteDatabase database) {

Uri result = null;

switch (sUriMatcher.match(uri)) {

case ALL_TASKS:

long id = database.insert(TASK_TABLE, “”, values);

if (id == -1) throw new SQLException(“Error inserting data: “

+ values.toString());

result = Uri.withAppendedPath(uri, String.valueOf(id));

// Update row with _data field pointing at a file...

File dataFile = Environment.getExternalStoragePublicDirectory(

Environment.DIRECTORY_

PICTURES);

dataFile = new File(dataFile, FILE_PREFIX + id + FILE_SUFFIX);

ContentValues valueForFile = new ContentValues();

valueForFile.put(“_data”, dataFile.getAbsolutePath());

update(result, values, null, null);

}

return result;

}

In this case, you write the JPG to the PICTURES directory on the external storage. Next, you override the

method ContentProvider.openFile(), which will return a ParcelFileDescriptor to the client.

You can then use that object for reading and writing directly to the file.

@Override

public ParcelFileDescriptor openFile(Uri uri, String mode) throws

FileNotFoundException {

if(sUriMatcher.match(uri) == SINGLE_TASK)

return openFileHelper(uri, mode);

else

return super.openFile(uri, mode);

}

Using openFileHelper()handles the actual opening of the file and passes it to the calling client. When you

want to read a Bitmap for a certain record in your ContentProvider, you can simply use the same Uri as

for the record, and the framework takes care of the rest, as shown here:

public Bitmap readBitmapFromProvider(int taskId, ContentResolver

resolver)

throws FileNotFoundException {

Uri uri = Uri.parse(“content://” + TaskProvider.AUTHORITY

+ “/” + TaskProvider.TASK_TABLE + “/” + taskId);

return BitmapFactory.decodeStream(resolver.openInputStream(uri));

}

Chapter 9: Data Storage and Serialization Techniques 179

Storing a file works similarly with ContentResolver.openOutputStream() with the correct Uri. This is

especially useful when you want to display data from your ContentProvider in a ListView that contains

both text and images, such as in the preceding example.

Serializing Data for Persistence

Using the ContentValues and Cursor classes when handling persistent data in your application is fine

when working with your ContentProvider, but if you ever need to send the data over the Internet or

share it with another device, you have a completely different challenge. To resolve this challenge, you need to

transform your data to a format that the receiving end can work with and that is suitable for sending over the

network. This technique is called serialization.

Serialization is the concept of taking an object in memory and writing it to a file (or other output) such that

the exact same memory representation can later be read (called deserialization). Android provides its internal

serialization API with the Parcelable interface, but it isn’t designed or well-suited for persistent storage on

files or for transferring over a network.

In this section, I discuss two suitable formats for persisting complex data, JSON and Google Protocol Buffers.

Both are well-supported by Android and have open-source implementations for most platforms, meaning that

they’re’ well-suited for transporting over networks.

JavaScript Object Notation

JSON is an abbreviation for JavaScript Object Notation, and it’s a subset of the JavaScript standard. The official

Android API has built-in support for reading and writing JSON data. This format is excellent for representing

complex objects that don’t contain binary values. It has also become somewhat of a de-facto standard for

sharing data with web services online.

The following example shows a simple JSON array with three objects containing the same information that was

stored in the TaskProvider in an earlier example. This format is excellent for sending tasks with an online

web service or for sharing directly with a friend.

[

{

“name”: “Laundry”,

“created”: 1370527168012,

“priority”: 5,

“owner”: “Erik”,

“status”: 1

},

{

“name”: “Groceries”,

“created”: 1370476882046,

“priority”: 3,

“owner”: “Linda”,

“status”: 2

},

180 Part II: Getting the Most Out of Components

{

“name”: “Buy new sofa”,

“created”: 1370326907735,

“priority”: 2,

“owner”: “Linda”,

“status”: 1

}

]

Reading JSON data from an InputStream is best done using the JsonReader API, as shown following:

public JSONArray readTasksFromInputStream(InputStream stream) {

InputStreamReader reader = new InputStreamReader(stream);

JsonReader jsonReader = new JsonReader(reader);

JSONArray jsonArray = new JSONArray();

try {

jsonReader.beginArray();

while (jsonReader.hasNext()) {

JSONObject jsonObject

= readSingleTask(jsonReader);

jsonArray.put(jsonObject);

}

jsonReader.endArray();

} catch (IOException e) {

// Ignore for brevity

} catch (JSONException e) {

// Ignore for brevity

}

return jsonArray;

}

private JSONObject readSingleTask(JsonReader jsonReader)

throws IOException, JSONException {

JSONObject jsonObject = new JSONObject();

jsonReader.beginObject();

JsonToken token;

do {

String name = jsonReader.nextName();

if (“name”.equals(name)) {

jsonObject.put(“name”, jsonReader.nextString());

} else if (“created”.equals(name)) {

jsonObject.put(“created”, jsonReader.nextLong());

} else if (“owner”.equals(name)) {

jsonObject.put(“owner”, jsonReader.nextString());

} else if (“priority”.equals(name)) {

jsonObject.put(“priority”, jsonReader.nextInt());

} else if (“status”.equals(name)) {

jsonObject.put(“status”, jsonReader.nextInt());

}

Chapter 9: Data Storage and Serialization Techniques 181

token = jsonReader.peek();

} while (token != null && !token.equals(JsonToken.END_OBJECT));

jsonReader.endObject();

return jsonObject;

}

Although it’s also possible to read the entire contents of the InputStream to a String and then pass that

to the constructor of JSONArray, the preceding method consumes less memory and will most likely be faster.

Likewise, the JsonWriter class allows you to efficiently write JSON data to an OutputStream, as shown here:

public void writeJsonToStream(JSONArray array, OutputStream stream) throws

JSONException {

OutputStreamWriter writer = new OutputStreamWriter(stream);

JsonWriter jsonWriter = new JsonWriter(writer);

int arrayLength = array.length();

jsonWriter.beginArray();

for(int i = 0; i < arrayLength; i++) {

JSONObject object = array.getJSONObject(i);

jsonWriter.beginObject();

jsonWriter.name(“name”).

value(object.getString(“name”));

jsonWriter.name(“created”).

value(object.getLong(“created”));

jsonWriter.name(“priority”).

value(object.getInt(“priority”));

jsonWriter.name(“status”).f

value(object.getInt(“status”));

jsonWriter.name(“owner”).

value(object.getString(“owner”));

jsonWriter.endObject();

}

jsonWriter.endArray();

jsonWriter.close();

}

Advanced JSON Handling with Gson

The JSONObject and JSONArray classes are convenient to work with as value objects in your code, but

they have some limitations and usually consume more memory than necessary. Likewise, JsonReader and

JsonWriter result in quite a lot of code if you have several different types of value objects. If you need more

advanced serialization and deserialization of JSON data, you can find an excellent open-source library called Gson.

You can find the documentation for Gson at https://code.google.com/p/google-gson. To

include Gson in your Gradle configuration, add the following to the dependencies: compile ‘com.

google.code.gson:gson:2.2.4’.

182 Part II: Getting the Most Out of Components

This library allows you to convert a Plain Old Java Objects (POJOs) into JSON and back. All you need to do is to

define your data as regular Java objects with getters and setters and include the Gson library in your project.

The following class shows a simple Java object that represents a task:

public class Task {

private String mName;

private String mOwner;

private Status mStatus;

private int mPriority;

private Date mCreated;

public Task() {

}

public Task(String name, String owner,

Status status, int priority, Date created) {

mName = name;

mOwner = owner;

mStatus = status;

mPriority = priority;

mCreated = created;

}

// Getters and setter omitted for brevity...

@Override

public boolean equals(Object o) {

if (this == o) return true;

if (o == null || getClass() != o.getClass()) return false;

Task task = (Task) o;

return mCreated.equals(task.mCreated) && mName.equals(task.mName);

}

@Override

public int hashCode() {

int result = mName.hashCode();

result = 31 * result + mCreated.hashCode();

return result;

}

public enum Status {

CREATED, ASSIGNED, ONGOING, CANCELLED, COMPLETED

}

}

Notice the use of Java enum for the status of the task. This object is much easier to work with in your application

than the raw JSONObject.

The following code shows how to read and write a Collection of Task objects. The serialized form is always

valid JSON data, so this choice becomes very convenient when working toward web services that publish data

in JSON format. When you’re also in control of the server-side code and it happens to be written in Java, you can

easily share the same set of Java code between your server and Android application.

Chapter 9: Data Storage and Serialization Techniques 183

public Collection<Task> readTasksFromStream(InputStream stream) {

InputStreamReader reader = new InputStreamReader(stream);

JsonReader jsonReader = new JsonReader(reader);

Gson gson = new Gson();

Type type = new TypeToken<Collection<Task>>(){}.getType();

return gson.fromJson(jsonReader, type);

}

public void writeTasksToStream(Collection<Task> tasks, OutputStream

outputStream) {

OutputStreamWriter writer = new OutputStreamWriter(outputStream);

JsonWriter jsonWriter = new JsonWriter(writer);

Gson gson = new Gson();

Type type = new TypeToken<Collection<Task>>(){}.getType();

gson.toJson(tasks, type, jsonWriter);

}

To make it simpler to use your POJOs toward a ContentProvider, you can implement two methods that

convert your objects to and from ContentValues and Cursor objects, as shown here:

public ContentValues toContentValues() {

ContentValues values = new ContentValues();

values.put(TaskProvider.TaskColumns.NAME, mName);

values.put(TaskProvider.TaskColumns.OWNER, mOwner);

values.put(TaskProvider.TaskColumns.STATUS, mStatus.ordinal());

values.put(TaskProvider.TaskColumns.PRIORITY, mPriority);

values.put(TaskProvider.TaskColumns.CREATED, mCreated.getTime());

return values;

}

public static Task fromCursor(Cursor cursor) {

Task task = new Task();

int nameColumnIdx

= cursor.getColumnIndex(TaskProvider.TaskColumns.NAME);

task.setName(cursor.getString(nameColumnIdx));

int ownerColumnIdx

= cursor.getColumnIndex(TaskProvider.TaskColumns.OWNER);

task.setOwner(cursor.getString(ownerColumnIdx));

int statusColumnIdx

= cursor.getColumnIndex(TaskProvider.TaskColumns.STATUS);

int statusValue = cursor.getInt(statusColumnIdx);

for (Status status : Status.values()) {

if(status.ordinal() == statusValue) {

task.setStatus(status);

}

}

int priorityColumnIdx

= cursor.getColumnIndex(TaskProvider.TaskColumns.PRIORITY);

task.setPriority(cursor.getInt(priorityColumnIdx));

int createdColumnIdx

= cursor.getColumnIndex(TaskProvider.TaskColumns.CREATED);

task.setCreated(new Date(cursor.getLong(createdColumnIdx)));

return task;

}

184 Part II: Getting the Most Out of Components

If your application will have many different POJOs and tables, it may be easier to integrate an ORM

(Object-Relational-Mapping) library to handle the serialization and deserialization between your

SQLite database and Java objects. Two ORM libraries present a good option for Android today,

greenDAO (http://greendao-orm.com) and OrmLight (http://ormlite.com).

Google Protocol Buffers

Google Protocol Buffer, or protobuf, is a way of encoding structured data in an efficient and extensible format.

It differs from JSON in that it supports binary data mixed with simple data types and also has an advanced and

extensible schema support. Protobuf has implementations for most software platforms, including a lite Java

variant suitable for Android.

You can find documentation, download links, and installation instructions for Google Protocol Buffers

at https://developers.google.com/protocol-buffers. Remember that for Android

you have to build the lite version of the tool, so you cannot use the one found in the central Maven

repository. Executing “mvn package –P lite” in the Java source directory does this. Check the

installation instructions for further details.

Although JSON lets you read and write arbitrary data to a JSONObject, protobuf requires the use of a schema

that defines the data you want to store. The schema defines a number of messages, where each message has a

number of name-value pair fields. Each field can be either one of the supported built-in primitive data types,

an enum, or another message. You can also specify whether a field is required or optional as well as some other

parameters. Once your protobuf schema is complete, you use the protobuf tools to generate the Java code for

your data. The generated Java classes can now be used for reading and writing protobuf data in a convenient

way.

The following code is a sample of a simple protobuf schema that defines the information similar to the

TaskProvider shown earlier:

package com.aptl.code.task;

option optimize_for = LITE_RUNTIME;

option java_package = “com.aptl.code.task”;

option java_outer_classname = “TaskProtos”;

message Task {

enum Status {

CREATED = 0;

ONGOING = 1;

CANCELLED = 2;

COMPLETED = 3;

}

Chapter 9: Data Storage and Serialization Techniques 185

message Owner {

required string name = 1;

optional string email = 2;

optional string phone = 3;

}

message Comment {

required string author = 1;

required uint32 timestamp = 2;

required string content = 3;

}

required string name = 1;

required uint64 created = 2;

required int32 priority = 3;

required Status status = 4;

optional Owner owner = 5;

repeated Comment comments = 6;

}

Note: Declarations in the beginning tell the protobuf tool what Java package and class name to use when

generating the code. There is also an instruction to generate a lite version that is supported by Android.

Deserializing a protobuf object from an InputStream is easy, as shown in the following example. The

generated Java code gives you a number of functions for merging the content from byte arrays, ByteBuffer,

and InputStream objects.

public TaskProtos.Task readBrotoBufFromStream(InputStream inputStream)

throws IOException {

TaskProtos.Task task = TaskProtos.Task.newBuilder()

.mergeFrom(inputStream).build();

Log.d(“ProtobufDemo”, “Read Task from stream: “

+ task.getName() + “, “

+ new Date(task.getCreated()) + “, “

+ (task.hasOwner() ?

task.getOwner().getName() : “no owner”) + “, “

+ task.getStatus().name() + “, “

+ task.getPriority()

+ task.getCommentsCount() + “ comments.”);

return task;

}

In this example, you see how to retrieve the values in a protobuf object. Note: Protobuf objects are

immutable. The only way to make a modification is to create a new builder from an existing object, set the new

value, and generate a Task object that will replace the old one. This makes protobuf a bit harder to work with

but forces you to use a better design when working with persistence of complex objects.

186 Part II: Getting the Most Out of Components

The following method shows how to construct a new protobuf object for a task. You start by creating a new

Builder for the specific object you want to construct and then you set the desired values and call Builder.

build() to create your immutable protobuf object.

public TaskProtos.Task buildTask(String name, Date created,

String ownerName, String ownerEmail,

String ownerPhone,

TaskProtos.Task.Status status,

int priority,

List<TaskProtos.Task.Comment> comments) {

TaskProtos.Task.Builder builder = TaskProtos.Task.newBuilder();

builder.setName(name);

builder.setCreated(created.getTime());

builder.setPriority(priority);

builder.setStatus(status);

if(ownerName != null) {

TaskProtos.Task.Owner.Builder ownerBuilder

= TaskProtos.Task.Owner.newBuilder();

ownerBuilder.setName(ownerName);

if(ownerEmail != null) {

ownerBuilder.setEmail(ownerEmail);

}

if(ownerPhone != null) {

ownerBuilder.setPhone(ownerPhone);

}

builder.setOwner(ownerBuilder);

}

if (comments != null) {

builder.addAllComments(comments);

}

return builder.build();

}

When you need to write a protobuf object to a file or to a network stream, the API provides a set of methods

for that. In the following code, you see an example where you can serialize (that is, write) a Task object to an

OutputStream.

public void writeTaskToStream(TaskProtos.Task task,

OutputStream outputStream)

throws IOException {

task.writeTo(outputStream);

}

The main advantage of protobuf is that it consumes much less memory and is faster to read and write than

JSON. Protobuf objects are also immutable, which is useful when you want to ensure that the values of the

object remain the same during its lifetime.

Chapter 9: Data Storage and Serialization Techniques 187

Application Data Backup

After you resolve all your persistence needs for your application, consider whether your application will benefit

from using the backup service for Android provided by Google. This service lets you back up your application’s

persistence data and restore it if the user performs a factory reset or upgrades to a new Android device.

Android provides a convenient API for managing backups, and it’s’ a feature that will be greatly appreciated by

users if they lose their device. The backup is securely stored on a cloud and will be restored only to a device with

the same Google ID.

The following XML snippet is a piece from a typical AndroidManifest.xml file:

<application

android:allowBackup=”true”

android:backupAgent=”MyBackupAgent”

android:icon=”@drawable/ic_launcher”

android:label=”@string/app_name”

android:theme=”@style/AppTheme”>

<meta-data android:name=”com.google.android.backup.api_key”

android:value=” backup-key-string”/>

...

</application>

To enable backup of your application’s data, you only need to specify the class name of your backup agent in

the android:backupAgent attribute. This class deals with the backup and restoration of application data

that you choose to back up for the user. The meta-data attribute in the preceding example specifies the API

key that you get once you register your application on the Google backup service. You can find the site for

registering your applications at https://developer.android.com/google/backup/signup.html.

When you register and receive your API key, paste it into the android:value attribute, as just shown.

Although the key is bound to your application’s package and cannot be used for other applications (that is, one

key per application), you should take care not to share it publicly in any code you publish.

The following class shows a simple backup agent used to back up and restore your default preferences file. Note:

The filename for the preference file you get from PreferenceManager.getDefaultPreferences() is

named <package-name>_preferences. This is an undocumented detail that makes a big difference when

it comes to backing up your preference files.

public class MyBackupAgent extends BackupAgentHelper {

public static final String PREFS_BACKUP_KEY = “prefsBackup”;

@Override

public void onCreate() {

super.onCreate();

188 Part II: Getting the Most Out of Components

SharedPreferencesBackupHelper sharedPreferencesBackupHelper

= new SharedPreferencesBackupHelper(this,

getPackageName() + “_preferences”);

addHelper(PREFS_BACKUP_KEY, sharedPreferencesBackupHelper);

}

}

The BackupAgentHelper class automatically handles both backup and restoration of the preference

files you choose to add to this helper. You can also add backup for other, regular files using the class

FileBackupHelper.

The backup agent provided for Android applications by Google is appropriate for small amounts of

data. Although it would be technically possible to back up a SQLite database, you would probably

be better off to first dump the contents of the database to a serialized format, compress the file, and

perform your backup on that dump.

In the Android SDK, you can find the command-line tool bmgr that allows you to force backup and restoration

operations on your application. This is useful when you’re developing your applications because you can check

that everything is working correctly.

Summary

Persisting data is central to most software development. The most common solution for Android applications

is either through the SharedPreference class or by using a ContentProvider with an SQLite database.

The SharedPreferences and ContentProvider API are both powerful and easy to use. Being able to get

the most out of these APIs can make a huge difference in the overall user experience.

However, when you need to transport data over the network, a number of options are available that aren’t

part of the Android APIs. Serializing data with JSON, and more specifically using the Gson library, is a powerful

technique that you should master. For the even more advanced situations where performance and size are

crucial, using Google Protocol Buffers can help you solve your problems.

Finally, consider the use of a backup agent in your application in order to provide users with a smooth

experience when they switch to a new Android device. Use the backup agent to store user preferences and

options that let them keep the same configuration between multiple devices.

Further Resources

Documentation

Guide to Settings: http://developer.android.com/guide/topics/ui/settings.html

Guide to Content Providers: http://developer.android.com/guide/topics/providers/

content providers.html

Chapter 9: Data Storage and Serialization Techniques 189

Websites

Full documentation and information about SQLite: www.sqlite.org

Google Gson: https://code.google.com/p/google-gson

Google Protocol Buffers—Tutorial for Java: https://developers.google.com/protocol-

buffers/docs/javatutorial. Also see https://developers.google.com/protocol-

buffers for additional information

Two ORM frameworks suitable for Android are greeDAO (http://greendao-orm.com) and OrmLight

(http://ormlite.com)

Documentation for the Backup Agent: https://developer.android.com/google/backup/

signup.html

Chapter 10

Writing Automated Tests

If I had to pick one thing that developers of mobile apps often miss, it would be testing, despite the fact that

high-quality software is the result of thorough testing. Consequently, this may be the most important chapter in

this book.

The traditional way of developing software has been to design the software architecture, implement the code,

and finally test the code, maybe by using automated tests to some degree. However, when testing is the last

thing done in the development cycle, often too little testing is done or the wrong things are tested.

Test Driven Development (TDD) takes a different approach to software development, and its principles lead to

high-quality software. With TDD, you start by defining your tests based on how your application will be used.

Then you start implementing code that fulfills these tests, continuing until all the tests are satisfied. At this

point, you can refactor your application code to optimize it for performance and to improve the overall design.

Using TDD involves a number of tools and techniques. First, you need a unit-testing framework for writing

the automated tests. This framework, which is included in the Android APIs, is the main focus of this chapter.

Second, you need a continuous integration and build server. This server automatically builds your application

and performs all the automated tests for every change in the code. Finally, you need a code-coverage tool that

tells you how much of the application’s code is really being tested.

Android Testing Principles

On Android, you can divide tests into two categories: unit and instrumentation tests. Although within the field

of TDD, there are other types of tests (for example, integration, functional, system, and component tests) in the

context of Android, I’m focusing on unit and instrumentation tests.

Unit testing works on a very fine-grained level, usually on individual methods and classes with all external

dependencies removed. On the other hand, instrumentation tests focus on verifying the behavior of a

component (Activity, Service, BroadcastReceiver or ContentProvider) in the overall system.

The goal with unit testing is to verify that your methods behave as expected and that they can handle

erroneous input without crashing. A framework called JUnit was developed for the Java programming

language. JUnit is also part of the official Android APIs in the junit package along with the Android-specific

testing framework under android.test. To do unit testing, you call a method in your application code and

test its result for the expected output. This is called, asserting the result, and there is a ready-made utility class

named Assert for this purpose. Each test you write should perform an assertion on the value, which in turn

tells the testing framework whether the test passed.

192 Part II: Getting the Most Out of Components

Each test is called a test case, and a set of test cases is called a test suite. Each test case starts with a setup where

all dependencies are created and initialized. When the test case is complete, a teardown is performed to release

the resources that were created during setup.

Because the methods in your application code often have dependencies to the system or other components,

you need a way to isolate a method when running a test. To do so, you use mock objects that simulate the

behavior of the dependent objects. The Android API provides a set of mock classes that you can use in your

tests. Mock objects are usually created in the setup of each test case and released during teardown.

When using the new Android build system based on Gradle (which I describe in Chapter 1), the default place

for all your test code is under <project root>/src/instrumentTest. The name of the default package

for tests is the same as your application package name with .test appended. All of this is configurable in

gradle.build.

What to Test

When you write your automated tests, test only your code. There’s no point in writing automated tests that

verify system functions and services. For instance, you don’t need to write a unit test that verifies a button is

pressed. Instead, write a test that verifies the button has an onClick listener and that the listener behaves as

expected.

Tests for user interfaces are some of the more complex tests you’ll write. However, again, keep in mind that you

should test your own code, not the functionality of Android’s UI classes. For instance, you don’t need to write

a test that verifies scrolling works in a ListView. Instead, focus on writing tests that verify the content of the

ListView and that a click on a specific element performs the expected action.

Because it’s hard to test large and complex methods, keep your tests as limited and focused as possible. Also,

make sure you test only one thing at a time, which makes it easier to detect where a bug occurred and to do

refactoring. It’s better to have many small test cases than a single large, complex test case for your Activity.

This approach also affects how you write your code because writing small test cases in advance will result in

better overall design in your code.

Refactoring your code to extract smaller methods from a single large method is good practice. Use code

coverage to detect which methods are covered by your tests and where you need to add additional test cases.

Make sure you run all your test cases for every code commit. This is where a continuous integration server

comes in handy. Be sure to configure your development environment so that it will trigger a new job on the

build server for every commit.

Basic Unit Testing

When you test classes that don’t depend on a component lifecycle, do the simplest form of unit testing using

the AndroidTestCase class. Doing so is useful when you can construct an object independently from any

component or other part of the Android framework.

Chapter 10: Writing Automated Tests 193

public class Util {

public static int byteArrayToInt(byte[] bytes) {

return bytes[3] & 0xFF |

(bytes[2] & 0xFF) << 8 |

(bytes[1] & 0xFF) << 16 |

(bytes[0] & 0xFF) << 24;

}

}

Consider the preceding utility class with a method for converting four bytes to an integer. You don’t need to

perform special setup or teardown for this test because the method takes simple inputs and is static. Only one

testing method is needed for the method in the Util class. Running the test on a device generates a pass, and

everything looks good.

public class UtilTest extends AndroidTestCase {

public void testBytesToIntConversion() {

int result = Util.byteArrayToInt(new byte[] {(byte) 127,

(byte) -1, (byte) -1, (byte) -1});

assertEquals(Integer.MAX_VALUE, result);

result = Util.byteArrayToInt(new byte[] {(byte) 0,

(byte) 0, (byte) 0, (byte) 0});

assertEquals(0, result);

result = Util.byteArrayToInt(new byte[] {(byte) -128,

(byte) 0, (byte) 0, (byte) 0});

assertEquals(Integer.MIN_VALUE, result);

}

}

The preceding code verifies that the method gives the correct answer given a valid input, but it doesn’t test

what happens when you give the method an invalid input. To do so, you add three test methods for the possible

invalid input: for a null array, for an array that is too short, and for when an array is too long. Here is the code for

the three test methods:

public void testBytesToIntWithNull() {

try {

int result = Util.byteArrayToInt(null);

} catch (IllegalArgumentException e) {

return;

}

fail();

}

public void testBytesToIntWithTooShortInput() {

try {

int result = Util.byteArrayToInt(new byte[] {1,2,3});

} catch (IllegalArgumentException e) {

return;

}

fail();

}

194 Part II: Getting the Most Out of Components

public void testBytesToIntWithTooLongInput() {

try {

int result = Util.byteArrayToInt(new byte[] {1,2,3,4,5,6,7,8,9});

} catch (IllegalArgumentException e) {

return;

}

fail();

}

You should expect to get an IllegalArgumentException when you have invalid input, or the test will fail.

If you run the tests now, all three new test methods will fail, indicating that you need to fix your utility method.

In the following code example I’ve modified the utility method to handle incorrect input. If you run the previous

tests again, they should all pass. This is an example of TDD, although very simplified.

public class Util {

public static int byteArrayToInt(byte[] bytes)

throws IllegalArgumentException {

if(bytes == null || bytes.length != 4) {

throw new IllegalArgumentException();

}

return bytes[3] & 0xFF |

(bytes[2] & 0xFF) << 8 |

(bytes[1] & 0xFF) << 16 |

(bytes[0] & 0xFF) << 24;

}

}

It’s a good practice to write the tests for your classes before you implement them completely so that you can

verify that they work as expected. As shown here, you should not only check for correct behavior in normal

cases but also test what happens when they receive a bad or unexpected input. Using this approach, you’ll

greatly improve the quality of your code.

Testing Activities

Testing your user interfaces means testing all your Activity classes. You’ll generally use either

ActivityUnitTestCase or ActivityInstrumentationTestCase2 when writing tests for your

Activities. The first class provides a more isolated testing where you have only a minimal connection to

the system infrastructure. This approach is helpful when you test methods in your Activity that in turn call

methods that interact with the system (such as Context.startService()) and you want to limit the scope

of your test.

The following code shows a simple Activity that sets up a View to be displayed, along with the method

startBackgroundJob() that will be used as a click listener in your view:

public class MainActivity extends Activity {

public static final String ACTION_START_BACKGROUND_JOB

= “startBackgroundJob”;

Chapter 10: Writing Automated Tests 195

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

}

public void startBackgroundJob(View view) {

Intent backgroundJob = new Intent(ACTION_START_BACKGROUND_JOB);

startService(backgroundJob);

}

}

In the XML layout, you assign the click listener using the attribute android:onClick:

<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”

tools:context=”.MainActivity”>

<Button android:id=”@+id/background_job_btn”

android:layout_width=”wrap_content”

android:layout_height=”wrap_content”

android:layout_centerInParent=”true”

android:gravity=”center”

android:text=”@string/start_background_job_label”

android:onClick=”startBackgroundJob” />

</RelativeLayout>

You now have two things to test for in your Activity: the existence of a click listener for the Button and that

the click listener calls Context.startService() with the correct Intent.

In the following example, you have two test methods, testIfButtonHasClickListener() and

testIfClickListenerStartsServiceCorrectly().

public class MainActivityTest extends ActivityUnitTestCase<MainActivity> {

private Intent mServiceIntent;

public MainActivityTest() {

super(MainActivity.class);

}

public void testIfButtonHasClickListener() {

startActivity(new Intent(Intent.ACTION_MAIN), null, null);

View testButton = getActivity().

findViewById(R.id.background_job_btn);

assertTrue(“Button is missing onClick listener!”,

testButton.hasOnClickListeners());

}

196 Part II: Getting the Most Out of Components

public void testIfClickListenerStartsServiceCorrectly() {

setActivityContext(new MyMockContext(getInstrumentation().

getTargetContext()));

startActivity(new Intent(Intent.ACTION_MAIN), null, null);

View testButton = getActivity().

findViewById(R.id.background_job_btn);

TouchUtils.clickView(this, testButton);

assertEquals(“Wrong Intent action for starting service!”,

“startBackgroundJob”, mServiceIntent.getAction());

}

public class MyMockContext extends ContextWrapper {

public MyMockContext(Context base) {

super(base);

}

@Override

public ComponentName startService(Intent serviceIntent) {

mServiceIntent = serviceIntent;

return new ComponentName(“com.aptl.code”, “NetworkService”);

}

}

}

The first test retrieves the Button in the current UI layout and verifies that it has a click listener. This is a

useful test for Button and similar views where you have assigned the click listener using the XML attribute

android:onClick.

The second test is a bit more complicated. Before the Activity in this test is started, you create a mocked

Context with your own implementation of Context.startService() and assign it as the Activity

context for the test. When the method Context.startService() is called in the click listener, your own

implementation is the one called. Here you store the incoming Intent so you can use it to verify the test. If the

Intent action string is what you’re expecting, then the test passes.

Testing Services

The Android testing APIs provide a class named ServiceTestCase that helps with setting up tests for a

service. In the following example you can see a very simple Service that I’ve created a test case for. This code

implements a simple Service according to the local binder pattern explained in Chapter 6:

public class MyService extends Service {

private LocalBinder mLocalBinder = new LocalBinder();

public IBinder onBind(Intent intent) {

return mLocalBinder;

}

Chapter 10: Writing Automated Tests 197

public class LocalBinder extends Binder {

public MyService getService() {

return MyService.this;

}

}

}

The next code example is a simple ServiceTestCase to verify the implementation:

public class MyServiceTest extends ServiceTestCase<MyService> {

public MyServiceTest() {

super(MyService.class);

}

@Override

public void setUp() throws Exception {

super.setUp();

setupService();

}

public void testBinder() throws Exception {

Intent serviceIntent = new Intent(getContext(), MyService.class);

IBinder binder = bindService(serviceIntent);

assertTrue(binder instanceof MyService.LocalBinder);

MyService myService = ((MyService.LocalBinder) binder).

getService();

assertSame(myService, getService());

}

@Override

public void tearDown() throws Exception {

shutdownService();

super.tearDown();

}

}

In this code you call setupService() to create all the necessary dependencies for the Service being

tested. In the testBinder() method you call the test-specific bindService() method, which returns the

IBinder instance that should be returned by onBind() in MyService. After you verify the correct class for

the IBinder instance, you call getService() and verify that the object returned is the same instance as the

Service already started by the test case.

While this example is very simple, it demonstrates how to use the ServiceTestCase for writing tests

for Service components. You should use this as much as possible for testing the Services in your own

applications.

198 Part II: Getting the Most Out of Components

Testing ContentProviders

Testing your ContentProvider is straightforward when you use the ProviderTestCase2 class. The goal

of testing your provider is to verify its public contract, which is to say that you test that a client receives the data

it requests.

The ProviderTestCase2 allows you to do tests on your ContentProvider against a Context (and

database) that is isolated from the default environment of your application. The following example shows a

number of tests for the TaskProvider that I cover in Chapter 9.

public class TaskProviderTest extends ProviderTestCase2<TaskProvider> {

private Uri ALL_TASKS_URI

= Uri.parse(“content://com.aptl.code.provider/task”);

private MockContentResolver mResolver;

public TaskProviderTest() {

super(TaskProvider.class, TaskProvider.AUTHORITY);

}

@Override

protected void setUp() throws Exception {

super.setUp();

mResolver = getMockContentResolver();

}

public void testDatabaseCreated() {

Cursor cursor = null;

try {

cursor = mResolver.

query(ALL_TASKS_URI, null, null, null, null);

// Database should be empty

assertNotNull(cursor);

assertFalse(cursor.moveToNext());

// Verify that we got all the columns

String[] allColumnsSorted

= new String[TaskProvider.ALL_COLUMNS.length];

System.arraycopy(TaskProvider.ALL_COLUMNS, 0,

allColumnsSorted, 0, allColumnsSorted.length);

Arrays.sort(allColumnsSorted);

String[] columnNames = cursor.getColumnNames();

Arrays.sort(columnNames);

assertTrue(Arrays.equals(allColumnsSorted, columnNames));

} finally {

if (cursor != null) {

cursor.close();

}

}

}

public void testCreateTaskWithDefaults() {

ContentValues values = new ContentValues();

values.put(TaskProvider.TaskColumns.NAME, “Do laundry”);

Chapter 10: Writing Automated Tests 199

values.put(TaskProvider.TaskColumns.OWNER, “Erik”);

Uri insertedUri = mResolver.insert(ALL_TASKS_URI, values);

assertNotNull(insertedUri);

Cursor cursor = mResolver.query(insertedUri,

null, null, null, null);

assertNotNull(cursor);

assertTrue(cursor.moveToNext());

int nameColumnIdx

= cursor.getColumnIndex(TaskProvider.TaskColumns.NAME);

assertEquals(cursor.getString(nameColumnIdx), “Do laundry”);

int ownerColumnIdx

= cursor.getColumnIndex(TaskProvider.TaskColumns.OWNER);

assertEquals(cursor.getString(ownerColumnIdx), “Erik”);

int statusColumnIdx

= cursor.getColumnIndex(TaskProvider.TaskColumns.STATUS);

assertEquals(cursor.getInt(statusColumnIdx), 0);

int priorityColumnIdx

= cursor.getColumnIndex(TaskProvider.TaskColumns.PRIORITY);

assertEquals(cursor.getInt(priorityColumnIdx), 0);

int createdColumnIdx

= cursor.getColumnIndex(TaskProvider.TaskColumns.CREATED);

SystemClock.sleep(500);

assertTrue(cursor.getLong(createdColumnIdx)

< System.currentTimeMillis());

}

public void testInsertUpdateDelete() {

ContentValues values = new ContentValues();

values.put(TaskProvider.TaskColumns.NAME, “Do laundry”);

values.put(TaskProvider.TaskColumns.OWNER, “Erik”);

Uri insertedUri = mResolver.insert(ALL_TASKS_URI, values);

assertNotNull(insertedUri);

values.put(TaskProvider.TaskColumns.PRIORITY, 5);

values.put(TaskProvider.TaskColumns.STATUS, 1);

int updated = mResolver.update(insertedUri, values, null, null);

assertEquals(updated, 1);

Cursor cursor = null;

try {

cursor = mResolver.query(insertedUri, null, null, null, null);

assertNotNull(cursor);

assertTrue(cursor.moveToNext());

int statusColumnIdx

= cursor.getColumnIndex(TaskProvider.TaskColumns.STATUS);

assertEquals(cursor.getInt(statusColumnIdx), 1);

int priorityColumnIdx

= cursor.getColumnIndex(TaskProvider.TaskColumns.PRIORITY);

200 Part II: Getting the Most Out of Components

assertEquals(cursor.getInt(priorityColumnIdx), 5);

} finally {

if (cursor != null) {

cursor.close();

}

}

try {

int deleted = mResolver.delete(insertedUri, null, null);

assertEquals(deleted, 1);

cursor = mResolver.query(insertedUri, null, null, null, null);

assertNotNull(cursor);

assertFalse(cursor.moveToNext());

} finally {

if (cursor != null) {

cursor.close();

}

}

}

public void testInsertInvalidColumn() {

try {

ContentValues values = new ContentValues();

values.put(TaskProvider.TaskColumns.NAME, “Do laundry”);

values.put(TaskProvider.TaskColumns.OWNER, “Erik”);

values.put(“nonExistingColumn”, “someData”);

Uri uri = mResolver.insert(ALL_TASKS_URI, values);

fail(“Should throw SQLException on wrong column name.”);

} catch (Exception e) {

assertTrue(e instanceof SQLException);

}

}

public void testInvalidUri() {

try {

Cursor cursor = mResolver.

query(Uri.parse(“content://”

+ TaskProvider.AUTHORITY + “/wrongPath”),

null, null, null, null);

fail(“Expected IllegalArgumentException!”);

} catch (Exception e) {

assertTrue(e instanceof IllegalArgumentException);

}

}

}

These tests verify that the database is created with the expected table and columns. Next, they verify that the

default values are created correctly when those columns are excluded in an insert-call. One test verifies that

inserts, updates, and deletes work as expected. Finally, you see a test verifying that you cannot insert records

using an invalid column name and a test for handling an invalid Uri.

Chapter 10: Writing Automated Tests 201

In a real-life application, you would add additional tests that cover many more cases to ensure that everything is

handled correctly. Let this serve as a guideline for how to write tests for your own ContentProviders.

Running Tests

To run your test, you can either use the built-in feature of Android Studio or execute Gradle from the command

line with the task connectedInstrumentTest. During a normal development cycle, using the built-in

runner from Android Studio is recommended because it gives you full integration between the test results and

your code. Figure 10-1 shows an example of how to configure a test-runner from within Android Studio.

Figure 10-1 Custom test run configuration in Android Studio

When developing a new feature, the temptation to run all your test cases every time you want to verify your

code can be impractical because of the time involved in your development cycle. To manage this issue, you can

define custom test suites and test runners that execute only a partial set of your tests cases.

The following example shows how to create a test runner that executes only the tests from

MainActivityTest shown earlier. This approach is useful when you’re focusing on developing a new UI

feature and want to perform only the tests that relate to that feature.

public class MyTestRunner extends InstrumentationTestRunner {

@Override

public TestSuite getAllTests() {

202 Part II: Getting the Most Out of Components

return new TestSuite(MainActivityTest.class);

}

}

You can construct your test runners and test suites multiple ways. Use the classes from android.unit.

suitebuilder to construct your custom set of tests for your needs.

Continuous Integration

When you have a team of developers working on the same project, it’s good practice to use a continuous

integration (CI) system for automatically running the tests whenever there is a change committed to your main

branch. Several CI servers are available. A free and open-source alternative is Jenkins CI, which you can find

at http://jenkins-ci.org. It has plug-in support for both the Gradle build system and Android, which

makes it a convenient choice.

To configure Jenkins CI to run your project, all you need to do is to setup a free-f-form project in Jenkins CI, add

a build step for invoking a Gradle script, and point out the root directory and Gradle build file for your project,

as shown in Figure 10-2.

Figure 10-2 Setting up Jenkins CI for executing the Gradle script for an Android project

Chapter 10: Writing Automated Tests 203

Summary

In this chapter, I covered how to write automated unit and instrumentation tests for your Android applications.

Writing instrumentation tests is the best way to find and eliminate bugs in your code. It is also the only way to

verify use-cases that can be very difficult to replicate in real life.

If you’re new at testing, below I recommend that you read the “Android Testing Guidelines,” as well as find out

more about TDD in general.

Further Resources

Android Testing Guidelines

Extensive guidelines for writing tests for Android: http://developer.android.com/tools/

testing/index.html

Books

Beck, Kent. Test Driven Development: By Example. Addison-Wesley, 2002.

Websites

More information about Jenkins CI: http://jenkins-ci.org

Part III

Pushing

the Limits

Chapter 11 Advanced Audio, Video, and Camera Applications

Chapter 12 Secure Android Applications

Chapter 13 Maps, Location, and Activity APIs

Chapter 14 Native Code and JNI

Chapter 15 The Hidden Android APIs

Chapter 16 Hacking the Android Platform

Chapter 17 Networking, Web Service, and Remote APIs

Chapter 18 Communicating with Remote Devices

Chapter 19 Google Play Services

Chapter 20 Distributing Applications on Google Play Store

Chapter 11

Advanced Audio, Video,

and Camera Applications

Although basic APIs are sufficient for working with audio, video, and camera, more advanced applications often

require more functionality. Ordinary recording and playback is possible with the Java APIs in Android; however,

the latency involved with higher-level APIs may sometimes be too much for them. And working with camera

applications such as augmented reality and other real-time scenarios can be difficult.

In this chapter, you find out how to get the most out of different media features in Android. Android allows you to

use both high-performance and low-level APIs, such as OpenGL ES for graphics and OpenSL ES for audio. I show

you how to use these APIs together and present some cases for using advanced audio, video, and camera features.

Some of the examples in this chapter include native C code, specifically the example related to

OpenSL ES. If you are not familiar with using the Android NDK for building applications with native C

code, you may want to read Chapter 14 before proceeding with this chapter.

Advanced Audio Applications

Music players were the first audio-centric applications for Android. Some could play music from an online

streaming server and some played music from locally stored files. As the Android platform evolved, more

advanced audio APIs were needed, and Google added APIs to allow low-latency audio streaming and recording.

The audio APIs in Android today provide some advanced features that you can integrate into your applications.

It’s now much easier to implement VoIP ( V oice o ver I nternet P rotocol) applications, build custom-streaming

music clients, and implement low-latency sound effects for your games. Also available are APIs to provide text-

to-speech and speech recognition features, allowing users to interact with applications using audio rather than

visual UI or touch technologies.

In this section, I explain how to use these features and provide example code that can help you get started.

Low-Latency Audio

Android has four APIs for doing audio playback (five, if you count MIDI) and three APIs for audio recording. The

following sections provide you with a brief overview of these APIs, along with code exemplifying the more

advanced ones.

208 Part III: Pushing the Limits

Audio Playback APIs

The default class for audio playback is the MediaPlayer class. This class provides basic audio playback

suitable for music players (or video) and a convenient API for playing audio from both streaming sources (such

as an online Internet radio) and local files. An advanced state machine is associated with a MediaPlayer

class that your application needs to track. You can use this API to embed music or video playback within your

application without extra processing or latency requirements.

The second option for audio playback is the SoundPool class, which provides a low-latency support useful for

sound effects and other short audio samples. You use this API to play samples of sound in a game. However, it

doesn’t support audio streaming, so you can’t use it for real-time audio streaming applications like VoIP.

The third option for audio playback is the AudioTrack class, which allows you to push a single audio stream

as raw PCM buffers to the audio hardware, allowing low-latency playback even for streaming scenarios.

AudioTrack usually provides latency low enough to be used in VoIP and similar applications.

The following code shows how to set up an AudioTrack instance for a typical VoIP scenario:

public class AudioTrackDemo {

private final AudioTrack mAudioTrack;

private final int mMinBufferSize;

public AudioTrackDemo() {

mMinBufferSize = AudioTrack.getMinBufferSize(16000,

AudioFormat.CHANNEL_OUT_MONO,

AudioFormat.ENCODING_PCM_16BIT);

mAudioTrack = new AudioTrack(AudioManager.STREAM_VOICE_CALL,

16000,

AudioFormat.CHANNEL_OUT_MONO,

AudioFormat.ENCODING_PCM_16BIT,

mMinBufferSize * 2,

AudioTrack.MODE_STREAM);

}

public void playPcmPacket(byte[] pcmData) {

if(mAudioTrack != null

&& mAudioTrack.getState() == AudioTrack.STATE_INITIALIZED)

{

if(mAudioTrack.getPlaybackRate()

!= AudioTrack.PLAYSTATE_PLAYING) {

mAudioTrack.play();

}

mAudioTrack.write(pcmData, 0, pcmData.length);

}

}

public void stopPlayback() {

if(mAudioTrack != null) {

mAudioTrack.stop();

mAudioTrack.release();

}

}

}

Chapter 11: Advanced Audio, Video, and Camera Applications 209

First, you determine the minimum buffer size for the audio stream. To do so, you need to know the sample rate,

whether the data is mono or stereo, and whether you will use 8-bit or 16-bit PCM encoding. The sample rate and

sample size are the parameters to AudioTrack.getMinBufferSize(), which returns the minimum buffer

size in bytes for this AudioTrack instance.

You create the AudioTrack instance with the correct parameters according to your needs. You adapt the first

parameter to the type of audio you’ll be working with. For a VoIP application, use STREAM_VOICE_CALL, and

for a streaming music application, use STREAM_MUSIC.

The second, third, and fourth parameters for the AudioTrack differ according to the situation. These are the

sample rate, stereo or mono, and sample size. A VoIP application would use 16 kHz in 16-bit mono while regular

music CDs use 44.1 kHz in 16-bit stereo. A high sample rate in 16-bit stereo requires a larger buffer and also

more data transfer, but will give you better quality playback. All Android devices support PCM playback in 8, 16,

and 44.1 kHz sample rates in either 8- or 16-bit stereo.

The buffer size parameter for the AudioTrack should be a multiple of the minimum buffer size. Deciding the

size of this buffer depends on your needs and may even vary because of network latency and other factors.

Always avoid having an empty buffer because that will cause glitches in the audio playback.

The final parameter to the constructor determines whether you’ll send audio data only once (MODE_STATIC)

or as a continuous stream of data (MODE_STREAM). In the first case, you need to send the entire audio clip at

once. When streaming, you can send arbitrary sized pieces of PCM data, which is probably what you want to do

when handling streaming music or a VoIP call.

Audio Recording APIs

The first API for recording audio (and also video) is the MediaRecorder API. This API is similar to

the MediaPlayer in that it maintains an internal state that you need to track in your code. With the

MediaRecorder, it’s only possible to capture recording to a file, so this API is not suitable for streaming

solutions.

If you need to stream the audio you’re recording, you can use the AudioRecord API, which is very similar to

the AudioTrack described in the previous section.

The following example shows how to set up an AudioRecord instance for recording 16-bit mono in 16 kHz

samples:

public class AudioRecordDemo {

private final AudioRecord mAudioRecord;

private final int mMinBufferSize;

private boolean mDoRecord = false;

210 Part III: Pushing the Limits

public AudioRecordDemo() {

mMinBufferSize = AudioTrack.getMinBufferSize(16000,

AudioFormat.CHANNEL_OUT_MONO,

AudioFormat.ENCODING_PCM_16BIT);

mAudioRecord = new AudioRecord(

MediaRecorder.AudioSource.VOICE_COMMUNICATION,

16000,

AudioFormat.CHANNEL_IN_MONO,

AudioFormat.ENCODING_PCM_16BIT,

mMinBufferSize * 2);

}

public void writeAudioToStream(OutputStream stream) {

mDoRecord = true;

mAudioRecord.startRecording();

byte[] buffer = new byte[mMinBufferSize * 2];

while(mDoRecord) {

int bytesWritten = mAudioRecord.read(buffer, 0, buffer.

length);

try {

stream.write(buffer, 0, bytesWritten);

} catch (IOException e) {

// Ignore for brevity...

mDoRecord = false;

}

}

mAudioRecord.stop();

mAudioRecord.release();

}

public void stopRecording() {

mDoRecord = false;

}

}

Because this is very similar to what you do for setting up an AudioTrack, this setup makes it useful to

combine these two classes when using VoIP and similar applications.

OpenSL ES for Android

Earlier in this chapter, I said there are four APIs for audio playback and three for audio recording. So far, I’ve

covered three for playback and two for recording. In this section, you find out about the final API, OpenSL ES,

which supports both playback and recording. This API is a standard from the Khronos Group (the same group

responsible for the OpenGL APIs).

This API provides low-level audio hardware access and low-latency features for dealing with audio playback

and recording. Whereas the other audio APIs in Android have a convenient Java API, OpenSL ES is currently

accessible only in native C using the Android NDK. In this section, I describe only the native part of the OpenSL

ES API . You can turn to Chapter 14 to find out how you can use the Android NDK and write JNI code to bridge

between Java and native C.

Chapter 11: Advanced Audio, Video, and Camera Applications 211

For this section, I assume that you have some knowledge of the C programming language.

The first part of this OpenSL ES example includes the declarations of the required include files and the global

objects that will be used in the code, as shown here:

#include <pthread.h>

// for native OpenSL ES audio

#include <SLES/OpenSLES.h>

#include <SLES/OpenSLES_Android.h>

static pthread_cond_t s_cond;

static pthread_mutex_t s_mutex;

static SLObjectItf engineObject = NULL;

static SLEngineItf engineEngine;

static SLObjectItf outputMixObject = NULL;

static SLObjectItf bqPlayerObject = NULL;

static SLPlayItf bqPlayerPlay;

static SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue;

The following code contains the callback function bqPlayerCallback() that’s called by the OpenSL ES

framework when a sample plays:

static void waitForPlayerCallback()

{

pthread_mutex_lock(&s_mutex);

pthread_cond_wait(&s_cond, &s_mutex);

pthread_mutex_unlock(&s_mutex);

}

SLresult enqueueNextSample(short* sample, int size, short waitForCallback)

{

if(waitForCallback)

{

waitForPlayerCallback();

}

return (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue,

nextBuffer,

nextSize);

}

void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context)

{

pthread_cond_signal(&s_cond);

}

212 Part III: Pushing the Limits

In this case, you’re feeding the output buffer from a different thread through the enqueueNextSample()

function. In order to synchronize the thread where the callback occurs with the thread calling the enqueue

function, you use a pthread_mutex. A call to waitForPlayerCallback() will block the thread until

pthread_cond_signal() is called in the callback (similar to how Object.wait()blocks in Java).

The initialization of the OpenSL ES engine is shown in the following code:

SLresult initOpenSLES()

{

// Use this to check the result of each operation..

SLresult result;

int speakers;

int channels = 2;

// We first create the mutex needed for our playback later

pthread_cond_init(&s_cond, NULL);

pthread_mutex_init(&s_mutex, NULL);

// Create and realize the engine

result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);

if(result != SL_RESULT_SUCCESS) return result;

result = (*engineObject)->Realize(engineObject,

SL_BOOLEAN_FALSE);

if(result != SL_RESULT_SUCCESS) return result;

result = (*engineObject)->GetInterface(engineObject,

SL_IID_ENGINE,

&engineEngine);

if(result != SL_RESULT_SUCCESS) return result;

// Create and realise the output mixer

const SLInterfaceID outputIds[1] = {SL_IID_VOLUME};

const SLboolean outputReq[1] = {SL_BOOLEAN_FALSE};

result = (*engineEngine)->CreateOutputMix(engineEngine,

&outputMixObject,

1,

outputIds,

outputReq);

if(result != SL_RESULT_SUCCESS) return result;

result = (*outputMixObject)->Realize(outputMixObject,

SL_BOOLEAN_FALSE);

if(result != SL_RESULT_SUCCESS) return result;

// Setup the output buffer and sink

SLDataLocator_AndroidSimpleBufferQueue bufferQueue =

{SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};

speakers = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;

SLDataFormat_PCM formatPcm = {SL_DATAFORMAT_PCM,

channels,

SL_SAMPLINGRATE_44_1,

SL_PCMSAMPLEFORMAT_FIXED_16,

SL_PCMSAMPLEFORMAT_FIXED_16,

speakers, SL_BYTEORDER_LITTLEENDIAN};

Chapter 11: Advanced Audio, Video, and Camera Applications 213

SLDataSource audioSource = {&bufferQueue, &formatPcm};

SLDataLocator_OutputMix dataLocOutputMix =

{SL_DATALOCATOR_OUTPUTMIX,

outputMixObject};

SLDataSink audioSink = {&dataLocOutputMix, NULL};

// Create a realize the player object

const SLInterfaceID playerIds[] =

{SL_IID_ANDROIDSIMPLEBUFFERQUEUE};

const SLboolean playerReq[] = {SL_BOOLEAN_TRUE};

result = (*engineEngine)->CreateAudioPlayer(engineEngine,

&bqPlayerObject,

&audioSource,

&audioSink,

1,

playerIds,

playerReq);

if(result != SL_RESULT_SUCCESS) return result;

result = (*bqPlayerObject)->Realize(bqPlayerObject,

SL_BOOLEAN_FALSE);

if(result != SL_RESULT_SUCCESS) return result;

result = (*bqPlayerObject)->GetInterface(bqPlayerObject,

SL_IID_PLAY,

&bqPlayerPlay);

if(result != SL_RESULT_SUCCESS) return result;

// Get the player buffer queue object

result = (*bqPlayerObject)->GetInterface(bqPlayerObject,

SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bqPlayerBufferQueue);

if(result != SL_RESULT_SUCCESS) return result;

// Register the callback function

result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue,

bqPlayerCallback,

NULL);

if(result != SL_RESULT_SUCCESS) return result;

return SL_RESULT_SUCCESS;

}

The basic concept for all initialization here is that you first create an object (for instance, the engine) and then

realize it. Next, you retrieve the interface to control the object.

In this example, you start by creating the engine, followed by the player object. You also create an output

buffer object where you write the audio samples to be played. The player is configured to play 16-bit stereo

(2 channels) in 44.1 kHz. Finally, you set the callback for the player buffer. The callback function will be called

after the buffer is ready to receive a new sample for playback.

Also notice that in the beginning of this function, you create the objects related to the mutex used in the

callback and the function for adding new samples.

214 Part III: Pushing the Limits

The final part of this example gives two functions for controlling the state of the player object (play and pause)

and the shutdown function that does the entire cleanup.

The following example shows how to use OpenSL ES for low-latency audio playback. This API is very useful for

the extreme low-latency scenarios or when most of your code is already in native C. For recording, you use this

API in much the same way as when doing playback but with different interfaces.

SLresult pausePlayback()

{

return (*bqPlayerPlay)->SetPlayState(bqPlayerPlay,

SL_PLAYSTATE_PAUSED);

}

SLresult startPlayback()

{

return (*bqPlayerPlay)->SetPlayState(bqPlayerPlay,

SL_PLAYSTATE_PLAYING);

}

void shutdownOpenSLES()

{

if (bqPlayerObject != NULL) {

(*bqPlayerObject)->Destroy(bqPlayerObject);

bqPlayerObject = NULL;

bqPlayerPlay = NULL;

bqPlayerBufferQueue = NULL;

}

if (outputMixObject != NULL) {

(*outputMixObject)->Destroy(outputMixObject);

outputMixObject = NULL;

}

if (engineObject != NULL) {

(*engineObject)->Destroy(engineObject);

engineObject = NULL;

engineEngine = NULL;

}

pthread_cond_destroy(&s_cond);

pthread_mutex_destroy(&s_mutex);

}

Text-to-Speech (TTS)

Although visual feedback is usually the fastest way to provide information to the user, doing so also requires the

user’s full attention to the device. When the user is not able to look at the device, you need some other means of

communicating. Android comes with a powerful API to do text-to-speech (TTS), which enables you to add voice

notifications and other spoken feedback in your application that don’t require users to look at the screen.

Chapter 11: Advanced Audio, Video, and Camera Applications 215

The following code shows how to use the TTS API:

public class TextToSpeechDemo implements TextToSpeech.OnInitListener {

private final TextToSpeech mTextToSpeech;

// Used to queue up messages before the TTS engine is initialized...

private final ConcurrentLinkedQueue<String> mBufferedMessages;

private Context mContext;

private boolean mIsReady;

public TextToSpeechDemo(Context context) {

mContext = context;

mBufferedMessages = new ConcurrentLinkedQueue<String>();

mTextToSpeech = new TextToSpeech(mContext, this);

}

@Override

public void onInit(int status) {

if (status == TextToSpeech.SUCCESS) {

mTextToSpeech.setLanguage(Locale.ENGLISH);

synchronized (this) {

mIsReady = true;

for (String bufferedMessage : mBufferedMessages) {

speakText(bufferedMessage);

}

mBufferedMessages.clear();

}

}

}

public void release() {

synchronized (this) {

mTextToSpeech.shutdown();

mIsReady = false;

}

}

public void notifyNewMessages(int messageCount) {

String message = mContext.getResources().

getQuantityString(R.plurals.msg_count,

messageCount, messageCount);

synchronized (this) {

if (mIsReady) {

speakText(message);

} else {

mBufferedMessages.add(message);

}

}

}

216 Part III: Pushing the Limits

private void speakText(String message) {

HashMap<String, String> params = new HashMap<String, String>();

params.put(TextToSpeech.Engine.KEY_PARAM_STREAM,

“STREAM_NOTIFICATION”);

mTextToSpeech.speak(message, TextToSpeech.QUEUE_ADD, params);

mTextToSpeech.playSilence(100, TextToSpeech.QUEUE_ADD, params);

}

}

Because the initialization of the TTS engine happens asynchronously, you may need to queue up messages

before you can perform the actual text-to-speech operation.

You can send several parameters to the TTS engine. In the preceding, you see how to decide which audio stream

to use for the spoken message. In this case, you use the same audio stream as the notification sounds.

Finally, if you are going to “speak” multiple messages, it’s a good practice to add a brief silence at the end of

each message. Doing so makes it clearer to the user when one message ends and another one starts.

Speech Recognition

Besides text-to-speech synthesis, Android can perform speech recognition. This feature is a bit more

complicated and doesn’t support as many languages as the text-to-speech API. However, it’s still a powerful

feature for providing an alternative input from the user when touch interaction is limited.

Note: You need to declare the user of the permission android.permission.RECORD_AUDIO before you

can use the speech recognition feature.

In the following code, you start by creating the SpeechRecognizer object and set the listener for the

callback. When the click listener doSpeechRecognition() is called, the speech recognition is initialized

with a language parameter and a flag indicating that you want partial results delivered during the processing.

public class SpeechRecognitionDemo extends Activity {

private SpeechRecognizer mSpeechRecognizer;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.speech_recognition_demo);

mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(this);

mSpeechRecognizer.

setRecognitionListener(new MyRecognitionListener());

}

@Override

protected void onDestroy() {

super.onDestroy();

mSpeechRecognizer.destroy();

}

public void doSpeechRecognition(View view) {

view.setEnabled(false);

Chapter 11: Advanced Audio, Video, and Camera Applications 217

Intent recognitionIntent =

new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);

recognitionIntent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS,

true);

recognitionIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE,

“en-US”);

mSpeechRecognizer.startListening(recognitionIntent);

}

private class MyRecognitionListener implements RecognitionListener {

@Override

public void onReadyForSpeech(Bundle bundle) {

}

@Override

public void onBeginningOfSpeech() {

((TextView) findViewById(R.id.speech_result)).setText(“”);

}

@Override

public void onRmsChanged(float rmsdB) {

// Not used

}

@Override

public void onBufferReceived(byte[] bytes) {

}

@Override

public void onEndOfSpeech() {

findViewById(R.id.do_speech_recognition_btn).setEnabled(true);

}

@Override

public void onError(int i) {

// Something went wrong...

findViewById(R.id.do_speech_recognition_btn).setEnabled(true);

}

@Override

public void onResults(Bundle bundle) {

ArrayList<String> partialResults =

bundle.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);

if (partialResults != null && partialResults.size() > 0) {

String bestResult = partialResults.get(0);

((TextView) findViewById(R.id.speech_result)).

setText(bestResult + “.”);

}

}

218 Part III: Pushing the Limits

@Override

public void onPartialResults(Bundle bundle) {

ArrayList<String> partialResults =

bundle.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);

if (partialResults != null && partialResults.size() > 0) {

String bestResult = partialResults.get(0);

((TextView) findViewById(R.id.speech_result)).

setText(bestResult);

}

}

@Override

public void onEvent(int i, Bundle bundle) {

// Not used...

}

}

}

The listener now receives a call to each method in turn. In this example, you display the partially recognized

result (best match) in onPartialResult() and keep doing so until you get the final result on onResult().

A more advanced application could also interpret the words and listen for certain commands. This way, your

application can keep performing speech recognition until the user explicitly tells it to stop—for example,

a dictation application that allows the user to add periods between sentences with the single word “stop”

followed by a brief silence.

Video Processing with OpenGL ES 2.0

Android supports hardware-accelerated graphics through the OpenGL ES 2.0 and 3.0 APIs. While OpenGL is

already used for the standard UI rendering and composition in Android, it is more commonly known as the API

used for building graphics-engines for 2D and 3D games. However, as OpenGL ES 2.0 and later require a dedicated

GPU for processing, it can also be used for real-time processing of input from a video or a camera feed. In this

section I show how to use OpenGL ES 2.0 on Android for adding real-time effects, starting with the video example.

In this section, I assume that the reader has some basic understanding of OpenGL ES 2.0.

In later versions of Android (starting from API level 11), you can use an extension called streaming texture to

continuously feed streams of images, like a video, to the OpenGL ES 2.0 context as a texture. Although it may be

interesting to show a video playing on a rotating cube, a more practical use is to add visual effects to a video.

In the following two examples I include only the relevant parts for using the streaming texture feature. For full

OpenGL ES 2.0 examples, see the “Further Resources” section as well as the OpenGL ES examples in the Android

SDK samples.

Chapter 11: Advanced Audio, Video, and Camera Applications 219

In the following code snippet you can see the beginning of a Renderer implementation that will be used by a

GLSurfaceView:public class MyVideoRenderer implements:

GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener {

private static int GL_TEXTURE_EXTERNAL_OES = 0x8D65;

private MediaPlayer mMediaPlayer;

private float[] mSTMatrix = new float[16];

private int muSTMatrixHandle;

The GL extension used for supporting this feature is called GL_TEXTURE_EXTERNAL_OES and is not defined

in the Android SDK. However, it’s just a constant that you can define yourself in your code as just shown.

The renderer also has a reference to the MediaPlayer that will be used to playback the video. Finally, a

transformation matrix and corresponding shader handle are defined. This matrix is later used when mapping

the texture coordinates from the video or camera feed in the fragment shader.

In the onSurfaceCreated() method shown in the following example, I show how to set up the texture that

will be used as an input.

public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {

... omitted for brevity ...

muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, “uSTMatrix”);

checkGlError(“glGetUniformLocation uSTMatrix”);

if (muSTMatrixHandle == -1) {

throw new RuntimeException(“Unable to retrieve uSTMatrix”);

}

// Setup the texture

int[] textures = new int[1];

GLES20.glGenTextures(1, textures, 0);

mTextureID = textures[0];

GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID);