Google GlassでLiveCardを実装する

こんにちはKID.Aです。

今回はLiveCardを使って、timelineに表示するアプリを作ってみます。
まず、実装の前にGoogle Glassの基礎動作やUIについて説明します。

基本動作

Google Glassを起動すると下記のホームが表示されます。

base
右スワイプで過去の情報、左スワイプで現在/未来の情報や設定画面に遷移できます。
これらの軸をtimelineと呼びます。

UI要素について

・Static card
テキスト、HTML、画像、ビデオなどを表示する。live cardsかimmersionsを実装できます。

base2
ホームからタップを押下して、static cardが表示されます。

・Live card
高頻度でレンダリングしたり、現時点で重要だったりするカードを表示します。
例えば、ストップウォチやカレンダーなどを作成する際に利用します。
pattern_livecard
動作は、上記のようにServiceを経由してLive cardを追加します。
pattern_livecard2
追加したLive cardはtimelineに残ります。また、下スワイプで終了させてもtimelineからは消えず、アプリは終了しません。Live cardを消したい場合はService経由で削除することができます。

・Immersion
timelineの上で体験できるAndroidのActivityを表示します。
Immersion
「Google Glassの簡単なアプリを作る」で作成したアプリもこちらになります。
下スワイプでアプリを終了させることができます。

参考URL
https://developers.google.com/glass/design/patterns

実装

それではLive cardを実装してみます。以下の手順を記載します。

  1. /res/layout/activity_main.xmlを作成
  2. これはLiveCardで表示するレイアウトになります。

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    
        <TextView
            android:id="@+id/textView1"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_alignParentLeft="true"
            android:layout_alignParentTop="true"
            android:gravity="center"
            android:text=""
            android:textSize="48dp" />
    
    </RelativeLayout>
    
  3. /res/values/strings.xmlに下記を追加します
  4. <string name="voice_trigger">LiveSample</string>
    <string name="action_finish">finish</string>
    
  5. /res/xml/voice_trigger.xmlを作成します
  6. <?xml version="1.0" encoding="utf-8"?>
    <trigger keyword="@string/voice_trigger" />
    
  7. /res/menu/main.xmlを作成します
  8. <menu xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        tools:context="com.example.glasslivecard.MainActivity" >
    
        <item
            android:id="@+id/action_finish"
            android:orderInCategory="100"
            android:showAsAction="never"
            android:title="@string/action_finish"/>
    
    </menu>
    
  9. MainAcitvity.javaを作成します
  10. これはLiveCardからタッチした際に表示するActivityです。
    中身はタッチするとServiceを終了する処理になっています。

    public class MainActivity extends Activity {
    
        @Override
        public void onAttachedToWindow() {
            super.onAttachedToWindow();
            openOptionsMenu();
        }
    
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            MenuInflater inflater = getMenuInflater();
            inflater.inflate(R.menu.main, menu);
            return true;
        }
    
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            switch (item.getItemId()) {
                case R.id.action_finish:
                    stopService(new Intent(this, MainService.class));
                    return true;
                default:
                    return super.onOptionsItemSelected(item);
            }
        }
    
        @Override
        public void onOptionsMenuClosed(Menu menu) {
            finish();
        }
    }
    
  11. MainSerivceを作ります
  12. こちらが今回のメインで、LiveCardを挿入するサービスになります。
    簡単にソースを説明すると、RemoteViews(別プロセス上のレイアウトを操作する場合に必要なView)を作成して、LiveCard#setViewsでレイアウトを設定しています。また、LiveCard#setActionでタッチパッドのタッチするとMainActivityが起動するように設定しています。
    LiveCard#publishは、actionとremote views(もしくはdirect rendering)を設定していれば、time lineにLive cardを表示できます。
    Serviceが終了するonDestoryの中で、LiveCardが表示されている場合にLiveCard#unpublishを使用して、time lineから非表示にしています。

    public class MainService extends Service {
    
    	@Override
        public IBinder onBind(Intent intent) {
    	    return null;
        }
    
        private static final String LIVE_CARD_TAG = "livecardSample";
    
        private LiveCard mLiveCard;
        private  RemoteViews remoteView;
        private final Handler mHandler = new Handler();
        private final UpdateLiveCardRunnable mUpdateLiveCardRunnable = new UpdateLiveCardRunnable();
        private static final long DELAY_MILLIS = 10 * 1000;
        private int count = 0;
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            if (mLiveCard == null) {
                mLiveCard = new LiveCard(this, LIVE_CARD_TAG);
    
        	    remoteView = new RemoteViews(getApplicationContext().getPackageName(), R.layout.activity_main);
                remoteView.setTextViewText(R.id.textView1, "" + count);
        	    mLiveCard.setViews(remoteView);
                Intent menuIntent = new Intent(this, MainActivity.class);
                menuIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
                mLiveCard.setAction(PendingIntent.getActivity(this, 0, menuIntent, 0));
                mLiveCard.publish(PublishMode.REVEAL);
                mHandler.postDelayed(mUpdateLiveCardRunnable, DELAY_MILLIS);
    
            } else {
                mLiveCard.navigate();
            }
    
            return START_STICKY;
        }
    
        private class UpdateLiveCardRunnable implements Runnable{
    
            private boolean mIsStopped = false;
    
            public void run(){
                if(!isStopped()){
                	count++;
                	remoteView = new RemoteViews(getApplicationContext().getPackageName(), R.layout.activity_main);
                    remoteView.setTextViewText(R.id.textView1, "" + count);
            	mLiveCard.setViews(remoteView);
                    mHandler.postDelayed(mUpdateLiveCardRunnable, DELAY_MILLIS);
                }
            }
    
            public boolean isStopped() {
                return mIsStopped;
            }
    
            public void setStop(boolean isStopped) {
                this.mIsStopped = isStopped;
            }
        }
    
        @Override
        public void onDestroy() {
            if (mLiveCard != null && mLiveCard.isPublished()) {
                mUpdateLiveCardRunnable.setStop(true);
                mLiveCard.unpublish();
                mLiveCard = null;
            }
            super.onDestroy();
        }
    
    }
    
  13. AndroidManifest.xmlの設定を記載します
  14. <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.glasslivecard"
        android:versionCode="1"
        android:versionName="1.0" >
    
        <uses-sdk
            android:minSdkVersion="19"
            android:targetSdkVersion="19" />
    
        <uses-permission android:name="com.google.android.glass.permission.DEVELOPMENT" />
    
        <application>
            <service
                android:name="com.example.glasslivecard.MainService"
                android:exported="true"
                android:label="@string/app_name" >
                <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" />
            </service>
    
            <activity android:name="com.example.glasslivecard.MainActivity" />
        </application>
    
    </manifest>
    

実際に動かしてみます

  1. 作成したアプリをインストールします
  2. アプリを起動します。(Livesampleをタッチします)
  3. device-2014-05-27-134748

  4. Live cardがtimelineに追加されます
  5. device-2014-05-27-134800
    device-2014-05-27-134844

  6. 10秒毎に数字がカウントアップしていきます
  7. base6

  8. タッチパッドをタッチすると、MainActivityが起動します
  9. device-2014-05-27-154915

  10. タッチするとServiceが終了して、timelineからLive cardが消えます

起動後、以下のようにLive cardがtimelineに追加されます。
run_livecard_home

LiveCardの実装と挙動について参考になれば幸いです。

KID.A

KID.A の紹介

楽して生きることと一発逆転を夢見ている、ちゃきちゃきのAndroiderです。 いろいろアプリを出しているのですが、いつもリリース後にターゲットユーザ数を2桁見誤っていたことに気付くので、残念でなりません。下方修正で、ヒットがでません。おしいです。 明日から本気出します。 よろしくお願いします。
カテゴリー: Android タグ: , , , パーマリンク

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です