Google Glassで作る近未来アプリケーション(2)

Google Glassで作る近未来アプリケーション(2)

GDKで開発できるGoogle Glassアプリの機能とは?

2014年3月5日

GDKを使うとGoogle Glassの機能をフルに活用したアプリを開発できる。3種類のUI(Static Card/Live Card/Immersion)/タッチジェスチャー/音声認識/位置情報/センサー/カメラなど、GDKの全体像を、コードを示しながら解説する。

Yahoo, inc 堀川 隆弘(poly.hatenablog.com
  • このエントリーをはてなブックマークに追加

はじめに

 Google Glassの機能や特徴を紹介した前回に引き続き、今回はGDKを利用した Google Glassアプリケーション(以下、Glassware)の開発方法について具体的に解説する。前回解説した通り、Glasswareを開発するにはMirror APIとGlass Development Kit(以下、GDK)の2つの方法があるが、現在開発者が好んで使っているのはGDKであり、本稿でもGDKに絞って解説する。

GDK概要

 GDKは前回述べた通りAndroid SDKの拡張で、Android SDKにカードUIやタッチジェスチャー、ボイスコマンドなどのGlass特有の機能を追加したものである。ActivityやServiceなど、Android SDKの主要なコンポーネントはそのまま使用できる。

 以下GDK特有の機能について概要を解説する。

User Interface

 GlasswareのUIを実装する方法は「Static Card」「Live Card」「Immersion」の3種類がある。以下の表にそれぞれの特徴をまとめる。

種類タイムライン上に表示されるかユーザー入力へのアクセスUIの自由度主な用途
Static Card 表示される アクセス不可 Cardのレイアウト(後述)に沿った形で実装する必要がある 静的コンテンツの表示
Live Card 表示される 制約があるがアクセス可能 制限なし ユーザーインタラクションの少ない動的コンテンツ
Immersion 表示されない 制限なくアクセス可能 制限なし ユーザーインタラクションの多い動的コンテンツ
表1 GlasswareでのUI実装方法一覧

 それぞれについて具体的に説明していこう。

Static Card

 Static Cardは、タイムライン上に表示される静的な情報を表示するカードである。次のコードに示すように、Cardクラスでカードのコンテンツを設定し、TimelineManagerクラスのinsertやupdateなどのメソッドを利用して、タイムラインへの挿入や更新を行う。

Java
TimelineManager timelineManager = TimelineManager.from(this);
Card card = new Card(context);
card.setText("Hello World!");
long timelineId = timelineManager.insert(card);
Static Cardの挿入

 カードのレイアウトは設定されたコンテンツに応じて、以下のように自動的にレイアウトされる。このデザインはStatic Cardに限らずGlassでは標準的なものなので、Cardクラスを使わずに自力で描画を行う際も、できる限りこのレイアウトに合わせた方がよいだろう。

図1 カードのレイアウト
  • 1 Card.setTextメソッドで設定されたテキストが560×240pxの赤い部分に表示される。文字列長によって、文字サイズは自動調整される。
  • 2 Card.setFooterメソッドで設定されたフッターが560×40pxの青い部分に表示される。
  • 3 全体サイズは640×360px。Card.addImageメソッドで画像が設定されており、かつ「Card.setImageLayout(Card.ImageLayout.FULL)」が指定されている場合は、この領域にフルで表示される。
  • 4 Card.addImageメソッドで画像が設定されており、かつ「Card.setImageLayout(Card.ImageLayout.LEFT)」が指定されている場合は、この紫色で囲まれた領域に、テキストではなく画像が表示される。
Live Card

 Live Cardはタイムライン上に表示され、動的に内容が更新されるカードである。Live Cardを作るには、バックグラウンドで動作するServiceが必要だ。図2はタイムラインとServiceの関係を図解したものである。音声コマンドで発行されるIntentによって、アプリのServiceが起動される。Serviceは、バックグランドに常駐し、必要に応じてカードの更新を行う。

タイムラインとLive Card Serviceの関係
図2 タイムラインとLive Card Serviceの関係

 Live Cardはセンサーなどのハードウェアには制限なくアクセスでき、またUIもさまざまなViewコンポーネントを組み合わせて自由に描画できる。ただし、ユーザー入力の取得には制限があり、例えばスワイプなどのイベントは取得できない(画面がタイムライン上に存在するため、スワイプ操作はタイムライン上の移動処理が優先される)。

 グーグルがGitHubに公開しているGDKサンプルのほとんどが、このLive Cardを利用したアプリだ。実際の利用例については、後でコンパスアプリのソースを基に解説する。

Immersion

 「Immersion」というのは日本語に訳すと「没入」という意味になり、その名が示す通り、「Glassのタイムラインで構成された世界をいったん離れ、別のアプリの中に浸ること」を意味する。AndroidのActivityで実装し、従来のAndroidアプリの開発と実装方法はほぼ同じである。ユーザー入力の取得は、タッチジェスチャーを含めて制限なくアクセスでき、UIも自由に組み立てることもできる。またセンサーやカメラなどのハードウェアもフル活用でき、実装の自由度は最も高い。

タッチジェスチャー

 GDKを通じて、タップやスワイプ・スクロールなど、Glassのタッチパッドでよく使われるジェスチャーを検知できる。タッチジェスチャーの検知には、GestureDetectorクラスが使われる。ちなみにAndroid SDKにも同名のクラスが含まれるが、Glass用のGestureDetectorクラスは属するパッケージが異なる。

GestureDetectorの登録

 GestureDetectorを利用するには、Activity単位(=画面全体)でジェスチャーを検知する方法と、View単位(=画面の一部)で検知する方法の2種類があるが、ここではActivity単位で検知する方法について解説する。

 ジェスチャーを検知するためには、次のコードに示す通り、まずGestureDetectorクラスのオブジェクトを作成し、ジェスチャー発生時のイベントを受け取るためにGestureDetector.BaseListenerインターフェースの実装を登録する。ActivityのonGenericMotionEventメソッドをオーバーライドして、GestureDetector.onMotionEventメソッドにMotionEventオブジェクトを渡せば、ジェスチャー発生時にBaseListener.onGestureメソッドが呼び出される。以下のサンプルコードでは、1本指タップと2本指タップ、左右のスワイプを検知しているが、それに加えて表2に挙げたジェスチャーイベントを受け取ることが可能である。また、GestureDetector.BaseListener以外にも、GestureDetector.FingerListenerインターフェースの実装で指本数の変化、GestureDetector.ScrollListenerインターフェースの実装でスクロール移動量や速度を取得できる。

Java
public class MainActivity extends Activity {
  private GestureDetector mGestureDetector;
  // ……省略……
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    // ……省略……
    mGestureDetector = createGestureDetector(this);
  }

  private GestureDetector createGestureDetector(Context context) {
    GestureDetector gestureDetector = new GestureDetector(context);
    gestureDetector.setBaseListener( new GestureDetector.BaseListener() {
      @Override
      public boolean onGesture(Gesture gesture) {
        if (gesture == Gesture.TAP) {
          // 1本指タップ時の動作
          return true;
        } else if (gesture == Gesture.TWO_TAP) {
          // 2本指タップ時の動作
          return true;
        } else if (gesture == Gesture.SWIPE_RIGHT) {
          // 右スワイプ時の動作
          return true;
        } else if (gesture == Gesture.SWIPE_LEFT) {
          // 左スワイプ時の動作
          return true;
        }
        return false;
      }
    });
    gestureDetector.setFingerListener(new GestureDetector.FingerListener() {
      @Override
      public void onFingerCountChanged(int previousCount, int currentCount) {
        // 指の本数が変化した際の処理
      }
    });
    gestureDetector.setScrollListener(new GestureDetector.ScrollListener() {
      @Override
      public boolean onScroll(float displacement, float delta, float velocity) {
        // スクロールが発生した際の処理
      }
    });
    return gestureDetector;
  }

  /*
   * Send generic motion events to the gesture detector
   */
  @Override
  public boolean onGenericMotionEvent(MotionEvent event) {
    if (mGestureDetector != null) {
      return mGestureDetector.onMotionEvent(event);
    }
    return false;
  }
}
CompassRenderer.java内のLiveCard描画部分
定数名イベント内容
Gesture.TAP 1本指タップ
Gesture.SWIPE_DOWN 1本指下スワイプ
Gesture.SWIPE_LEFT 1本指左(後ろ方向)スワイプ
Gesture.SWIPE_RIGHT 1本指右(前方向)スワイプ
Gesture.SWIPE_UP 1本指上スワイプ
Gesture.LONG_PRESS 1本指長押し
Gesture.TWO_TAP 2本指タップ
Gesture.TWO_SWIPE_DOWN 2本指下スワイプ
Gesture.TWO_SWIPE_LEFT 2本指左スワイプ
Gesture.TWO_SWIPE_RIGHT 2本指右スワイプ
Gesture.TWO_SWIPE_UP 2本指上スワイプ
Gesture.TWO_LONG_PRESS 2本指長押し
Gesture.THREE_TAP 3本指タップ
Gesture.THREE_LONG_PRESS 3本指長押し
表2 GDKで取得できるジェスチャーイベント一覧

音声認識

 GDKを使って、Glasswareを起動するための音声コマンドを定義したり、ユーザーがしゃべった言葉をそのまま取得するための音声認識画面を起動したりできる。なお、現時点では音声認識に対応する言語は英語のみなので、以下の例でも英語の文章を例として解説する。

音声コマンドの定義

 Glasswareを起動する(「ok glass」に続く)音声コマンドを定義するためには、まずres/xmlフォルダー内に、XMLリソースファイルを用意する必要がある。ファイル名は自由だが、ここでは仮に「my_voice_trigger.xml」というファイルを作ったと仮定する。以下の例では「hello glass」という音声コマンドを定義している。

XML
<?xml version="1.0" encoding="utf-8"?>
<trigger keyword="hello glass" />
音声コマンドの定義

 以下のように<input>タグをXMLファイルに追加することで、音声コマンドの後にさらに音声認識を促し、しゃべったフレーズを取得できる。「ok glass, google ……検索語句……」の後に検索語句を認識させるのと同じ要領だ。

XML
<?xml version="1.0" encoding="utf-8"?>
<trigger keyword="hello glass">
  <input prompt="tell me what's on your mind" />
</trigger>
音声認識を追加で行う場合の音声コマンドの定義
アプリの起動

 音声コマンド認識後は、音声コマンド用のアクションがセットされたIntentが発行されるため、Intent FilterをAndroidManifest.xmlファイルに追加し、ActivityまたはServiceが起動するようにしておく。以下の例では、MainActivityクラスが音声コマンドに反応して起動する。

XML
<?xml version="1.0" encoding="utf-8"?>
<application ……省略……>
  <activity android:name=".MainActivity">
    <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/my_voice_trigger" />
  </activity>
  // ……省略……
</application>
音声コマンドでActivityを起動するためのAndroidManifest.xmlファイルの記述例
音声認識結果の取得

 音声コマンドの後に追加で音声認識を行った場合、IntentのExtraに認識結果が入っているので、ActivityやServiceで以下のように文字列が取得できる。

Java
ArrayList<String> voiceResults = getIntent().getExtras()
    .getStringArrayList(RecognizerIntent.EXTRA_RESULTS);
音声認識結果の取得
音声認識画面の起動

 ホーム画面で音声コマンドを使う以外に、GlasswareはRecognizerIntent.ACTION_RECOGNIZE_SPEECHアクションを指定してIntentを発行することで、好きなタイミングで音声認識の画面を呼び出せる。結果はActivity.onActivityResultメソッドをオーバーライドして受け取る。

Java
private static final int SPEECH_REQUEST = 0;

private void displaySpeechRecognizer() {
  Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
  startActivityForResult(intent, SPEECH_REQUEST);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  if (requestCode == SPEECH_REQUEST && resultCode == RESULT_OK) {
    List<String> results = data.getStringArrayListExtra(
        RecognizerIntent.EXTRA_RESULTS);
    // resultsに音声認識された結果が入っている
  }
  super.onActivityResult(requestCode, resultCode, data);
}
音声認識画面の起動

位置情報

 位置情報の取得に関しては、以下のコードに示すように、Android SDKに含まれるLocationManagerLocationProviderCriteriaなどのクラスを利用する。

 ただし、Glass特有の注意事項もある。前回解説したように、Glass自体はGPSチップを持っていない。位置情報のプロバイダーとしては、Glass自身のWi-Fi接続や、Bluetooth経由でスマートフォンのGPSなどのリモートプロバイダーを含め、複数のリソースが使われる可能性がある。そのためGlassでは、どれか1つの位置情報プロバイダーを使うのではなく、全ての使用可能なプロバイダーから位置情報を検出することを推奨している。

Java
LocationManager locationManager =
  (LocationManager) getSystemService(Context.LOCATION_SERVICE);

// この例ではCriteriaで正確性の高い位置情報のみを取得するように指定しているが、
// 必要に応じてどんな条件(Criteria)でも構わない。
Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_FINE);

// 有効な全ての位置情報プロバイダーに対してリスナーを登録する。
List<String> providers = locationManager.getProviders(criteria, true);
for (String provider : providers) {
  locationManager.requestLocationUpdates(provider, minTime, minDistance, listener);
}
位置情報を利用する例

センサー

 各種センサーは、Android SDKに含まれるSensorManagerクラスを通じてアクセスできる。以下が利用可能なセンサーの一覧である。

定数名イベント内容
TYPE_ACCELEROMETER 加速度センサー
TYPE_GRAVITY 重力センサー
TYPE_GYROSCOPE ジャイロスコープセンサー
TYPE_LIGHT 照度センサー
TYPE_LINEAR_ACCELERATION 線形加速度センサー
TYPE_MAGNETIC_FIELD 磁気センサー
TYPE_ORIENTATION 方向センサー(ただし利用は非推奨)
TYPE_ROTATION_VECTOR 回転ベクトルセンサー
表3 Glasswareで利用可能なセンサー一覧

カメラ

 Glassでカメラを活用するには、2種類のオプションがある。1つは、Glassデフォルトのカメラ撮影用ActivityをIntentで呼び出す方法、もう1つは、Android SDKのCamera APIを利用する方法だ。

 いずれの場合でも、Glassではいくつか注意が必要な点がある。まずデフォルトのActivityを利用する場合、以下のサンプルコードのようにMediaStore.ACTION_IMAGE_CAPTUREアクションのIntentでActivityを起動し、onActivityResultメソッドでその結果の画像を利用するという手順になるが、onActivityResultメソッドが呼ばれた時点で、画像ファイルがまだ作成されていない可能性がある。対応としては、onActivityResultメソッド内ではファイルシステムのObserverを登録し、実際のファイルの作成がObserverに通知された時点で、ファイルを利用した処理を行うことが推奨されている。少し複雑な処理になるので、実際には公式ドキュメントに記載されているソースコードを参考にしてほしい。

Java
private void takePicture() {
  Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
  startActivityForResult(intent, TAKE_PICTURE_REQUEST);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  if (requestCode == TAKE_PICTURE_REQUEST && resultCode == RESULT_OK) {
    String picturePath = data.getStringExtra(
        CameraManager.EXTRA_PICTURE_FILE_PATH);
    // picturePath変数に写真画像が保存されている。
    // ただし、ファイルがまだ作成されていない可能性があるので、ファイル作成を待つためのObserverを登録しておく。
  }

  super.onActivityResult(requestCode, resultCode, data);
}
デフォルトのカメラ撮影用Activityを利用する例

 また、Camera APIを利用する場合、カメラがロックされていてアクセスできない(=Camera.open()に失敗する)というケースがまれに発生する。これは、Glassのホームアプリ(=タイムラインを表示しているGlassware)がカメラを利用しており、音声コマンドなどで別のGlasswareを立ち上げると、ホームアプリがカメラの解放を完了するまでに若干の時間がかかることが原因である。Glasswareからカメラにアクセスする際は、起動してから数百ms程度のタイムラグを持たせるなどの工夫が必要だ。

まとめと次回

 GDKは、Glass特有のCardやタッチジェスチャー、音声コマンドなど、主にUI回りの機能をAndroid SDKの拡張として提供するものである。今回は、このGDKを使うと、Google Glassの3種類のUI(Static Card/Live Card/Immersion)/タッチジェスチャー/音声認識/位置情報/センサー/カメラなどの機能を実装できることを紹介した。

 次回は、実際のGDK開発の手順を解説する。また、サンプルアプリ(=コンパスアプリ)のソースコードを読み解きながら、よりGlassらしいアプリの実装方法について説明する。

※以下では、本稿の前後を合わせて5回分(第1回~第5回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。

1. Google Glassアプリケーション開発の基礎知識

Glassware(=Glassアプリ)開発を始めるなら、日本上陸前の今がチャンス。米国シリコンバレー在住の筆者が、現地ならではの視点でGlassware開発を解説する連載スタート。

2. 【現在、表示中】≫ GDKで開発できるGoogle Glassアプリの機能とは?

GDKを使うとGoogle Glassの機能をフルに活用したアプリを開発できる。3種類のUI(Static Card/Live Card/Immersion)/タッチジェスチャー/音声認識/位置情報/センサー/カメラなど、GDKの全体像を、コードを示しながら解説する。

3. 初めてのGoogle Glassアプリ開発(GDK編)

いよいよGlass開発を実践。GDKの開発環境の構築手順と、Glassware(=Glassアプリ)の作成/実行方法を説明。また、サンプルアプリのソースコードを読み解きながら、よりGlassらしいアプリの実装方法について説明する。

4. Google Glassで動くARアプリケーションの実装

Google Glassの「眼鏡型」という特性を生かしたAR(Augmented Reality: 拡張現実)アプリの開発を、サンプルソースを交えながら解説。

5. Google Glassでハンドジェスチャーを認識させてみよう

Google Glassの上で動くジェスチャーUIの実装方法を、サンプルコードを交えながら解説する。

イベント情報(メディアスポンサーです)

Twitterでつぶやこう!


Build Insider賛同企業・団体

Build Insiderは、以下の企業・団体の支援を受けて活動しています(募集概要)。

ゴールドレベル

  • グレープシティ株式会社
  • 日本マイクロソフト株式会社