2011年2月9日水曜日

AndroidでGoogle Web履歴をHTTP GET


Gfootというアプリをマーケットにリリースした。
アプリをリリースしたけど、何をやったのか?調べたのか?
書き残しておかないと忘れてしまいそうなので、
そろそろまとめてみる。
・ブラウザを利用しないでCookieを解決する
・RSSのパース
がメインかな?

GoogleのWeb履歴を見る際のCookieの扱いについては、ここを参照しました。
このサイトによると、履歴を見るためにログインするわけだが、そのためのCookieをログイン画面の
HTMLをGETで取得することで、解決していることがわかる。
また、ログインする際にformの情報をPOSTするわけだが、その情報も同時に取得している。
ただ、これだけだとAndroid上でどうやってログイン処理すればよいのかが
具体的にはわからないので、ここを参考にしてみた。
上記二つのサイトを参考に組み合わせてAndroidからGoogle Web履歴にアクセスしている。
以下は、説明に要らない部分など削っているので、変なところもあるかと思うが、
基本的には下記で得られた_HttpClient(DefaultHttpClientクラス)を利用すれば、履歴の取得が可能だ。
ちなみにHTMLをパースしてinput部分を取得するために
jericho-html-3.1.jarを利用しています。(LGPLなので使うだけならソースの公開義務はないはず。)
それと当然ですが、ネットワーク系の処理をするときにUIスレッドで処理すると
操作性が悪くなるので、AsyncTaskを利用してます。

@Override
  protected DefaultHttpClient doInBackground(String... params) {
   String email = params[0];
   String passwd = params[1];
   
   _HttpClient.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.BROWSER_COMPATIBILITY);
   _HttpClient.getParams().setParameter("http.connection.timeout", 5000);
   _HttpClient.getParams().setParameter("http.socket.timeout", 3000);
   
   HttpGet httpget = new HttpGet(https://www.google.com/accounts/ServiceLogin?hl=ja&nui=1&service=hist);
   HttpResponse response;
   String result = null;
   try {
    response = _HttpClient.execute(httpget);
    int status_code = response.getStatusLine().getStatusCode();
    Log.v("Gfoot","status_code="+status_code);
    if (status_code < 400){
     InputStream istream = response.getEntity().getContent();
     InputStreamReader reader = new InputStreamReader(istream);
     BufferedReader objBuf = new BufferedReader(reader);
     StringBuilder builder = new StringBuilder();
     String sLine;
     while((sLine = objBuf.readLine()) != null){
      builder.append(sLine);
     }
     result = builder.toString();
     istream.close();
    } else {
     _HttpClient = null;
    }
   } catch (ClientProtocolException e) {
    _HttpClient = null;
    e.printStackTrace();
   } catch (IOException e) {
    _HttpClient = null;
    e.printStackTrace();
   }
   if (result == null) {
    return null;
   }
   
   
   String name;
   String value;
   Source source = new Source(result);
   List linkElements = source.getAllElements(HTMLElementName.INPUT);
   int count = linkElements.size();
   List nameValuePair = new ArrayList(count);
   for (Element linkElement : linkElements) { 
    name = linkElement.getAttributeValue("name");
    value = linkElement.getAttributeValue("value");
    if ("Email".equals(name)) {
     value = email;
    }
    if ("Passwd".equals(name)) {
     value = passwd;
    }
    nameValuePair.add(new BasicNameValuePair(name, value));
   }
   // この段階でPOSTするために必要な情報は、nameValuePairに含まれている。
   
   HttpPost httppost = new HttpPost(https://www.google.com/accounts/ServiceLoginAuth?service=hist);
   try {
    httppost.setEntity(new UrlEncodedFormEntity(nameValuePair));
    response = _HttpClient.execute(httppost);
   } catch (UnsupportedEncodingException e) {
    _HttpClient = null;
    e.printStackTrace();
   } catch (ClientProtocolException e) {
    _HttpClient = null;
    e.printStackTrace();
   } catch (IOException e) {
    _HttpClient = null;
    e.printStackTrace();
   }
   
   return _HttpClient;
  }

ここでログインの際に
https://www.google.com/accounts/ServiceLogin?service=hist
というのを利用して、ログインのためのCookieを取得しているが、
これはどうやって知るのか?履歴以外はどうなのか?
気になるところだと思う。
このサイトがかなりいろいろな情報をもたらしてくれる。
service=histは履歴をさしているわけだ。

次回は、履歴やBookmarkをRSS形式で取得する部分と
パースについて。
また、おまけでアプリケーション全体において永続的なデータを作る方法。

2011年2月6日日曜日

Androidマーケットに初登録


アンドロイドマーケットに初めて登録しました。
マーケットへのリンク
いろいろ調べたことは、また後日に書こうかと思います。

2011年1月27日木曜日

Google Bookmarks API 。。。は無いらしい


Google Bookmarksが便利だし、履歴も含めたアプリ作れないものかと思って少し調べてみた。
結論としては、公式にはAPIはない模様。。。

ただし。XML形式でデータは取れる。

https://www.google.com/bookmarks/lookup?start=25&output=xml
lookup → リストを取得の意

https://www.google.com/bookmarks/find?q=maps.google.com&start=25&output=xml
find → 検索の意
q=maps.google.com → キーワード"maps.google,com"で検索
start=25 → 25個目から取得
utput=xml → XML形式で取得


本当に欲しかったのは履歴なので、それは後日調べることにする。

以下追記
履歴は
https://www.google.com/history/find?output=rss&st=maps&q=
で、RSS形式でデータが取れる模様。
BookmarkもRSS形式で取得可能だった。
st=maps → google mapsのデータを取得している。
st部分を変更すれば、他の種類のデータが取れそう。
ただ、wgetは出来なそう。
ここを見たけど、ブラウザのCookieをりようすれば可能との事。

2011年1月18日火曜日

Google Bookmarksを共有


最近知ったんだけど、
Google Bookmarksを旅行での写真や動画を共有するのにちょうどいい。

写真→picasaへアップロード
動画→youtubeとかほかの動画サイトへアップ

で、ばらばらなものを一つにまとめる。
かつ、プライバシーの観点から閲覧を許可制にする。
これのためのGoogle Bookmarks
https://www.google.com/bookmarks/l
にアクセスすと英語表記のページになる。(最後の「l」が大事)
label等で分けたりしたBookmarkにチェックをつけて、
Copy to listを選択して、リスト作成したり、すでにあるリストに追加したり。

作ったリストは、左上の「My lists」から見られる。
リストを共有したい場合は、リストを表示した状態で、
Shareを選択すると表示されるポップアップで、
「People with access」タブの「Invite people」に
見せても良い人のGmailのアドレスを打ち込んでinviteでOK。

visibilityはprivateにするのをお忘れなく。

結構便利。

2011年1月5日水曜日

android build on 32bit (froyo/gingerbread)


froyo(2.2)から、Androidのソースをbuildしようとしても
64bitじゃないからムリー。みたいな事をいわれてビルドできなかった。
しばらく放置してたけど、もう一度調べてみたら、
出来たのでメモをとっておく。

1)
$ repo init -u git://android.git.kernel.org/platform/manifest.git -b froyo
もしくは、
$ repo init -u git://android.git.kernel.org/platform/manifest.git -b gingerbread
でソースコード取得。

2)
$ repo sync

3)
64bitの記述があるところを修正
32-bit support in froyo/2.2を参考にすればいける。

./build/core/main.mk
の以下のように編集
ifeq ($(BUILD_OS),linux)
build_arch := $(shell uname -m)
-ifneq (64,$(findstring 64,$(build_arch))) ←コメントアウトしたり削除したり
+ifneq (i686,$(findstring i686,$(build_arch))) ←追加するコード
$(warning ************************************************************)
$(warning You are attempting to build on a 32-bit system.)

4)
以下の4つの中に
./external/clearsilver/cgi/Android.mk
./external/clearsilver/java-jni/Android.mk
./external/clearsilver/util/Android.mk
./external/clearsilver/cs/Android.mk

LOCAL_CFLAGS += -m64
LOCAL_LDFLAGS += -m64
という記述があるので、コメントアウトするか
LOCAL_CFLAGS += -m32
LOCAL_LDFLAGS += -m32
のように32bitに書き換える。

5)
$ make -j2
で、コンパイル開始。
コアの数や作業状況によっては、-j4としたほうがコンパイルは早くなる。


手順4)を抜かしてみたけど自分の環境では、
64bit用のヘッダーがないよー。みたいなエラーが出たので、
ダメみたいです。

最後は、以下を実行すればemulatorが立ち上がる。
./out/host/linux-x86/bin/emulator -kernel ./prebuilt/android-arm/kernel/kernel-qemu -system ./out/target/product/generic -ramdisk ./out/target/product/generic/ramdisk.img -data ./out/target/product/generic/userdata.img -skindir ./sdk/emulator/skins/ -skin HVGA


froyoでもgingerbreadでも同じ手順でbuild出来ました。

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で何が可能なのか解ってたかも。。。