LoginSignup
1
1

More than 5 years have passed since last update.

Glassware (Live Card)のスケルトン

Last updated at Posted at 2014-05-30

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アプリケーションは以下に図示するような構造をとります。

livecard-summary.png

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.xmlres/values/styles.xml に定義)

res/values/colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
  <color name="black">#ff000000</color>
  <color name="white">#ffffffff</color>
</resources>
res/values/styles.xml
  <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()します。

LiveCardService.java
    @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()します。

LiveCardService.java
    @Override
    public void onDestroy() {
        if (_card != null && _card.isPublished()) {
            _card.unpublish();
            _card = null;
        }
        super.onDestroy();
    }

コンテンツの更新

任意のタイミングでRemoteViewに対してコンテンツの更新を行い、setViews()でcardに反映します。今回の例では、menu activityから呼び出すためのupdate()というメソッドを定義しています。LiveCardServiceContentsは、"Contents #<呼び出された回数>"という文字列を返すだけの小さなクラスです。

LiveCardService.java
    public void update() {
        _views.setTextViewText(R.id.contents, LiveCardServiceContents.get());
        _card.setViews(_views);
    }

AndroidManifest.xml

Androidコマンドが生成した AndroidManifest.xml にはserviceに関する記述がありませんので、これを追加しておく必要があります。

AndroidManifest.xml
        <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 に記述します。

AndroidManifest.xml
        <activity android:name="LiveCardMenuActivity"
                  android:label="@string/app_name"
                  android:theme="@style/MenuTheme"
                  android:exported="true" />
res/values/styles.xml
  <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/ に配置するだけです。

AndroidManifest.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>
res/xml/voice_trigger_start.xml
<?xml version="1.0" encoding="utf-8"?>
<trigger keyword="@string/voice_trigger_start" />
res/values/strings.xml
<?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は動いているでしょうか。
voice_menu.png
最後にインストールしたものは先頭に出るようです。

一方、"ok glass"をタップした場合は次のような選択画面が表示されます。
voice_trigger_start.png
アイコンは res/drawable/ic_launcher.png です。

この画面をタップ、またはvoice triggerでサービスを呼び出すと、cardが表示されます。
livecard.png

次に、このcardをタップしてメニューを呼び出してみましょう。
menu_update.png

コンテンツが透過されています。
ここでタップするとコンテンツが更新されます。

livecard_updated.png

コンテンツが表示されているときに左右にスワイプすると、このcardがtimeline上にあることを確認できます。

最後に、メニューでupdateから右にスワイプするとexitを選択することができます。
menu_exit.png

これをタップするとcardが削除されます。Timelineをスワイプしてももはやcardは出てきません。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1