2011年1月3日月曜日

WebViewで画像を表示



リモートの画像を読み込む方法として、
ImageViewを使うよりもWebViewを使うほうが良いのではないかと思ったので、
サンプルソースを書いてみた。
WebViewImageLoad01.tar.gz


AsyncTaskを利用して、バックグラウンドで
mClient = new DefaultHttpClient();
mGetMethod = new HttpGet();
mGetMethod.setURI(new URI(uri));
HttpResponse resp = mClient.execute(mGetMethod);
InputStream is = resp.getEntity().getContent();
Bitmap bit = createBitmap(is);
のようにする手もあるが、
uriに指定された画像が、大きすぎてメモリ不足になったりしたときに落ちる。。。
WebViewを利用しておけば、この点はWebkitのほうで何とかしてくれる?とか
思ったので、とりあえずやってみた。
でも、画像が読み込めなかったときに、標準ブラウザの「?」が表示されちゃうから
やっぱり無しかな。。。

loadImageは、画像をhttpで取得して表示
loadImage2は、トリミングして表示
loadImageFromSDは、sdcardから取得した画像を表示
loadImageFromSD2は、トリミングして表示
トリミング自体は、CSSで記述しています。

表示してみるとわかるが、
setBackgroundColor(Color.parseColor("#00000000"))を利用することで
WebViewの背景を透明にできる。さらにlayoutのファイルで
backgroundを指定するれば、色つきの透過とか色々できる模様

上がloadImageで、下がloadImage2。
ちなみに上記スクリーンショットは
eclair-x86で、ubuntuでつくったイメージをWindowsのVMplayerで実行しているところ。

もとの画像は以下のもの。(ちなみに戸田恵梨香さん。勝手に使っているけど平気かな。。。)



CustomWebViewClientは、シーケンスを見るためにWebViewClientを拡張しただけです。
package com.matsuhiro.android.webimageload01;

import java.io.File;

import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Environment;
import android.webkit.WebView;

public class WebImageload01 extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        WebView web1 = (WebView) findViewById(R.id.web1);
        loadImage(web1, "http://img.article.pchome.net/00/26/58/73/pic_lib/wm/Toda_Erika1063.jpg");
        
        WebView web2 = (WebView) findViewById(R.id.web2);
        loadImage2(web2, "http://img.article.pchome.net/00/26/58/73/pic_lib/wm/Toda_Erika1063.jpg");
        
        WebView web3 = (WebView) findViewById(R.id.web3);
        loadImageFromSD(web3, "DCIM/100MEDIA/IMAG0077.jpg");
        
        WebView web4 = (WebView) findViewById(R.id.web4);
        loadImageFromSD2(web4, "DCIM/100MEDIA/IMAG0077.jpg");
        
    }
    
    private void loadImage(final WebView webview, final String image_uri) {
     final String html_src = "<html><head><style type=\"text/css\">body { margin: 0px; background: transparent; background-color: transparent;}</style></head><body><img src=\""+image_uri+"\" width=\"210\" height=\"210\"/></body></html>";
     webview.setBackgroundColor(Color.parseColor("#00000000"));
     new Thread() {
      @Override
      public void run() {
       CustomWebViewClient clieant = new CustomWebViewClient();
       webview.setWebViewClient(clieant);
          webview.loadData(html_src, "text/html", "UTF-8");
      }
     }.start();
    }
    
    private void loadImage2(final WebView webview, final String image_uri) {
     final String html_src = "<html><head><style type=\"text/css\">body { margin: 0px; background: transparent; background-color: transparent;}p.thumb a{ display:block; float:left; width:210px; height:210px; line-height:210px; overflow:hidden; position:relative; z-index:1}p.thumb a img{ float:left; position:absolute; top:0px; left:-32px;} </style></head><body><p class=\"thumb\"><a href=\"#\"><img src=\""+image_uri+"\" width=\"280\" height=\"210\"/></a></p></body></html>";
     webview.setBackgroundColor(Color.parseColor("#00000000"));
     new Thread() {
      @Override
      public void run() {
       CustomWebViewClient clieant = new CustomWebViewClient();
       webview.setWebViewClient(clieant);
          webview.loadData(html_src, "text/html", "UTF-8");
      }
     }.start();
    }
    
    private void loadImageFromSD(final WebView webview, String file_name) {
     final String html_src = "<html><head><style type=\"text/css\">body { margin: 0px; background: transparent; background-color: transparent;} </style></head><body><img src=\""+file_name+"\" width=\"210\" height=\"210\"/></body></html>";
     webview.setBackgroundColor(Color.parseColor("#00000000"));
     final File directory = Environment.getExternalStorageDirectory();
     new Thread() {
      @Override
      public void run() {
       CustomWebViewClient clieant = new CustomWebViewClient();
       webview.setWebViewClient(clieant);
       webview.loadDataWithBaseURL("file://"+directory.getAbsolutePath()+"/", html_src, "text/html", "UTF-8", "");
      }
     }.start();
    }
    
    private void loadImageFromSD2(final WebView webview, String file_name) {
     final String html_src = "<html><head><style type=\"text/css\">body { margin: 0px; background: transparent; background-color: transparent;}p.thumb a{ display:block; float:left; width:210px; height:210px; line-height:210px; overflow:hidden; position:relative; z-index:1}p.thumb a img{ float:left; position:absolute; top:-60px; left:0px;} </style></head><body><p class=\"thumb\"><a href=\"#\"><img src=\""+file_name+"\" width=\"210\" height=\"351\"/></a></p></body></html>";
     webview.setBackgroundColor(Color.parseColor("#00000000"));
     final File directory = Environment.getExternalStorageDirectory();
     new Thread() {
      @Override
      public void run() {
       CustomWebViewClient clieant = new CustomWebViewClient();
       webview.setWebViewClient(clieant);
          webview.loadDataWithBaseURL("file://"+directory.getAbsolutePath()+"/", html_src, "text/html", "UTF-8", "");
      }
     }.start();
    }
    
}



レイアウトは以下のような感じ。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent">
 <ScrollView  
    android:id="@+id/ScrollView"  
    android:layout_height="fill_parent"  
    android:layout_width="fill_parent">
    <LinearLayout
   android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent" 
  android:background="@drawable/icon">
   <TextView  
       android:layout_width="fill_parent" 
       android:layout_height="wrap_content" 
       android:text="@string/hello"/>
      <WebView android:id="@+id/web1"
       android:layout_width="300dp" 
       android:layout_height="300dp"
       android:background="#2A0000FF" />
      <WebView android:id="@+id/web2"
       android:layout_width="300dp" 
       android:layout_height="300dp"
       android:focusable="false" 
       android:background="#2A00FFFF"/>
      <WebView android:id="@+id/web3"
       android:layout_width="300dp" 
       android:layout_height="300dp"
       android:background="#2AFF00FF" />
      <WebView android:id="@+id/web4"
       android:layout_width="300dp" 
       android:layout_height="300dp"
       android:focusable="false"
       android:background="#2A00FF00" />
  </LinearLayout>
    </ScrollView>
</LinearLayout>

2010年12月1日水曜日

android.permission.INJECT_EVENTS



androidに外部からイベントを流せたら面白いな。
とか思っていて、Event周りを調べていたけど、
よくよく考えると、Androidのテストフレームワークって
テスト用のパッケージ作って、ソコからUI操作するし、
Monkeyとか言うテストツールも外部からイベント流しているな。
とか思ったので、Monkeyのソースを見てみたら、
injectKeyEventとかがあった。
Serviceの口として
// These can only be called when injecting events to your own window,
// or by holding the INJECT_EVENTS permission.
boolean injectKeyEvent(in KeyEvent ev, boolean sync);
boolean injectPointerEvent(in MotionEvent ev, boolean sync);
boolean injectTrackballEvent(in MotionEvent ev, boolean sync);
見たいにIWindowManager.aidlに定義してある。
なんか、外部からいじれるっぽいな。
INJECT_EVENTS
にもマニフェストの説明として普通にINJECT_EVENTSについて説明あるし。
最初からデベロッパーサイトのManifestのpermissionについての説明
を読んどけば、Androidで何が可能なのか解ってたかも。。。

2010年11月29日月曜日

hardwareのeventを拡張しようと思ったら



まだ、メモ書き程度だけど、調べたことを忘れるのもイヤなのでメモしておく。
動かしたわけではなく、あくまで読んでみた結果だけど。
EventLoopの肝は、
eclair/frameworks/base/services/java/com/android/server/KeyInputQueue.java
の Thread mThread = new Thread("InputDeviceReader") {
の部分。
ここで、hardwareからイベントを取得している。
具体的には、readEventで、こいつの実態はJNIを利用して、
android_server_KeyInputQueue_readEvent()
といった関数として定義されている。
この関数の中でさらにEventHub::getEventをコールすることで
/dev/からいろんなデバイスのイベントを取得している。
EventHub::getEventのなかで、イベントのcodeやscancodeを作ったりしている。
それを上のmThreadで読んでいるわけだ。
で、ここからイベント配信をしている。
詳しい仕組みは、今後読み解いていく。

もし、ハードウェア的にデバイスを足したいときは、
JNI層から手を入れる必要があり、
擬似的にイベントを発行するデバイスをJava層で作ろうとしたら、
readEventあたりを書き換えることになりそう。
もしくは、別スレッドを作って、ソコからイベントキューに勝手にイベントを足すとか。

2010年11月24日水曜日

Xacti買った

スノーボードのシーズンがやってきました。
今年は、ボードやっているときにビデオでもとろうかと思って
Xacti買いました。
HTC DesireのカメラもHD動画対応なので、
両方を持って撮影して、比較してみた。

まずDesire


んで、こっちがXacti


当然だけどXactiの方がいいですね。

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動画もアップしてみる。
撮影するときにコマが飛ぶことがあるみたいです。

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