2012年9月10日月曜日

4.0(ICS)でCamera.takePicture呼び出し時にシャッター音が鳴らない?

4.0(ICS)で、Camera シャッター音が鳴らなくなりました。???

よくある実装サンプル。 mCamera.autoFocus(new AutoFocusCallback() { @Override public void onAutoFocus(boolean success, Camera camera) { camera.takePicture(null, null, pictureCallback); } }); private Camera.PictureCallback pictureCallback = new Camera.PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { // save picture } }; takePictureの第1引数のShutterCallbackをnull で呼び出してます。
4.0(ICS)以降では、nullにしているとシャッター音が鳴らなくなっているようです。

空実装のShutterCallbackを指定すると鳴るようになりました。 mCamera.autoFocus(new AutoFocusCallback() { @Override public void onAutoFocus(boolean success, Camera camera) { camera.takePicture(shutterCallback, null, pictureCallback); } }); private Camera.PictureCallback pictureCallback = new Camera.PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { // save picture } }; private Camera.ShutterCallback shutterCallback = new Camera.ShutterCallback() { @Override public void onShutter() { // NOP } };
GALAXY NEXUS SC-04D で試しましたが、端末依存とかあるのでしょうか。。。

2012年8月17日金曜日

FragmentからstartActivityForResult

FragmentからstartActivityForResultすると、戻ってきた時にActivityのonActivityResultとFragmentのonActivityResultが呼ばれるようなので。。。覚え書き

ソースコードを見るとよくわかるね。

startActivityForResult の動作

Fragment
ActivityのstartActivityFromFragmentを呼んでます。 /** * Call {@link Activity#startActivityForResult(Intent, int)} on the fragment's * containing Activity. */ public void startActivityForResult(Intent intent, int requestCode) { if (mActivity == null) { throw new IllegalStateException("Fragment " + this + " not attached to Activity"); } mActivity.startActivityFromFragment(this, intent, requestCode); }
FragmentActivity
requestCodeをビット演算させてActivityのsuper.startActivityForResultを呼んでます。 /** * Called by Fragment.startActivityForResult() to implement its behavior. */ public void startActivityFromFragment(Fragment fragment, Intent intent, int requestCode) { if (requestCode == -1) { super.startActivityForResult(intent, -1); return; } if ((requestCode&0xffff0000) != 0) { throw new IllegalArgumentException("Can only use lower 16 bits for requestCode"); } super.startActivityForResult(intent, ((fragment.mIndex+1)<<16) + (requestCode&0xffff)); }

onActivityResult の動作

FragmentActivity
requestCodeをビット演算でFragmentからstartActivityForResultActivityが実行されてるかチェックして、 FragmentのonActivityResultの呼び出しを制御しています。 /** * Dispatch incoming result to the correct fragment. */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { int index = requestCode>>16; if (index != 0) { index--; if (mFragments.mActive == null || index < 0 || index >= mFragments.mActive.size()) { Log.w(TAG, "Activity result fragment index out of range: 0x" + Integer.toHexString(requestCode)); return; } Fragment frag = mFragments.mActive.get(index); if (frag == null) { Log.w(TAG, "Activity result no fragment exists for index: 0x" + Integer.toHexString(requestCode)); } else { frag.onActivityResult(requestCode&0xffff, resultCode, data); } return; } super.onActivityResult(requestCode, resultCode, data); }
Fragment
/** * Receive the result from a previous call to * {@link #startActivityForResult(Intent, int)}. This follows the * related Activity API as described there in * {@link Activity#onActivityResult(int, int, Intent)}. * * @param requestCode The integer request code originally supplied to * startActivityForResult(), allowing you to identify who this * result came from. * @param resultCode The integer result code returned by the child activity * through its setResult(). * @param data An Intent, which can return result data to the caller * (various data can be attached to Intent "extras"). */ public void onActivityResult(int requestCode, int resultCode, Intent data) { }

Y.A.M の 雑記帳: Android Support Package の Fragment から startActivityForResult() を使うときの注意点

2012年8月2日木曜日

XOOM (海外ROM版) JellyBean (4.1.1) アップデート

XOOM (海外ROM版)に、JellyBean (4.1.1)のOTAが来たのでアップデートしてみた。

UI
Nexus7のNavigationBarのPhone UIとは異なり、CombindBarのTablet UIになってます。


ホームボタン長押しで、Google Nowが表示されます。

残念な事に Portrait にしか対応してませんでした。。。



システム情報
Android Version 4.1.1
Kernel Version 2.xのまま (Nexus 7は3.x)

Nortification
4.1から追加された BigStyleにも対応してます。
通知の拡張は、下から上に2タッチでスワイプさせます。




こんな感じ〜。


2012年7月18日水曜日

ライブラリにソースとJavadocをアタッチ

ようやく、ADT r20で、libsのライブラリのソースとJavadocのjar/zipを添付出来るようになりました。


その手順メモです。

元ネタ
Issue 28658 - android - Attaching source code for jar files in Android dependencies


1.  libsのライブラリと同じ名前でpropertiesファイルを作成します。


2. propertiesファイルに、srcとdocのパスorファイルを記述します。
android-support-v4.jar.properties
src=/Applications/android-sdk/extras/android/support/v4/src/
doc=
gson-2.2.1.jar.properties
src=../libsrc/gson-2.2.1-sources.jar
doc=../libsrc/gson-2.2.1-javadoc.jar
 ※ファイルはzipでもOKでした。

3. プロジェクトのAndroid Dependencies のjarアイコンに ドキュメントマークが付きます。

4. プロジェクト -> Properties -> Java Build Path -> Libraries の Android Dependencies の各ライブラリにソースとJavaDocがアタッチされています。


設定が反映されない場合、プロジェクトを閉じて開き直すと良いかもしれません。

一応 githubにプロジェクトを置いておきます。
https://github.com/granoeste/UseThirdPartyLib

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ファイルにも再配布や免責事項に付いて記載がありますので確認しておいた方がよいと思います。






でわでわ。