Showing posts with label android. Show all posts
Showing posts with label android. Show all posts

Tuesday, July 26, 2011

Hack for Japan 遠野会場へ行ってきました

遠野でのHack For Japanイベントに行ってきました。




作ったもの





36:30ぐらいから。

コメントとして,

  • 状況は変化していくので,updateもできるような機能がほしい
  • PersonFinderと連携してほしい
  • 緊急退避情報として,現在地付近のこれまでの津波の高さ(「波高」,「浸水高」,「遡上高」?)を表示して,近くの高台の場所を緊急避難場所として表示する。過去の教訓をもとにした資料に,1時間は動くなという石碑があったのでそれを参考に。

(不足していたらご指摘いただけませんでしょうか)などがあり,今後の課題とします。

改善点

開発環境の準備

今回,現地でAndroid SDKをインストールしたのですが,Step 4のAdding Platforms and Other Componentsで,全てのコンポーネントをインストールしようとして,この作業で1,2時間ぐらいかかった。今回,アイデアソンでしゃべった内容を当日作ることになったので,必ずしも全員が開発環境わけではないので,できれば事前告知しておけばよかった。

妥協リストの作成

まずは動くものを成果物にあげようというコンセプトで,実装する際に妥協したこともあった。たとえば,設定項目(送信先メールアドレス,SMS送信先電話番号)は,保存できていない。このリストを共有できれば,後日協力者を集めるのに役立てられるだろう。

今回遠野まごころネットさんの方とお話する機会があり,支援物資管理などICTの技術が利用できるところは多々あるが,瞬発力も必要ということで,速やかに動くものを提供する,柔軟に変更点に対応するなど,表に出てくることが必要かと思う。

Wednesday, May 18, 2011

Google APIs Client Library for JavaのAndroid向けライブラリのソースを公開しました。

英語版の日本語版です。


Google APIs Client Library for Java
のAndroid向けライブラリのソースを公開しました。
(URL は http://code.google.com/p/android-library-google-api-java/ です。)


単に,Google SpreadSheets に対して,OAuth 1.0a で

  • 新しいSpreadSheetの作成
  • worksheet entryの追加
  • セルの更新
ができます。他のサービスについても作成していきますが,なんせあまりいいエンジニアじゃないので,スピードは相当遅いです。JavaDocも何も作成していなくて申し訳ございません。 何か,要望,提案や間違いの指摘,バグレポートがありましたら こちらよりご連絡ください。

Published my android library to use Google APIs Client Library for Java

I upload sources of my android library to use Google APIs Client Library for Java.


This is a just sample for Google Spread Sheet, but it can create new spreadsheet and worksheet, rename the title of worksheet entry, and put cells on your worksheet with OAuth1.0a.


URL is http://code.google.com/p/android-library-google-api-java/.


I am preparing JavaDoc and classes for other services. But I am not good engineer, so speed to update migh be very slow.


Please contact me if you have any requests, suggestions, corrections and bug reports.

Sunday, May 08, 2011

Google Spreadsheets を Androidから扱う (5) DataModelの定義

Google-API-Java-Clientを用いて,Google Docsにアクセスするためには,GData-Java-Clientとは異なり,自分でデータモデルを定義する必要があります。

First, you need to invest in writing a custom data model for the Google API. Please read the JavaDoc for the new XML data model in google-api-java-client.

参照元 (What if I have a larde code base that uses gdata-java-client)

ここでは,次のfeedを表すデータモデルを具体的に定義してみましょう。feed は
QueryResponses
に掲載されているデータを利用しました。



  http://www.example.com/feed/1234.1/posts/full
  2005-09-16T00:42:06Z
  Books and Romance with Jo and Liz
  
  
  
  
  
    Elizabeth Bennet
    liz@gmail.com
  
  Example Generator Engine
  2
  0
  
    http://www.example.com/feed/1234.1/posts/full/4521614025009481151
    2005-01-09T08:00:00Z
    2005-01-09T08:00:00Z
    
    This is the title of entry 1009
    
      
This is the entry body of entry 1009
Elizabeth Bennet liz@gmail.com
http://www.example.com/feed/1234.1/posts/full/3067545004648931569 2005-01-07T08:00:00Z 2005-01-07T08:02:00Z This is the title of entry 1007
This is the entry body of entry 1007
Elizabeth Bennet liz@gmail.com

まず,root要素 feed について定義していきます。

public class GoogleDocFeed {
    // TBD
}

feed要素の属性にある名前空間に関係した属性,"xmlns:atom","xmlns:openSearch","xmlns:gd" は今回扱いません。
一方,属性 "gd:etag" は今後使うことも考慮して,データモデルを定義します。
import com.google.api.client.util.Key;

public class GoogleDocFeed {
    @Key("gd:etag")
    public string gd_etag;
    // TBD
}

つぎに,<id>http://www.example.com/feed/1234.1/posts/full</id> のように,属性を持たず,かつ内部に子要素を含まない要素の
データモデルを定義していきます。

    @Key  
    public string id;      // for http://www.example.com/feed/1234.1/posts/full

    @Key  
    public string updated; // for 2005-09-16T00:42:06Z

つぎに,link要素を定義していきます。feed要素にはlink要素を子要素として複数持つことができますので,


と定義します。class Link は以下のように定義します。


次に,author要素を定義します。
public class Author {
    /* for 
     *  
     *    Elizabeth Bennet
     *    liz@gmail.com
     *  
     */

    @Key
    public String name;

    @Key
    public String email;
}

その他に,属性と要素の内容にテキストコンテンツが与えられているtitle要素を定義します。

JavaDoc Package com.google.api.client.googleapis.xml.atom
に,
The optional value parameter of this @Key annotation specifies the XPath name to use to represent the field. For example, an XML attribute a has an XPath name of @a, an XML element <a> has an XPath name of a, and an XML text content has an XPath name of text().
とありますように,@Key("text()") と指定して,テキストコンテンツの内容を表します。

public class Title {
    /* for 
     *   This is the title of entry 1007
     */

    @Key("@type")
    public String type;

    @Key("text()")
    public String context;
}

他の要素も同様にして,上記で与えられている feed 要素を定義するデータモデル GoogleDocFeed が定義できます。


import com.google.api.client.util.Key;

public class Category {

    /*  
     *  
     */

    @Key("@scheme")
    public String scheme;

    @Key("@term")
    public String term;

}

public class Content {

    /*
     *  
     *    
This is the entry body of entry 1007
*
*/ @Key("@type") public String type; @Key("text()") public String content; } public class Link { /* * */ @Key("@rel") public String rel; @Key("@type") public String type; @Key("@href") public String href; } public class Author { /* for * * Elizabeth Bennet * liz@gmail.com * */ @Key public String name; @Key public String email; } public class Title { /* for * This is the title of entry 1007 */ @Key("@type") public String type; @Key("text()") public String context; } public class GoogleDocEntry { @Key public string id; @Key public string published; @Key public string updated; @Key public Category category; @Key public Title title; @Key public Content content; @Key("link") public List links; @Key public Author author; } public class GoogleDocFeed { @Key("gd:etag") public string gd_etag; @Key public string id; // for http://www.example.com/feed/1234.1/posts/full @Key public string updated; // for 2005-09-16T00:42:06Z @Key("link") public List links; @Key public Author author; @Key public Generator generator; @Key("openSearch:totalResults") public int totalResults; @Key("openSearch:startIndex") public int startIndex; @Key("entry") public List entries; }


今回は,具体的なコンテントをベースにしてデータモデルを定義しましたが,
schema は用意されていますので(例
Google Spreadsheets API Reference Guide (v3.0)
),それを参考にして各自でデータモデルを定義できます。


Monday, March 28, 2011

ContentProviderのテストの仕方

今回,BottomPriceProvider というContentProviderの単体テストを行なう BottomPrivceProviderTest を作成してみましたので,そのメモを記していきます。

クラスの作成

ProviderTestCase2>T extends android.content.ContentProvider<を継承して作成します。
public class BottomPriceProviderTest extends ProviderTestCase2 {
...
}

コンストラクタの生成

super class から コンストラクタを自動生成すると,次のようなコードが生成されます。
 public BottomPriceProviderTest(Class providerClass,
            String providerAuthority) {
        super(providerClass, providerAuthority);
        // TODO Auto-generated constructor stub
    }
面倒なので,引数を指定しない版も作成しておきます。
 public BottomPriceProviderTest() {
        super(BottomPriceProvider.class, BottomPriceProvider.AUTHORITY);
        // TODO Auto-generated constructor stub
    }
ここでの,BottomPriceProvider.AUTHORITY は ContentProviderを提供する際に指定するContentのAuthority名です。

setUp, tearDown

setUpでは,フィールド mMockContext にgetMockContext()で得られるContextを格納しておきます。
tearDownでは特になにもしません。
    private Context mMockContext;
    @Override
    protected void setUp() throws Exception {
        // TODO Auto-generated method stub
        super.setUp();
        mMockContext = getMockContext();
    }

    @Override
    protected void tearDown() throws Exception {
        // TODO Auto-generated method stub
        super.tearDown();
    }

テスト

まずは簡単なところで,insert/query/deleteのテストを書いてみます。 テスト用のContentValuesを挿入,検索し,その検索結果が挿入したContentValuesの内容と一致することを確認し,最後にテスト用ContentValuesを削除します。
    public void testInsertDeleteRecord() {
        final ContentResolver resolver = mMockContext.getContentResolver();
        final Uri uri = BottomPrice.Prices.CONTENT_URI;
        ContentValues cv = new ContentValues();
        int price = 480;
        String jan_code_str = "4901111725133";
        cv.put(BottomPrice.Prices.PRICE, price);
        cv.put(BottomPrice.Prices.JAN_CODE, jan_code_str);
        Uri insertUri = resolver.insert(uri, cv);
        String price_id = insertUri.getPathSegments().get(1);
        
        // Query
        Uri queryUri = Uri.withAppendedPath(uri, price_id);
        String[] projection = new String[] {
                BottomPrice.Prices._ID,
                BottomPrice.Prices.PRICE,
                BottomPrice.Prices.JAN_CODE,
            };
        Cursor c = resolver.query(queryUri, projection, null, null, null);
        assertEquals("query resuult", 1, c.getCount());
        c.moveToFirst();
        assertEquals("Jan Code", jan_code_str, c.getString(c.getColumnIndex(BottomPrice.Prices.JAN_CODE)));
        assertEquals("Price", price, c.getInt(c.getColumnIndex(BottomPrice.Prices.PRICE)));
        c.close();
        
        resolver.delete(queryUri, null, null);
    }

実際にテストを実行してみましょう。
テスト Errorとなり,Failure Trace をみると
android.database.SQLException: Failed to insert row into content://com.damburisoft.android.app.bottomrpice.provider.bottompriceprovider/prices
at com.damburisoft.android.app.bottomrpice.provider.BottomPriceProvider.insert(BottomPriceProvider.java:107)
at android.content.ContentProvider$Transport.insert(ContentProvider.java:150)
at android.content.ContentResolver.insert(ContentResolver.java:629)
at com.damburisoft.android.provider.BottomPriceProviderTest.testInsertDeleteRecord(BottomPriceProviderTest.java:50)
at java.lang.reflect.Method.invokeNative(Native Method)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:169)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:154)
at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:430)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1447)
とあるので,これを直していくことによって自分のContentProviderを創り上げていきます。

Sunday, January 30, 2011

repackage? android app

先日のblog articleで署名を突破できないという指摘を受けました。当然の指摘です。署名を検証するアプリケーション配布サイトですと問題はないのでしょう。
結局は自分が浅はかであることを実感してソースコードがないandroid アプリをいじることができるかどうか,試してみました。ただ,既に知られている情報をただ単に追っただけなので,新規情報量はないです。

参考

まずは,"Push Me"というボタンを押すと,Toastで"Hello World"というmessageを表示するアプリケーションを用意しました。


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Button b = (Button)findViewById(R.id.Button01);
    }

    private class ButtonOnClickListener implements View.OnClickListener {

        @Override
        public void onClick(View v) {
            if (v.getId() == R.id.Button01) {
                Toast.makeText(getApplicationContext(), "Hello World!", Toast.LENGTH_LONG).show();
            }
            
        }
    }

一方,"Push Me"というボタンを押すと,Toastで"Hello World"というmessageを表示すると同時に,call_log を読むアプリケーションを用意しました。同様に,キーとなるコード部分を下に記しておきます。

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Button b = (Button)findViewById(R.id.Button01);
        b.setOnClickListener(new ButtonOnClickListener());
    }

    private class ButtonOnClickListener implements View.OnClickListener {

        @Override
        public void onClick(View v) {
            if (v.getId() == R.id.Button01) {
                Toast.makeText(getApplicationContext(), "Hello World!", Toast.LENGTH_LONG).show();
                queryContentProvidersInfo();
            }
            
        }

        private void queryContentProvidersInfo() {
            String[] projections = new String[] {
                    CallLog.Calls._ID,
                    CallLog.Calls.NUMBER,
                    CallLog.Calls.TYPE,
                    CallLog.Calls.DURATION,
                    CallLog.Calls.DATE,
                    CallLog.Calls.CACHED_NAME,
            };
            
            Uri calls = CallLog.Calls.CONTENT_URI;
            ContentResolver cr = getContentResolver();
            Cursor c = cr.query(calls, projections, null, null, CallLog.Calls.DEFAULT_SORT_ORDER);
            if (c != null) {
                getColumnData(c);
                c.close();
            }
        }

        private void getColumnData(Cursor c) {
            if (c.moveToFirst()) {
                String number;
                int numberColumn = c.getColumnIndex(CallLog.Calls.NUMBER);
                String cashed_name;
                int cashed_nameColumn = c.getColumnIndex(CallLog.Calls.NUMBER);
                do {
                    number = c.getString(numberColumn);
                    cashed_name = c.getString(cashed_nameColumn);
                    Log.d(TAG, "number: " + number);
                    if (cashed_name != null) {
                        Log.d(TAG, "cashed_name: " + cashed_name);
                    }
                } while (c.moveToNext());
            }
        }
    }

apkからbaksmaliを用いて,この部分の処理をdiassembleしたsmali型式コードを以下に貼り付けます。 単にmessageを表示する箇所。

# virtual methods
.method public final onClick(Landroid/view/View;)V
    .registers 5

    invoke-virtual {p1}, Landroid/view/View;->getId()I

    move-result v0

    const/high16 v1, 0x7f05

    if-ne v0, v1, :cond_9b

    iget-object v0, p0, Lcom/damburisoft/app/android/helloworld/a;->a:Lcom/damburisoft/app/android/helloworld/HelloWorldActivity;

    invoke-virtual {v0}, Lcom/damburisoft/app/android/helloworld/HelloWorldActivity;->getApplicationContext()Landroid/content/Context;

    move-result-object v0

    const-string v1, "Hello World!"

    const/4 v2, 0x1

    invoke-static {v0, v1, v2}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;

    move-result-object v0

    invoke-virtual {v0}, Landroid/widget/Toast;->show()V

    :cond_9b
    return-void
.end method

call_logを読む箇所。

# virtual methods
.method public final onClick(Landroid/view/View;)V
    .registers 10

    const/4 v3, 0x0

    const/4 v4, 0x1

    const-string v7, "ReadInfo"

    const-string v6, "number"

    invoke-virtual {p1}, Landroid/view/View;->getId()I

    move-result v0

    const/high16 v1, 0x7f05

    if-ne v0, v1, :cond_9b

    iget-object v0, p0, Lcom/damburisoft/android/app/readinfo/a;->a:Lcom/damburisoft/android/app/readinfo/ReadInfo;

    invoke-virtual {v0}, Lcom/damburisoft/android/app/readinfo/ReadInfo;->getApplicationContext()Landroid/content/Context;

    move-result-object v0

    const-string v1, "Hello World!"

    invoke-static {v0, v1, v4}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;

    move-result-object v0

    invoke-virtual {v0}, Landroid/widget/Toast;->show()V

    const/4 v0, 0x6

    new-array v2, v0, [Ljava/lang/String;

    const/4 v0, 0x0

    const-string v1, "_id"

    aput-object v1, v2, v0

    const-string v0, "number"

    aput-object v6, v2, v4

    const/4 v0, 0x2

    const-string v1, "type"

    aput-object v1, v2, v0

    const/4 v0, 0x3

    const-string v1, "duration"

    aput-object v1, v2, v0

    const/4 v0, 0x4

    const-string v1, "date"

    aput-object v1, v2, v0

    const/4 v0, 0x5

    const-string v1, "name"

    aput-object v1, v2, v0

    sget-object v1, Landroid/provider/CallLog$Calls;->CONTENT_URI:Landroid/net/Uri;

    iget-object v0, p0, Lcom/damburisoft/android/app/readinfo/a;->a:Lcom/damburisoft/android/app/readinfo/ReadInfo;

    invoke-virtual {v0}, Lcom/damburisoft/android/app/readinfo/ReadInfo;->getContentResolver()Landroid/content/ContentResolver;

    move-result-object v0

    const-string v5, "date DESC"

    move-object v4, v3

    invoke-virtual/range {v0 .. v5}, Landroid/content/ContentResolver;->query(Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;

    move-result-object v0

    if-eqz v0, :cond_9b

    invoke-interface {v0}, Landroid/database/Cursor;->moveToFirst()Z

    move-result v1

    if-eqz v1, :cond_98

    const-string v1, "number"

    invoke-interface {v0, v6}, Landroid/database/Cursor;->getColumnIndex(Ljava/lang/String;)I

    move-result v1

    const-string v2, "number"

    invoke-interface {v0, v6}, Landroid/database/Cursor;->getColumnIndex(Ljava/lang/String;)I

    move-result v2

    :cond_60
    invoke-interface {v0, v1}, Landroid/database/Cursor;->getString(I)Ljava/lang/String;

    move-result-object v3

    invoke-interface {v0, v2}, Landroid/database/Cursor;->getString(I)Ljava/lang/String;

    move-result-object v4

    const-string v5, "ReadInfo"

    new-instance v5, Ljava/lang/StringBuilder;

    const-string v6, "number: "

    invoke-direct {v5, v6}, Ljava/lang/StringBuilder;->(Ljava/lang/String;)V

    invoke-virtual {v5, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v3

    invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v3

    invoke-static {v7, v3}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

    if-eqz v4, :cond_92

    const-string v3, "ReadInfo"

    new-instance v3, Ljava/lang/StringBuilder;

    const-string v5, "cashed_name: "

    invoke-direct {v3, v5}, Ljava/lang/StringBuilder;->(Ljava/lang/String;)V

    invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v3

    invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v3

    invoke-static {v7, v3}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

    :cond_92
    invoke-interface {v0}, Landroid/database/Cursor;->moveToNext()Z

    move-result v3

    if-nez v3, :cond_60

    :cond_98
    invoke-interface {v0}, Landroid/database/Cursor;->close()V

    :cond_9b
    return-void
.end method

上記のsmali型式のコードを比較して,call_logの内容を読む処理を前者の単にToastでmessageを表示するアプリケーションに追加してみます。追加した結果のsmali型式ファイルは以下の通りです。

# virtual methods
.method public final onClick(Landroid/view/View;)V
    .registers 10

    const/4 v3, 0x0

    const/4 v4, 0x1

    const-string v7, "ReadInfo"

    const-string v6, "number"

    invoke-virtual {p1}, Landroid/view/View;->getId()I

    move-result v0

    const/high16 v1, 0x7f05

    if-ne v0, v1, :cond_1b

    iget-object v0, p0, Lcom/damburisoft/app/android/helloworld/a;->a:Lcom/damburisoft/app/android/helloworld/HelloWorldActivity;

    invoke-virtual {v0}, Lcom/damburisoft/app/android/helloworld/HelloWorldActivity;->getApplicationContext()Landroid/content/Context;

    move-result-object v0

    const-string v1, "Hello World!"

    const/4 v2, 0x1

    invoke-static {v0, v1, v2}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;

    move-result-object v0

    invoke-virtual {v0}, Landroid/widget/Toast;->show()V

    const/4 v0, 0x6

    new-array v2, v0, [Ljava/lang/String;

    const/4 v0, 0x0

    const-string v1, "_id"

    aput-object v1, v2, v0

    const-string v0, "number"

    aput-object v6, v2, v4

    const/4 v0, 0x2

    const-string v1, "type"

    aput-object v1, v2, v0

    const/4 v0, 0x3

    const-string v1, "duration"

    aput-object v1, v2, v0

    const/4 v0, 0x4

    const-string v1, "date"

    aput-object v1, v2, v0

    const/4 v0, 0x5

    const-string v1, "name"

    aput-object v1, v2, v0

    sget-object v1, Landroid/provider/CallLog$Calls;->CONTENT_URI:Landroid/net/Uri;

    iget-object v0, p0, Lcom/damburisoft/app/android/helloworld/a;->a:Lcom/damburisoft/app/android/helloworld/HelloWorldActivity;

    invoke-virtual {v0}, Lcom/damburisoft/app/android/helloworld/HelloWorldActivity;->getContentResolver()Landroid/content/ContentResolver;

    move-result-object v0

    const-string v5, "date DESC"

    move-object v4, v3

    invoke-virtual/range {v0 .. v5}, Landroid/content/ContentResolver;->query(Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;

    move-result-object v0

    if-eqz v0, :cond_1b

    invoke-interface {v0}, Landroid/database/Cursor;->moveToFirst()Z

    move-result v1

    if-eqz v1, :cond_18

    const-string v1, "number"

    invoke-interface {v0, v6}, Landroid/database/Cursor;->getColumnIndex(Ljava/lang/String;)I

    move-result v1

    const-string v2, "number"

    invoke-interface {v0, v6}, Landroid/database/Cursor;->getColumnIndex(Ljava/lang/String;)I

    move-result v2

    :cond_60
    invoke-interface {v0, v1}, Landroid/database/Cursor;->getString(I)Ljava/lang/String;

    move-result-object v3

    invoke-interface {v0, v2}, Landroid/database/Cursor;->getString(I)Ljava/lang/String;

    move-result-object v4

    const-string v5, "ReadInfo"

    new-instance v5, Ljava/lang/StringBuilder;

    const-string v6, "number: "

    invoke-direct {v5, v6}, Ljava/lang/StringBuilder;->(Ljava/lang/String;)V

    invoke-virtual {v5, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v3

    invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v3

    invoke-static {v7, v3}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

    if-eqz v4, :cond_92

    const-string v3, "ReadInfo"

    new-instance v3, Ljava/lang/StringBuilder;

    const-string v5, "cashed_name: "

    invoke-direct {v3, v5}, Ljava/lang/StringBuilder;->(Ljava/lang/String;)V

    invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v3

    invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v3

    invoke-static {v7, v3}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

    :cond_92
    invoke-interface {v0}, Landroid/database/Cursor;->moveToNext()Z

    move-result v3

    if-nez v3, :cond_60

    :cond_18
    invoke-interface {v0}, Landroid/database/Cursor;->close()V

    :cond_1b
    return-void
.end method

さて,この編集した.smaliファイルでclasses.dexをアセンブルして,HelloWorld.apkのclasses.dexに入れ替えます。
% java -Xmx1G -jar ~/lib/smali-1.2.6.jar -o classes.dex out
% zip ~/HelloWorldApp.apk classes.dex
classed.dexを入れ替えたHelloWorld.apkをもう一度署名しなおして,emulatorにインストールしてみました。実行した結果,SecurityExceptionが発生しました。

E/AndroidRuntime(  251): java.lang.SecurityException: Permission Denial: reading com.android.providers.contacts.CallLogProvider uri content://call_log/calls from pid=251, uid=10041 requires android.permission.READ_CONTACTS
E/AndroidRuntime(  251):  at android.os.Parcel.readException(Parcel.java:1247)
E/AndroidRuntime(  251):  at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:160)
E/AndroidRuntime(  251):  at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:114)
E/AndroidRuntime(  251):  at android.content.ContentProviderProxy.bulkQueryInternal(ContentProviderNative.java:330)
E/AndroidRuntime(  251):  at android.content.ContentProviderProxy.query(ContentProviderNative.java:366)
E/AndroidRuntime(  251):  at android.content.ContentResolver.query(ContentResolver.java:245)
E/AndroidRuntime(  251):  at com.damburisoft.app.android.helloworld.a.onClick(Unknown Source)
E/AndroidRuntime(  251):  at android.view.View.performClick(View.java:2408)
E/AndroidRuntime(  251):  at android.view.View$PerformClick.run(View.java:8816)
E/AndroidRuntime(  251):  at android.os.Handler.handleCallback(Handler.java:587)
E/AndroidRuntime(  251):  at android.os.Handler.dispatchMessage(Handler.java:92)
E/AndroidRuntime(  251):  at android.os.Looper.loop(Looper.java:123)
E/AndroidRuntime(  251):  at android.app.ActivityThread.main(ActivityThread.java:4627)
E/AndroidRuntime(  251):  at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime(  251):  at java.lang.reflect.Method.invoke(Method.java:521)
E/AndroidRuntime(  251):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
E/AndroidRuntime(  251):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
E/AndroidRuntime(  251):  at dalvik.system.NativeStart.main(Native Method)

AndroidManifest.xmlを編集していませんので,当然の結果です。ただ,Content Provider CallLogs.Callにアクセスする機能を追加できたのではないかと思われます。

今回は,パッケージに入っているクラスをsmali型式のファイルを通して編集することによって,動作を変更いたしましたが,編集ではなく新規にクラスを追加して実行させることも可能ではないのでしょうか。 いろいろと検索してみますと,disassembleしてアプリをハックする方法がいろいろとありますので,そんな難しいことではないのかもしれません。

Sunday, January 16, 2011

PackageManager (ApplicationInfo)

Android SDKには, PacageManagerという,デバイス上にインストールされたアプリケーションパッケージに関する情報を扱うクラスが提供されております。
これで,どんな情報が取得できるか,試してみました。

最初は, public abstract List getInstalledApplications (int flags)で得られるApplicationInfoにどんな情報が含まれているかについて調べてみました。


    @Override
    protected void onResume() {
        // TODO Auto-generated method stub
        super.onResume();
        mPackageManager = getPackageManager();
        getApplicationInfoList();
    }

    private void getApplicationInfoList() {
        List installedAppList = mPackageManager
                .getInstalledApplications(PackageManager.GET_META_DATA | 
                        PackageManager.GET_SHARED_LIBRARY_FILES | 
                        PackageManager.GET_UNINSTALLED_PACKAGES);
        showApplicationInfos(installedAppList);
    }

    private void showApplicationInfos(List infos) {
        if (infos != null) {
            for (ApplicationInfo info : infos) {
                showApplicationInfo(info);
            }
        }
    }

    private void showApplicationInfo(ApplicationInfo info) {
        if (info != null) {
            Log.d(TAG, "showApplicationInfo");
            Log.d(TAG, "className: " + info.className);
            Log.d(TAG, "packagename: " + info.packageName);
            Log.d(TAG, "name: " + info.name);
        }
    }


結果は,こんな感じです。

D/PkgInfoSampleAppActivity(20363): showApplicationInfo
D/PkgInfoSampleAppActivity(20363): className: null
D/PkgInfoSampleAppActivity(20363): packagename: com.sonyericsson.android.contentmanager.contentprovider.webmedia
D/PkgInfoSampleAppActivity(20363): name: null
D/PkgInfoSampleAppActivity(20363): showApplicationInfo
D/PkgInfoSampleAppActivity(20363): className: null
D/PkgInfoSampleAppActivity(20363): packagename: com.noshufou.android.su
D/PkgInfoSampleAppActivity(20363): name: null
D/PkgInfoSampleAppActivity(20363): showApplicationInfo
D/PkgInfoSampleAppActivity(20363): className: null
D/PkgInfoSampleAppActivity(20363): packagename: com.damburisoft.android.app.showmyicon
D/PkgInfoSampleAppActivity(20363): name: null
packagename は得られましたが,nameやclassNameはなぜかnullです。もっと詳細な情報を得るにはどうしたらいいか,引き続き課題です。