Google Glassで作る近未来アプリケーション(3)
初めてのGoogle Glassアプリ開発(GDK編)
いよいよGlass開発を実践。GDKの開発環境の構築手順と、Glassware(=Glassアプリ)の作成/実行方法を説明。また、サンプルアプリのソースコードを読み解きながら、よりGlassらしいアプリの実装方法について説明する。
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]を選択してインストールする。
Glasswareの作成と実行
インストールが完了すると、EclipseでAndroidプロジェクトを作成する際に、GDKで作成できるようになる。メニューバーから[File]-[New]-[Android Application Project]で新規アプリケーション作成ウィザードを開き(次の画面)、[Compile With]欄で[Glass Development Kit]を選択してみよう。[Application Name]欄に適当な名前を入力し、あとはデフォルト状態のまま、[Next]ボタンを押し続け、最後に[Finish]ボタンを押して、プロジェクトを作成する。
プロジェクトが作成されたら、さっそくGlassで動かしてみよう。Eclipseのメニューバーから[Run]-[Run As]-[Android Application]で起動すると、以下のような画面がGlassに表示される。
見て分かる通り、このサンプルの見た目はAndroidアプリそのものである。Glassの標準的なUIとは全く異なるものになっているが、これはスマートフォン用のtheme(テーマ)がデフォルトで適用されているのが原因である。AndroidManifest.xmlファイルから<application>に適用されているtheme設定を削除すると、よりGlassっぽく表示されるようになる。
コンパスアプリのソースコードを読んでみよう
よりGlassらしいアプリケーションの実装方法を理解するために、公式サンプルの1つであるコンパスアプリのソースコード(以下、ソース)を読んでみよう。ソース全体は https://github.com/googleglass/gdk-compass-sample からダウンロードできる。
コンパスアプリの概要
コンパスアプリは、音声コマンド「Show a compass」で起動できる。起動すると、以下のように方角を示す画面が表示され、顔の向きによってリアルタイムで方角も変わっていく。
タッチパッドをタップすると、メニューが表示され(次の画面)、「Read aloud」のメニューを選択すると、現在の方角を360°式の方位角で読み上げてくれる。
コンパスアプリは、起動するとLive Cardとしてタイムラインに挿入される。後でコンパスを使いたくなったら、タイムラインから左スワイプで瞬時にアクセスできる。
アプリケーションの構成
ソースの全体構成を見てみよう。主要なのは、com.google.android.glass.sample.compassパッケージ直下の5つのクラスである。それぞれの役割は以下の通り。
クラス名 | 役割 |
---|---|
CompassService | 音声コマンドのIntentを受けて起動され、Live Cardの挿入を行うServiceクラス |
CompassRender | 描画用スレッドを管理し、Live Cardの描画や更新を行う |
CompassView | コンパス画面を描画するための独自のViewクラス |
OrientationManager | 方角を得るために必要なセンサーと連携するクラス |
CompassMenuActivity | タップした際に表示されるメニュー画面を管理するActivityクラス |
Live Cardの表示
CompassServiceは、音声コマンドを受けて起動されるServiceである。AndroidManifest.xmlファイル内で以下のように、音声コマンドのIntentを受けるように設定されている。
<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>
|
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が起動するようになっている。
@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;
}
|
また、LiveCardオブジェクトを作成する際に、CompassRendererクラスのオブジェクトをSurfaceHolderのコールバックとして指定している(SurfaceHolderはAndroid SDKに含まれ、Canvasを介した高速な描画をサポートするインターフェースである)。CompassRendererオブジェクトは、内部で描画用スレッドを作成し、検出された方位に応じて作成されたViewをCanvasに書き込んでいる(次のコード)。
// 描画用スレッドから呼ばれる描画更新メソッド
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);
}
}
}
}
|
センサーを使った方位の検出
方位の検出は、OrientationManagerクラスで行われる。OrientationManagerは、回転ベクトルセンサー(TYPE_ROTATION_VECTOR)と磁気センサー(TYPE_MAGNETIC_FIELD)を利用している。実際に角度の検出に使われているのは回転ベクトルセンサーのみで、磁気センサーは回転ベクトルセンサーのaccuracy(正確性)を判断するためだけに使われている。これは、「正しい回転ベクトルセンサーのaccuracyが取得できない」というGlassの不具合に起因している。
以下のコードは、方位検出用のセンサーの登録と、実際の方位検出を実施している箇所である。
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);
……省略……
}
}
|
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();
}
}
};
|
音声合成処理
「Read aloud」で方位角を読み上げる機能では、Android SDKに含まれるTTS(Text To Speech)の機能を利用している。
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) {
}
});
……省略……
}
|
まとめと次回
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: 拡張現実)アプリの開発を、サンプルソースを交えながら解説。