mandag 16. juni 2014

Developing with Glass

Introduction

Once you have finished playing with the glasses and showing off to your friends you probably will like to start developing for the glasses. Hopefully these entries will help you. 

We have no stinking glasses


To get smarted with the mirror API you don't need glasses stinking or not. It is possible to simulate the behavior using the mirror playground. Be warned in my experience it's not completely identical but close enough for you to play around with.

Looking trough the Mirror

The first and probably recommended way of doing development for Glass is using the Mirror API. Getting good books are scares, and to be quite honest not worth it unless you are deeply unfamiliar with java development and really interested in learning about the google hosted appspot.
Sadly the documentation provided by Google falls short in some of the same areas. So here is how I chosen to solve this for prototyping.

First of all follow the procedure described here to setup a new application and retrieve an access token. 

Add your client Id, secret and request token in the following or similar program.

public class GenCredentials {
    private static String CLIENT_ID = <INSERT YOUR CLIENT ID>
      private static String CLIENT_SECRET = <INSERT YOUR CLIENT SECRET>

      private static String REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob";
     

    public static void main(String[] args)
    {
        // TODO Auto-generated method stub
        try {
            HttpTransport httpTransport = new NetHttpTransport();
            JsonFactory jsonFactory =  new JacksonFactory();
   List<String> scopes=Arrays.asList("https://www.googleapis.com/auth/userinfo.profile",
                   "https://www.googleapis.com/auth/glass.timeline",
                   "https://www.googleapis.com/auth/glass.location");
            GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
                httpTransport, jsonFactory, CLIENT_ID, CLIENT_SECRET, scopes)
                .setAccessType("offline")
                .setApprovalPrompt("auto").build();
           
            String url = flow.newAuthorizationUrl().setRedirectUri(REDIRECT_URI).build();
            System.out.println("Please open the following URL in your browser then type the authorization code:");
            System.out.println("  " + url);
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            String code = br.readLine();
           
            GoogleTokenResponse response;
           
                response = flow.newTokenRequest(code).setRedirectUri(REDIRECT_URI).execute();
            System.out.println(response.getAccessToken());
            System.out.println(response.getRefreshToken());
            System.out.println(response.getExpiresInSeconds());
           
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
       
    }




Save the access token, refresh token and token expiry in the way and manner of your own choosing. 

Add them to a program similar to this (paste the values), read from file or retrieve in whatever manner you choose.

public class InsertTimeLine
{

     /**
       * Insert a new timeline item in the user's glass with an optional
       * notification and attachment.
       *
       * @param service Authorized Mirror service.
       * @param text timeline item's text.
       * @param contentType Optional attachment's content type (supported content
       *        types are "image/*", "video/*" and "audio/*").
       * @param attachment Optional attachment stream.
       * @param notificationLevel Optional notification level, supported values are
       *        {@code null} and "AUDIO_ONLY".
       * @return Inserted timeline item on success, {@code null} otherwise.
       */
      public static TimelineItem insertTimelineItem(Mirror service, String text, String contentType,
          InputStream attachment, String notificationLevel) {
        TimelineItem timelineItem = new TimelineItem();
        timelineItem.setText(text);
        if (notificationLevel != null && notificationLevel.length() > 0) {
          timelineItem.setNotification(new NotificationConfig().setLevel(notificationLevel));
        }
        try {
          if (contentType != null && contentType.length() > 0 && attachment != null) {
            // Insert both metadata and attachment.
            InputStreamContent mediaContent = new InputStreamContent(contentType, attachment);
            return service.timeline().insert(timelineItem, mediaContent).execute();
          } else {
            // Insert metadata only.
            return service.timeline().insert(timelineItem).execute();
          }
        } catch (IOException e) {
          System.err.println("An error occurred: " + e);
          return null;
        }
      }
     
      public static void main(String args[])
      {
          try {
            String clientId=<INSERT YOUR CLIENT ID>
              String clientSecret=<INSERT YOUR CLIENT SECRET>
              String accessToken=<YOUR ACCESS TOKEN GOES HERE>
              String refreshToken=<YOUR REFRESH TOKEN GOES HERE>
              Long refreshTimeOut= new Long(3600);
             
              HttpTransport httpTransport = new NetHttpTransport();
                JsonFactory jsonFactory =  new JacksonFactory();
                ListableMemoryCredential store = new ListableMemoryCredential();
             
              GoogleCredential credential = new GoogleCredential.Builder().setJsonFactory(jsonFactory)
                        .setTransport(httpTransport).setClientSecrets(clientId, clientSecret).build();
                credential.setAccessToken(accessToken);
                credential.setRefreshToken(refreshToken);
                credential.setExpiresInSeconds(refreshTimeOut);
                store.store("xxxx", credential);
                String GLASS_SCOPE = "https://www.googleapis.com/auth/glass.timeline "
                          + "https://www.googleapis.com/auth/glass.location "
                          + "https://www.googleapis.com/auth/userinfo.profile";
                new GoogleAuthorizationCodeFlow.Builder(new NetHttpTransport(), jsonFactory ,
                        clientId, clientSecret, Collections.singleton(GLASS_SCOPE)).setAccessType("offline")
                        .setCredentialStore(store).build();
                //Mirror mirror=new Mirror.Builder(new UrlFetchTransport(), new JacksonFactory(), credential).setApplicationName("GlassPlayGround").build();
                Mirror mirror=new Mirror.Builder(new NetHttpTransport(), new JacksonFactory(), credential).setApplicationName("GlassPlayGround").build();
               
                Timeline tl=mirror.timeline();
                TimelineItem bloom_item=new TimelineItem();
                bloom_item.setText("HABA NADA");
                bloom_item.setTitle("NADA HABA");
                bloom_item.setHtml(<YOUR HTML GOES HERE> );
                bloom_item.setNotification(new NotificationConfig().setLevel("AUDIO_ONLY"));
                LinkedList<MenuItem> men_list=new LinkedList<MenuItem>();
               
                MenuItem play_bloom= new MenuItem();
                play_bloom.setAction("PLAY_VIDEO");
                play_bloom.setId("12345");
                play_bloom.setPayload(<YOUR STREAM SOURCE GOES HERE>);
                men_list.addFirst(play_bloom);
               
                MenuItem open_stock=new MenuItem();
                open_stock.setAction("OPEN_URI");
                open_stock.setId("1111");
                open_stock.setPayload(<WEB PAGE 1 GOES HERE>);
                men_list.addLast(open_stock);
               
                MenuItem open_dnb=new MenuItem();
                open_dnb.setAction("OPEN_URI");
                open_dnb.setId("9999");
                open_dnb.setPayload(<SECOND WEB PAGE GOES HERE>);
                men_list.addLast(open_dnb);
               
                MenuItem delete_item=new MenuItem();
                delete_item.setAction("DELETE");
                delete_item.setId("54321");
                delete_item.setPayload("Go Away");
                men_list.addLast(delete_item);
               
                bloom_item.setMenuItems(men_list);
                TimelineItem ret=mirror.timeline().insert(bloom_item).execute();
                System.out.println(ret);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
           
           
                 
      }

}


For dependencies (please notice that some of these may not be needed remove and test at your own pleasure).

    <dependency>
        <groupId>commons-lang</groupId>
        <artifactId>commons-lang</artifactId>
        <version>2.4</version>
</dependency>


<dependency>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client</artifactId>
<version>1.18.0-rc</version>
</dependency>
  <dependency>
        <groupId>com.google.http-client</groupId>
        <artifactId>google-http-client-jackson2</artifactId>
        <version>1.15.0-rc</version>
    </dependency>
   
   <dependency>
    <groupId>com.google.apis</groupId>
    <artifactId>google-api-services-mirror</artifactId>
    <version>v1-rev50-1.18.0-rc</version>
</dependency>

 
    <dependency>
    <groupId>com.google.api-client</groupId>
    <artifactId>google-api-client-extensions</artifactId>
    <version>1.6.0-beta</version>
</dependency>

<dependency>
    <groupId>com.google.http-client</groupId>
    <artifactId>google-http-client</artifactId>
    <version>1.16.0-rc</version>
</dependency>
<dependency>
    <groupId>com.google.oauth-client</groupId>
    <artifactId>google-oauth-client</artifactId>
    <version>1.18.0-rc</version>
</dependency>

   
   
      
 


That's it. You are now ready to explore the mirror api in a simple and understandable manner.  All things being said and done the mirror api gives you the opertunity to send notifications to the glass. They may be fancy, multimedia notifications but still that what you get. In other words this is suitable for push scenarios where some event happens and is pushed to the users glass. 

Pushing information can in many ways be compared to pushing commercials but with the important distinction of having a user opt out. To much information or even a fairly small amount of irrelevant information will cause the user to opt out on a device as intimate as the glass, a phone or any wearable device at all. 
So if you are going to use this commercially you either must be 100% certain of the relevance of your push or you need to make it really easy for the user to control and filter the notification feature. 

Neither of these approaches are a walk in the park, but neither is it impossible.

To be fair, this is not completely true it is possible to pin a card which means it will stay earli on in the timeline and thus making it not really a timeline. The content of a card might be on demand video, HTML pointing to a web page or service for content. So it is possible to get some kind of pull behavior also using the mirror api. 

Going native with the gdk

This is probably a misnomer since development will be done I java which arguably for most cases can be seen as less native that most other languages. 
However by using the gdk you will make an application which very specifically targets the glass, using its advantages and avoiding its pitfalls. 
The actual development is quite similar to general purpose android development keeping the statements above in mind. If you are completely new to android development the best advice would be not to try to do any glass development yet. Do a stint doing some general purpose android development beyond hello world and come back after you have done that.  It is much easier to play with your android phone first. If you don't have android phone but a cheap cut throat tablet, it's not going to useful for much else but it will be more than sufficient for Android programing 101. If you are not willing to even go that far you will be able to use the emulator provided with the SDK. 

Setting up the environment

As a knowledgeable Android programmer you have already set up the Android SDK with your favourite development environment. First thing to do is to be sure that it's up to date, GDK is pretty new so older SDK will not support it. 
Be aware that you at this point really need glasses, stinking or otherwise, there are currently no emulator support for glass. 

Make an android project and be sure to target glass and the appropriate version of Android only.
For those of you using eclipse this is what you would be looking for. 

    • Minimum and Target SDK Versions: 19 (There is only one Glass version, so minimum and target SDK are the same.)
    • Compile with: Glass Development Kit Developer Preview
    • Theme: None (ADT and Android Studio usually assign a theme automatically, even if you specify no theme, so remove the android:theme property from your manifest after creating a project.)

Starting the development

The Google docs have some great starters. I'll be back with more information once I have had time to do more within this area.

Ingen kommentarer:

Legg inn en kommentar