Thursday, November 03, 2011

android関連のarticleは11月6日までに全削除いたします。

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)
),それを参考にして各自でデータモデルを定義できます。


Thursday, April 07, 2011

Google Spreadsheets を Androidから扱う (4)

前回,access Token と access TokenSecretを取得しました。 これを用いて,自分のアカウントのspread sheet一覧を取得してみます。
Retrieving a list of spreadsheetsを読みますと,
https://spreadsheets.google.com/feeds/spreadsheets/private/full
に送信すれば,認証されたユーザのspreadsheetsのfeedを入手することができます。
具体的なコードは以下の通りです。

HttpTransporへの署名

String token = "your access token";
String tokenSecret = "your access tokenSecret";
HttpTransport transport = new ApacheHttpTransport();
OAuthParameters parameters = createOAuthParameters(token, tokenSecret);
parameters.signRequestsUsingAuthorizationHeader(transport);
getSpreadSheets(transport);
各メソッドの内容は以下の通りです。
createOAuthParameters
public OAuthParameters createOAuthParameters(String token, String tokenSecret) {
    OAuthParameters authorizer = new OAuthParameters();
    authorizer.consumerKey = "anonymous";
    authorizer.signer = createOAuthSigner(tokenSecret);
    authorizer.token = token;
    return authorizer;
}
createOAuthSigner
public OAuthHmacSigner createOAuthSigner(String tokenSecret) {
    OAuthHmacSigner signer = new OAuthHmacSigner();
    if (tokenSecret != null) {
        signer.tokenSharedSecret = tokenSecret;
    }
    signer.clientSharedSecret = "anonymous";
    return signer;
}
getSpreadSheet
public void getSpreadSheets(HttpTransport transport) {
    // Set AtomParser
    AtomParser parser = new AtomParser();
    XmlNamespaceDictionary dictionary = new XmlNamespaceDictionary();
    dictionary.set("", Atom.ATOM_NAMESPACE);
    dictionary.set("docs", "http://schemas.google.com/docs/2007");
    dictionary.set("batch", "http://schemas.google.com/gdata/batch");
    dictionary.set("gd", "http://schemas.google.com/g/2005");
    dictionary.set("gd:etag", "W/"DEMCQHk7eSt7ImA9WhZSGUw."");
    dictionary.set("openSearch", "http://a9.com/-/spec/opensearch/1.1/");
    dictionary.set("app", "http://www.w3.org/2007/app");
    parser.namespaceDictionary = dictionary;
    transport.addParser(parser);

    // Set Http Headers
    HttpHeaders headers = transport.defaultHeaders;
    headers.set("User-Agent", appname);
    headers.set("GData-Version", "3.0");

    HttpRequest request = transport.buildGetRequest();
    request.url = new GoogleUrl("https://spreadsheets.google.com/feeds/spreadsheets/private/full");
    try {
        HttpResponse response = request.execute();
        // null chekc していないので注意
        InputStreamReader inr = new InputStreamReader(response.getEntity().getContent()); 
        BufferedReader reader = new BufferedReader(inr, 256);
        while ((str = reader.readLine()) != null) {
            Log.d(TAG, line);
        }
        reader.close();
        inr.close();
    } catch (IOException e) {
        Log.e(TAG, e.getMessage());
        e.printStackTrace();
    }
}
これを実行し,正常にHttpResponseが得られますと,以下のようなXML documentが得られます。
<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearch/1.1/' xmlns:gd='http://schemas.google.com/g/2005' gd:etag='W/"DEMCQHk7eSt7ImA9WhZSGUw."'>
  <id>https://spreadsheets.google.com/feeds/spreadsheets/private/full</id>
  <updated>2011-04-04T11:27:41.701Z</updated>
  <category scheme='http://schemas.google.com/spreadsheets/2006' term='http://schemas.google.com/spreadsheets/2006#spreadsheet'/>
  <title>Available Spreadsheets - Maskes(user's email address)</title>
  <link rel='alternate' type='text/html' href='http://docs.google.com'/>
  <link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='https://spreadsheets.google.com/feeds/spreadsheets/private/full'/>
  <link rel='self' type='application/atom+xml' href='https://spreadsheets.google.com/feeds/spreadsheets/private/full'/>
  <openSearch:totalResults>11</openSearch:totalResults>
  <openSearch:startIndex>1</openSearch:startIndex>
  <entry gd:etag='"BBQbQxQLQit7ImBr"'>
    <id>https://spreadsheets.google.com/feeds/spreadsheets/tRsAk0wSkUi4wCBZxOJITuw</id>
    <updated>2011-04-04T04:52:59.849Z</updated>
    <category scheme='http://schemas.google.com/spreadsheets/2006' term='http://schemas.google.com/spreadsheets/2006#spreadsheet'/>
    <title>Hack For Japan Projects</title>
    <content type='application/atom+xml;type=feed' src='https://spreadsheets.google.com/feeds/worksheets/tRsAk0wSkUi4wCBZxOJITuw/private/full'/>
    <link rel='http://schemas.google.com/spreadsheets/2006#tablesfeed' type='application/atom+xml' href='https://spreadsheets.google.com/feeds/tRsAk0wSkUi4wCBZxOJITuw/tables'/>
    <link rel='alternate' type='text/html' href='https://spreadsheets.google.com/ccc?key=0Amb6cvTCzTQRdFJzQWswd1NrVWk0d0NCWnhPSklUdXc'/>
    <link rel='self' type='application/atom+xml' href='https://spreadsheets.google.com/feeds/spreadsheets/private/full/tRsAk0wSkUi4wCBZxOJITuw'/>
    <author>
      <name>Masked</name>
      <email>Masked</email>
    </author>
  </entry>
</feed>
次回は,得られた XML Document の parse を行います。

Tuesday, April 05, 2011

Google Spreadsheets を Androidから扱う (3)

前回,Appを認証した際に得られるoauth_verifierとoauth_tokenの値が返ってくることまで確認しました。

今回は,これらの値と前々回,Request Toke取得途中に得られた,OAuthCredentialsResponseの値と一緒に,AccesssTokenを取得します。

oauth_verifier, oauth_tokenの取得

ブラウザからのcallback時に発行されるIntentの中のURIデータを取得し,それをOAuthCallbackUrl に引き渡します。

Uri uri = this.getIntent().getData();
OAuthCallbackUrl callbackUrl;
if (uri != null) {
    Log.d(TAG, "uri: " + uri.toString());
    callbackUrl = new OAuthCallbackUrl(uri.toString());
}

GoogleOAuthGetAccessTokenの作成

上で作成した,OAuthCallbackUrl callbackUrl と,Request Token取得時に得られた OAuthCredentialsResponse credentials (Request Token)を用いて GoogleOAuthGetAccessToken を作成します。

    HttpTransport transport = new ApacheHttpTransport();
    GoogleOAuthGetAccessToken accessToken = new GoogleOAuthGetAccessToken();
    accessToken.transport = transport;
    accessToken.temporaryToken = callbackUrl.token;
    accessToken.verifier = callbackUrl.verifier;
    accessToken.signer = createOAuthSigner(credentials);
    accessToken.consumerKey = "anonymous";

Access Tokenの取得

GoogleOAuthGetAccessToken accessTokenをexecute()することによって,Access Tokenを取得します。


    OAuthCredentialsResponse response = accessToken.execute();

この返り値 OAuthCredentialsResponse response に含まれている, (access) token と(access) tokenSecret が,今後Spreadsheetsにアクセスするための認可されたHTTP requestを生成する際に必要となります。

次回は,自分のアカウントのSpreadSheet一覧を取得します。

Monday, April 04, 2011

Google Spreadsheets を Androidから扱う (2)

OAuthの続きです。

Implict intent

androidには,intentでactivity, service, broadcastに応答させる昨日があります。この機能を利用して,Browser上でリンクをクリックして,インストールされているactivityを起動する方法があります。
とくに,
Consider, for example, what the browser application does when the user follows a link on a web page. It first tries to display the data (as it could if the link was to an HTML page). If it can't display the data, it puts together an implicit intent with the scheme and data type and tries to start an activity that can do the job. If there are no takers, it asks the download manager to download the data. That puts it under the control of a content provider, so a potentially larger pool of activities (those with filters that just name a data type) can respond.
とありますように,ブラウザでWebページ上のリンクをクリックして, データが表示されない場合,該当する暗黙のintentを発行するようになっています。 OAuth認証では,この機能を利用して,callback urlに自分のアプリを起動できるようなurlを指定します。
Google I/O 2010 Buzz Android Sample for Java Client Library for Google API's version 2.2.0-alphaの実装を参考にして,話を進めていきます。

Request Token (Temporary credentials token) の取得

前回,GoogleOAuthAuthorizeTemporaryTokenUrlを作成いたしましたが,その補足です。
Request Tokenを取得するために,GoogleOAuthGetTemporaryTokenを利用します。
    GoogleOAuthGetTemporaryToken temporaryToken = new GoogleOAuthGetTemporaryToken();
    OAuthHmacsinger signer = new OAuthHmacSigner();
    signer.clientSharedSecret = "anonymous";
    temporaryToken.signer = signer;
    temporaryToken.displayName = appname;
    temporaryToken.consumerKey = "anonymous";
    temporaryToken.scope = "https://spreadsheets.google.com/feeds/";
    temporaryToken.transport = transport;
    temporaryToken.callback = "x-myspreadsheet://com.example.abekatsu/";
前回との違いは,callbackの指定です。custom schemeを使うなという意見がありますが,custom schemeを用いたアプリの起動が広まっている現状を踏まえ,今回は利用します。
競合を避けるために,host部分をjavaのpackage名のように,自分自身のFQDNをTop levelから書いてみました。

GoogleOAuthAuthorizeTemporaryTokenUrlの作成

    OAuthCredentialsResponse response = temporaryToken.execute();
    GoogleOAuthAuthorizeTemporaryTokenUrl authorizeUrl = new GoogleOAuthAuthorizeTemporaryTokenUrl();
    authorizeUrl.template = "mobile";
    authorizeUrl.set("scope", scope);
    authorizeUrl.set("domain", "anonymous");
    authorizeUrl.set("xoauth_displayname", appname);
    authorizeUrl.temporaryToken = response.token;
この authorizeUrl で得られるURLを引数にしてブラウザを起動します。
     Intent intent = new Intent(Intent.ACTION_VIEW);
     intent.setData(Uri.parse(temporaryTokenUrl.build()));
     startActivity(intent);


実際に実行しますと,GoogleのAccountページが表示されます。ここで,「Grant access」をクリックすると,上記のcallbackで指定したURLを含む暗黙のIntentが発行されます。
















暗黙のIntentの取得

先程のステップで得られた,暗黙のIntentを取得してみましょう。
AndroidManifest.xml
まずは,AndroidManifest.xmlで,このIntentを補足するように指定します。
    
        
            
                
                
            
            
            
                
                
                
                
            
        
    
暗黙のIntentでは,カテゴリーが "android.intent.category.DEFAULT" となります。
Activityの編集
ここでは,単純に Intent に含まれているデータを表示させてみます。
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        boolean isViewAction = Intent.ACTION_VIEW.equals(getIntent().getAction());
        if (isViewAction) {
            Uri uri = this.getIntent().getData();
            if (uri != null) {
                Log.d(TAG, "uri: " + uri.toString());
            }
        }
    }
実際に,アプリケーションを実行して Grant access すると次のデータを含む 暗黙のIntentが発行されます。
D/OkodukaiNoteActivity(  235): uri: x-okozukai:///?oauth_verifier=ここにoauth_verifierの文字列&oauth_token=ここにoauth_tokenの文字列
次回はこのauth_tokenをGoogleにauthorizeします。

Wednesday, March 30, 2011

Google Spreadsheets を Androidから扱う (1)

Androidアプリで扱うデータを外部のSpreadsheetに保存することを目的とします。

library

今回はGoogle API Client Library for Javaの1.3.1-alphaを使ってみます。
とりあえず,プロジェクトに次の3つのjarファイルを参照するように設定いたしました。
  • google-api-client-1.3.1-alpha.jar
  • google-api-client-googleapis-1.3.1-alpha.jar
  • dependencies/guava-r08.jar
google-api-client-extensions-1.3.1-alpha.jarは Google App Engine用なので,今回は含めません。依存関係は試行錯誤の結果です。dependecies以下のjar(soruceは除く)を予め含めた方が今後楽かもしれません。

認証

Spreadsheetなど扱う前に,アプリケーションへ認証を与えることが必要になります。 ここではOAuthを用いた認証を行います。
具体的な手順は, OAuth 1.0a for Google API's JavaDocに記載されています。
まずは,temporary credentials token ("request token") を入手します。 installed application では signature method に "HMAC-SHA1",consumerKeyとclientSharedSecretに"anonymos"を使うように求められていますので,それに従います。
    HttpTransport transport = new ApacheHttpTransport();
    GoogleOAuthGetTemporaryToken temporaryToken = new GoogleOAuthGetTemporaryToken();
    OAuthHmacsinger signer = new OAuthHmacSigner();
    signer.clientSharedSecret = "anonymous";
    temporaryToken.signer = signer;
    temporaryToken.displayName = appname;
    temporaryToken.consumerKey = "anonymous";
    temporaryToken.scope = "https://spreadsheets.google.com/feeds/";
    temporaryToken.transport = transport;
displayNameには,このアプリケーションの名前(String 型)を入れます。scopeに入るべき値は,Google Data Protocol FAQを参照してください。
それでは,TemporaryTokenの値を入手してみます。
    OAuthCredentialsResponse credentials = temporaryToken.execute();
戻り値のtoken値を利用して,GoogleOAuthAuthorizeTemoraryTokenUrlを作成します。これで,ユーザにアクセスしていただくurlが得られます。
    if (credentials != null) {
        GoogleOAuthAuthorizeTemporaryTokenUrl temporaryTokenUrl = new GoogleOAuthAuthorizeTemporaryTokenUrl();
        temporaryTokenUrl.template = "mobile";
        temporaryTokenUrl.temporaryToken = credentials.token;
        // Uri uri = new Uri(temporaryTokenUrl.build());
        Log.d(TAG, "temoraryTokenUrl: " + temporaryTokenUrl.build());
    }
実行すると, temoratyTokenUrl の値として,
https://www.google.com/accounts/OAuthAuthorizeToken?btmpl=mobile&oauth_token=...some_oauth_token....
が得られました。
この次は,このURLをWebViewに表示させまして,アプリケーションの認証を行います。

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, March 27, 2011

今後の方針 (Mar/27/2011 version)

自分の方針というのを決めていこうと考える。ただ,一度決めた物絶対ということではなく,何度も推敲を重ねていこうと。

  • ◯◯とITを念頭に。ITの中だけで閉じることに何らかの疑問がある。ITを通して現実の世界にある問題を解決する手助けをしよう。
  • 80%の力で
    重要な時に歯を食いしばるのはいいが,常に歯を食いしばっている状況はおかしいはず。全力で90分フルに走れるフットボール選手はいないように,要素要素で力を抜けるように。
  • 人は迷惑をかけて生きている。迷惑をかけているかどうか気にしない。自分が無理なく生きていくためにやれることをまず探す。

Sunday, January 30, 2011

taintdroid code reading 3.

I read a diff of services/java/com/android/server/LocationManagerService.java.
It is almost the same as the diff of telephony. When location update message is received, the location is stored in Taint object as following:


location.setLatitude(Taint.addTaintDouble(location.getLatitude(), tag));

But I cannot understand the reason why obtained value is set again, that is, why setLatitude is called.
And the other point I cannot understand is that how to distinguish the values. For example, the below is a code to store latitude and location.

    location.setLatitude(Taint.addTaintDouble(location.getLatitude(), tag));
    location.setLongitude(Taint.addTaintDouble(location.getLongitude(), tag));

where tag is given here as following:

    int tag = Taint.TAINT_LOCATION;
    if (LocationManager.GPS_PROVIDER.equals(provider)) {
        tag |= Taint.TAINT_LOCATION_GPS;
    }
    if (LocationManager.NETWORK_PROVIDER.equals(provider)) {
        tag |= Taint.TAINT_LOCATION_NET;
    }

Latitude and Longitude are added by addTaingDouble, but I cannot see how to distinguish latitude and longitude.
I paste the whole diff below.


diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index bbb43d7..4a5846a 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -68,6 +68,10 @@ import com.android.internal.location.LocationProviderProxy;
 import com.android.internal.location.MockProvider;
 import com.android.internal.location.GpsNetInitiatedHandler;
 
+// begin WITH_TAINT_TRACKING
+import dalvik.system.Taint;
+// end WITH_TAINT_TRACKING
+
 /**
  * The service class that manages LocationProviders and issues location
  * updates and alerts.
@@ -1526,6 +1530,30 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
                         Location location = (Location) msg.obj;
                         String provider = location.getProvider();
 
+   // begin WITH_TAINT_TRACKING
+   int tag = Taint.TAINT_LOCATION;
+   if (LocationManager.GPS_PROVIDER.equals(provider)) {
+       tag |= Taint.TAINT_LOCATION_GPS;
+   }
+   if (LocationManager.NETWORK_PROVIDER.equals(provider)) {
+       tag |= Taint.TAINT_LOCATION_NET;
+   }
+   location.setLatitude(Taint.addTaintDouble(location.getLatitude(), tag));
+   location.setLongitude(Taint.addTaintDouble(location.getLongitude(), tag));
+   if (location.hasAltitude()) {
+       location.setAltitude(Taint.addTaintDouble(location.getAltitude(), tag));
+   }    
+   if (location.hasSpeed()) {
+       location.setSpeed(Taint.addTaintFloat(location.getSpeed(), tag));
+   }    
+   if (location.hasBearing()) {
+       location.setBearing(Taint.addTaintFloat(location.getBearing(), tag));
+   }    
+   if (location.hasAccuracy()) {
+       location.setAccuracy(Taint.addTaintFloat(location.getAccuracy(), tag));
+   }    
+   // end WITH_TAINT_TRACKING
+
                         // notify other providers of the new location
                         for (int i = mProviders.size() - 1; i >= 0; i--) {
                             LocationProviderProxy proxy = mProviders.get(i);

Also, the diff of location/java/com/android/internal/location/GpsLocationProvider.java is almost the same handling as LocationManagerService.java, that is, when location updated message is received, the values are set in Taint object.

Next, I'll read the diff for media.

taintdroid code reading 2.

These are diffs for framework/base/telephony.

diff --git a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
index a5188ce..78904c5 100755
--- a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
@@ -78,6 +78,10 @@ import java.net.Socket;
 import java.util.ArrayList;
 import java.util.List;
 
+// begin WITH_TAINT_TRACKING
+import dalvik.system.Taint;
+// end WITH_TAINT_TRACKING
+
 /**
  * {@hide}
  */
@@ -1276,6 +1280,9 @@ public class GSMPhone extends PhoneBase {
                 }
 
                 mImei = (String)ar.result;
+  // begin WITH_TAINT_TRACKING
+  Taint.addTaintString(mImei, Taint.TAINT_IMEI);
+  // end WITH_TAINT_TRACKING
             break;
 
             case EVENT_GET_IMEISV_DONE:

This diff is to store IMEI number to Taint object. Taint.addTaintString(mImei, Taint.TAINT_IMEI); is called in public void setMsisdnNumber(String alphaTag, String number, Message onComplete).


diff --git a/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java b/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java
index d711a80..6beee1b 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java
@@ -38,6 +38,9 @@ import com.android.internal.telephony.MccTable;
 
 import java.util.ArrayList;
 
+// begin WITH_TAINT_TRACKING
+import dalvik.system.Taint;
+// end WITH_TAINT_TRACKING
 
 /**
  * {@hide}
@@ -236,6 +239,9 @@ public final class SIMRecords extends IccRecords {
             Message onComplete) {
 
         msisdn = number;
+ // begin WITH_TAINT_TRACKING
+ Taint.addTaintString(msisdn, Taint.TAINT_PHONE_NUMBER);
+ // end WITH_TAINT_TRACKING
         msisdnTag = alphaTag;
 
         if(DBG) log("Set MSISDN: " + msisdnTag +" " + msisdn);
@@ -488,6 +494,11 @@ public final class SIMRecords extends IccRecords {
                 }
 
                 imsi = (String) ar.result;
+  // begin WITH_TAINT_TRACKING
+  //if (imsi != null) {
+      //Taint.addTaintString(imsi, Taint.TAINT_IMSI);
+  //}
+  // end WITH_TAINT_TRACKING
 
                 // IMSI (MCC+MNC+MSIN) is at least 6 digits, but not more
                 // than 15 (and usually 15).
@@ -617,6 +628,9 @@ public final class SIMRecords extends IccRecords {
                 adn = (AdnRecord)ar.result;
 
                 msisdn = adn.getNumber();
+  // begin WITH_TAINT_TRACKING
+  Taint.addTaintString(msisdn, Taint.TAINT_PHONE_NUMBER);
+  // end WITH_TAINT_TRACKING
                 msisdnTag = adn.getAlphaTag();

Summary

These changes stores IMEI, Phone number (ISDN) and ICCID in Taint object

Quesion

When are these values stored in Taint object?

Next, I'll read services/java/com/android/server/LocationManagerService.java.

taintdroid code reading 1.

I am curious how taintdroid track apps which use senseitive information.
Firstly, I check the diff between taintdroid and android.
According to TaintDroid Build Instructions, it modifies the source under dalvik/ and frameworks/base.

We can obtain the list of modified files by:

  git diff --name-only 0e9d568ec6b946e77bc0ec1903acac1ef916e6d1
for dalvik/, and
  git diff --name-only 562ac30bddb37b8bebeedfb035111dda41187332.
for framework/base.

The list for dalvik is too huge, is 456 files, so firstly we check one for framework/base.


README_TAINTDROID.txt
api/current.xml
cmds/servicemanager/Android.mk
cmds/servicemanager/binder.c
core/java/android/hardware/Camera.java
core/java/android/hardware/SensorManager.java
core/java/android/os/Parcel.java
core/jni/Android.mk
core/jni/android_util_Binder.cpp
include/binder/Parcel.h
libs/binder/Android.mk
libs/binder/Parcel.cpp
location/java/com/android/internal/location/GpsLocationProvider.java
media/java/android/media/AudioRecord.java
media/java/android/media/MediaRecorder.java
media/jni/Android.mk
media/jni/android_media_MediaRecorder.cpp
services/java/com/android/server/LocationManagerService.java
telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
telephony/java/com/android/internal/telephony/gsm/SIMRecords.java

Next, I'll check what changes were made in services and telephont.

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してアプリをハックする方法がいろいろとありますので,そんな難しいことではないのかもしれません。

Thursday, January 27, 2011

どのように,偽のandroid appをつくるのか?

GeiminiというAndroid OSを標的とした,Trojanの報告がある。
Lookout社のGeimini発見のレポート(Blog)

このTrojanは,として,第三者が運営するアプリ配布サイトで配布されている正規アプリの海賊版と一緒になって配布されている。
ここでは正規アプリの海賊版を簡単に作成できるかどうか考察してみたい。

Androidアプリケーションのリバースエンジニアリングより引用すると,

Androidアプリケーションのリバースエンジニアリングをする場合には、目的として「解析だけしたい」場合と、「解析した上で、さらに動作を自分好みに変更する」、つまりアプリケーションの改造までを行いたい場合があるだろう。前者の場合、JDによるJavaソースコード形式への(ときに不完全な)変換で十分な場合もあるだろう。この場合、読みにくいsmali形式のファイルと格闘する必要がないかもしれない。しかし後者、つまり改造までを行いたい場合、アプリケーション内の目的の箇所を自分の意図を達成するように書き換え、ふたたびAndroidアプリケーションとして動作するよう、正しくアセンブルしなおす必要がある。smaliはこれを可能にしてくれる。非常に精度が高いディスアセンブル・アセンブルが可能なので、classes.dex -> smali -> classes.dexという変換が可能なのだ。
つまり,既に apk ファイルから,classを解析して(dex2jar),ディスアセンブル(smali)して,再びclasses.dexを作成するツールが存在することを示している。
そこで,今回次のようなデモシナリオを提案する。
  • ボタンを押したら Toast Messageを表示するAndroid Appを作成する。
  • ボタンをクリックしたとき,その裏で,Identifyを読み取るコードを埋め込む。
  • 再びアセンブルしてパッケージを作成する。
  • この作業がどれだけ簡単かどうかは,後日考察してみたい。

    Monday, January 17, 2011

    Update PutYourMind to 1.2

    Update PutYourMind to version 1.2

    new feature
    add webpage title with url to your readitlater list.

    PutYourMindを1.2に更新いたしました。

    新規機能
    WebPageのURLをReadItLaterリストに追加する際に,そのページのtitleも一緒に登録するようにしました。

    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です。もっと詳細な情報を得るにはどうしたらいいか,引き続き課題です。

    Tuesday, January 11, 2011

    Update PutYourMind to 1.1

    Update PutYourMind to version 1.1

    new feature
    add webpage title with url to your readitlater list.

    PutYourMindを1.1に更新いたしました。

    新規機能
    WebPageのURLをReadItLaterリストに追加する際に,そのページのtitleも一緒に登録するようにしました。

    Sunday, January 09, 2011

    PutYourMind (android app)

    The 2nd. Android application made breaking out.

    This application catches an implicit intent, which data has a uri data. And put its uri data to your readitlater list.
    This application requires your readitlater account.

    Please search it on Android Market by PutYourMind, or use the following QR code.

    突発的に作成したアンドロイドアプリ第2弾。

    URIデータを含む暗黙的なインテントをキャッチして,そのデータをReadItLaterに追加します。

    Android Marketに公開しましたので, PutYourMindで検索するか,QRコードからダウンロードしてみてください。


    アイコンとアプリ名,なんかいいのがないかどうか募集です。

    Saturday, January 08, 2011

    ShowMyIcon (Android Application)

    When I went to a twitter off-line party, who present are a fan of Vegalta Sendai, it was a time to introduce oneself. Someone used a mobile phone to show her/his twitter's icon image to attendees. Though I used an Android phone, I didn't prepare my icon image to show them. So I create a simple application, ShowMyIcon, to show my twitter icon on Android phone.

    Just put your twitter's name.
    Then this application downloads your icon image file and stores it inside. Once you download, you can show it in without internet connections.

    This application does not require user authorization. So it is possible for spoofing.

    This application, ShowMyIcon is available from an android market.
    Please search by "ShowMyIcon" or access the following QR code: