2012年6月15日金曜日

PNGから不要なメタデータの削除しよう。

Icon Design Guidelines に記載されてるように PNGの圧縮を行う。

When saving image assets, remove unnecessary metadata

Although the Android SDK tools will automatically compress PNGs when packaging application resources into the application binary, a good practice is to remove unnecessary headers and metadata from your PNG assets. Tools such as OptiPNG or Pngcrush can ensure that this metadata is removed and that your image asset file sizes are optimized.


PNGから不要なメタデータの削除




参考
  png圧縮をして容量、メモリにやさしいアプリ制作 - 素人のアンドロイドアプリ開発日記


ツール
  PNGGauntlet - PNG Compression Software
  PNGOUTWin - A Batch Mode PNG Optimizer and Converter for Windows.
  OptiPNG
  ImageOptim — make websites and apps load faster (Mac app)




Mac環境なので、ImageOptimを使用しました。


メタ情報は次のコマンドで確認することができます。
 /Applications/ImageOptim.app/Contents/MacOS/pngcrush -n -v xxxx.png




5% ~ 10% ものによっては 30% ファイルサイズが削減されてました。




画像が劣化しているかどうか確認したかったのですが、いいツールが有りませんでした。。。

2012年5月24日木曜日

2012 夏モデル端末の ナビゲーションバー 対応状況

端末下部のハードウェアキーが、4.0から標準のタッチパネル上のソフトウェアキー置き換わった端末のチェックとメモ

注)
 ホームページ上の写真から判断しているので間違えがあるかもしれません。
 スペック一覧は他のサイトでまとめられているのでそっちを確認してください。

2011年12月20日火曜日

SQLite暗号化OSS「SQLCipher for Android」を使ってみた。

こんにちは@katsummyです。

※Twitterとデベロッパー名が違いますのであしからず^^


Android Advent Calendar 2011 の参加への投稿となります。
Android Advent Calenderについてはこちらを参照下さい。


勢いで参加したものの、先の方々がガチな技術や面白いネタの投稿をされてるのを見て恐縮してます。
。。゛(ノ><)ノ ヒィ
初心者向けな内容です。過度の期待はしないで下さい。


ネタは敷居高そうだし、技術的な投稿の内容を考えておりましたが、
あるところでAndroidの端末内のデータの暗号化について話題が上がったので、
その際に考えてた内容とSQLiteの暗号化について記載したいと思います。



Android 3.x 以降では、ストレージ暗号化機能を使用することができます。
SDカードや内蔵ストレージをすべて暗号化できますので安全性は高くなります。
その端末が専用に使用しているのであれば、有効では無いかと思います。しかし、他の用途でも使用している場合、ひとつのアプリのためだけに、全体を暗号化するのはどうかなと思います。
そもそも、Android 2.3.x 以前の端末では使用できません。


アプリ個別に管理しているファイルを暗号化する。
端末レベルで暗号化するより、比較的導入が容易かと思います。

Androidで、データを管理する方法としては、
・ファイル (.TXT .CSV .XML etc...)
・プリファレンス(XML)
・SQLite
これらのファイルを暗号化します。

ファイルの暗号化は、AES/DES/RSA暗号などを用いれば良いと思います。コードサンプルはググれば多く見つかると思うので記載はか割愛します。
プリファレンス(XML)は、それ自信は暗号化されませんので、中に持つ値を暗号化する必要があります。

SQLiteは、暗号化機能を持っていないため、中のレコード自体は暗号化されていません。バイナリー化もされていない(?)ようなので、ダンプをとると容易に中身を参照することができてしまいます。レコードに格納する値を暗号化する方法もありますが、SQLのクエリーやソートが使用できなくなってしまいます。

SQLiteを暗号化するオープンソース・ソフトウェアにSQLCipherがあります。
CやPHP、iPhoneで使用することができてましたが、Android版も11月末に正式版?( v1 FINAL)がリリースされました。





使用するにはgithubからダウンロードしましすが、ソースコードが不要の場合、
https://github.com/guardianproject/android-database-sqlcipher/tree/master/dist/SQLCipherForAndroid-SDK
からダウンロードすれば良いと思います。
必要な


SQLCipherをプロジェクトに設定する手順


1) libs フォルダをプロジェクトにコピーします。
 - commons-codec.jar
 - guava-r09.jar
 - sqlcipher.jar
 + armeabi
  - libdatabase_sqlcipher.so
  - libsqlcipher_android.so
  - libstlport_shared.so


 ※ ターゲットが2.2以下の場合、 assets フォルダもプロジェクトにコピーします。
 + assets
  - icudt44l.zip


2) ソースコードの修正
 a) importするパッケージはSQLiteをSQLCipherに変更します。
  import android.database 
  -->>
  import info.guardianproject.database


 b) 初期処理としてライブラリのロードを行います。
  SQLiteDatabase.loadLibs(context);


 c) データベースをオープンする時にパスワードを指定します。
  SQLiteOpenHelper.getWritableDatabase("thisismysecret"):

  SQLiteOpenHelper.getReadableDatabase("thisismysecret"):


 後は、SQLiteと同じ記述となります。


サンプルソース
import android.database.Cursor; import info.guardianproject.database.sqlcipher.SQLiteDatabase; import android.app.Activity; import android.content.ContentValues; import android.os.Bundle; import android.util.Log; public class SQLDemoActivity extends Activity { EventDataSQLHelper mSQLHelper; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // SQLCipherライブラリのイニシャルロード SQLiteDatabase.loadLibs(this); String password = "foo123"; mSQLHelper = new EventDataSQLHelper(this); // パスワードを指定して書き込み可能なデータベースを開く SQLiteDatabase db = mSQLHelper.getWritableDatabase(password); // レコードのインサート for (int i = 1; i < 10; i++) { addEvent("こんにちは Android イベント: " + i, db); } db.close(); // パスワードを指定して読み込み可能なデータベースを開く db = mSQLHelper.getReadableDatabase(password); // クエリー Cursor cursor = db.query(EventDataSQLHelper.TABLE, null, null, null, null, null, null); startManagingCursor(cursor); // レコードの読み込み StringBuilder ret = new StringBuilder("Saved Events:\n\n"); while (cursor.moveToNext()) { long id = cursor.getLong(0); long time = cursor.getLong(1); String title = cursor.getString(2); ret.append(id + ": " + time + ": " + title + "\n"); } Log.i("sqldemo", ret.toString()); db.close(); } @Override public void onDestroy() { super.onDestroy(); mSQLHelper.close(); } private void addEvent(String title, SQLiteDatabase db) { ContentValues values = new ContentValues(); values.put(EventDataSQLHelper.TIME, System.currentTimeMillis()); values.put(EventDataSQLHelper.TITLE, title); db.insert(EventDataSQLHelper.TABLE, null, values); } }
import info.guardianproject.database.sqlcipher.SQLiteDatabase; import info.guardianproject.database.sqlcipher.SQLiteOpenHelper; import android.content.Context; import android.provider.BaseColumns; import android.util.Log; // SQLiteOpenHelper public class EventDataSQLHelper extends SQLiteOpenHelper { // データベス名 private static final String DATABASE_NAME = "events.db"; private static final int DATABASE_VERSION = 1; // テーブル名 public static final String TABLE = "events"; // カラム public static final String TIME = "time"; public static final String TITLE = "title"; // コンストラクタ public EventDataSQLHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { String sql = "create table " + TABLE + "( " + BaseColumns._ID + " integer primary key autoincrement, " + TIME + " integer, " + TITLE + " text not null);"; Log.d("EventsData", "onCreate: " + sql); db.execSQL(sql); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }


ダンプ
$ hexdump -C events.db
  • SQLite
00000000  53 51 4c 69 74 65 20 66  6f 72 6d 61 74 20 33 00  |SQLite format 3.|
...
00000e30  00 00 00 00 00 31 09 04  00 05 5b 01 34 42 1d 75  |.....1....[.4B.u|
00000e40  20 e3 81 93 e3 82 93 e3  81 ab e3 81 a1 e3 81 af  | ...............|
00000e50  20 41 6e 64 72 6f 69 64  20 e3 82 a4 e3 83 99 e3  | Android .......|
00000e60  83 b3 e3 83 88 3a 20 39  31 08 04 00 05 5b 01 34  |.....: 91....[.4|
00000e70  42 1d 74 ec e3 81 93 e3  82 93 e3 81 ab e3 81 a1  |B.t.............|
00000e80  e3 81 af 20 41 6e 64 72  6f 69 64 20 e3 82 a4 e3  |... Android ....|
00000e90  83 99 e3 83 b3 e3 83 88  3a 20 38 31 07 04 00 05  |........: 81....|
00000ea0  5b 01 34 42 1d 74 d4 e3  81 93 e3 82 93 e3 81 ab  |[.4B.t..........|
00000eb0  e3 81 a1 e3 81 af 20 41  6e 64 72 6f 69 64 20 e3  |...... Android .|
…
  • SQLCipher
00000000  5a f7 b6 fa 3e 78 f2 9b  10 71 bc cd 64 e8 f4 01  |Z...>x...q..d...|
00000010  11 7c 48 1b 3f d9 15 e3  70 01 68 c0 d0 7c be 0b  |.|H.?...p.h..|..|
00000020  af 55 b5 95 7b a3 d2 83  90 61 31 0a 83 ce 52 13  |.U..{....a1...R.|
00000030  0a ac 02 a3 39 ad ec e2  92 65 3f 01 c6 d6 5d 97  |....9....e?...].|
00000040  b0 d0 f3 a3 5a c2 3b 1d  58 b4 d2 41 2a d0 bf 39  |....Z.;.X..A*..9|
00000050  3c 96 88 8b 66 ff d1 de  73 c5 c8 2e d6 52 6b 7d  |<...f...s....Rk}|
00000060  6c 7e f9 41 bb 9e 00 bb  3e d6 2e 9a dc 07 da 0f  |l~.A....>.......|
00000070  fe 0e 14 38 63 34 01 3e  e7 70 c4 4c 20 da c5 d5  |...8c4.>.p.L ...|
…
暗号化されているのが分かると思います。

なお、サンプルコードは、パスワードをソースコードに記述してますが、
端末を識別する情報やGoogleアカウント情報など一意となる情報から、
生成した方が良いと思います。

※ IMEI(端末識別番号)、IMSI(加入者識別番号)、ICCID(SIMカード固有番号)、Android IDの文字列を組み合わせて、ハッシュ化するとか〜。


SQLiteのデータベースは通常/data/data/package/databasesに作成されるためアプリ以外のユーザ権限で取得することはできませんがルートユーザでは可能になります。
また、データベースの保管先は、SDCardに指定することも可能です。


Rooted端末対策として、保管をSDCard指定する際には、SQLCipherを利用してみてはどうかなと思います。



ライセンス
同梱されているLICENSEファイルには、Apache License Version 2.0とされていました。
別にSQLCIPHER_LICENSEファイルにも再配布や免責事項に付いて記載がありますので確認しておいた方がよいと思います。






でわでわ。




2011年10月22日土曜日

Conversion to Dalvik format failed with error 1

ADT14 にアップデートしたら「Conversion to Dalvik format failed with error 1」でまくったので、その時の対応メモ。


原因は参照ライブラリでした。

プロジェクトツリーに、参照しているライブラリのソースパスが追加されてました。 

プロジェクトのプロパティでLibraryに追加しています。
これは問題ありません。

Java  Build Path に参照ライブラリのパスが追加されていました。
こいつが悪さしてるっぽいので削除します。

プロジェクトツリーに、参照しているライブラリのソースパスが残っているので、
これも削除してしまいました。


これでエラーが出なくなりました。


ActionBarSherlock の Issues にも対応方法が上がってました。
#79: not compatible with Android SDK Tools 14 - Issues - JakeWharton/ActionBarSherlock - GitHub https://github.com/JakeWharton/ActionBarSherlock/issues/79#issuecomment-2464701

2011年10月13日木曜日

List of libraries for android

[覚書] Android用各種ライブラリ一覧
  • Back
    • Twitter4J 
      • A Java library for the Twitter API
    • Android ROME Feed Reader
      • ROME is an open source Java tool for parsing, generating, and publishing RSS and Atom feeds. Android ROME Feed Reader is a repackaging of ROME so that it works on Android devices.
    • Spring Android
      • A Rest Client for Android
      • Auth support for accessing secure APIs
  • Graph
    • AChartEngine
      • AChartEngine is a charting library for Android applications. It currently supports the following chart types:
        • line chart / area chart / scatter chart / time chart / bar chart / pie chart / bubble chart / doughnut chart / range (high-low) bar chart / dial chart / gauge / combined (any combination of line, cubic line, scatter, bar, range bar, bubble) chart / cubic line chart
    • AndroidPlot
      • AndroidPlot is a pure Java API for creating dynamic and static charts within your Android application. AndroidPlot currently supports the following types of charts:
        • Line charts / Scatter charts / Bar charts / Step charts
    • ChartDroid
      • ChartDroid is an Intent-based "library application" for static chart and graph generation on Android. It can graph/plot/display numerical data in many representations.
  • Game
    • AndEngine
      • Free Android 2D OpenGL Game Engine
    • libgdx
      • Android/desktop game development framework



2011年9月28日水曜日

Android からApp EngineのフォームにPOSTする

「Google アカウントを使用して Google App Engine の認証を行う」の続きです。

Google App Engine のスタートガイドで作成しているゲストブックのフォームにポストするサンプルです。



以前のソースはActivityクラスにコードを記載してましたが、もう少し汎用的なクラスを作成しました。

GoogleServiceAuthenticator


  • コンストラクタにはContextを指定してます。
  • getGoogleAccounts() で、アカウントリストを取得できます。
  • execute()で、認証を実行してます。
    • 引数 accountには、リストから選ばられた一意のアカウントを指定します。typeは、Googleのアカウントタイプ(AppEngineの場合は"ah")。postExecuteCallbackには、認証終了後にコールしたいクラスを指定します。
    • AccountManagerのgetAuthTokenを呼び出します。
      • GetAuthTokenCallbackをcallbackに指定してます。
      • GetAuthTokenCallbackのrun()の認証処理の実態のgetAuthToken()を呼び出してます。
    • getAuthToken()では、認証処理を失敗時のリトライのためにループさせてます。
      • getLoginUrl()を呼び出して、アプリのUrlを作成。
        • https://yourappl.appspot.com/_ah/login?continue=http://localhost/&auth=[authToken]
        • authTokenは、AccountManagerから取得した文字列を指定してます。
      • urlを読んで、レスポンスを取得。
      • 取得したレスポンスが500の場合、AccountManagerのキャッシュからauthTokenを削除して、リトライをさせてます。
      • 認証に成功したら、Cookieから、ASCID または、SACSIDを取得します。
        • httpsで認証した場合は、SACSID、httpの場合は、ASCIDが取得できます。
    • その後、postExecuteCallbackを実行させてます。
      • postExecuteCallback内で、App Engineにアクセスするときに、Http Headerの CookieにASCID/SACSIDをセットする必要があります。


Activityの実装例

GoogleServiceAuthExampleActivity


フルプロジェクトはgithubに上げました。
http://github.com/granoeste/GoogleServiceAuthExample

Google アカウントを使用して Google App Engine の認証を行う

AccountManager から、端末に登録されているGoogleアカウントを取得して、
そのアカウントでGoogle App Engineの認証を行う簡単なサンプルです。


AndroidManifest.xmlは、次のパーミッションを指定します。

  • android.permission.GET_ACCOUNTS
  • android.permission.MANAGE_ACCOUNTS
  • android.permission.USE_CREDENTIALS




Activity


AccountManagerから、AccountsByTypeに"com.google"を指定して、Googleアカウントの一覧を取得します。
それをリスト表示してます。
※リストは簡易的に標準のレイアウトを使用してます。

public class AccountList extends ListActivity { protected AccountManager accountManager; protected Intent intent; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); accountManager = AccountManager.get(getApplicationContext()); Account[] accounts = accountManager.getAccountsByType("com.google"); this.setListAdapter(new ArrayAdapter( this, android.R.layout.simple_list_item_1, accounts)); } @Override protected void onListItemClick(ListView l, View v, int position, long id) { Account account = (Account)getListView().getItemAtPosition(position); AccountManagerFuture accountManagerFuture = accountManager.getAuthToken(account, "ah", null, this, null, null); Bundle authTokenBundle; try { authTokenBundle = accountManagerFuture.getResult(); String authToken = authTokenBundle.get( AccountManager.KEY_AUTHTOKEN).toString(); Log.i(TAG,"authToken:"+authToken); } catch (OperationCanceledException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (AuthenticatorException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
リストで選んだAccountでGoogle App Engineの認証を行ってます。
AccountManager#getAuthToken() の 第2引数に AuthTokenTypeを指定します。
"ah"がGoogle App EngineのAuthTokenTypeとなってます。

AuthTokenTypeについては、次のサイトが参考になると思います。
  Account Managerについて - adsaria mood
  Google Data APIs Frequently Asked Questions - Google Base Data API - Google Code

認証画面が表示されます。

ユーザがAllowを選択するとauthTokenを取得することができます。