Google Play Services - Pushing the Limits - Android Programming: Pushing the Limits (2014)

Android Programming: Pushing the Limits (2014)

Part III. Pushing the Limits

Chapter 19. Google Play Services

Google provides a wide range of online services that can be used on websites, desktop applications, or mobile

applications like Android. In Chapter 17, I discuss how to integrate online web services with your application,

but in those examples, I use third-party services outside the Google ecosystem. In this chapter, I focus on online

services managed by Google.

You now have available a set of APIs called Google Play Services that lets you integrate Google APIs with very

little effort, and I cover a some of those services in this chapter.

The new Location API covered in Chapter 13 is also part of Google Play Services. However, as the

Location API is a bit different from the ones covered here, it deserves its own chapter.

In this chapter, you find out how to get authorization to the online services such as Google Drive, services

running on the Google Cloud Platform, and the new Google Play Games APIs. You also discover how to set up

push notifications for your application using the Google Cloud Messaging service.

You can include the Google Play Services APIs in your project by adding the following to your Gradle build-file

dependencies section:

compile ‘com.google.android.gms:play-services:3.1.36’

Authorization

Before you can start using any of the Google Play Services, or any other Google services, you need to retrieve

an authorization token for the Google account of the user. To do so, you first need to fetch the account name

for the user.

The easiest way to retrieve the account name for a user is to use the following code:

public void doConnectAccounts(MenuItem menuItem) {

Intent intent = AccountPicker.newChooseAccountIntent(null, null,

new String[] {“com.google”}, false,

“Pick one of your Google accounts to connect.”,

null, null, null);

startActivityForResult(intent, ACCOUNT_REQUEST);

}

image

Calling AccountPicker.newChooseAccountIntent()starts an Activity that returns the chosen

account. By passing “com.google” as one of the allowed account types, the picker will filter out all other

account types. If multiple accounts are available, the user will see a dialog box where he can select the one he

wants to use (see Figure 19-1).

Figure 19-1 The dialog box presented when there are

multiple Google accounts available

After the picker Activity returns with a result, you check whether the call was successful and retrieve the

selected account name, as shown in the following code:

protected void onActivityResult(int requestCode, int resultCode,

Intent data) {

super.onActivityResult(requestCode, resultCode, data);

if(requestCode == ACCOUNT_REQUEST) {

if(resultCode == RESULT_OK) {

String accountName

= data.getStringExtra(AccountManager.KEY_ACCOUNT_

NAME);

mPrefs.edit().putBoolean(PREFS_IS_AUTHORIZED, true)

.putString(PREFS_SELECTED_ACCOUNT, accountName).apply();

Log.d(TAG, “Picked account: “ + accountName);

invalidateOptionsMenu();

authorizeAccount(accountName);

} else {

Log.e(TAG, “No account picked...”);

}

}

}

In this case, you also store the account name in a SharedPreferences along with a flag indicating that the

user successfully selected an account.

Before you request an auth token, you must decide on the scope of the permissions you need for the services

you’ll use. These scopes are standard OAuth2 scopes, and you can find them for each Google API in their

respective online documentation. In this case, you request access to the user’s App Data on Google Drive (see

the section “Google Drive Application Data” later in this chapter), the user information (such as full name), and

the user’s Google+ information. The following code shows how such a String should look.

public static final String AUTH_SCOPE =

“oauth2:https://www.googleapis.com/auth/drive.appdata “ +

“https://www.googleapis.com/auth/userinfo.profile “ +

“https://www.googleapis.com/auth/plus.me”;

After you have the account name, you can fetch an auth token using the class GoogleAuthUtil. This

operation must run off the main thread because it performs network operations, so as shown in the following

code, you wrap it in an AsyncTask:

class MyAuthTokenTask extends AsyncTask<String,Void,String> {

@Override

protected String doInBackground(String... accountName) {

String authToken = null;

try {

authToken = GoogleAuthUtil.getToken(ServicesDemo.this,

accountName[0], AUTH_SCOPE);

} catch (IOException e) {

Log.e(TAG, “Error getting auth token.”, e);

} catch (UserRecoverableAuthException e) {

Log.d(TAG, “User recoverable error.”);

cancel(true);

startActivityForResult(e.getIntent(), TOKEN_REQUEST);

} catch (GoogleAuthException e) {

Log.e(TAG, “Error getting auth token.”, e);

}

return authToken;

}

@Override

protected void onPostExecute(String result) {

// Auth token acquired – start performing API requests

if (result != null) {

mPrefs.edit().putString(PREFS_AUTH_TOKEN, result).apply();

}

}

}

This operation will always fail on the user’s first attempt (unless she authorized this application on another

device earlier), so you need to catch the UserRecoverableAuthException and call startActivityFor

Result() using the Intent contained in the exception. This brings up another dialog where the user is

informed about the API permissions your application has requested (not to be confused with standard Android

permissions). The following code shows how the onActivityResult() callback method looks in this case:

protected void onActivityResult(int requestCode, int resultCode, Intent

data) {

super.onActivityResult(requestCode, resultCode, data);

if(requestCode == ACCOUNT_REQUEST) { // Account picked

if(resultCode == RESULT_OK) {

String accountName

= data.getStringExtra(AccountManager.KEY_ACCOUNT_

NAME);

mPrefs.edit().putBoolean(PREFS_IS_AUTHORIZED, true)

.putString(PREFS_SELECTED_ACCOUNT, accountName).

apply();

invalidateOptionsMenu();

new MyAuthTokenTask().execute(accountName);

} else {

Log.e(TAG, “No account picked...”);

}

} else if(requestCode == TOKEN_REQUEST) { // Token requested

if(resultCode == RESULT_OK) {

// Try again...

new MyAuthTokenTask().

execute(mPrefs.getString(PREFS_SELECTED_ACCOUNT,

null));

}

}

}

Note that you modify the earlier onActivityResult() to handle recovery from errors that might

occur on the first call to GoogleAuthUtil.getToken(). All you do here is retry the execution of

MyAuthTokenTask because you should now have the right permissions.

Google Drive Application Data

Every Android user who has a Google account also has access to the Google Drive cloud storage service. This

service has a number of convenient APIs for reading and writing files and data to the user’s Google Drive

storage. With this the Google Drive API, you can store data in Google Drive that can be seen or accessed by only

one application. If you want your application to provide the capability of syncing its working data online, you’ll

find that this API is very handy.

image

This application data feature should not be confused with the backup features built into Android, where the

developer cannot control when files are backed up. This is also different from the Game Cloud Save API, which

is focused on saving game states across devices. You should use Google Drive and the Application Data features

for larger amounts of data that is only valid for your application—for instance, a sketching application that saves

files in a proprietary format.

As with the Location API described in Chapter 13, you need to add your application’s package name and SHA1

key string in the Google API Console, as shown in Figure 19-2. Create a new Client ID and add the information

needed. After this is done, your application (the package name you specified) will be authorized to access

Google Drive on behalf of users.

Figure 19-2 Adding a new Client ID for Google Drive API access

Before you get started with the Android code, you need to add the necessary dependencies to your Gradle

build file.

compile ‘com.google.api-client:google-api-client-android:1.16.0-rc’

compile ‘com.google.apis:google-api-services-drive:v2-rev89-1.15.0-rc’

The first line adds the generic Google Client for Android library. The second line adds the Google Drive–specific

library that you will use in this case.

Because your applications have already received an auth token from the authorization step (shown in the

previous section), you can now create a GoogleAccountCredentials that will be used by the Drive API.

The returned Drive instance will be authorized to access the user’s private App Data. The following code

shows the createDriveService() that implements this:

public Drive createDriveService(accountName) {

try {

GoogleAccountCredential googleAccountCredential =

GoogleAccountCredential.usingOAuth2(this,

Arrays.asList(DriveScopes.DRIVE_APPDATA));

googleAccountCredential.setSelectedAccountName(accountNAme);

Drive.Builder builder =

new Drive.Builder(AndroidHttp.newCompatibleTransport(),

new AndroidJsonFactory(),

googleAccountCredential);

return builder.build();

} catch (IOException e) {

Log.e(TAG, “Error”, e);

} catch (GoogleAuthException e) {

Log.e(TAG, “Error”, e);

}

return null;

}

When you want to store data that is private to your application in the App Data folder in Google Drive, you simply

create a File object (note that this is not the java.io.File class, but one specific for Google Drive API) and

populate it with the relevant metadata. You provide it with the content (in this case, a ByteArrayContent)

and insert the file into Google Drive. The result will be a file that is hidden in the user interface of Google Drive.

The following code shows how to add a new file to the App Data “folder” of Google Drive:

class MyGoogleDriveAppDataTask extends AsyncTask<JSONObject, Void,

Integer> {

@Override

protected Integer doInBackground(JSONObject... jsonObjects) {

String accountName = mPrefs.getString(PREFS_SELECTED_ACCOUNT,

null);

Drive drive = createDriveService(accountName);

int insertedFiles = 0;

for (JSONObject jsonObject : jsonObjects) {

String dataString = jsonObject.toString();

String md5 = getMD5String(dataString.getBytes());

File file = new File();

file.setTitle(md5);

String mimeType = “application/json”;

file.setMimeType(mimeType);

file.setParents(Arrays.

asList(new ParentReference().setId(“appdata”)));

ByteArrayContent content

= new ByteArrayContent(mimeType,

dataString.getBytes());

try {

drive.files().insert(file,content).execute();

image

insertedFiles++;

} catch (IOException e) {

Log.e(TAG, “Failed to insert file with content “

+ dataString, e);

}

}

return insertedFiles;

}

private String getMD5String(byte[] data) {

MessageDigest mdEnc = null;

try {

mdEnc = MessageDigest.getInstance(“MD5”);

} catch (NoSuchAlgorithmException e) {

Log.e(TAG, “Error retrieving MD5 function!”, e);

return null;

}

mdEnc.update(data, 0, data.length);

return new BigInteger(1, mdEnc.digest()).toString(16);

}

}

The user can see the space used by your application, delete its content, and disconnect your app from her

Google Drive account through the Manage Apps menu in the Google Drive settings (see Figure 19-3).

Figure 19-3 The apps connected to the user’s Google Drive account

image

Reading and updating files is done through the same API as just shown. Note that all of the operations require

network access, so remember to perform these operations off the main thread.

Google Cloud Endpoints

Google has gathered all its cloud-based developer resources and called it Google Cloud Platform. This is basically

an improved interface for App Engine, Compute Engine, Cloud Datastore, and all the other online services that

Google provides. Most of these services have a free tier, so getting started with building a cloud backend for

your mobile application is very easy. To get started, visit https://cloud.google.com/console and

create a new project.

The easiest way to get started with your own Cloud Platform backend is to generate a basic App Engine

backend from Android Studio (see Figure 19-4).

Figure 19-4 Generating App Engine endpoints with Android Studio

After the code generation is complete, you’ll have two new modules ending with “endpoint” and “AppEngine.”

You can now start to add new POJOs (Plain Old Java Objects) with getters and setters that can be queried,

inserted, updated, and deleted from your Android application.

The following code is an example of a simple POJO for storing tasks:

@Entity

public class TaskInfo {

@Id

private String id;

private String title;

private String content;

public TaskInfo() {

}

public TaskInfo(String id, String title, String content) {

this.id = id;

this.title = title;

this.content = content;

}

public String getId() {

return id;

}

public void setId(String id) {

this.id = id;

}

public String getTitle() {

return title;

}

public void setTitle(String title) {

this.title = title;

}

public String getContent() {

return content;

}

public void setContent(String content) {

this.content = content;

}

}

Following this example, you add this TaskInfo class to the AppEngine module in the project and then run

“Generate endpoint” followed by “Generate Client Libraries” from the Tools menu again. This creates the client-

side libraries that you can use in your application.

The AsyncTask shown next is an example of how to add a new TaskInfo:

class AddTaskInfo extends AsyncTask<TaskInfo, Void, Void> {

@Override

protected Void doInBackground(TaskInfo... taskInfos) {

mTaskInfoEndpoint = CloudEndpointUtils.

updateBuilder(new Taskinfoendpoint.Builder(

AndroidHttp.newCompatibleTransport(),

new JacksonFactory(),

new HttpRequestInitializer() {

public void initialize(HttpRequest

httpRequest) {

}

})

).build();

for (TaskInfo taskInfo : taskInfos) {

try {

mTaskInfoEndpoint.insertTaskInfo(taskInfo).execute();;

} catch (IOException e) {

Log.e(TAG, “Error inserting task.”, e);

}

}

return null;

}

}

Note: Because they will perform network calls that would otherwise block the main thread, all these calls need

to happen on a dedicated thread.

The following shows how to use the same endpoint to query for all TaskInfo instances stored in your App

Engine instance:

class GetAllTaskInfo extends AsyncTask<Void, Void, List<TaskInfo>> {

@Override

protected List<TaskInfo> doInBackground(Void... voids) {

try {

mTaskInfoEndpoint = CloudEndpointUtils.

updateBuilder(new Taskinfoendpoint.Builder(

AndroidHttp.newCompatibleTransport(),

new JacksonFactory(),

new HttpRequestInitializer() {

public void initialize(HttpRequest

httpRequest) {

}

})).build();

return mTaskInfoEndpoint.listTaskInfo().execute().getItems();

} catch (IOException e) {

Log.e(TAG, “Error performing query.”, e);

cancel(true);

}

return null;

}

@Override

protected void onPostExecute(List<TaskInfo> taskInfos) {

super.onPostExecute(taskInfos);

mTaskInfoArrayAdapter.clear();

mTaskInfoArrayAdapter.addAll(taskInfos);

mTaskInfoArrayAdapter.notifyDataSetChanged();

}

}

The same approach can be used for updating and deleting existing TaskInfo objects.

Integrating a simple cloud backend can introduce some great features in your application. When the data your

application uses can be shared across all the user’s devices, including the user’s web browser, you can create

very powerful user experiences.

Google Cloud Messaging

When your application communicates with an online backend, such as a service implemented using Google

Cloud Endpoints, consider using server-side push notifications to notify the application when new data is

available. This reduces the need for devices to send unnecessary polling requests to your service, which drains

battery fast and also increases the load on your servers.

Google supports push notifications through the Google Cloud Messaging (GCM) service, which is part of

Google Play Services. Implementing GCM in your application allows you to send messages to the devices

running your application. The most common use for this is to let Android applications know that there’s new

data to fetch online; this is called a tickle and eliminates the need for doing recurring polling operations from

the application. You can also send application data with a payload of up to 4K using GCM.

To integrate GCM in your application, you must enable it in your Google API Console and set up a Server key for

your application under API Access. Also make sure that Google Cloud Messaging is enabled for your project.

GCM Client

On the client-side of your GCM integration, which is your Android application, you need to add a number of

elements to your manifest.

Here’s an example of how the manifest looks for the application with the package name gom.aptl.

myclouddemo.

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

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

package=”com.aptl.myclouddemo”

android:versionCode=”1”

android:versionName=”1.0”>

<uses-sdk

android:minSdkVersion=”18”

android:targetSdkVersion=”18”/>

<uses-permission

android:name=”com.google.android.c2dm.permission.RECEIVE”/>

<uses-permission android:name=”android.permission.INTERNET”/>

<uses-permission android:name=”android.permission.GET_ACCOUNTS”/>

<uses-permission android:name=”android.permission.WAKE_LOCK”/>

<permission

android:name=”com.aptl.myclouddemo.permission.C2D_MESSAGE”

android:protectionLevel=”signature”/>

<uses-permission

android:name=”com.aptl.myclouddemo.permission.C2D_

MESSAGE”/>

<application

android:allowBackup=”true”

android:icon=”@drawable/ic_launcher”

android:label=”@string/app_name”

android:theme=”@style/AppTheme”>

<activity

android:name=”.CloudPlatformDemo”

android:label=”@string/app_name”>

<intent-filter>

<action android:name=”android.intent.action.MAIN”/>

<category android:name=”android.intent.category.

LAUNCHER”/>

</intent-filter>

</activity>

<receiver

android:name=”.MyGcmReceiver”

android:permission=”com.google.android.c2dm.SEND”>

<intent-filter>

<action

android:name=”com.google.android.c2dm.intent.

RECEIVE”/>

<category android:name=”com.aptl.myclouddemo”/>

</intent-filter>

</receiver>

<service android:name=”.MyGcmService”/>

</application>

</manifest>

Note that the new permission declared prevents other applications from receiving the same incoming message.

Also, the permission on the receiver tag ensures that no other application can send a fake message to your

application.

The first time your application starts, you need to call GoogleCloudMessaging.register() using the

sender ID, as shown in the following example:

class RegisterGcm extends AsyncTask<Void, Void, Void> {

@Override

protected Void doInBackground(Void... voids) {

try {

GoogleCloudMessaging cloudMessaging =

GoogleCloudMessaging.getInstance(CloudPlatformDemo.this);

String registrationID = cloudMessaging.register(SENDER_ID);

SharedPreferences preferences

= PreferenceManager

.getDefaultSharedPreferences(CloudPlatformDemo.this);

preferences.edit().

putString(PREFS_GCM_REG_ID, registrationID).

apply();

cloudMessaging.close();

} catch (IOException e) {

Log.e(TAG, “GCM Error.”, e);

}

return null;

}

}

The sender ID is the same as the project number that you can find on the Google Cloud Console. You will also

need to refresh the registration because it eventually times out. The default timeout for a GCM registration is

seven days.

Whenever your server sends a GCM message to your client, it triggers a call to the receiver registered for the

GCM messages. Following is a simple example where the message, stored as a Bundle in the Intent, is

passed to an IntentService for further processing:

public class MyGcmReceiver extends BroadcastReceiver {

public void onReceive(Context context, Intent intent) {

GoogleCloudMessaging cloudMessaging =

GoogleCloudMessaging.getInstance(context);

String messageType = cloudMessaging.getMessageType(intent);

if(GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType))

{

// Pass the message to the IntentService for processing...

Intent serviceIntent = new Intent(context,

MyGcmService.class);

serviceIntent.putExtras(intent.getExtras());

context.startService(serviceIntent);

}

setResultCode(Activity.RESULT_OK);

}

}

Before your server can send GCM messages to a user’s device, the application must provide its registration ID

to the server. The easiest way to do so is to use the DeviceInfoEndpoint that’s generated by the Google

Cloud Endpoint features in Android Studio.

The AsyncTask shown next illustrates how to use the generated endpoint for delivering the registration ID to

the client:

class SendRegistrationId extends AsyncTask<Void, Void, Void> {

@Override

protected Void doInBackground(Void... voids) {

try {

SharedPreferences preferences

= PreferenceManager

.getDefaultSharedPreferences(CloudPlatformDemo.this);

String registrationId = preferences.

getString(PREFS_GCM_REG_ID,

null);

DeviceInfo deviceInfo = new DeviceInfo();

deviceInfo.setDeviceRegistrationID(registrationId);

deviceInfo.setTimestamp(System.currentTimeMillis());

deviceInfo.setDeviceInformation(“Device Info...”);

Deviceinfoendpoint deviceinfoendpoint =

CloudEndpointUtils.updateBuilder(

new Deviceinfoendpoint.Builder(

AndroidHttp.newCompatibleTransport(),

new JacksonFactory(),

new HttpRequestInitializer() {

public void initialize(HttpRequest

httpRequest)

{

}

})

).build();

deviceinfoendpoint.insertDeviceInfo(deviceInfo).execute();

} catch (IOException e) {

Log.e(TAG, “Error inserting device info.”, e);

}

return null;

}

}

The final part is sending the GCM message from the server to the clients whenever a new TaskInfo has been

inserted (see previous section). The easiest way to do so is to modify the method TaskInfoEndpoint.

insertTaskInfo() in the App Engine module, as shown in the following code. The added code is shown in

bold. In this case, a simple tickle message is sent, telling the client that new data is available online.

@ApiMethod(name = “insertTaskInfo”)

public TaskInfo insertTaskInfo(TaskInfo taskInfo) throws IOException {

EntityManager mgr = getEntityManager();

try {

if (containsTaskInfo(taskInfo)) {

throw new EntityExistsException(“Object already exists”);

}

mgr.persist(taskInfo);

Sender sender = new Sender(API_KEY);

CollectionResponse<DeviceInfo> deviceInfos

= endpoint.listDeviceInfo(null, 10);

Message message = new Message.Builder().

addData(“message”, “Task Inserted”).build();

for (DeviceInfo deviceInfo : deviceInfos.getItems()) {

sender.send(message, deviceInfo.getDeviceRegistrationID(), 5);

}

} finally {

mgr.close();

}

return taskInfo;

}

Google Play Game Services

At Google IO 2013, developers were introduced to the new online services for games. Google Play Game

Services let developers include social elements such as achievements and leaderboards, but also real-time

multiplayer support in their games.

In this section, I cover the use of the real-time multiplayer support. This is the most technically advanced feature

in this service, and by mastering it, you should have no problem integrating the other features as well.

The core of the real-time multiplayer consists of two basic concepts: a virtual room where the game takes place

and a number of participants. Players can be invited to a room or automatically matched (auto-matching of

random players). A room is created when the first player wishes to start a new multiplayer session. All players

in a multiplayer game are participants in the same room. One player can send invitations to other players from

their Google+ circles.

The easiest way to implement the real-time multiplayer service in your game is to add the BaseGameUtils

library project to your application, which can be found at https://github.com/playgameservices/

android-samples. Here, you’ll find the class BaseGameActivity, which you should extend instead of the standard Activity class, and the GameHelper, which contains a number of useful helper methods.

Next, you need to enable the Google Play Games Services for your game. You can either follow the instructions

found at https://developers.google.com/games/services/console/enabling, or you can sign in to the Google API Console, enable the necessary services for your project (Google+ API, Google Play

Game Services, Google Play Game Management, and Google Play App State), and create a new Client ID for

installed applications, as shown in Figure 19-2. Next, take the first part of the Client ID (the numbers, followed

by .apps.googleusercontent.com) and add that as a string resource value in your application.

Now, you add a new metadata tag inside your applications tag of your manifest, as shown next. This connects

the Google Play Games Services to your application.

<meta-data android:name=”com.google.android.gms.games.APP_ID”

android:value=”@string/app_id” />

In the UI shown at startup, you add a button for the user to log in to Google+ with your game. If possible, use

the readymade SignInButton in your XML layout, as shown here:

<com.google.android.gms.common.SignInButton

android:id=”@+id/sign_in_button”

android:layout_centerHorizontal=”true”

android:layout_width=”wrap_content”

android:layout_height=”wrap_content”

android:onClick=”doSignIn”/>

In the click-callback shown next, you simply call the method beginUserInitiatedSignIn() from the

base class BaseGameActivity, and the user can sign in:

public void doSignIn(View view) {

beginUserInitiatedSignIn();

}

You’ll have two callbacks for the sign-in process, onSignInFailed() and onSignInSucceeded(), which

are used to process the result. If sign-in is successful, you can proceed by displaying a UI for inviting players or

seeing existing invitations.

In the two click-callbacks shown in the following code, you invite new players or display existing invitations by

starting the games client Activity:

public void doInvitePlayers(View view) {

Intent intent = getGamesClient().getSelectPlayersIntent(1, 3);

startActivityForResult(intent, INVITE_PLAYERS_RC, null);

}

public void doSeeInvitations(View view) {

Intent intent = getGamesClient().getInvitationInboxIntent();

startActivityForResult(intent, SEE_INVITATIONS_RC, null);

}

When inviting players, you must specify the maximum and minimum number of players for a game. These calls

open a new Activity from the Google Play Game Services framework that lets the user either pick opponents

or invite additional players.

Because there are two ways to start a multiplayer game, either through an invitation or by inviting players, you

need two different methods for creating the room for this game session. The following code shows how to

handle these two cases:

private void handlePlayersInvited(Intent intent) {

ArrayList<String> players = intent.

getStringArrayListExtra(GamesClient.EXTRA_PLAYERS);

int minAutoMatchPlayers = intent.

getIntExtra(GamesClient.EXTRA_MIN_AUTOMATCH_PLAYERS, 0);

int maxAutoMatchPlayers = intent.

getIntExtra(GamesClient.EXTRA_MAX_AUTOMATCH_PLAYERS, 0);

Bundle autoMatchCriteria = null;

if (minAutoMatchPlayers > 0 || maxAutoMatchPlayers > 0) {

autoMatchCriteria =

RoomConfig.createAutoMatchCriteria(minAutoMatchPlayers,

maxAutoMatchPlayers, 0);

}

RoomConfig.Builder roomConfigBuilder

= RoomConfig.builder(this);

roomConfigBuilder.addPlayersToInvite(players);

roomConfigBuilder.setMessageReceivedListener(this);

roomConfigBuilder.setRoomStatusUpdateListener(this);

if (autoMatchCriteria != null) {

roomConfigBuilder.setAutoMatchCriteria(autoMatchCriteria);

}

getGamesClient().createRoom(roomConfigBuilder.build());

}

private void handleInvitationResult(Intent intent) {

Bundle bundle = intent.getExtras();

if (bundle != null) {

Invitation invitation =

bundle.getParcelable(GamesClient.EXTRA_INVITATION);

if(invitation != null) {

RoomConfig.Builder roomConfigBuilder

= RoomConfig.builder(this);

roomConfigBuilder

.setInvitationIdToAccept(invitation.getInvitationId())

.setMessageReceivedListener(this)

.setRoomStatusUpdateListener(this);

getGamesClient().joinRoom(roomConfigBuilder.build());

}

}

}

The two interfaces RoomStatusUpdateListener and RoomUpdateListener provide a set of callback

methods for the different state changes of your multiplayer session. How you use these callbacks depends

completely on your game. The simplest solution is to direct all callbacks to the same handler where you

determine the state of the game, as shown in the following code:

@Override

public void onPeerJoined(Room room, List<String> arg1) {

updateRoom(room);

}

public void updateGameState(Room room) {

mParticipants = room.getParticipants();

// TODO: Implement change of game state here...

}

The first method shown here is one of the callbacks from RoomStatusUpdateListener. In this case, you

simply call updateGameState() with the Room as a parameter. In this method, you get the current list of

participants and then update the game state accordingly. Remember that a participant can disconnect from an

ongoing game at any time, so your code needs to deal with this and act accordingly.

During the game, each player will perform actions that will result in an update being sent to the other players.

The following code shows how to send a real-time message to all participants in a room.

public void sendGameUpdate(byte[] data, Room room) {

getGamesClient().sendUnreliableRealTimeMessageToAll(data,

room.getRoomId());

}

Data Messaging

The messaging part of the real-time multiplayer API is very interesting. It allows you to send messages with data

between players in a Room without any additional servers or other infrastructure needed by you. Three types of

communication are supported by this API:

Reliable messaging: Allows you to send a message with a maximum of 1400 bytes with reliable data

delivery, integrity, and ordering. This means that all messages will arrive and in the right order. However,

because of network latency and such, there can be a significant delay between two messages. This should

be used for data that needs to be delivered but where latency is not an issue.

Unreliable messaging: This allows you to send messages with a maximum of 1168 bytes. There is no

guarantee of delivery or data arriving in order. Integrity of individual messages is preserved, but you need

to implement your game logic so that it doesn’t depend on every one of these messages being delivered or

if they’re delivered in the right order. These messages usually have a much lower latency than the reliable

messages and can thus be used for a real-time scenario.

Socket-based messaging: This enables streaming of data between players, instead of delivering distinct

messages. The socket-based messaging has the same limits as unreliable messaging, but usually has lower

latency than reliable messaging. However, this method of communication is more complex, and you should

use unreliable or reliable messaging as much as possible.

All three types of messaging are done through the GameClient class. Receiving messages is handled by

adding a RealTimeMessageReceivedListener to the Room before starting a game session.

Messaging Strategy

When using the real-time multiplayer API, your players can connect across networks. Google’s infrastructure

behind these services allows players to be on completely different networks and still be able to send messages

with relatively low latency.

However, you still need a smart strategy for sending messages between participants in a game. Although

the name of the service implies real-time aspects, that’s not altogether the case. There may be hundreds

of milliseconds or even seconds in latency between two players, depending on their respective network

configuration.

The goal is to send as few messages as possible and only with data that’s absolutely necessary. For instance, in

the case of a multiplayer racing game, you need to communicate two things from a participant: current velocity

(speed and direction) and current position. Each instance of the game then calculates the position of the

local player’s cars and updates the view accordingly. The position of the different players won’t match exactly

between the players, but it will be close enough for the game to feel consistent.

When a player changes the speed or direction of his car, your game will send an update with this information to

the other participants. Because this will change frequently, these messages should be sent as unreliable messages

to reduce latency. When receiving one of these messages, the game will update the speed and direction of the

local representation of that participant accordingly. Because the order for these messages isn’t guaranteed, they

should also contain a sequence number so that your game can discard messages that arrive out of order.

Because unreliable messages might also be lost, you also need to send a few reliable messages that can be used

as more reliable checkpoints in the game. This way, even if one participant drops a number of messages with

speed and direction updates, the other players will eventually receive the correct position for that player.

The following example uses the class Car to represent each participant in the racing game:

class Car {

String participantId;

Point position;

Point velocity;

}

public static final byte POSITION_DATA = 1;

public static final byte VELOCITY_DATA = 2;

private HashMap<String, Car> mCars = new HashMap<String, Car>();

@Override

public void onRealTimeMessageReceived(RealTimeMessage realTimeMessage) {

String participantId = realTimeMessage.getSenderParticipantId();

Car car = mCars.get(participantId);

byte[] messageData = realTimeMessage.getMessageData();

switch (messageData[0]) {

case POSITION_DATA:

car.position.set(messageData[1], messageData[2]);

break;

case VELOCITY_DATA:

car.velocity.set(messageData[1], messageData[2]);

break;

}

}

public void simpleRaceGameLoop() {

for (Car car : mCars.values()) {

car.position.offset(car.velocity.x, car.velocity.y);

}

SystemClock.sleep(100);

}

Whenever a message arrives, you check the first byte to see if it’s an update to a car’s position or its velocity.

Here, you update the state of the variable for that Car.

Meanwhile, the simpleRaceGameLoop()loops continuously during the game and updates the position

of each car using the last known velocity. This method is called dead-reckoning and is used in many types of

multiplayer games.

For a real game, you need to add additional information to these messages and also implement some logic to

handle the cases when the player crashes the car. Also, you need to have a separate message to determine who

the winner is, and in this case, the easiest way is to let each participant game calculate his finishing time and

broadcast it. This way, each participant constructs the final result using the input from the other participants.

The preceding example uses the raw byte array directly for the necessary data, which can work fine if you have

only a small variation of your messages and don’t send complex data structures. However, as soon as your

messages increase in complexity, I highly recommend that you use Google Protocol Buffers. See Chapter 9 for

details on how to integrate this into your application.

Implementing real-time multiplayer games is usually very complicated. But with the new Google Play Game

Services APIs, implementation has now became much easier. You no longer need to deal with all the difficult

aspects of networking and can focus on the actual game content, thus I expect a lot of really innovative

multiplayer games to appear.

Summary

By adding a cloud backend to your application, you can greatly enhance the user experience. Many users have

more than one device, and by utilizing the online services available for syncing data or interacting with existing

Google services, your application can fit more seamlessly into the entire ecosystem.

The Google Play Services APIs for Android provide a set of very powerful APIs for authorization, real-time

multiplayer integration, and push notifications using GCM, to name a few. You can select the parts of these

services and APIs that are most appropriate for your application.

Because most of these services have a free tier on the Google Cloud Platform, you can start experimenting to

get a better understanding of how they work and what they can provide.

Further Resources Websites

Main site for Google Play Services: http://developer.android.com/google/index.html

Main site for Google Cloud Platform: https://cloud.google.com

Google Play Game Services samples and helper libraries: https://github.com/playgameservices/android-samples

Multiplayer API for Google Play Games Services: https://developers.google.com/games/services/common/concepts/realtimeMultiplayer

Synchronized stopwatch demo using Google Cloud Endpoints and Cloud Messaging: http://

bradabrams.com/2013/06/google-io-2013-demo-android-studio-cloud-endpoints-synchronized-stopwatch-demo

image