2010年10月31日日曜日

AndroidのCameraアプリ


Androidのカメラアプリだけど、
思いの他はまりました。。。なのでメモとっておきます。
HTC Desire(2.2)で動作確認したアプリを貼り付けときます。
SampleUseCamera.tar.gz
写真とって、保存するだけです。

中身ですが、
デベロッパーサイトには書いてあるんだけど、
意外と他のサイトやサンプルでは違うことが書いてあったりしたのがManifest

uses-permission android:name="android.permission.CAMERA"
uses-feature android:name="android.hardware.camera"
uses-feature android:name="android.hardware.camera.autofocus"
が必要なのと、以下の二つも念のため追加しました。
uses-feature android:name="android.hardware.camera.flash"
uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"


で、一番ハマッたのがシャッターの処理だ。
Webや本にあるサンプルだと
シャッターを切ったときにアプリが落ちる。。。だけじゃなく
端末のリソースを解放せずに落ちるので、端末全体でカメラが使えなくなったり。。。
Camera.autoFocusで設定するコールバックの中で、
Camera.takePictureを実行するのが一般的のようだが、
TakePicture後にすぐにもう一度Camera.autoFocusが実行されると落ちるし、
他のActivityを立ち上げようとすると落ちるし。。。
根本的な原因はわからないですが、
画面をタッチしてシャッターを切るようにしてあるコードで、
DOWNで切って、UPでも切ってしまっているのが問題の模様。
なので、


void takePictureAutoFocus(ShutterCallback shutter, PictureCallback raw, PictureCallback jpeg) {
_shutter = shutter;
_raw = raw;
_jpeg = jpeg;

synchronized (lockObject) {
if (takingpicture != false) {
return;
}
takingpicture = true;
}

camera.autoFocus(new AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, final Camera camera) {
camera.takePicture(_shutter, _raw, _jpeg);

new Thread() {
@Override
public void run() {
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
}
synchronized (lockObject) {
takingpicture = false;
}
if (_auto_focus_listener != null) {
_auto_focus_listener.onTakenPicture();
}
camera.startPreview();
}
}.start();
}
});
}

みたいにして、ちょっと時間がたつまで待ってます。
本当は、もっとちゃんとタイミング計りたいけど、
コードが結合する。。。ので、まあいいか。
CameraPreviewに書いてありますが、

private static final int PREVIEW_WIDTH = 800;
private static final int PREVIEW_HEIGHT = 480; // 800x480 = WVGA
private static final int PICTURE_WIDTH = 1280;
private static final int PICTURE_HEIGHT = 768;

みたいに、端末の画面サイズ(コード的にはカメラのpreviewのサイズ)がWVGA
保存する写真のサイズが1280x768にしてあります。

Java自体なれてないので、変なとこあるとは思いますが、
とりあえずは動くサンプルということで。

2010年10月25日月曜日

dispatchKeyEventについて


androidのkeyイベントについて調べたので、
忘れないようにメモしとく。

結論から書くと、androidのキーイベントの
イベント配信の方法は、GTKと同じで、
親から順番にイベントが通知されていき
最終的にフォーカスが当たっている
もしくは当たっていたアイテムにキーイベントが通知される。
といったものだ。
うーん。子供からキーイベントが通知されて、
親にバブルアップしていったほうが拡張性が高いんだがな。。。


アプリを作る人に最初に見えるのは、
ActivityのdispatchKeyEventで、
そこからGroupView->View->ユーザ定義のView
といった具合だ。
なので、
上下キーをこっちのViewに通知して
左右キーはあっちのViewに通知する
みたいな処理をしようと思ったら、
ActivityでdispatchKeyEventをOverrideして、
そこでイベント処理をしてやらなければいけない。
しかもフォーカス依存になっているから、
そのたびにフォーカスの移動しなければいけないのでは。。。
やっぱandroidって、タッチパネル前提のプラットフォームだなぁと
改めて感じた。

ちなみに、確認のために書いてみたのは
下のようなコードです。


HelloAndroid.java

package com.example.helloandroid;

import java.util.Random;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.content.Intent;


public class HelloAndroid extends Activity{

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_UP) {
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_0:
Log.v("HelloAndroid","Activity KEYCODE_0");
return true;
default:
}
}
Log.v("HelloAndroid","Activity");
return super.dispatchKeyEvent(event);
}

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);


Button b_true = (Button)findViewById(R.id.button_true);
b_true.setOnKeyListener(new OnKeyListener(){
public boolean onKey(View v, int keyCode, KeyEvent event){
if (event.getAction() == KeyEvent.ACTION_DOWN) {
Log.v("HelloAndroid","button true");
return true;
}
return false;
}
});

Button b_false = (Button)findViewById(R.id.button_false);
b_false.setOnKeyListener(new OnKeyListener(){
public boolean onKey(View v, int keyCode, KeyEvent event){
if (event.getAction() == KeyEvent.ACTION_DOWN) {
Log.v("HelloAndroid","button false");
}
return false;
}
});

ListView listView = (ListView) findViewById(R.id.listview);
String[] items = new String[] {"First", "Second", "Third", "Fourth"};

ArrayAdapter adapter = new ArrayAdapter (this,
android.R.layout.simple_list_item_1,
items);

listView.setAdapter(adapter);
listView.setOnKeyListener(new OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event){
if (event.getAction() == KeyEvent.ACTION_DOWN) {
Log.v("HelloAndroid","listView false");
}
return false;
}
});

LinearLayout linear = (LinearLayout)findViewById(R.id.parent);
linear.setOnKeyListener(new OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event){
if (event.getAction() == KeyEvent.ACTION_DOWN) {
Log.v("HelloAndroid","linear false"); // これは永久に出ません。なぜならLinearLayoutではdispatchしているだけだから。
}
return false;
}
});
}
}


ほかにも色々書いてあったコードから、
抜き取ったので、きちんと動くかはナゾ。

いかは、コールバック

HelloAndroid [Android Application]
HelloAndroid [Android Application]
DalvikVM[localhost:8617]
Thread [<3> main] (Suspended (breakpoint at line 97 in HelloAndroid$5))
HelloAndroid$5.onKey(View, int, KeyEvent) line: 97
ListView(View).dispatchKeyEvent(KeyEvent) line: 3678
ListView(ViewGroup).dispatchKeyEvent(KeyEvent) line: 746
ListView.dispatchKeyEvent(KeyEvent) line: 1943
LinearLayout(ViewGroup).dispatchKeyEvent(KeyEvent) line: 748
FrameLayout(ViewGroup).dispatchKeyEvent(KeyEvent) line: 748
LinearLayout(ViewGroup).dispatchKeyEvent(KeyEvent) line: 748
PhoneWindow$DecorView(ViewGroup).dispatchKeyEvent(KeyEvent) line: 748
PhoneWindow$DecorView.superDispatchKeyEvent(KeyEvent) line: 1655
PhoneWindow.superDispatchKeyEvent(KeyEvent) line: 1102
HelloAndroid(Activity).dispatchKeyEvent(KeyEvent) line: 2038
HelloAndroid.dispatchKeyEvent(KeyEvent) line: 40
PhoneWindow$DecorView.dispatchKeyEvent(KeyEvent) line: 1631
ViewRoot.deliverKeyEventToViewHierarchy(KeyEvent, boolean) line: 2368
ViewRoot.handleFinishedEvent(int, boolean) line: 2338
ViewRoot.handleMessage(Message) line: 1641
ViewRoot(Handler).dispatchMessage(Message) line: 99
Looper.loop() line: 123
ActivityThread.main(String[]) line: 4363
Method.invokeNative(Object, Object[], Class, Class[], Class, int, boolean) line: not available [native method]
Method.invoke(Object, Object...) line: 521
ZygoteInit$MethodAndArgsCaller.run() line: 860
ZygoteInit.main(String[]) line: 618
NativeStart.main(String[]) line: not available [native method]
Thread [<13> Binder Thread #2] (Running)
Thread [<11> Binder Thread #1] (Running)
Thread [<15> Binder Thread #3] (Running)

2010年10月8日金曜日

HTC Desire(X06HT)でCloud to Device Messaging

10月8日にHTC Desire(H06HT)がandroid 2.2にアップデートされて、
HD動画が撮影できるようになったり、
標準のブラウザのWLAN切り替え時の動作の不具合が改修されていたり、
microSDからアプリを起動できるようになったり、
追加された機能はいろいろあったけど、
個人的に一番気になっていたのは、
Cloud to Device Messaging
簡単にいうと端末側に対してリモートからintentを発行する仕組みだ。
このAPIに対応したアプリを作るには、サーバも用意しなければいけないが、
今すぐ利用できるものとして、
chrometophone
があるので試してみた。

以下試した動画

ちょっと解りにくいけど、
画面右上のchrometophoneのアイコンをクリックすると
「端末に転送しました」のポップアップが出て
chromeで開いているページのURL(動画ではyahoo)をandroid端末に送って、
intentを発行してブラウザを立ち上げている。
スゲー。
この仕組みを使えば、android搭載の携帯端末からintentを発行して、
android搭載のテレビやSTBをコントロールするといったことが可能そう。

ついでにHD動画もアップしてみる。
撮影するときにコマが飛ぶことがあるみたいです。

あんまりいい動画じゃないですね。。。そのうち清流での動画でもとりたいです。