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

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

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

2014年3月12日

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

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

GDK開発環境のセットアップ

 前回は、一通りGDKの概要を見てきたので、今回は実際にGDKの開発環境を作ってみよう。

 開発環境はAndroid SDKを使える環境なら何でも構わないが、本連載ではGlassware開発で一般的によく使われていると思われる統合開発環境(IDE)の「Eclipse」を使う。以下、ADT Bundle、またはADTプラグインがインストールされたEclipseを使うことを前提とする。

GDKのインストール

 GDKは、Android SDK Managerからインストールできる。Eclipseの(メニューバーの)[Window]メニューから[Android SDK Manager]を立ち上げてみよう。次の画面のようなダイアログが表示されるので、「Android 4.0.3 (API 15)」フォルダーの下にある[Glass Development Kit Sneak Peek]を選択してインストールする。

Android SDK Manger
図3 Android SDK Manger

Glasswareの作成と実行

 インストールが完了すると、EclipseでAndroidプロジェクトを作成する際に、GDKで作成できるようになる。メニューバーから[File]-[New]-[Android Application Project]で新規アプリケーション作成ウィザードを開き(次の画面)、[Compile With]欄で[Glass Development Kit]を選択してみよう。[Application Name]欄に適当な名前を入力し、あとはデフォルト状態のまま、[Next]ボタンを押し続け、最後に[Finish]ボタンを押して、プロジェクトを作成する。

図4 新規アプリケーション作成ウィザード

 プロジェクトが作成されたら、さっそくGlassで動かしてみよう。Eclipseのメニューバーから[Run]-[Run As]-[Android Application]で起動すると、以下のような画面がGlassに表示される。

Glass上での実行結果
図5 Glass上での実行結果

 見て分かる通り、このサンプルの見た目はAndroidアプリそのものである。Glassの標準的なUIとは全く異なるものになっているが、これはスマートフォン用のtheme(テーマ)がデフォルトで適用されているのが原因である。AndroidManifest.xmlファイルから<application>に適用されているtheme設定を削除すると、よりGlassっぽく表示されるようになる。

コンパスアプリのソースコードを読んでみよう

 よりGlassらしいアプリケーションの実装方法を理解するために、公式サンプルの1つであるコンパスアプリのソースコード(以下、ソース)を読んでみよう。ソース全体は https://github.com/googleglass/gdk-compass-sample からダウンロードできる。

コンパスアプリの概要

 コンパスアプリは、音声コマンド「Show a compass」で起動できる。起動すると、以下のように方角を示す画面が表示され、顔の向きによってリアルタイムで方角も変わっていく。

コンパスアプリスクリーンショット
図6 コンパスアプリのスクリーンショット

 タッチパッドをタップすると、メニューが表示され(次の画面)、「Read aloud」のメニューを選択すると、現在の方角を360°式の方位角で読み上げてくれる。

コンパスアプリのメニュー選択画面
図7 コンパスアプリのメニュー選択画面

 コンパスアプリは、起動するとLive Cardとしてタイムラインに挿入される。後でコンパスを使いたくなったら、タイムラインから左スワイプで瞬時にアクセスできる。

アプリケーションの構成

 ソースの全体構成を見てみよう。主要なのは、com.google.android.glass.sample.compassパッケージ直下の5つのクラスである。それぞれの役割は以下の通り。

クラス名役割
CompassService 音声コマンドのIntentを受けて起動され、Live Cardの挿入を行うServiceクラス
CompassRender 描画用スレッドを管理し、Live Cardの描画や更新を行う
CompassView コンパス画面を描画するための独自のViewクラス
OrientationManager 方角を得るために必要なセンサーと連携するクラス
CompassMenuActivity タップした際に表示されるメニュー画面を管理するActivityクラス
表4 コンパスアプリの主要クラス一覧

Live Cardの表示

 CompassServiceは、音声コマンドを受けて起動されるServiceである。AndroidManifest.xmlファイル内で以下のように、音声コマンドのIntentを受けるように設定されている。

XML
<service
  android:name="com.google.android.glass.sample.compass.CompassService"
  android:label="@string/app_name"
  android:icon="@drawable/ic_compass"
  android:enabled="true" >

  <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/compass_show" />
</service>
コンパスアプリのAndroidManifest.xmlファイル

 Intentが発行されると、CompassService.onStartCommand(Intent, int, int)メソッドが呼び出される。TimelineManager.createLiveCard(int)メソッドでLiveCardクラスのオブジェクトを作成し、LiveCard.publish(PublishMode)メソッドで、Live Cardを実際に発行している。publishメソッドの引数は、「PublishMode.REVEAL」と「PublishMode.SILENT」の2種類がある。コンパスアプリで利用されている「PublishMode.REVEAL」を指定した場合、作成されたLive Cardの画面に遷移するのに対し、「PublishMode.SILENT」の場合はバックグラウンドでタイムラインに挿入される。またLiveCard.setAction(PendingIntent)メソッドでは、カードがクリックされた際に発行するIntentを指定でき、コンパスアプリではCompassMenuActivityが起動するようになっている。

Java
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
  if (mLiveCard == null) {
    mLiveCard = mTimelineManager.createLiveCard(LIVE_CARD_ID);
    mRenderer = new CompassRenderer(this, mOrientationManager, mLandmarks);

    // Live Cardの描画をCompassRendererで行うため、
    // SurfaceHolderのイベントコールバックをCompassRendererが受け取る。
    mLiveCard.setDirectRenderingEnabled(true).getSurfaceHolder().addCallback(mRenderer);

    // Live Cardでタップされた際にCompassMenuActivityを立ち上げるための設定。
    Intent menuIntent = new Intent(this, CompassMenuActivity.class);
    menuIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
    mLiveCard.setAction(PendingIntent.getActivity(this, 0, menuIntent, 0));

    // Live Cardをタイムラインに挿入
    mLiveCard.publish(PublishMode.REVEAL);
  }

  return START_STICKY;
}
CompassService.javaファイル内のLive Card初期化部分

 また、LiveCardオブジェクトを作成する際に、CompassRendererクラスのオブジェクトをSurfaceHolderのコールバックとして指定している(SurfaceHolderはAndroid SDKに含まれ、Canvasを介した高速な描画をサポートするインターフェースである)。CompassRendererオブジェクトは、内部で描画用スレッドを作成し、検出された方位に応じて作成されたViewをCanvasに書き込んでいる(次のコード)。

Java
// 描画用スレッドから呼ばれる描画更新メソッド
private synchronized void repaint() {
  Canvas canvas = null;

  try {
    canvas = mHolder.lockCanvas();
  } catch (RuntimeException e) {
    Log.d(TAG, "lockCanvas failed", e);
  }

  if (canvas != null) {
    // mLayout(=CompassRenderが内部で保持するViewオブジェクト)をタイムラインのCanvasに書き込む。
    mLayout.draw(canvas);

    try {
      mHolder.unlockCanvasAndPost(canvas);
    } catch (RuntimeException e) {
      Log.d(TAG, "unlockCanvasAndPost failed", e);
    }
  }
}

……省略……

// 描画用スレッド
private class RenderThread extends Thread {
  ……省略……
  @Override
  public void run() {
    while (shouldRun()) {
      long frameStart = SystemClock.elapsedRealtime();
      repaint();
      long frameLength = SystemClock.elapsedRealtime() - frameStart;

      long sleepTime = FRAME_TIME_MILLIS - frameLength;
      if (sleepTime > 0) {
        SystemClock.sleep(sleepTime);
      }
    }
  }
}
CompassRenderer.javaファイル内のLive Card描画部分

センサーを使った方位の検出

 方位の検出は、OrientationManagerクラスで行われる。OrientationManagerは、回転ベクトルセンサー(TYPE_ROTATION_VECTOR)と磁気センサー(TYPE_MAGNETIC_FIELD)を利用している。実際に角度の検出に使われているのは回転ベクトルセンサーのみで、磁気センサーは回転ベクトルセンサーのaccuracy(正確性)を判断するためだけに使われている。これは、「正しい回転ベクトルセンサーのaccuracyが取得できない」というGlassの不具合に起因している。

 以下のコードは、方位検出用のセンサーの登録と、実際の方位検出を実施している箇所である。

Java
public void start() {
  if (!mTracking) {
    ……省略……
    // 回転ベクトルセンサーの登録
    mSensorManager.registerListener(mSensorListener,
      mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR),
      SensorManager.SENSOR_DELAY_UI);

    // 磁気センサーの登録
    mSensorManager.registerListener(mSensorListener,
      mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD),
      SensorManager.SENSOR_DELAY_UI);
    ……省略……
  }
}
OrientationManager.javaファイル内の方位検出用センサーの登録
Java
private SensorEventListener mSensorListener = new SensorEventListener() {

  @Override
  public void onAccuracyChanged(Sensor sensor, int accuracy) {
    if (sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
      // 磁気センサーは方位の正確性を判断するために利用。正確でない場合は、別途、警告を表示する。
      mHasInterference = (accuracy < SensorManager.SENSOR_STATUS_ACCURACY_HIGH);
      notifyAccuracyChanged();
    }
  }

  @Override
  public void onSensorChanged(SensorEvent event) {
    if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) {
      // 回転ベクトルセンサーから現在の方位角を求める
      SensorManager.getRotationMatrixFromVector(mRotationMatrix, event.values);
      SensorManager.remapCoordinateSystem(mRotationMatrix, SensorManager.AXIS_X,
        SensorManager.AXIS_Z, mRotationMatrix);
      SensorManager.getOrientation(mRotationMatrix, mOrientation);

      // ピッチ角(=Glassの上下の傾き)の取得。Glassの傾きが急な場合は、正確な値が取れない旨の警告を別途、表示している。
      mPitch = (float) Math.toDegrees(mOrientation[1]);

      // 方位角を計算する
      float magneticHeading = (float) Math.toDegrees(mOrientation[0]);
      mHeading = MathUtils.mod(computeTrueNorth(magneticHeading), 360.0f)
        - ARM_DISPLACEMENT_DEGREES;

      notifyOrientationChanged();
    }
  }
};
OrientationManager.javaファイル内の方位検出部分

音声合成処理

 「Read aloud」で方位角を読み上げる機能では、Android SDKに含まれるTTS(Text To Speech)の機能を利用している。

Java
public class CompassBinder extends Binder {
  public void readHeadingAloud() {
    // 方位角の取得と、読み上げ文字列の構築。
    float heading = mOrientationManager.getHeading();

    Resources res = getResources();
    String[] spokenDirections = res.getStringArray(R.array.spoken_directions);
    String directionName = spokenDirections[MathUtils.getHalfWindIndex(heading)];

    int roundedHeading = Math.round(heading);
    int headingFormat;
    if (roundedHeading == 1) {
      headingFormat = R.string.spoken_heading_format_one;
    } else {
      headingFormat = R.string.spoken_heading_format;
    }

    String headingText = res.getString(headingFormat, roundedHeading, directionName);
    mSpeech.speak(headingText, TextToSpeech.QUEUE_FLUSH, null);
  }
}

private TextToSpeech mSpeech;

@Override
public void onCreate() {
  super.onCreate();
  ……省略……
  // TTSオブジェクトの初期化
  mSpeech = new TextToSpeech(this, new TextToSpeech.OnInitListener() {
    @Override
    public void onInit(int status) {
    }
  });
  ……省略……
}
音声合成処理(CompassService.java)

まとめと次回

 GDKは、Glass特有のCardやタッチジェスチャー・音声コマンドなど、主にUI周りの機能をAndroid SDKの拡張として提供するものである。コンパスアプリのソースを見て分かる通り、センサーの利用や音声合成など主要なロジックの実装はAndroid SDKのAPIを利用しており、Androidアプリ開発と何ら異なるところはない。Androidアプリ開発者は、Glassの実機を持っていなくても、Androidアプリとして開発していれば、後でGlassに移植するときも大部分のソースを再利用できるだろう。

 次回は、NDKとOpenCVを使った高度なアプリケーション開発を解説する予定だ。

※以下では、本稿の前後を合わせて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でつぶやこう!