What are we talking about today
Choices: Design, Patterns and what nots
Dive into Mirror API
Making Mirror do more with App Engine
The GDK!
Glass: not your average screen
You should be designing for Glass
Direct ports = not a great choice
Don't get in the way
Supplement the users life, don't take it over
Stay contextual and relevent
Info at the right place, right time
Avoid unexpected actions
Be aware otherwise your user will turn you off
Build for people
The robots are okay, they're not interested in Glass
Design has never been more important
We need some building blocks
- UI elements: static cards, live cards, immersion
- Invocation: "OK Glass", voice menu, touch menu, contextual voice
The actual "doing" part
- Focus on the action, not the agent
- Cut down the time between intent and action
- Easy to say
Understanding the patterns
Periodic
Ongoing task
Immersion
Periodic
- Insert timeline cards into the timeline without invocation
- Good case for notifications from an remote service
- Most common case for Mirror API
From Google's Glass Design Docs - Periodic
Ongoing task
- Long running live cards
- User can leave/return as needed
- Only the GDK
From Google's Glass Design Docs - Ongoing
Immersion
- Take over the timeline experience
- Users leave by swiping down
- Only the GDK (Android activities)
From Google's Glass Design Docs - Ongoing
Tools that rock: Glassware Flow Designer
Easiser way to design your app flow
What if my app doesn't fit the pattern?
- Consider your end goal for your application
- GDK: real-time, offline, access to the hardware
- Mirror API: platform indepent, common infratructure, use built-in functionality
- Hybrid approaches: Mirror card adds menu item that invokes intent to start
There is no one answer
- OH: "Java is the only way to write for Glass".
- OH: "Mirror API isn't powerful".
Both statements above = NOT TRUE
Decide for yourself, don't let others decide for you.
Only you know your goal and spec.
Jumping in to the Mirror API
- You're platform independent! You can choose your language and platform.
- You can make raw requests or for simplier access utilize the Google APIs Client Libs for your language
OAuth
- Create your Client ID for you app under APIs & auth > Credentials
- You can set the Redirect URI's to wherever your project will be dev/deployed
Great! Let's fire something up!
- Doing the sign in - Google+ Sign In button
- Button implements JavaScript hooks in client side that call our server
connectServer: function() {
console.log(this.authResult.code);
$.ajax({
type: 'POST',
url: "//" + window.location.host + '/connect?state=' + this.state,
contentType: 'application/octet-stream; charset=utf-8',
success: function(result) {
console.log(result);
},
processData: false,
data: this.authResult.code
});
}
Server handles the tokens
try:
user_credentials = _oauth_flow().step2_exchange(request.data)
except FlowExchangeError:
response = make_response(json.dumps('Failed to upgrade the authorization code.'), 401)
response.headers['Content-Type'] = 'application/json'
return response
# Set our session
session['credentials'] = user_credentials
session['user_id'] = user_credentials.id_token['sub']
Once we have tokens we get into Mirror API
- Timeline
- Menu
- Subscriptions
I thought the Mirror API had more?
Yes, it does. But let's dive into the basics first.
The timeline and you: Insert a card
- Timeline consists of cards or various types and styles
- Inserting a welcome card with a user auth'ed Mirror service
mirror_service = _authorized_mirror_service(user_credentials)
mirror_service.timeline().insert(body={
'notification': {'level': 'DEFAULT'},
'text': 'Welcome to Glass! This is my very first timeline card insert.'
).execute()
Timeline cards have lots of options
mirror_service.timeline().insert(body={
'notification': {'level': 'DEFAULT'},
'html': '<article><section><p class="text-auto-size">Welcome to
<em class="yellow">Glass!</em> This is my very first timeline card insert.</p>
</section></article>'
).execute()
Timeline cards: more options!
mirror_service.timeline().insert(body={
'notification': {'level': 'DEFAULT'},
'location': {
'latitude': 37.7692,
'longitude': 120.8569,
"displayName": "Season 6 Dreams Meetup",
"address": "Group Study Room F, Greendale Community College"
}
).execute()
Timeline cards: more options!
mirror_service.timeline().insert(body={
'notification': {'level': 'DEFAULT'},
'text': 'Video time!',
'payload': 'http://mymystical.url/video.mp4',
'menuItems': [{'action': 'PLAY_VIDEO'}]
).execute()
Was that a Menu Item in that last example?
- Menu options give uses the ability to act on a timeline card
- Lots of built in options: REPLY, DELETE, READ_ALOUD, NAVIGATE, OPEN_URI and many more
- Can set custom menu options with CUSTOM that callback to a subscription
A basic MenuItem example
mirror_service.timeline().insert(body={
'notification': {'level': 'DEFAULT'},
'text': 'Timeline card with a couple menu options.',
'menuItems': [
{'action': 'CUSTOM', 'id': 'my-id', 'values': [ { 'displayName': "My Menu Item" } ] },
{'action': 'DELETE'}]
}).execute()
From Menu Actions to Subscriptions
- When a user acts on a menu item, we can listen for those action via a Subscription
- We set a callbackUrl that allows our application to receive a POST notification from the Mirror API.
A sample subscription for timeline
mirror_service.subscriptions().insert(body={
'collection': 'timeline',
'userToken': user_id,
'verifyToken': 'something_only_you_know',
'callbackUrl': 'https://my-project-id.appspot.com/myGlassCallback',
'operation': ['UPDATE']
}).execute()
Breaking it down
- collection: timeline or location
- userToken: something to id your user
- verifyToken: something only you know to verify notification
- operation: UPDATE, INSERT, DELETE
Then there's callbackUrl
- In dev, you can use the SSL proxy
- In prod, you have to use an SSL endpoint
- The problem: no one wants to deploy remote even with a SSL proxy
- Solution: ngrok
ngork for the win
- Allows you to create a reverse tunnel
- Gives you an nice web panel to see incoming requests
- Gives you an SSL endpoint
~ $ ngrok -authtoken YOURTOKEN TARGETPORT
~ $ ngrok 8080
Ngrok viewing Mirror callback data
The code change is tiny
mirror_service.subscriptions().insert(body={
'collection': 'timeline',
'userToken': user_id,
'verifyToken': 'something_only_you_know',
'callbackUrl': 'https://RANDOM.ngrok.com/myGlassCallback',
'operation': ['UPDATE']
}).execute()
That's the crash course
I must do more!
Making Mirror shine
- Mirror has a lot of power, we can send all kinds of sticky, relevent data
- But how do we do that?
- To the samples!
Request: How do you send a card daily?
- Daily sort of jobs generally require cron
- If we're on our own boxes, we could schedule a script in crontab
- If we're on App Engine, we can use App Engines Cron
A simple App Engine Cron job
@glassdailycard.route('/dailyjob')
def dailyjob():
today = datetime.date.today()
query_get_todays_card = CronCards.query(CronCards.date == today)
result_get_todays_card = query_get_todays_card.get()
# Do we have a card to send today?
if result_get_todays_card is not None:
timelinecard_body = {
'notification': {'level': 'DEFAULT'},
'text': result_get_todays_card.card,
'menuItems': [{'action': 'DELETE'}]
}
query_get_users = UserProperties.query()
for user in query_get_users.fetch():
user_credentials = _credentials_for_user(user.key.id()).get()
_authorized_mirror_service(user_credentials).timeline().insert(body=timelinecard_body).execute()
response = make_response("{}", 200)
response.headers['Content-Type'] = 'application/json'
return response
Request: How do you send a card random in future?
- Generally requires a work queue of some type
- If we're on our own boxes, we could use beanstalkd
- If we're on App Engine, we can use App Engines Push Task Queue
A simple App Engine Task Queue
@glasstaskfuture.route('/glassCallback', methods=['POST'])
def glassCallback():
"""Our callback when the use selects out CUSTOM menu option on the start card."""
notification = request.data
if random.choice([True, False]):
# we're going to send it along to the task queue
google.appengine.api.taskqueue.add(
url='/taskHandler',
payload=notification,
countdown=60)
else:
now = datetime.datetime.now()
future = now + datetime.timedelta(0,60)
google.appengine.api.taskqueue.add(
url='/taskHandler',
payload=notification,
eta=future)
# It's cool Mirror API
#
# Remember, we must return 200 as quickly as we can!
#
return('OK')
glass-task-future
- Simple starter project that shows basics of using App Engine Task Queue with a subscription callback
- Uses Google+ Sign-In
- Repo: justinribeiro/glass-task-future
What about the GDK?
- Like developing for Android
- For when you need more access to Glass hardware
UPDATE: Nice things that happened this week
- Whoo hooo, notification sync with WearableExtender!
- Includes voice reply, pages, stacks...the works
- I'll talk more about this during Wear talk
The Live card
- When you need that long running context
- "OK Glass"...voice trigger
- Low-Freq updates to Android views
- High-Freq updates can use graphics framework (OpenGL)
Example: low frequency mqtt live card
Because we need to talk to Arduino people.
Of course that code is on Github
Push it: Glass to the web
- Lots of ways to do this
- I heart the MQTT - let's use a broker!
- Glass > Broker > WebSocket > Your Browser
A Glass like View
- UI Widgets: CardBuilder and CardScrollAdapter
- CardBuilder.Layout allows us to create Glass-like cards
- We can convert to an Android View or RemoteViews
- CardScrollAdapter gives us Glass-like timeline
Sensors oh my!
- Host of sensors for use: TYPE_ACCELEROMETER, TYPE_LIGHT, TYPE_ROTATION_VECTOR
- Touch Gesture > D-pad key events
- Location can be tricky: don't use LocationManager.getBestProvider()
Speaking of location...
LocationManager locationManager; // initialized elsewhere
// This example requests fine accuracy and requires altitude, but
// these criteria could be whatever you want.
Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_FINE);
criteria.setAltitudeRequired(true);
List<string> providers = locationManager.getProviders(
criteria, true /* enabledOnly */);
for (String provider : providers) {
locationManager.requestLocationUpdates(provider, minTime,
minDistance, listener);
}
</string>
With great GDK power, comes overheating
- If you push the hardware too hard you will get overheating messages
- Offload if you can, your battery is limited
- Just because you can, doesn't mean you should
Further reading / resources