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.
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.
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();
}
}
}
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>
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.)
Ingen kommentarer:
Legg inn en kommentar