Archive for the ‘Android apps’ Category

Re-installation failed due to different application signatures. が出たとき

2012年2月18日

Emulatorでアプリをデバッグしようとしたとき、以下のようなエラーが発生した

[2012-02-18 16:54:35 - IntervalTimer] Installing IntervalTimer.apk...
[2012-02-18 16:54:46 - IntervalTimer] Re-installation failed due to different application signatures.
[2012-02-18 16:54:46 - IntervalTimer] You must perform a full uninstall of the application. WARNING: This will remove the application data!
[2012-02-18 16:54:46 - IntervalTimer] Please execute 'adb uninstall stndstn.intervaltimer' in a shell.
[2012-02-18 16:54:46 - IntervalTimer] Launch canceled!

adb は SDKの /platform-tools フォルダ にあった。Emulatorが起動した状態で、SDKの /platform-tools フォルダで以下のコマンドを実行し、解消できた。

$ ./adb uninstall stndstn.intervaltimer

画像をViewerで開く

2011年11月3日
 File f = new File(ファイル名);
 Uri data = Uri.fromFile(f);
 Intent intent = new Intent(Intent.ACTION_VIEW);
 intent.setDataAndType(data, "image/jpeg");
 startActivity(intent);

起きろ!…起きろってば!(深い眠りに入ったアプリを叩き起こす)

2011年8月27日

カメラに、タイマーとSMS着信で撮影する機能をつけてみた。

SMS着信からの撮影は問題なかったが、意外にも、タイマーでの撮影がうまくかない。deep sleep に入ったときに反応しなくなってしまう。

まずは、セルフタイマーカメラで使用したCountDownTimerを使用してみた。Debugしているとき、つまりUSBでPCと接続しているときはほぼ正常に動いていたが、PCから離して単体で動かすと、長い時間動かしているうちにタイマーが反応しなくなってしまう。イベントはキューにたまってるようで、デバイスに触ったり、なにかのタイミングで急に動き出すが、たまっていた分を一度に実行しようとしてプログラムは落ちてしまう。

なにか別の手段を考えないといけない。deep sleep で検索して、まずは以下を参照。

http://developer.android.com/reference/android/os/SystemClock.html

ACTION_TIME_TICK を listen する方法かあるようだ。Receiver を使うので SMS の受信の仕組みと同じ。SMS の受信はスリープ時でも動いているので、これなら大丈夫かと思ったが、試してみたらだめだった。SMS の Receiver と同じように PackageManager.setComponentEnabledSetting() で Receiver を DONT_KILL_APP に設定してもだめだった。

スリープから覚めた途端にキューにたまった分を実行し始めるので。もしかするとタイマーは動いているが、その先の処理がとまっているだけかもしれない、と考え、タイマー処理で PowerManager で WakeLock してみる。しかしだめだった。起動から終了まで PowerManager で WakeLock をかけたままにする方法もあるが、これでは、スリープに入るのを防ぐことはできるが、これではバッテリーが持たないろう。タイマーで動作させる意味が無い。

もう一度 SystemClock の説明を読み返してみると、AlermManager なら deep sleep でも動作すると書いてある。なるほど、確かにアラームはスリープ状態でも決まった時間に動作する。これならきっと大丈夫だ。

        AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        Intent intent = new Intent(this, <起動させたいComponentのClass>);
        PendingIntent pi = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        am.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + <間隔(ミリ秒)>, <間隔(ミリ秒)>, pi);

AlarmManager.setInexactRepeating() の2番目の引数は、最初に起動する時刻を指定するが、最初の引数で指定した方法で指定しなければならない。この例だと elapsedRealtime を使って指定する。 Time.toMills() で得られる値とは異なる。また、あとでキャンセルするためには、指定した PendingIntent の参照が必要。

この方法で、deep sleep に入ったときでも起動できるようになった。

これで、タイマーまたはSMSを受信したタイミングで、カメラで画像を撮影し、Picasa へのアップロードを行うアプリができた。(→ダウンロード

Picasaに画像をアップする(Android)

2011年8月21日

Android から Picasa に画像をアップロードするサンプルがあった。

http://gdata-java-client.googlecode.com/svn/tags/2.2.1-alpha/sample/picasa/picasa-atom-android-sample/instructions.html

ビルドして実行みたが、認証画面らしきものが出てくるが、先に進めない。コードを見てみると、認証方式が OAuth に指定されていた。これを ACCOUNT_MANAGER に変更すると、動いた!

アプリ上での操作は、アルバムの作成、削除、名前の編集くらいしかできないが、画像の送信機能も実装されている。別の画像閲覧アプリ(Albumなど)で画像を共有する機能を選択すると、選択肢の中にこのアプリが追加されていて、これを使って Picasa に画像を転送できる。なお、事前に Android の Account Manager にGoogleアカウントが登録されている必要がある。

このサンプルを参考に、アプリを作成してみた(ソースはこちら)。以前作った、Intent でカメラを起動させるアプリにこの送信機能を追加する。

まず、com.google.api.data.sample.picasa.model のパッケージをサンプルプロジェクトからそのままコピーする。

別途 gdata-android-2.2.1-alpha.zip をダウンロードし、展開。(サンプルから持ってきてもいいはず)

プロジェクトには、上のZIP を展開した中にあるJARをExternal Jarとして追加。

サンプルから Picasa に関するコードだけをコピーして PicasaTool というクラスを作成。Account Manager しか使わないので、他の認証タイプに関連した不要なコードは削除。

Account Manager をIntent で起動して結果を得るという処理で Activity の onActivityResult をオーバーライドする必要があるが、作成したPicasaTool は Activity ではないので、Activity の onActivityResult から呼び出される Callback メソッドを定義し、ここに必要な処理を実装。

カメラで撮影された画像をそのまま送信すると大きすぎるかもしれないので、まず Bitmap の compress メソッドで圧縮する。

Picasa に送信する処理には、以下の3つの情報が必要。

  • ファイルの Uri
  • ファイルサイズ
  • ファイル名(実際に送信するファイルの名前とは違ってもいい。Picasa 上の写真につける名前)

送信に対する応答のHttpResponse には、そのアップロードした画像にアクセスするためのURLなどが入っている。


Activityを分割

2011年8月13日

カメラのActivityをカメラと画像表示画面とに分けてみた。(ソース

画像表示画面用にPictViewerというActivityを作成し、画像表示機能に関するコードを移す。但しMediaScannerは両方で使うのでカメラ側にも残す。

AndroidManifest.xml に Activity を追加。

        <activity android:name=".PictViewer"
        		  android:theme="@android:style/Theme.NoTitleBar" >
        </activity>

カメラ側には、サムネイルを表示したボタンを押されたときに、画像表示画面を呼び出す為のコードを作成。

        Intent i = new Intent(this, PictViewer.class);
        startActivity(i);

分割することで、動作がかなり安定した。

これでまずまず使えるレベルになったので、Market にUp

 

Androidでカメラを使うアプリを作成ー機能追加

2011年8月7日

前回作成したセルフタイマー専用カメラに機能追加。(ソース)

  • 外部メモリへの保存
  • メディアスキャナーへ画像を登録
  • オートフォーカス
  • 画像表示画面でGalleryによる画像選択を可能に。
  • 画像削除機能

外部メモリへの保存

まず、 Environment.getExternalStorageState() で外部メモリが使用可能か否かを判定。

        String state = Environment.getExternalStorageState();
        if (!Environment.MEDIA_MOUNTED.equals(state)) {
            return false;
        }

Environment.getExternalStorageDirectory() で外部メモリへのパスを取得。

    	File directory = Environment.getExternalStorageDirectory();

データは、外部メモリ内の “Android/data/<パッケージ名>” というフォルダに保存しないといけないようなので、そのフォルダがなければ作成(mkdirs)する。

    	File directory = Environment.getExternalStorageDirectory();
    	String dirpath = "Android/data/" + pkgname + "/files";
    	File subdirectory = new File(directory, dirpath);
    	if(subdirectory.exists() == false) {
        	subdirectory.mkdirs();
    	}

BufferedOutputStream を開いて書き込む。

    	File f = new File(subdirectory.getPath(), filename);
    	OutputStream out = new BufferedOutputStream(new FileOutputStream(f));
	out.write(data);

メディアスキャナーへ画像を登録

MediaScannerConnectionで画像ファイルを登録すると、内蔵の画像ビューアーなどで画像が見れるようになる。このとき、サムネイルも作ってくれる。

MediaScannerConnectionのインスタンス作成時に、MediaScannerConnection.MediaScannerConnectionClient のインタフェースの参照が必要。今回はActivity本体にこのインタフェースを実装した。

スキャンを開始する前に connect() を呼び出す。完了すると onMediaScannerConnected() が呼び出される。パスを指定して scanFile() を呼び出し、スキャンしたらすぐにdisconnect している。この使い方が正しいのか、よくわからないが、とりあえず機能しているように見える。

    public void onMediaScannerConnected () {
        if(mMsc.isConnected()) {
            mMsc.scanFile(mScanPath, null);
            mMsc.disconnect();
        }
    }

スキャンが完了すると、onScanCompleted (String, Uri) が呼び出される。スキャンしたファイルのパスとURIが取得できるが、今回はどちらもつかわないので、ここでは何もしていない。

オートフォーカス

Camera の autoFocus (Camera.AutoFocusCallback) を呼び出すと、オートフォーカスができたタイミングで AutoFocusCallback の onAutoFocus (boolean, Camera) が呼び出される。この中で、Camera の takePicture () を呼び出すだけでよかった。takePicture() を呼び出すところは既にできていたので、実質、追加したのは2行だけ。

        mCam.autoFocus(new Camera.AutoFocusCallback() { //追加
            @Override
            public void onAutoFocus(boolean success, Camera camera) { //追加
                camera.takePicture(null, null, new Camera.PictureCallback() { //ここは同じ
                    @Override
                    public void onPictureTaken(byte[] data, Camera camera) {
           /* 省略。ここでデータを保存した後、startPreview を呼び出す */
                    }
                });        
            } //追加
        }); //追加

画像表示画面でGalleryによる画像選択を可能に

下のサムネイルから画像を選択

最初はApiDemo の ImageSwitcher + Gallery の組み合わせをそのまま使うつもりだったが、イメージを2つ用意しないといけない為か、メモリ不足で落ちることが多かったので、ImageSwitcher はやめて、普通の ImageView に変更。

Gallery の詳しい説明は

ApiDemo の ImageSwitcher のコードは

また、この修正の過程で ImageView.setImageURI() の使い方が判明。実は、前回からこのメソッドを使って画像を表示させようとしていたが、うまくいかず、結局自分で Bitmap を作成することにしたのだが、前回は

        Uri uri = Uri.fromFile(new File(path));
        imgView.setImageURI(uri);

のように、Uri.fromFile() から作成した Uri を使っていたが、だめだった。しかし、今回改めて調べたところ、下のように Uri.parse() を使えば表示できるとの情報があり、ためしてみると、表示できた!(・・・何が違うんだ!)

        Uri uri = Uri.parse(path);
        imgView.setImageURI(uri);

しかし、結局 setImageUri() では表示が遅くなるので、自分で Bitmap を作成する方法に戻してしまった。きっと、setImageUri() ではファイルのフルサイズのイメージを作成してしまうので、遅いのかもしれない。(自分で Bitmap を作成するときは、画面の大きさに合わせて縮小している。)

画像削除機能

画像を表示しているときに、その画像を削除できる機能を追加。メニューボタンからとコンテキストメニューから呼び出せる。

コンテキストメニューの使い方

また、画像を削除した際も、メディアスキャナーに登録すると、サムネイルを削除してくれる。これを行わないと、内蔵の画像ビュアーには「壊れた画像」的に表示が残ってしまう。

問題点

  • スクリーンロックがかかると、必ず再起動してしまう。
    • OnResume ではなく、OnCreate が呼び出されてしまう。変数もみな初期化されている。そのせいで、画像表示中にロックされると、カメラに戻ってしまう。なぜ?
  • ロックから戻るときの不具合が多い。
    • カメラのインスタンス取得に失敗し、プレビュー表示ができていなかったり、落ちてしまったりする。
  • Activityの分割
    • Activityをカメラと画像表示で分けた方がいいのかもしれない。

R.java を削除してしまった!

2011年7月30日

自動的に作成されるはずなのに、ProjectをBuildしても戻ってこない。

gen [Generated Java Files] を右クリックして [Restore from Local History…]から元に戻すことができたが、Layoutへの変更を反映しなくなってしまった。作り直すために Project をClearしたら、R.Java は消えてしまった。

再度、[Restore from Local History…]から元に戻して、いろいろと調べてみたところ、R.Java のプロパティからGroupの書き込み権限がなくなっていたのが原因らしい。R.Java のProperties画面で[Restore Default]を押すと元に戻った。

Androidのアプリでカメラを使う(Intentを使う)

2011年7月25日

Androidのアプリからカメラを使うには・・・実はもっと簡単な方法があった。。。orz

Intentを使えばよかったらしい。

本当に簡単だった。
サンプルプログラムにはボタンとImageViewがあり、ボタンが押されたらIntentを作成してstartActivityForResultを呼び出す。2個目の引数の0(ゼロ)は、Actionの成功時にrequestCodeとして返される。1つのプログラムで複数のIntentActionを使う場合は、これで判別できる。

	Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
	startActivityForResult(intent, 0);

カメラでシャッターが押されたら(画像がキャプチャされたら)onActivityResult が呼び出される。
IntentからBundleを使ってExtra dataを取り出す。
(”data”というKeyだったが、1つしかないので、ここではあまり気にしない)
取り出したObjectをImageにキャストしてImageViewで表示する/

    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == 0) {
            if (resultCode == RESULT_OK) {
            	//If the EXTRA_OUTPUT is not present, then a small sized image is returned as a Bitmap object in the extra field. 
            	Bundle b = data.getExtras();
                ImageView img = (ImageView)this.findViewById(stndstn.intenttest.R.id.imageView1);
                Set keys = b.keySet();
                Iterator k = keys.iterator();
                while(k.hasNext()) {
                	Object o = b.get(k.next());
                	if(o.getClass() == Bitmap.class) {
                    	Bitmap bmp = (Bitmap)o;
                    	img.setImageBitmap(bmp);                		
                    	break;
                	}                				
                }
            }
        }
    }

カメラのシャッターは自分で押さないといけないが、アプリからカメラ機能を使う状況を考えると、普通はこれで十分だろう。ただし、セルフタイマーなどのようにプログラムからシャッターを切るような動作はできないようにみえる。

Androidでカメラを使うアプリを作成

2011年7月24日

Androidでカメラをつかった簡単なプログラムを作成してみた。(ソースはこちら)

標準でついているカメラにはセルフタイマーがないので、セルフタイマー専用のカメラを作ってみる。

左上のカメラのアイコンを押すと撮影。左下は前回撮影した画像。

残り秒数がカウントダウンされる

★カメラの使用方法については、開発者サイトの以下のリンクを参照(android.hardware.Camera)

要旨だけまとめると

①カメラのインスタンスを取得 open(int).

②既存の設定(デフォルト)を取得 getParameters().

③必要であれば、2で取得した Camera.Parameters オブジェクトを変更しsetParameters(Camera.Parameters) を呼び出して変更した設定を反映

④必要なら setDisplayOrientation(int). で画像の向きを設定

⑤初期化済みの SurfaceHoldersetPreviewDisplay(SurfaceHolder). に渡す。これがないとプレビューができない。

startPreview() を呼び出してプレビューを開始。写真を撮る前にプレビューが開始されないといけない。

takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback) を呼び出すと撮影される。イメージが作成されると各コールバックが呼び出されるので、そこでデータを取得できる。

⑧撮影後はプレビューは止まっているので、再開するには startPreview() を呼び出す。

stopPreview() でプレビューを停止

⑩他のアプリケーションがカメラを使えるように、カメラを release() する。アプリケーションは onPause() でカメラをリリースしなければならない(そして onResume() で再び open() する).

上のリンクの情報はよくまとめられているので、ここ情報だけでほぼ十分だった。(個人的な意見ですが、Appleの開発者サイトに比べるとGoogleの開発者サイトのほうがわかりやすいですね。)

■このプログラムの仕様

・プレビュー画面左上のボタンを押すと、10秒後に撮影される。

・プレビュー画面上で残り秒数がカウントダウンされる。

・撮影した画像のサムネイルが左下に表示され、サムネイルをクリックすると画像が表示される。

・画像は最後の一枚しか保存されない。(内蔵メモリに保存するので・・・)

プレビュー用画面のレイアウトは、プレビュー表示用のSurfaceViewの上に、オーバーレイ状にボタンとラベルを配置。

オーバーレイ状に画面を重ねるので、一番下のレイアウトはFrameLayout、その上にSurfaceViewとRelativeLayoutを重ねる。

RelativeLayoutの左上にタイマー開始のボタン(カメラのアイコン)、真ん中にカウントダウン表示用のラベル、左下にサムネイル表示用のイメージボタンを配置。

画面レイアウト

なお、プレビュー画面の方向を変えることができなかったので、画面の向きはLandscapeに固定する。

画像確認用の画面はImageViewのみで構成する。

また、Manufestに以下の項目が必要

<uses-permission android:name=”android.permission.CAMERA” />
<uses-feature android:name=”android.hardware.camera” />

■各処理の説明

1.初期化処理

(1)OnCreate

  • まずプレビュー画面が表示されるように設定(setContentView)
  • 画面の向きをLandscapeに設定
  • 各コントロールの参照を取得
  • SurfaceViewのSurfaceHolderにコールバックのリスナーを設定(Activityにコールバックを実装するので、this を指定)
  • SurfaceViewのSurfaceHolderのTypeを SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS に設定。※これを設定しないとプレビュー開始時にエラーになる。
  • 画面上の配置するコントロールのVisibiityを設定。ボタンはVisible、カウントダウン用のラベルはInvisible
  • ボタンが押された際の動作を記述

(2)OnResume

  • カメラのインスタンスを取得 open(int).(①)

(3)surfaceCreated ※SurfaceHolderのコールバック

  • Surfaceが初期化されたので、カメラのsetPreviewDisplayに設定する(⑤)

(4)surfaceChanged ※SurfaceHolderのコールバック

  • Surfaceのサイズなどが決まったタイミングで呼び出されると思われる。ここでgetParametersでパラメータを取得する(②)。後で画面のサイズを取得する際に使用する。
  • 他にも設定したいParameterがあればここで設定(③、④)したらよいのではないかと思われるが、今回は特に必要がなかったので設定していない。(※設定するタイミングについては、実はまだよくわかっていない。現状では、SuspendからResumeしたときに、例外が発生して落ちてしまうことが多い。カメラのインスタンスを取得できないのが原因らしいが、このへんのタイミングについては、もっと調べる必要がある。)
  • カメラのstartPreviewを呼び出す。

2.タイマーのスタート

(1)ボタンが押されたら、カウントダウンのラベルをVisibleに設定し、CountDownTimerを作成してスタートさせる。

(2)CountDownTimerのonTickでカウントダウンのラベルの数字を更新。

(3)CountDownTimerのonFinishでカウントダウンのラベルをInvisibleに戻し、カメラのtakePictureを呼び出す。

3.撮影

(1)カメラのtakePictureを呼び出す。このプログラムではJPEGデータのコールバックだけを指定。

(2)JPEGデータのコールバック(onPictureTaken)で、データを取得してファイルに保存。サムネイル用のビットマップを更新した後、カメラのstartPreviewを呼び出す。

4.画像の保存

★内蔵メモリへのデータ保存については以下のリンクを参照

(1)各アプリケーション用に指定された領域にファイルを作成する。(openFileOutput)

(2)画像データは byte[] なので、これを FileOutputStream に書き込んで、閉じる

  FileOutputStream fos = this.openFileOutput(name, Context.MODE_WORLD_WRITEABLE);
  fos.write(data);
  fos.close();

5.サムネイル表示の更新

(1)getFileStreamPath で、4で各アプリケーション用に指定された領域に保存したファイルのパスを取得。

(2)カメラのParameterから画像サイズを取得し、サムネイル用にするには元の大きさの何%にしたらよいかを計算。

(3)BitmapFactory.decodeFile を呼び出し、Bitmap を作成。

(4)サムネイル表示用のImageButtonにBitmapを設定。

■わかったこと、及び課題

1.保存したファイルにアクセスできない

今回は、openFileOutput を使って、内蔵メモリの各アプリケーション用に指定された領域にファイルを作成した。ここは他のアプリケーションからはアクセスできないように設計されているのかもしれない。画像のようなデータは外部メモリに保存したほうがよかった。撮った画像データにアクセスできないと意味が無いので、後で修正します。

2.レベル8以上でないと使えない便利なAPIが多い

サムネイルを作成したり、外部メモリへの保存先を指定したり、といった「これは使いたい」と思ったAPIが、レベル8以上の指定になっていて使うことができず、残念な思いをした。

3.SuspendからResumeしたときに、例外が発生して落ちてしまうことが多い。

内蔵のカメラではそのようなことはないので、なにかが足りないはずだ。エラーの内容を見るとカメラのインスタンスを取得できていないようだ。カメラの準備ができてないタイミングでアクセスしようとしてしまっているのか?まだ調査が必要。

4.オートフォーカス

Manufestに以下のように設定しておけば自動的にオートフォーカスになるのかと思っていたが

     <uses-feature android:name=”android.hardware.camera.autofocus” android:required=”false” />
どうも違うらしい。オートフォーカス用のコールバックを実装しないといけないようなので、今回はパス。

メニューとボタン

2011年7月2日

ゴルフのスコアカウンターにメニューボタンと戻るボタンの処理を追加して、少し使いやすくしてみる。戻るボタンのデフォルトの処理では、アプリを終了させてしまうので、データが消えてしまう。プレー中にデータが消えてしまうと大変なので、戻るボタンを押すとワーニングを表示するようにする。そうなるとアプリを終了させる方法がなくなるので、メニューからCloseを選択して終了できるようにする。また、スコアデータをクリアするメニューも追加する。

ソース

■ボタンのイベントハンドラ

戻るボタンを押すとワーニングが表示

戻るボタンを押すとワーニングが表示

ActivityのonKeyDownをOverrideする。keyCodeを見てどのボタンが押されたか判定。trueを返すと、後続のイベント処理は行われなくなる。

   @Override
   public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
	    	// Show dialog, if YES, close this application.
	    	m_alertdlg_close.show();
        	return true;
        }
        return false;
    }

ワーニングのダイアログ(m_alertdlg_close)は onCreate の中で作成

        //Alert dialog for Closing Application
    	builder.setMessage("If closed, data will be cleared. If you just hide this window, press HOME key. Are you sure you close this application? ")
    	       .setCancelable(false)
    	       .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
    	           public void onClick(DialogInterface dialog, int id) {
    	        	   finish();
    	           }
    	       })
    	       .setNegativeButton("No", new DialogInterface.OnClickListener() {
    	           public void onClick(DialogInterface dialog, int id) {
    	                dialog.cancel();
    	           }
    	       });
    	m_alertdlg_close = builder.create();

■メニューの実装

メニューボタンを押すとメニューが表示

メニューボタンを押すとメニューが表示

実装方法は、開発者サイトの Dev Guide > User Interface > Creating Menus に書いてある通り。丁寧に書いてあるので、すぐに実装できた。

http://developer.android.com/guide/topics/ui/menus.html