Google Glassでポモドーロテクニック (3/3)
吉村(@alterakey)
こんにちは、ATLでウェアラブルデバイスの研究をしている吉村です。今回は前回に引き続き、Androidで組み立てたプロトタイプをGoogle Glass実機へポーティングします。
エントリポイントの作成
これまでActivityを使用していたエントリポイントをServiceへ移動します。
Activityを削除し、Serviceから直接LiveCardの挿入/削除を行なうようにします。
src/main/java/com/gmail/altakey/myapplication/TimerService.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
private TimerCard mLiveCard; @Override public void onCreate() { ... mLiveCard = new TimerCard(this, TAG); mLiveCard.attach(this); mLiveCard.setAction(PendingIntent.getActivity(this, 1, new Intent(this, TimerMenuActivity.class), 0)); mLiveCard.publish(LiveCard.PublishMode.REVEAL); ... } @Override public void onDestroy() { ... mLiveCard.unpublish(); mLiveCard = null; ... } |
今度はLiveCardで表示するようになるのでActivityへBroadcastを飛ばしていたところをTimerCardの直接操作へ書き換えます。
1 2 3 |
private void updateView() { mLiveCard.setRemaining(getRemaining(getDueMillis())); } |
最後にTimerServiceをAndroidManifest.xmlへ登録します。
src/main/AndroidManifest.xml:
1 2 3 |
<application android:label="@string/app_name" android:icon="@drawable/ic_launcher"> <service android:name=".service.TimerService" /> </application> |
タイムラインへの表示
先程さっさと使ってしまいましたがTimerViewで表示していたところをLiveCard化し、TimerCardとして切り出しておきましょう。
src/main/java/com/gmail/altakey/myapplication/TimerCard.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class TimerCard extends LiveCard { private RemoteViews mViews; public TimerCard(Context context, String tag) { super(context, tag); init(context); } private void init(final Context context) { mViews = new RemoteViews(context.getPackageName(), R.layout.card_timer); setViews(mViews); } public void setRemaining(final long remainingMillis) { final TimerReader reader = new TimerReader(remainingMillis); mViews.setTextViewText(R.id.min, String.format("%02d", reader.minutes)); mViews.setTextViewText(R.id.sec, String.format("%02d", reader.seconds)); setViews(mViews); } } |
どうですか?TimerCardの実装とTimerViewの実装、似ていませんか?
TimerViewは削除します。
メニューの作成
Google Glassの場合、メニューを呼び出した際に下のActivityの動作を停止する必要があります。このようなことをするために、透明Activityを間に一枚入れてそこからオプションメニューを呼び出してやります。オプションメニューで良いんです。あとはGlassが標準の形で出してくれます。
src/main/java/com/gmail/altakey/myapplication/TimerMenuActivity.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
public class TimerMenuActivity extends Activity { @Override public void onAttachedToWindow() { super.onAttachedToWindow(); openOptionsMenu(); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.timer, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.reset_menu_item: final Intent intent = new Intent(this, TimerService.class); intent.setAction(TimerService.ACTION_RESET); startService(intent); break; case R.id.exit_menu_item: stopService(new Intent(this, TimerService.class)); break; } return super.onOptionsItemSelected(item); } @Override public void onOptionsMenuClosed(Menu menu) { super.onOptionsMenuClosed(menu); finish(); } } |
こちらの定義もAndroidManifest.xmlへ加えておきましょう。
src/main/AndroidManifest.xml:
1 2 3 4 |
<application android:label="@string/app_name" android:icon="@drawable/ic_launcher"> ... <activity android:name=".activity.TimerMenuActivity" android:theme="@style/Theme.Menu" /> </application> |
スタイルの定義はこんな感じに。
src/main/res/values/styles.xml:
1 2 3 4 5 6 7 8 9 |
<?xml version="1.0" encoding="utf-8"?> <resources> <style name="Theme.Menu" 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> </resources> |
ボイスコマンドの指定
あとはVoiceTrigger系のものをTimerServiceでハンドリングします。ここでは「START A WORKOUT」を使用します。
src/main/java/com/gmail/altakey/myapplication/TimerService.java:
1 2 3 4 5 6 7 8 9 10 |
@Override public int onStartCommand(Intent intent, int flags, int startId) { ... if (ACTION_START.equals(action)) { ... } else if (VoiceTriggers.ACTION_VOICE_TRIGGER.equals(action)) { start(); } ... } |
voice.xmlにも指定します。
src/main/res/xml/voice.xml:
1 2 |
<?xml version="1.0"?> <trigger command="START_A_WORKOUT" /> |
AndroidManifest.xmlにも指定します。
src/main/AndroidManifest.xml:
1 2 3 4 5 6 7 8 9 |
<application android:label="@string/app_name" android:icon="@drawable/ic_launcher"> <service android:name=".service.TimerService"> <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" /> </service> ... </application> |
音の調整
GlassのMUSICチャンネルはヘッドフォン端子に向いているためそのままでは音が出ません。これでは困るので、NOTIFICATIONチャンネル(骨伝導トランスデューサ)を使うようにします。人に聞こえにくいという特徴を持つ骨伝導ですが、周波数が高いとかなり音漏れを起こします。クリック音が漏れると非常に耳障りなのでこちらの再生レートも落としてしまいましょう。カチッ、ではなくゴリッ、とした音になりますが格段に音漏れを低減することができます。
src/main/java/com/gmail/altakey/myapplication/TimerService.java:
1 2 3 4 5 6 7 8 9 |
private static class Ticker { ... public void prepare() { if (mPool == null) { mPool = new SoundPool(2, AudioManager.STREAM_NOTIFICATION, 0); ... public void tick() { if (mPool != null) { mPool.play(mSoundTick, 1.0f, 1.0f, 0, 0, 0.2f); |
完成!
ここまで来たらビルドして実機でテストしてみましょう。「Ok glass, start a workout」と言うとGlasswareが立ち上がりカウントダウンを始めたでしょうか。タップするとカウントダウンが一時停止してメニューが開いたでしょうか。そこからExitで終了できるでしょうか。
まとめ
以上、長かったですが簡単なポモドーロタイマーを作成してみました。環境構築から普通のAndroidでプロトタイピングして最後に一気にポーティングという進め方をして来ましたが、この方法はGlassが手元にない場合や触れる時間が少ない場合などにきっと役に立つのではないかと思います。ではまた。