Monday, June 30, 2014

Chrome Dev Editor - Building Apps on the Chrome Platform

Devon Carew and I spoke about how we built the Chrome Dev Editor on the Chrome Platform at Google I/O 2014. It was great to see developers excited about Google building a dev editor for web development that also works on Chromebooks! I'm looking forward to unlock many more dev workflows in the Chrome Dev Editor. You can learn more about the Chrome Dev Editor on our GitHub page and install it from the Chrome Web Store.


Thursday, July 26, 2012

Building Awesome Android Apps with Google Cloud Endpoints


Here's a follow up on my Google IO 2012 talk on building awesome Android Apps using Google Cloud Endpoints. You can sign up for the Google Cloud Endpoints Trusted Tester program and try this out!

Google Cloud Endpoints allows you to define business logic on App Engine and access them via RESTful or RPC APIs on multiple platforms including Android, IOS and JavaScript. Cloud Endpoints are built using Google’s API infrastructure, which means that you get all the benefits of Google APIs (Google+ API, GMail API etc):

  • Built-in authentication support
  • APIs Console to manage your APIs
  • APIs Explorer to discover and try out your APIs on a browser
  • Automatically generated client libraries for Android, IOS and JavaScript



The Google Plugin for Eclipse (GPE) allows you to create and easily consume Cloud Endpoints.  GPE provides the following features to help you develop Cloud Endpoints:

  • Automatic generation of an Endpoint class that contains code to create, get, list, update and delete your entity class - an entity class is just a POJO with JPA/JDO persistence annotations.
  • Automatic generation of API configuration.

GPE provides the following additional features for Cloud Endpoints for Android:

  • Generation of an App Engine Backend project.
  • Generation of a strongly typed client library for Android. GPE copies over the generated client library and their dependencies to the Android project.
  • Support for Google Cloud Messaging (GCM). GCM allows your Endpoints to notify Android devices of changes to any API resources on the App Engine.

Get started with Cloud Endpoints by picking one or more of the following target platforms. Do note that you can use the same App Engine backend for one or more target platforms.

  • Android
  • IOS
  • Javascript

Cloud Endpoints for Android

Prerequisites:

Workflows:
  • Creating an Android Application that uses Cloud Endpoints
  • Adding Authentication to the Cloud Endpoints
  • Using Google Cloud Messaging with Cloud Endpoints

Creating an Android Application that uses Cloud Endpoints


  1. Create an Android project using the ADT’s Android Application Wizard.
  2. Create an App Engine Backend project for the Android project.
  3. Create your Entity class (POJO) - this is a class that represents the Endpoint resource with a bunch of class attributes, setter and getter functions. You can add JPA annotations (@Entity, @Id etc) or JDO annotations to specify that this entity class needs to be persisted. As an example, here is a class representing a Note.

package com.cloudnotes;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class Note {

 @Id
 private String id;
 private String description;
 private String emailAddress;

 public Note() {
 }

 public String getId() {
return id;
 }
 
 public String getDescription() {
   return description;
 }

 public String getEmailAddress() {
   return emailAddress;
 }
 

 public void setId(String idIn) {
   this.id = idIn;
 }
 

 public void setDescription(String description) {
   this.description = description;
 }

 public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
 }
}

  1. Generate the Endpoint class for each Entity class. This generates an Endpoint class that has the implementation for insert, update, remove, get, list methods on the Endpoint class using JPA or JDO as specified in the Entity class.

The @Api annotation indicates that this is an Endpoint class. You can modify this class as per your needs. To add additional methods, you will need to add the @ApiMethod annotations for these methods. Example: a searchNote(String note) method would have the annotation: @ApiMethod (httpMethod=”GET”, name=”note.search”)


Note:
    • In the Endpoint methods, the return value type cannot be simple type such as String or int. The return value needs to be a POJO, an array or a Collection.
    • The listXXX(), getXXX(id) are exposed as HTTP GETs, insertXXX(XXX) is exposed as a HTTP POST, updateXXX(XXX) is exposed as a HTTP PUT, and removeNote(Note) is exposed as a HTTP DELETE.

  1. Generate Cloud Endpoint Client Library for the App Engine project by right-clicking the App Engine project, and selecting Google > Generate Cloud Endpoint Client Library
  2. Modify your Android Application to call into your defined Endpoint. You can write a class that extends the Android AsyncTask to make sure that the network call to the Endpoint does not block any UI thread.

The following code snippet shows how you can call into Noteendpoint.insertNote()

Builder endpointBuilder = new Noteendpoint.Builder(
AndroidHttp.newCompatibleTransport(),
new JacksonFactory(),
new HttpRequestInitializer() {
public void initialize(HttpRequest httpRequest) { }
});
 

Noteendpoint endpoint = CloudEndpointUtils.updateBuilder(
endpointBuilder).build();
        try {
Note result = endpoint.insertNote(note).execute();
} catch (IOException e) {
//Handle exception
}

  1. Test Cloud Endpoints in the local environment.
    • Run App Engine local development server for testing. Hit the Run button on the App Engine application to start the local development server.
    • In your Android project, set CloudEndpointUtils.LOCAL_ANDROID_RUN to true. This indicates that the Android application connects to the local development server.
    • Run your Android application in your emulator.

  1. Test Cloud Endpoints with App Engine.
    • Set the Application ID for the App Engine project.
    • Deploy the App Engine project to App Engine.
    • In your Android project within the CloudEndpoints class, set LOCAL_ANDROID_RUN to false. This indicates that the Android application connects to the remote App Engine app.
    • Run your Android application in your emulator.

Adding Authentication to the Cloud Endpoints

Cloud Endpoints can be authenticated with Google Accounts using OAuth 2.0. This allows your Endpoint to verify and know the identity of the authenticated user. The following steps will help you add Authentication to your Cloud Endpoints.

Note: Android authentication code shown here requires the google-play-services.jar file  to be included in your libs/ folder. This JAR file has not yet been released. I will update this blog once the JAR file is released.

  1. Register a client ID in the Google APIs Console for your Android application
  2. Register an App Engine App ID.
  3. In the Endpoint class, under the @Api annotation (or the @ApiMethod annotation), set the registered Client ID the “clientIds” attribute, and the App Engine App ID as the “audience” attribute. The following code snippet shows this:

@Api(name = "noteendpoint",
clientIds = {"7232929396-4d7boi9meg5qmir9q80eg048qfval6rc.apps.googleusercontent.com
"},
audiences = {"cloudnotes2012.appspot.com"})
public class NoteEndpoint {...}

  1. Modify your methods to take in a User object (com.google.appengine.api.users.User) as an additional parameter. For an authenticated call, App Engine automatically fills in this attribute with the logged on user. The following code shows adding a User parameter to the listNote() method.
   

public List<Note> listNote(User user) {...}

  1. Modify your methods to modify the Datastore queries to query by the logged in User’s email address. The following code snippet shows querying for Notes for a given User.
Query query = mgr.createQuery("select n from Note n where n.emailAddress = :emailAddress");
query.setParameter("emailAddress", user.getEmail());

  1. GoogleAccountCredential.java allows you to manage Google Account selection and authorization of Google Accounts on the Android Application. You can modify your Android Application to use GoogleAccountCredential to get a handle to the Endpoint.

To fetch a GoogleAccountCredential instance:
 

GoogleAccountCredential credential = GoogleAccountCredential.usingAudience(

context, audience);

where:
context is the Android Application Context
audience is the App Engine App ID (eg:cloudnotes2012.appspot.com)

To specify the credential while creating a handle to the Endpoint:



Builder endpointBuilder = new Noteendpoint.Builder(
AndroidHttp.newCompatibleTransport(),
new JacksonFactory(),
credential);  
Noteendpoint endpoint = CloudEndpointUtils.updateBuilder(
endpointBuilder).build();

Using Google Cloud Messaging with Cloud Endpoints

Google Cloud Messaging allows your Cloud Endpoints to send notifications to registered Android devices whenever the state of a resource changes.

For example, let’s say a user uses a Note application from 2 devices: Device A and Device B. If the user is adding a note from Device A. Google Cloud Messaging can be used in the Cloud Endpoint for the insert operation to ping Device B to indicate that a note has been added.


You can perform the following steps to use Google Cloud Messaging with Cloud Endpoints.

  1. In the Google APIs Console, enable Google Cloud Messaging


  1. In the Google APIs Console, create a server API key. This will help App Engine securely communicate with the Google Cloud Messaging Server.

  1. In the App Engine project, DevicePing.java is a helper class that allows you to ping all registered devices. You should update DevicePing.API_KEY to specify the API key obtained in the previous step.
  2. In the App Engine project, you can modify the generated Endpoint class to send pings to registered devices. The following code snippet can be inserted in the insertNote(Note) method to send a ping to all registered devices.

DevicePing.pingAllDevices(user.getEmail() + ":" + note.getId() + ":insert");

  1. In the Android project, GCMIntentService.java is a helper class that helps register the device and perform actions when a message is received from the Google Cloud Messaging Server.
    1. Specify the Google APIs Project ID in GCMIntentService.java in the PROJECT_ID field. The Project ID can be obtained from the URL to the API console project. Example: https://code.google.com/apis/console/?pli=1#project:<PROJECT_ID>:services
    2. Modify GCMIntentService.onMessage() to handle the incoming GCM notification.
    3. In your Android project, call GCMIntentService.register(..) from your application (eg: onCreate() of your activity) to register the device with the Google Cloud Messaging Server and App Engine.



I/O Overview Talks





Java Codelab

Appendix


Source Code for GoogleAccountCredential.java


import com.google.android.gms.auth.GoogleAuthException;
import com.google.android.gms.auth.GoogleAuthUtil;
import com.google.android.gms.auth.TransientAuthException;
import com.google.android.gms.common.AccountPicker;
import com.google.api.client.googleapis.extensions.android2.auth.GoogleAccountManager;
import com.google.api.client.http.BackOffPolicy;
import com.google.api.client.http.ExponentialBackOffPolicy;
import com.google.api.client.http.HttpExecuteInterceptor;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpUnsuccessfulResponseHandler;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import android.accounts.Account;
import android.content.Context;
import android.content.Intent;
import java.io.IOException;
/**
* Manages account selection and authorization for Google accounts.
*/
public final class GoogleAccountCredential implements HttpRequestInitializer {
 final Context context;
 final String scope;
 private String accountName;
 private final GoogleAccountManager accountManager;
 private Account account;
 /**
  * @param context context
  * @param scope scope to use on {@link GoogleAuthUtil#authenticate}
  */
 private GoogleAccountCredential(Context context, String scope) {
   accountManager = new GoogleAccountManager(context);
   this.context = context;
   this.scope = scope;
 }
 /**
  * Constructor a new instance using OAuth 2.0 scopes.
  *
  * @param context context
  * @param scopes OAuth 2.0 scopes
  * @return new instance
  */
 public static GoogleAccountCredential usingOAuth2(Context context, String... scopes) {
   Preconditions.checkArgument(scopes.length != 0);
   String scope = "oauth2:" + Joiner.on(' ').join(scopes);
   return new GoogleAccountCredential(context, scope);
 }
 /** Sets the audience scope to use with Google Cloud Endpoints. */
 public static GoogleAccountCredential usingAudience(Context context, String audience) {
   Preconditions.checkArgument(audience.length() != 0);
   String scope = "audience:" + audience;
   return new GoogleAccountCredential(context, scope);
 }
 /**
  * Sets the selected Google account name (e-mail address), for example {@code "johndoe@gmail.com"}
  * , or {@code null} for none.
  */
 public GoogleAccountCredential setAccountName(String accountName) {
   this.accountName = accountName;
   account = accountManager.getAccountByName(accountName);
   return this;
 }
 public void initialize(HttpRequest request) {
   RequestHandler handler = new RequestHandler();
   request.setInterceptor(handler);
   request.setUnsuccessfulResponseHandler(handler);
   request.setBackOffPolicy(new ExponentialBackOffPolicy());
 }
 /**
  * Returns the selected Google account name (e-mail address), for example
  * {@code "johndoe@gmail.com"}, or {@code null} for none.
  */
 public String getAccountName() {
   return accountName;
 }
 /** Returns the selected Google account or {@code null} for none. */
 public Account getAccount() {
   return account;
 }
 /** Returns all Google accounts or {@code null} for none. */
 public Account[] getAllAccounts() {
   return accountManager.getAccounts();
 }
 /**
  * Returns an intent to show the user to select a Google account, or create a new one if there are
  * none on the device yet.
  *
  * <p>
  * Must be run from the main UI thread.
  * </p>
  */
 public Intent newChooseAccountIntent() {
   return AccountPicker.newChooseAccountIntent(account,
       null,
       new String[] {GoogleAccountManager.ACCOUNT_TYPE},
       true,
       null,
       null,
       null,
       null);
 }
 /**
  * Returns an OAuth 2.0 access token.
  *
  * <p>
  * Must be run from a background thread, not the main UI thread.
  * </p>
  */
 public String getToken() throws IOException {
   BackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
   while (true) {
     try {
       try {
         return GoogleAuthUtil.getToken(context, accountName, scope);
       } catch (TransientAuthException e) {
         // network or server error, so retry use
         long backOffMillis = backOffPolicy.getNextBackOffMillis();
         if (backOffMillis == BackOffPolicy.STOP) {
           throw e;
         }
         // sleep
         try {
           Thread.sleep(backOffMillis);
         } catch (InterruptedException e2) {
           // ignore
         }
       }
     } catch (GoogleAuthException exception) {
       IOException io = new IOException();
       io.initCause(exception);
       throw io;
     }
   }
 }
 class RequestHandler implements HttpExecuteInterceptor, HttpUnsuccessfulResponseHandler {
   /** Whether we've received a 401 error code indicating the token is invalid. */
   boolean received401;
   String token;
   public void intercept(HttpRequest request) throws IOException {
     token = getToken();
     request.getHeaders().setAuthorization("Bearer " + token);
   }
   public boolean handleResponse(
       HttpRequest request, HttpResponse response, boolean supportsRetry) {
     if (response.getStatusCode() == 401 && !received401) {
       received401 = true;
       GoogleAuthUtil.invalidateToken(context, token);
       return true;
     }
     return false;
   }
 }
}