Google GlassでTimeline上で動的にコンテンツを表示するLive Cardを使ったアプリケーションのつくりかたについてまとめました。
Live Cardとは
Google Glassのアプリケーション(glassware)には、大きな分類として以下の2つがあります。
1. TimelineにCardとして表示するアプリケーション(Live Card)
2. Timelineとは関係なく実行するアプリケーション(Immersion)
ここでは、これらのうちより基本的なアプリケーションであるLive Cardアプリケーションを作ってみることにします。
なお、この記事で作成したLive Cardはgithubに公開しているので、スケルトンとして自由に使用してください。
https://github.com/blackaplysia/livecard
Live Cardアプリケーションの基本的な構造
まず、典型的なLive Cardアプリケーションは以下に図示するような構造をとります。
Google Glassを起動、あるいはグラスをかけることでスリープから戻ると、まず時刻と"ok glass"と表示された画面が表示されます。ここから左右にスワイプする(操作としてはglassの右のフレームを指で前後になぞる)ことでさまざまな画面が表示されますが、このそれぞれの画面をcard、全体の流れをtimelineと呼びます。Timelineは最近更新されたものほど左に表示され、もっとも左にはシステム設定があります。
Live Cardは、このtimeline上のcardにコンテンツを表示するglasswareです。それでは、このglasswareがどのような画面遷移になるのかを説明しましょう。
まず、"ok glass"のcardで"ok glass"と呼びかけると、main voice menuが表示されます。これはインストールされているglasswareのリストです。このリストの中から、該当する表示どおりに"google"、"take a picture"などと呼びかけると、該当するglasswareが呼び出されます。このしくみがvoice triggerです。なお、画面遷移は必ずしも音声を使わずとも、タップとスワイプだけで遷移することができますが、voice triggerを登録していなければリストに表示されず、したがって、glassの操作によって起動することができません。
Live Cardはパッケージが任意のタイミングでコンテンツを更新するため、大抵の場合、activityではなくserviceとして実装します。Serviceからcardを更新する方法は頻度によって2つの方法が推奨されています。1つは更新頻度が高くない場合で、RemoteView
を使います。通常のandroid端末でnotificationを更新する場合と同様の方法です。いまひとつはDirectRenderingCallback
(OpenGLの場合はGlRenderer
)を実装する方法です。今回はRemoteView
を使用します。
また、glasswareは通常cardをタップするとオプションメニューを表示します。そこで、そのためのmenu activityが必要となります。Menu activityの実装には、glassware特有のことは何もありません。
Androidコマンドによる前準備
今回は、glasswareの特徴を明確にするために、普通のandroidアプリケーションのテンプレートを変更していくことにします。
まず、Android SDKのandroidコマンドでプロジェクトを生成します。
$ mkdir livecard
$ cd livecard
$ android -v create project -n livecard -p . -t "Google Inc.:Glass Development Kit Preview:19" -k com.blackaplysia.livecard -a LiveCardMenuActivity
以下のようなファイル群が生成されます。
$ tree .
----------------------------------------------------------------
.
├── AndroidManifest.xml
├── ant.properties
├── bin
├── build.xml
├── libs
├── local.properties
├── proguard-project.txt
├── project.properties
├── res
│ ├── drawable-hdpi
│ │ └── ic_launcher.png
│ ├── drawable-ldpi
│ │ └── ic_launcher.png
│ ├── drawable-mdpi
│ │ └── ic_launcher.png
│ ├── drawable-xhdpi
│ │ └── ic_launcher.png
│ ├── layout
│ │ └── main.xml
│ └── values
│ └── strings.xml
└── src
└── com
└── blackaplysia
└── livecard
└── LiveCardMenuActivity.java
13 directories, 13 files
----------------------------------------------------------------
Goole Glassは今のところ1種類しかありませんので、さまざまな端末用のdrawableリソースは不要です。1つを汎用のディレクトリ res/drawable に移動し、それ以外を削除します。
$ mv res/drawable-hdpi/ res/drawable
$ rm -r res/drawable-xhdpi
$ rm -r res/drawable-mdpi
$ rm -r res/drawable-ldpi
次に、リリースのためのキーストアを生成します。キーストアとエイリアス名は ant.properties に追記しておきましょう。
$ keytool -genkeypair -v -keystore ../.keystore/livecard_keystore -storepass pass -dname "CN=blackaplysia, OU=Unknown, O=blackaplysia, L=shinjuku, ST=tokyo, C=jp" -alias livecard -keypass pass -keyalg RSA -validity 10000
$ echo "key.store=../.keystore/livecard_keystore" >> ant.properties
$ echo "key.alias=livecard" >> ant.properties
Glasswareの実装
ここまでは、通常のandroidアプリケーションととくに違いがありませんでした。ここからは、glasswareの実装をしていきます。
レイアウト
まず最初にレイアウト( res/layout/main.xml )を変更します。今回の例はスケルトンですので、layoutは最低限の修正のみ施しています。Androidコマンドが生成したファイルとの差異を表示してみましょう。
diff --context ../original/res/layout/main.xml res/layout/main.xml
*** ../original/res/layout/main.xml 2014-05-30 08:43:14.097590445 +0900
--- res/layout/main.xml 2014-06-03 09:02:40.079083868 +0900
***************
*** 3,13 ****
--- 3,16 ----
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
+ android:background="@color/black"
>
<TextView
+ android:id="@+id/contents"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Hello World, LiveCardMenuActivity"
+ style="@style/CardText"
/>
</LinearLayout>
TextView
を更新するためのidを設定したほか、背景色を@color/black
に、テキストのスタイルを@style/CardText
にしています。(それぞれ、 res/values/colors.xml 、 res/values/styles.xml に定義)
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#ff000000</color>
<color name="white">#ffffffff</color>
</resources>
<style name="CardText">
<item name="android:textColor">@color/white</item>
<item name="android:textSize">54px</item>
</style>
Service
次にserviceを実装します。Serviceの実装は、起動時処理、終了時処理、更新処理の3点です。
onStartCommand()
Cardに対応するRemoteView
を準備し、setViews()
でcardに紐付けます。同様に、menu activityはPendingIntent
の対象としてsetAction()
で紐付けます。最後に、このService自体をattach()
でcardに紐付け、publish()
します。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(getServiceTag(), "LiveCard Service started");
if (_card == null) {
_card = new LiveCard(this, CARD_TAG);
_views = new RemoteViews(getPackageName(), R.layout.main);
_card.setViews(_views);
Intent menuIntent = new Intent(this, LiveCardMenuActivity.class);
menuIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
_card.setAction(PendingIntent.getActivity(this, 0, menuIntent, 0));
_card.attach(this);
_card.publish(PublishMode.REVEAL);
update();
} else {
_card.navigate();
}
return START_STICKY;
}
onDestroy()
onDestroy()
では、cardをunpublish()
します。
@Override
public void onDestroy() {
if (_card != null && _card.isPublished()) {
_card.unpublish();
_card = null;
}
super.onDestroy();
}
コンテンツの更新
任意のタイミングでRemoteView
に対してコンテンツの更新を行い、setViews()
でcardに反映します。今回の例では、menu activityから呼び出すためのupdate()
というメソッドを定義しています。LiveCardServiceContents
は、"Contents #<呼び出された回数>"という文字列を返すだけの小さなクラスです。
public void update() {
_views.setTextViewText(R.id.contents, LiveCardServiceContents.get());
_card.setViews(_views);
}
AndroidManifest.xml
Androidコマンドが生成した AndroidManifest.xml にはserviceに関する記述がありませんので、これを追加しておく必要があります。
<service android:name="LiveCardService"
android:label="@string/app_name"
android:exported="true">
... Voice triggerに関する記述(後述) ...
</service>
Menu activity
前述したように、menu activityの実装にはglassware特有のことは一切ありません。Android一般と同様のoption menuを実装します。今回の例では、service側でコンテンツを更新するために、IBinder
を経由して直接LiveCardService
のインスタンスを取得し、後でメソッドを呼び出す方法をとっています。
Menu activityには特徴的なことはありませんが、メニューアイテムの見た目に関しては、何も指定しないとこれも通常のandroidアプリによく似た見た目になってしまうため、 AndroidManifest.xml でテーマを指定します。この例では、デフォルトのテーマであるTheme.DeviceDefault
を少し変更したテーマMenuTheme
を定義して利用しています。テーマの実体は res/values/style.xml に記述します。
<activity android:name="LiveCardMenuActivity"
android:label="@string/app_name"
android:theme="@style/MenuTheme"
android:exported="true" />
<style name="MenuTheme" parent="@android:style/Theme.DeviceDefault">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowAnimationStyle">@null</item>
</style>
Voice trigger
最後に、voice triggerを実装します。これは音声認識を活用するわりには非常に簡単で、AndroidManifest.xmlでpermissionおよびintent-filterの設定を行い、triggerの対象となる文字列リソースを res/xml/ に配置するだけです。
<manifest>
...
<uses-permission android:name="com.google.android.glass.permission.DEVELOPMENT" />
...
<application>
<service>
...
<intent-filter>
<action android:name="com.google.android.glass.action.VOICE_TRIGGER" />
</intent-filter>
<meta-data
android:name="com.google.android.glass.VoiceTrigger"
android:resource="@xml/voice_trigger_start"
/>
...
</service>
</application>
</manifest>
<?xml version="1.0" encoding="utf-8"?>
<trigger keyword="@string/voice_trigger_start" />
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="voice_trigger_start">Card Sample</string>
</resources>
なお、permissionについては、 https://developers.google.com/glass/develop/gdk/starting-glassware に以下のように記述されています。(XE16~XE17で確認)
If you want to use voice commands that are not available in VoiceTriggers.Command, you can request an Android permission to do so.
Note: This feature is for development purposes only, so you cannot launch your Glassware in MyGlass until you submit a new command for approval. Do this as soon as possible, before you finish your Glassware. It takes time for us to approve and build a model for it).
つまり、正式にはGoogleに依頼してVoiceTriggers.Command
に登録してもらう必要があり、それを行わなければ正式なglasswareとしてローンチすることができない、ということです。
実行結果
それでは実行してみます。
$ ant release
$ adb install -r bin/livecard-release.apk
まず、voice triggerは動いているでしょうか。
最後にインストールしたものは先頭に出るようです。
一方、"ok glass"をタップした場合は次のような選択画面が表示されます。
アイコンは res/drawable/ic_launcher.png です。
この画面をタップ、またはvoice triggerでサービスを呼び出すと、cardが表示されます。
次に、このcardをタップしてメニューを呼び出してみましょう。
コンテンツが透過されています。
ここでタップするとコンテンツが更新されます。
コンテンツが表示されているときに左右にスワイプすると、このcardがtimeline上にあることを確認できます。
最後に、メニューでupdateから右にスワイプするとexitを選択することができます。
これをタップするとcardが削除されます。Timelineをスワイプしてももはやcardは出てきません。