こんにちは、KID.Aです。
前回の更新からだいぶ期間が経ってしまいました。
今回もGoogle Glassの開発に関しての記事で、アプリからカメラを使用します。
通常のAndroidの場合ですとIntentを使用してカメラを呼び出しますが、Google Glassでも同じです。
異なる箇所は、結果がonActivityResultに返却された場合に、Intentsの定数を使用してデータを受け取る箇所です。
実装方法
今回はgoogle のサンプルソースを動かします。
https://developers.google.com/glass/develop/gdk/camera
※コメント追加や、CameraManager.EXTRA_PICTURE_FILE_PATHをIntents.EXTRA_PICTURE_FILE_PATHに変更したり(Depricatedになっているため)、若干修正は加えています。
package com.example.glasscamera; import java.io.File; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.os.FileObserver; import android.provider.MediaStore; import android.util.Log; import android.view.KeyEvent; import com.google.android.glass.app.Card; import com.google.android.glass.content.Intents; public class MainActivity extends Activity { // 写真撮影時の通知用の値 private static final int TAKE_PICTURE_REQUEST = 1; @Override protected void onCreate(Bundle bundle) { super.onCreate(bundle); // 画面に文字を表示するためCardを使用する Card card = new Card(this); card.setText("タッチパッドをタップすると写真を撮影できます"); setContentView(card.getView()); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // タッチパッドがタップされた場合 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { takePicture(); return false; } else { return super.onKeyDown(keyCode, event); } } @Override protected void onResume() { super.onResume(); } @Override protected void onPause() { super.onPause(); } /** * 写真撮影を開始する */ private void takePicture() { Log.d(MainActivity.class.getName(), "call takePicture"); // Intent経由で撮影して、写真のデータをもらう Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); // 第二引数のTAKE_PICTURE_REQUESTで呼び元を判定する startActivityForResult(intent, TAKE_PICTURE_REQUEST); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { Log.d(MainActivity.class.getName(), "call onActivityResult"); // 結果がOKで、呼び元がTAKE_PICTURE_REQUESTの場合 if (requestCode == TAKE_PICTURE_REQUEST && resultCode == RESULT_OK) { Log.d(MainActivity.class.getName(), "TAKE_PICTURE_REQUEST & RESULT_OK"); // 撮影結果の保存場所を取得する String picturePath = data.getStringExtra(Intents.EXTRA_PICTURE_FILE_PATH); // 保存されているか確認する processPictureWhenReady(picturePath); } super.onActivityResult(requestCode, resultCode, data); } /** * 写真が保存されるまで待機するメソッド * @param picturePath 写真のファイルパス */ private void processPictureWhenReady(final String picturePath) { Log.d(MainActivity.class.getName(), "call processPictureWhenReady"); final File pictureFile = new File(picturePath); if (pictureFile.exists()) { // 写真が存在する場合 Log.d(MainActivity.class.getName(), "pictureFile is exists"); // ここで写真を使用した処理を書く // また、進捗状況インジゲータや待ち画像を表示している場合は消す処理を書く } else { // 写真が存在しない場合 Log.d(MainActivity.class.getName(), "pictureFile is not exists"); // ここで進捗状況インジゲータや待ち画像などを表示する処理を書く // ファイルパスを取得する final File parentDirectory = pictureFile.getParentFile(); // FileObserverでディレクトリを監視する FileObserver observer = new FileObserver(parentDirectory.getPath(), FileObserver.CLOSE_WRITE | FileObserver.MOVED_TO) { // MOVED_TOやCLOSE_WRITE後に保留中のイベントから保護するためのフラグ private boolean isFileWritten; @Override public void onEvent(int event, String path) { Log.d(MainActivity.class.getName(), "call event"); // 既に書き込まれている場合は何もしない if (!isFileWritten) { File affectedFile = new File(parentDirectory, path); // 写真ファイルが書き込まれたチェックする isFileWritten = affectedFile.equals(pictureFile); Log.d(MainActivity.class.getName(), "affectedFile = " + affectedFile.getAbsolutePath()); if (isFileWritten) { Log.d(MainActivity.class.getName(), "file is written"); // ファイルが書き込まれている場合は、FileObserverを停止する stopWatching(); // ファイルが書き込まれている場合は、UI ThreadでprocessPictureWhenReadyを呼ぶ runOnUiThread(new Runnable() { @Override public void run() { Log.d(MainActivity.class.getName(), "runOnUiThread"); processPictureWhenReady(picturePath); } }); } } } }; observer.startWatching(); } } }
解説
ではソースの解説をしていきます。
- まず、起動したらonCreateが呼ばれて、Cardが呼ばれます。Cardは文字を表示するために使用します。
- 次にタッチパッドがタップされると、onKeyDownが呼ばれます。キーイベントの挙動や種別はこの記事がわかりやすいと思います。その中でtakePictureを呼んでいます。
- takePictureは通常のAndroidアプリと同じく、MediaStore.ACTION_IMAGE_CAPTUREを使用してIntentでカメラを起動できます。
- カメラで撮影後、onActivityResultに結果が返ってきます。Intents.EXTRA_PICTURE_FILE_PATHで画像のファイルパスが返却されます。Google DeveloperではCameraManager.EXTRA_PICTURE_FILE_PATHが使用されていますが、既にDepricatedされているためIntents.EXTRA_PICTURE_FILE_PATHに変更しています。
- 画像のファイルパスが返ってきますが、実際にはまだ保存されていない状態のため、FileObserverを使用してディレクトの監視を行います。
- processPictureWhenReadyが呼ばれた際に、pictureFile.exists()でまだファイルがないため、結果がfalseに返却されます。その後、FileObserverでディレクトリを監視してくれます。
- ファイルが追加されるとonEventが呼ばれます。
- onEventで追加されたファイルがIntents.EXTRA_PICTURE_FILE_PATHと同じファイル名だったらprocessPictureWhenReadyを呼びます。processPictureWhenReadyが呼んだ際に、pictureFile.exists()で既にファイルが存在するため、結果にtrueが返却されます。
動かしてみる
実際に動かしています。
以下は実行の際のログになります。
call takePicture
call onActivityResult
TAKE_PICTURE_REQUEST & RESULT_OK
call processPictureWhenReady
pictureFile is not exists
call event
affectedFile = /storage/emulated/0/DCIM/Camera/20140731_170059_533.jpg.tmp
call event
affectedFile = /storage/emulated/0/DCIM/Camera/20140731_170059_533.jpg
file is written
call event
runOnUiThread
call processPictureWhenReady
pictureFile is exists
call eventが2度来ていて、一度目は添付ファイルが追加され、二度目に実際のファイルが追加されているのがわかります。
これでカメラを利用することができます。
今回はIntentを用いてカメラを利用しましたが、CameraとSurfaceHolderを使用するとリアルタイムに画像を使用することもできます。リアルタイムに画像を使えると、某マンガであったような戦闘力を表示するスカウターアプリなどを作ることができるかもしれません。
英語の分からない私は、ココのサイトを見てかなり勉強させていただいております。
今、グラスをいじっているのですが、デフォルトのカメラ画像大きいのでサイズを変更しようとしているのですが
うまくいきません、intent bitmap 等 色々やってみましたが、うまくいかず煮詰まっております。もし、よろしかったらKID.A様のコードを元に助言などいただけませんでしょうか?