2013/12/08

PreferenceActivity で isValidFragment を override していないと RuntimeException が発生する

Eclipse の ADT ではいくつかのテンプレートが用意されていて、お決まりの class を作成する場合とても重宝しますNew → Other → Android → Android Object で選択できます。
Settings Activity もそのひとつ。
このテンプレートを使用すると、multi-pane layout の設定画面を手軽に作成することが出来ます。

multi-pane layout というのは、以下のように小型スマートフォンと大型タブレットで表示形式を変えるレイアウトのことです。
スマートフォンの設定画面
タブレットの設定画面

ところが、このタブレット用の画面を Android 4.4 (Kitkat) のタブレットで表示すると以下のような RuntimeException が発生し、アプリが落ちるようになってしまいました。
テンプレートを使用するとアプリが落ちてしまうというは、ちょっとダサい状況ですね。

FATAL EXCEPTION: main
Process: com.kokufu.android.apps.sqliteviewer, PID: 32372
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.kokufu.android.apps.sqliteviewer/com.kokufu.android.apps.sqliteviewer.base.SettingsActivity}: java.lang.RuntimeException: Subclasses of PreferenceActivity must override isValidFragment(String) to verify that the Fragment class is valid! com.kokufu.android.apps.sqliteviewer.base.SettingsActivity has not checked if fragment com.kokufu.android.apps.sqliteviewer.base.SettingsActivity$DisplayPreferenceFragment is valid.
 at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2176)
 at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2226)
 at android.app.ActivityThread.access$700(ActivityThread.java:135)
 at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1397)
 at android.os.Handler.dispatchMessage(Handler.java:102)
 at android.os.Looper.loop(Looper.java:137)
 at android.app.ActivityThread.main(ActivityThread.java:4998)
 at java.lang.reflect.Method.invokeNative(Native Method)
 at java.lang.reflect.Method.invoke(Method.java:515)
 at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:777)
 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:593)
 at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.RuntimeException: Subclasses of PreferenceActivity must override isValidFragment(String) to verify that the Fragment class is valid! com.kokufu.android.apps.sqliteviewer.base.SettingsActivity has not checked if fragment com.kokufu.android.apps.sqliteviewer.base.SettingsActivity$DisplayPreferenceFragment is valid.
 at android.preference.PreferenceActivity.isValidFragment(PreferenceActivity.java:898)
 at android.preference.PreferenceActivity.switchToHeaderInner(PreferenceActivity.java:1179)
 at android.preference.PreferenceActivity.switchToHeader(PreferenceActivity.java:1219)
 at android.preference.PreferenceActivity.onCreate(PreferenceActivity.java:564)
 at android.app.Activity.performCreate(Activity.java:5243)
 at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1087)
 at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2140)
 ... 11 more
2013/11/27

Android の Files と Cache の違い

Android はアプリの情報を格納するための場所として、以下のようなメソッドを提供しています。
それぞれ、以下のようなパスになります。Xperia Acro HD (SO-03D) で動作させてみた結果。デバイスによって異なる可能性があります。
getFilesDir()
/data/data/com.kokufu.android.test.exstoragetest/files

getExternalFilesDir()
/mnt/sdcard/Android/data/com.kokufu.android.test.exstoragetest/files

getCacheDir()
/data/data/com.kokufu.android.test.exstoragetest/files/cache

getExternalCacheDir()
/mnt/sdcard/Android/data/com.kokufu.android.test.exstoragetest/files/cache

見てのとおり、ほぼ同じディレクトリを表しているのですが、どう違うのでしょうか?

getCacheDir() の JavaDoc には、以下のように書かれています。

Returns the absolute path to the application specific cache directory on the filesystem. These files will be ones that get deleted first when the device runs low on storage. There is no guarantee when these files will be deleted.
Note: you should not rely on the system deleting these files for you; you should always have a reasonable maximum, such as 1 MB, for the amount of space you consume with cache files, and prune those files when exceeding that space.

(意訳)
アプリ固有のキャッシュディレクトリの絶対パスを返します。ここに格納されているファイルは、ストレージの容量が少なくなった場合削除されます。いつ削除されるかは保証されていません。
Note: システムがこれらのファイルを消すことを期待してはいけません。妥当な上限(たとえば 1MB など)を決め、キャッシュファイルがそれらの値を超えるようであれば、あなたが削除しなければなりません。


つまり、Cache ディレクトリは勝手に消される可能性があるけれど、いつ消されるかは保証されていないってことですね。
逆に言うと、Files ディレクトリはシステムに勝手に消されることはありません。

しかし、忘れてはいけないのが以下の画面の存在。

設定 → アプリ

この画面で「データを削除」を選択された場合、有無を言わさず Files ディレクトリ以下が(Cache ディレクトリも含めて)削除されてしまいます。
システムに勝手に消されることは無いけれど、ユーザに消されることはあるということです。



2013/11/26

Raspberry Pi に の pulse-audio で音がブツブツ切れるので USB DAC (LXU-OT2) をつないでみた

我が家の Raspberry Pi に pulse-audio を入れてみました。

pulse-audio のインストール
$ sudo apt-get install pulseaudio pulseaudio-module-zeroconf 

pulse-audio で再生。
$ paplay /usr/share/sounds/alsa/Rear_Right.wav
すると、音がブツブツ途切れるではありませんか。
全く使い物になりません。

ALSA で再生してみる。
$ aplay /usr/share/sounds/alsa/Rear_Right.wav
こちらは、ちゃんと再生されます。

pulse-audio のバージョンとかいろいろ考えたのですが、とりあえず別の DAC で試してみるのはどうかと思いつきました。
というのも、先日買った Stereo (2013年1月号) という雑誌に USB-DAC (LXU-OT2) が付録でついていて、活用できていなかったので付録につられて買っただけです。オーディオマニアではありません。

Raspberry Pi の上に重ねてみると、調度良い大きさ。

早速、USB で接続してみました。
USB-DAC を使用する方法はいろいろありますが、今回は使い物にならないデフォルトの音声出力を切ってしまうことにしました。
以下のように、1行コメントアウトするだけです。

/etc/modules
#snd-bcm2835

USB-DAC を使って pulse-audio で再生してみると、音が途切れること無く再生されました。
検索しても同じような症状を見つけられなかったので、私の Raspberry Pi のハード的な欠陥なのかもしれません。

ちなみに、 Stereo (2013年1月号) は月刊誌なので今では手に入りにくいようですただ、今調べたところ、10000円くらいで売りに出てるサイトもありました。再販したんでしょうか?
ちなみに、最初のお値段は 2800円でした。


以下なんかが代替商品になるかもしれません。

上海道場【初段】 超小型 手のひらサイズ USB DAC (D/Aコンバーター DAコンバーター) Donyaダイレクト DN-USBDAC ZY [メ04] |【上海問屋】通販サイト

購入していないのでなんとも言えませんが…
購入したらレビューしたいと思います。

2013/11/25

Android のソースコードでバージョン間の差分を簡単に確認する方法

Android の良い所は、いざとなったら OS のコードまで追っていくことが出来ることですNexus や エミュレータ以外では必ずしも正確ではありませんが。
アプリが不思議な動作をした時も自分のコードが悪いのか、OSのバグなのか追うことが可能です。
例えば、私は先日 MultiSelectListPreference の不思議な動作に出会い、結局 Android のバグであることに気づきました。

上記のバグ、特定の Android のバージョンだけで起こるのです。
そういう時に役に立つのが、バージョン間の比較を行うことの出来るサイト。

私は GrepCode というサイトを利用させてもらっています。
Android に限らず 様々なオープンソースプロジェクトのソースコードを保持していて、ブラウザ上で簡単にバージョン間の差分チェックができます。

Android のソースコードは以下。
GrepCode: android - Java Project - Source Code

2013/11/24

Dialog Theme を適用した Activity に ActionBar を表示する

Android でダイアログを表示する際、DialogFragment を使用するのではなく、Activity に Dialog Theme を適用して表示することが出来ます。
何かのリザルトを表示する時等、使い方によっては大変便利です。

ところで、せっかく Activity を使っているのだから、メニューを表示したいと思うのは私だけでしょうか?
Android 2.x 系だとメニューボタンを押したら、下からニュッとメニューが表示されるので、Dialog Theme を適用した Activity でもメニューを使用する事が出来ます。
しかし、Android 3.x から導入されたメニューは ActionBar。 Dialog Theme を適用すると表示されなくなってしまいます。

とはいえ、物は Activity なので、以下のように Custom Theme を作成することで、Action Bar を復活させることが可能です。
2013/11/21

MultiSelectListPreference にバグがあり、勝手に選択解除されることがある

Android の MultiSelectListPreference、便利なのですが致命的なバグがあることがわかりました。
以下のような手順で操作をすると、勝手に選択が解除されます。
場合によっては Preference の内容も書き換えてしまいます。

この問題は少なくとも 4.1.1_r1 で直っています。
2013/11/16

Eclipse に .project の無い既存の Android プロジェクトをインポートする

@Eclipse Juno SR2
@ADT Rev. 22.3
@Android SDK Rev. 19

以前、「Eclipse に既存の Android プロジェクトをインポートする」というエントリで以下のように書きました。

.project ファイルがない場合は何も表示されません。その場合は、Eclipse でインポートできるサンプル等ではないので、別の方法で Eclipse に取り込んでやる必要があります。

その後、「別の方法」が提供されたので書いておこうと思います結構すぐに出来るようになったのですが、書こうと思いながら大分時間が経ってしまいました…

File → New → Other...

Android → Android Project from Existing Code

Root Directory を入力

選択したディレクトリを再帰的に検索してプロジェクト一覧が表示されます。
この時、プロジェクト名は AndroidManifest.xml 内で宣言されているアプリ名になります。
別の名前にしたい場合は、ここで変更することも可能です。

Finish を選択すると、選択したプロジェクトがインポートされます。

2013/11/14

Android SDK をアップデートしたら BufferOverflowException が出るようになった

最近、ちょっと古い Android Project を Eclipse で実行しようとすると以下のようなエラーが起こるようになりました。

Android Console の出力
[2013-11-14 22:04:14 - Dex Loader] Unable to execute dex: java.nio.BufferOverflowException. Check the Eclipse log for stack trace.
[2013-11-14 22:04:14 - LifeCycleTest] Conversion to Dalvik format failed: Unable to execute dex: java.nio.BufferOverflowException. Check the Eclipse log for stack trace.

調べてみると、Eclipse を再起動してみろとか、いろいろ書かれているのですが…

私の環境では再起動してもダメで、以下のように Project Build Target を最新にしてやるとうまく動くようになりました。

Project → Properties → Android → Project Build Target


エラーの内容からすると、生成された dex ファイルに問題がある様子。
ということは、Build に失敗しているわけで、心当たりを探してみると、一つ思い当たることが。

先日、Android SDK のバージョンを 19Android 4.4 KitKat にした時に、古い Android SDK Build-tools を全て Delete したのです。
Android SDK Build-tools Rev. 19 以外が Not installed


時間が無くて調べきれていませんが、対応する Android SDK Build-tools が無いと、正しく dex ファイルが生成できないのではないかと思われます。


追記
ちょっと気になったので確認してみましたが、結論としては Android SDK Build-tools は関係なく、現時点(2013/11/14) の最新SDK Android SDK Tools Rev. 22.3
Android SDK Platform-tools Rev. 19
では Project Build Target が API 15Android 4.0.3 以前の Project のビルドにはことごとく失敗します。
Android SDK をアップデートしたら、Project Build Target もアップデートしないといけないようです。

そもそも、Project Build Target のバージョンはなるべく最新に保ち、Deprecated なものなどに気を配って開発していくべきですので、あまり問題はないと思いますが、リリース直前の複数のプロジェクトを抱えているとやっかいなことになりそうです。
まぁ、そういう場合は SDK のインストールディレクトリから分けるべきなのでしょう。。。

2013/10/02

Android Layout の Lint を個別に表示しなくする方法

ADT (Android Development Tools) の Lint、どんどん進化してきて便利ですね。
しかし、"Nested weights are bad for performance" のように修正不可能なものもあります工夫次第で修正できる場合もありますが

この Lint、「Java のコード内で表示された場合はアノテーションで無視することが出来るけど、Layout XML 内では個別に無視できない」と思っていました。
昨日までは。

しかし、Warning が表示されている行で Ctrl + 1 を押すとなんと、"Add ignore 'NestedWeights' to elemnt" というサジェストが表示されるではないですか。

選択してみると、以下のように tools:ignore="NestedWeights" が追加され Warning が表示されなくなりました。

この機能、どうも ADT 17 で入ったみたいですADT 17 が出たのが 2012年3月 なので、1年半も気づかなかったわけですね…
ぐぅ


2013/10/01

Eclipse CDT 上で Android NDK を使って実行ファイルを作成する方法

Android 用の実行ファイルを Eclipse CDT で作成する方法を書いておきたいと思います。
Android プロジェクトの中に実行ファイルを作成することも可能で、そちらの方が簡単です。
この記事は、「独立したバイナリ作るのに Java との混合プロジェクトを作るなんて気持ち悪い。」って思う人向けですそうそう居まい
2013/09/30

Eclipse CDT 上で Android NDK を使った JNI 作成環境を構築する

昔はコマンドラインから ndk-build を叩かないといけなかった Android NDK ですが、 いつの頃からか、Eclipse CDT 上でビルド出来るようになりました。
せっかくの統合開発環境ですから、これを使わない手はありません。
というわけで、環境構築方法を書いておこうと思います。

以下、前提条件です。
  • ADT インストール済みで Java アプリケーションが作れる
  • CDT がインストール済み
  • Android Native Development Tools がインストール済み

Preprocessor Include Paths を無効にする

Eclipse 4.2 (JUNO) 以上を使用している場合は、以下の作業をしておかないと include path が適切に処理されません。
Android Native Development Tools が新しい CDT の機能に対応していないのが原因のようなので、そのうち必要なくなるかもしれません。

Window → Preferences → C/C++ → Property Pages Settings

Display "Preprocessor Include Paths" page のチェックを外す。
この作業は Workspace 毎に一度行えば大丈夫です。


既存のAndroidプロジェクトに JNI を追加する

まず、普通に Android アプリケーションプロジェクトを作成します。
その後、
プロジェクトを右クリック → Android Tools → Add Native Support...

jni ディレクトリが作成され、Android.mk 等が作成されているはずです。

ビルドする

Project → Build Project
libs 以下に soファイルが作成されます。

あとは、普通のJNI プロジェクトとして作成すればOKです。

2013/09/27

Android NDK で出力ディレクトリを libs 以外にする方法

Android NDK でデフォルトディレクトリを jni 以外にしてみたのは良いのですが、実行ファイルなのに libs ディレクトリに出力されるのはいかがなものかと名は体を表すべきなのです。
というわけで、libs ディレクトリ以外に出力する方法も書いておきます。

方法は簡単で、以下のように NDK_APP_DST_DIR に出力先のディレクトリを指定してやるだけです。

$ ndk-build NDK_APP_DST_DIR="${PWD}/out/\$(TARGET_ARCH_ABI)"

デフォルトだと、out/armeabi 以下に成果物が出力されます。

TARGET_ARCH_ABI の前の $ がエスケープされていることに注意してください。
TARGET_ARCH_ABI はターゲットアーキテクチャで、ビルドターゲットによって変わります。
つまり、このコマンドを実行した段階では展開してはいけません。
後展開するように、$ をエスケープしているわけです。

Windows の場合は以下のようになります。
> ndk-build.cmd NDK_APP_DST_DIR="%cd%/out/$(TARGET_ARCH_ABI)"

Windows の場合は $ が環境変数として展開されませんので、エスケープする必要はありません。


Android NDK でデフォルトディレクトリを jni 以外にする方法

Android NDK のビルドツール ndk-build はデフォルトでプロジェクト直下の jni ディレクトリを見に行き、その Application.mk もしくは Android.mk から処理を開始します。
JNI 製作時は便利なのですが、実行ファイルを作りたい時などは、「jni というディレクトリ名はどうなのよ?」って思いますJNI じゃなくても、jni ディレクトリ以下に作っておけば良いという考え方もあるかもしれませんが、やはり名は体を表すべきです。
他人(未来の自分)が混乱するものは極力避けるべきでしょう。


というわけで、任意のプロジェクト構成で ndk-build する方法を探ってみました。
例として、以下のような構成のプロジェクトで Android 用の実行ファイルを作成してみます。

$(NDK_PROJECT_PATH)/
                   |-src/
                   |    |-HelloWorld.c
                   |    |-Android.mk
                   |
                   |-Application.mk

Application.mk を作成する

正確には、Application.mk を作成しなくても良いのですが、柔軟なプロジェクト構成のためにはあったほうが良いでしょう。
基本的には、プロジェクトディレクトリの直下に配置します。

Application.mk
APP_PROJECT_PATH := $(NDK_PROJECT_PATH)

# ターゲットの Android.mk を指定する
APP_BUILD_SCRIPT := $(APP_PROJECT_PATH)/src/Android.mk

プロジェクト作成

プロジェクトの作成は、jni 以下に作成するのと同様に行います。
例えば、以下のような感じです。

src/Android.mk
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := a.out
LOCAL_SRC_FILES := HelloWorld.c

include $(BUILD_EXECUTABLE)

本筋とは関係ないですが、一応ソースコードも載せておきます。

src/HelloWorld.c
#include <stdio.h>

int main(int argc, char *argv[]) {
 printf("Hello world.");
 return 0;
}

ビルド

プロジェクト直下で、ndk-build を実行します。
この時、以下のようにオプションを追加するところがポイント。

$ ndk-build NDK_PROJECT_PATH=${PWD} NDK_APPLICATION_MK="\$(NDK_PROJECT_PATH)/Application.mk"
Compile thumb  : a.out <= HelloWorld.c
Executable     : a.out
Install        : a.out => libs/armeabi/a.out

NDK_PROJECT_PATH にプロジェクトのルートディレクトリ。
NDK_APPLICATION_MK に Application.mk ファイル。
上記2点を適切に指定する必要があります。

実行すると、無事 libs/armeabi/a.out が作成されました。

また、Windows の場合は以下で同様の処理が可能です。
> ndk-build.cmd NDK_PROJECT_PATH=%cd% NDK_APPLICATION_MK="$(NDK_PROJECT_PATH)/Application.mk"
"Compile thumb : a.out <= HelloWorld.c
Executable     : a.out
Install        : a.out => libs/armeabi/a.out

Windows の場合は $(PWD) が定義されていませんので、同様の環境変数 %cd% を使用してやる必要があるわけです。

参考
作成した実行ファイルをエミュレータ等に送って実行したりできます。
Android NDK で .so ではなく、実行ファイルをつくる - Hacking My Way ~ itogのhack日記


2013/09/17

ミュージックサーバ用に SONY SRS-D5 を買ってみた

BeagleBoard-xM でミュージックサーバを構築したので、新たにスピーカーを買ってみました。

最初は、以前から持っている audio-technica AT-SP92を使いまわそうと思っていたのです。

このスピーカー、小さい割には良い音なので BeagleBoard-xM にはピッタリなのですが、既に別の用途で使ってたので、別のものも検討してみようかなと。
古い AT-SP92

で、検討した結果、SONY SRS-D5を買ってみました。
決め手は、5000円を切る値段とオートパワーオン/オフ機能です。
BeagleBoard-xM がせっかく低消費電力なのに、スピーカーがつきっぱなしというのは残念ですから。

で、買ってみてどうかというと、今のところ、かなり満足しています。
少し大きいのが玉にキズですかね。
音響に関しては素人なので、音質云々は語れないのですが、結構良い音なのではないかと思います。
オートパワーオン/オフ機能もうまく動いています。
サブウーファーがコンポの本体みたい
本物の本体は普段は後ろに隠しておきます

今回はリビングに設置するので、大きめでも良いという結論になって買ってみました。
とはいえ、BeagleBoard-xM と比べるとその大きさが際立ちます。
まー、どうせ本体は見えない所にあるわけですから、別に構わないんですけど。

しかし、MPDは便利です。
家中に設置したら面白いだろうなー。狭い家なんで全く意味ないですけどね…。



ちなみに、他にもオートパワーオン/オフ機能がついたスピーカーを探したのですが、そんなに数は無さそうです。
今回の用途に使えそうなのは、以下くらいでしょうか。
2013/09/16

MPDroid の使い方

2013/09/15

BeagleBoard-xM 上で MPD を動かし、ミュージックサーバを構築する

我が家では NAS を設置して、その中に音楽ファイルを入れています。
PC間で音楽をシェアして便利に使っているのですが、一つ問題が。

私の持っているノートPCは音がひどいのです。
音楽を聴くのにはとても使えません。
リビングなどで作業する時には、小さなスピーカーも一緒に持って行くのですが、これがとても面倒臭い。

そこで、リビングに MPD (Music Player Daemon) サーバを設置してみることにしました。
MPD はリモートで音楽を再生するためのサーバで、スマホなどから遠隔操作で音楽再生を行うことが出来ます。

しかし、リビングに新しいPCを設置するなんて本末転倒ですリビングで作業するときにはそのPCを使えば良い訳ですから
そんな時こそ、BeagleBoard-xM の出番以前、購入したもの。
新規購入の場合は BeagleBone BlackRaspberry PiCubox 等も候補に上がりそうです。

小さいし、低消費電力なので、MPDサーバにはぴったりと言えます。
5W 以下のようなので、電気代は100円/月以下で抑えられそう。

というわけで、実際にやってみました。
構成の簡略図は以下のような感じ。

NAS に入っている音楽ファイルを、BeagleBoard-xM にネットワークマウントし、それをスピーカーから鳴らすわけです。
音楽の再生・停止や音量のコントロールはスマホ等、同一ネットワーク内にある MPD クライアントから行います。

以降は、BeagleBoard-xM に Ubuntu 13.04 がインストールされていて、 ディスプレイ、キーボード、スピーカーが接続されている状態を前提としています。
2013/09/12

BeagleBoard-xM 上に Ubuntu 環境を構築する

BeagleBoard-xM で MPD サーバを構築しようと思い立ちまして、やってみました。
OS の選択はいくつかあるのですが、今回は扱いやすい Ubuntu 13.04 にしてみました。
参考
http://elinux.org/BeagleBoardUbuntu
Method 1: Download a Complete Pre-Configured Image

以下は、デスクトップマシン(Ubuntu 12.04 LTS)での作業です。
2014/2/22 追記
Raw Image を直接書き込む方法を書きました。
この方法ならば Windows でも大丈夫です。
穀風: BeagleBoard-xM 上に Ubuntu 環境を構築する (Windows 編)

Raw Image を用意

まずは、イメージをダウンロードして解凍。
その後、チェックサムを確認します。
$ wget https://rcn-ee.net/deb/rootfs/raring/ubuntu-13.04-console-armhf-2013-08-24.tar.xz
$ echo "026d8809821c9b6438f1d8ec4991aa58  ubuntu-13.04-console-armhf-2013-08-24.tar.xz" | md5sum -c - 
ubuntu-13.04-console-armhf-2013-08-24.tar.xz: OK
$ tar xJf ubuntu-13.04-console-armhf-2013-08-24.tar.xz

micro SD カードスロットの確認

カードリーダに micro SD card を入れ、以下のコマンドを実行。
$ cd ubuntu-13.04-console-armhf-2013-08-24/
$ sudo ./setup_sdcard.sh --probe-mmc
Are you sure? I Don't see [/dev/idontknow], here is what I do see...

fdisk -l:
Disk /dev/sda: 120.0 GB, 120033041920 bytes
Disk /dev/sdb: 1000.2 GB, 1000203804160 bytes
Disk /dev/sdc: 2000.4 GB, 2000398934016 bytes
Disk /dev/sdd: 1000.2 GB, 1000203804160 bytes
Disk /dev/sde: 3951 MB, 3951034368 bytes
Disk /dev/mapper/isw_ecbcbgbcii_Volume0: 1000.2 GB, 1000201129984 bytes
Disk /dev/mapper/isw_ecbcbgbcii_Volume0p1: 1000.2 GB, 1000199946240 bytes

# 以下略
今回は 4GB の micro SD Card を用意したので、/dev/sde が対象であることがわかります。

イメージの書き込み

以下のコマンドでイメージを micro SD Card に書き込みます。
注意:/dev/sde はご自分のデバイスと読み替えてください。誤ったデバイスを指定すると、その内容を全消去してしまいます!
$ sudo ./setup_sdcard.sh --mmc /dev/sde --uboot beagle_xm --swap_file 512

確認のメッセージが表示されます。
くどいようですが、間違ったデバイスを指定していないことを確認の上、y を入力します。
Are you 100% sure, on selecting [/dev/sde] (y/n)?

書き込みが始まります。
少々、時間がかかります。
以下のようなメッセージが表示されたら終了です。
Finished populating rootfs Partition
-----------------------------
setup_sdcard.sh script complete
-----------------------------
The default user:password for this image:
ubuntu:temppwd
-----------------------------
デフォルトのユーザ名とパスワードが表示されているので覚えておきます。
ユーザ名: ubuntu
パスワード: temppwd

起動

micro SD Card を BeagleBoard-xM に挿入し、電源をつなぎます。
この時、ディスプレイ、キーボード、イーサネットはつないでおいた方がよいです。

ディスプレイに起動画面が表示され、数秒でログイン画面になりました。
先ほどの ubuntu:temppwd でログインします。

パスワード変更

必須ではないですが、セキュリティ的にはパスワードを変更しておいた方が良いでしょう。
$ passwd
Changing password for ubuntu.
(current) UNIX password: 
Enter new UNIX password: 
Retype new UNIX password: 
passwd: password updated successfully

以上でOSのインストール終了です。
2013/08/29

SQLiteDatabase.openDatabase で StackOverflowError

私が GooglePlay に出しているアプリ SQLiteViewer に以下のようなクラッシュレポートが届きました。

java.lang.StackOverflowError
at java.util.HashMap.<init>(HashMap.java:138)
at java.util.HashMap.<init>(HashMap.java:174)
at java.util.LinkedHashMap.<init>(LinkedHashMap.java:119)
at android.util.LruCache.<init>(LruCache.java:81)
at android.database.sqlite.SQLiteDatabase$1.<init>(SQLiteDatabase.java:2321)
at android.database.sqlite.SQLiteDatabase.setMaxSqlCacheSize(SQLiteDatabase.java:2321)
at android.database.sqlite.SQLiteDatabase.<init>(SQLiteDatabase.java:2072)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1129)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1086)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1146)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1086)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1146)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1086)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1146)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1086)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1146)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1086)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1146)
at ...

どうも、SQLiteDatabase#openDatabase を再帰的に呼んでいる様子。
明らかに、SQLiteDatabase 内部で再帰呼び出しに入っているので、Android SDK 内のバグなのは間違いありません。
とはいえ、原因が分からない事には対策しようが無いので、とりあえず AOSP のコードを見てみることにしました。

が…クラッシュレポートには、発生時の端末名も Android のバージョン番号も記載がありません。
仕方がないので、Android 2.1 から順に見ていくことにしました。

参考
バージョン間の違いを見ていくのは結構面倒くさいのですが、以下のサイトを使うと効率的にコードをチェックすることができます。
GrepCode: android - Java Project - Source Code

すると、Android 4.0 の SQLiteDatabase.java に以下のようなコードが

SQLiteDatabase.java (Android 4.0)
    /**
     * Open the database according to the flags {@link #OPEN_READWRITE}
     * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}.
     *
     * <p>Sets the locale of the database to the  the system's current locale.
     * Call {@link #setLocale} if you would like something else.</p>
     *
     * <p>Accepts input param: a concrete instance of {@link DatabaseErrorHandler} to be
     * used to handle corruption when sqlite reports database corruption.</p>
     *
     * @param path to database file to open and/or create
     * @param factory an optional factory class that is called to instantiate a
     *            cursor when query is called, or null for default
     * @param flags to control database access mode
     * @param errorHandler the {@link DatabaseErrorHandler} obj to be used to handle corruption
     * when sqlite reports database corruption
     * @return the newly opened database
     * @throws SQLiteException if the database cannot be opened
     */
    public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags,
            DatabaseErrorHandler errorHandler) {
        SQLiteDatabase sqliteDatabase = openDatabase(path, factory, flags, errorHandler,
                (short) 0 /* the main connection handle */);
 
        // set sqlite pagesize to mBlockSize
        if (sBlockSize == 0) {
            // TODO: "/data" should be a static final String constant somewhere. it is hardcoded
            // in several places right now.
            sBlockSize = new StatFs("/data").getBlockSize();
        }
        sqliteDatabase.setPageSize(sBlockSize);
        sqliteDatabase.setJournalMode(path, "TRUNCATE");
 
        // add this database to the list of databases opened in this process
        synchronized(mActiveDatabases) {
            mActiveDatabases.add(new WeakReference<sqlitedatabase>(sqliteDatabase));
        }
        return sqliteDatabase;
    }
 
    private static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags,
            DatabaseErrorHandler errorHandler, short connectionNum) {
        SQLiteDatabase db = new SQLiteDatabase(path, factory, flags, errorHandler, connectionNum);
        try {
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.i(TAG, "opening the db : " + path);
            }
            // Open the database.
            db.dbopen(path, flags);
            db.setLocale(Locale.getDefault());
            if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
                db.enableSqlTracing(path, connectionNum);
            }
            if (SQLiteDebug.DEBUG_SQL_TIME) {
                db.enableSqlProfiling(path, connectionNum);
            }
            return db;
        } catch (SQLiteDatabaseCorruptException e) {
            db.mErrorHandler.onCorruption(db);
            return SQLiteDatabase.openDatabase(path, factory, flags, errorHandler);
        } catch (SQLiteException e) {
            Log.e(TAG, "Failed to open the database. closing it.", e);
            db.close();
            throw e;
        }
    }


986行目で openDatabase(String, CursorFactory, int, DatabaseErrorHandler, short) を呼んでいるのですが、そのオープン時に SQLiteDatabaseCorruptException が発生した場合、1024行目で openDatabase(String, CursorFactory, int, DatabaseErrorHandler) を呼んでしまいます。
これはそのまま、986行目に行くので、同じメソッドが呼ばれ、そのメソッド内では同じデータベースを開こうとするので、当然 SQLiteDatabaseCorruptException が呼ばれ…
という現象が起きてしまうわけです。

報告されたスタックトレースと行番号がずれているので、絶対とは言えませんが、現象からしてこの問題で間違いないと思われます。

この問題の修正方法ですが、 今回の場合、事前にデータベースが壊れているかどうかを知る術がありませんので、SQLiteDatabase#openDatabase()try & catchで囲んで、データベースが壊れている旨のエラーメッセージを出すしか回避策はなさそうです。

ちなみに、この問題は Android 4.1 では解消しているようですAndroid 3.x 系はソースコードが公開されていないので、わかりません。

2013/08/15

Ubuntu 12.04 LTS で 左Ctrl と Caps Lock キーを入れ替える方法2種

いつも忘れてしまうので、備忘録がわりに。

X Window System スタート時

/etc/default/keyboard を以下のように書き換える
XKBMODEL="jp106"
XKBLAYOUT="jp"
XKBVARIANT="106"
XKBOPTIONS="ctrl:swapcaps"

ログイン時


System Settings → Keyboard

Typing → Layout Settings

Layouts → Options...

Ctrl key position → Swap Ctrl and Caps Lock
2013/08/14

Android の AutoCompleteTextView で文字と背景が白くなってしまう

Android 2.3 以前では AutoCompleteTextView などドロップダウンメニューの文字色が白になってしまい見えなくなってしまうという不具合が時々発生します。

たとえば、以下のコードを実行すると、
    AutoCompleteTextView mAutoCompleteTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mAutoCompleteTextView = (AutoCompleteTextView) findViewById(R.id.autoCompleteTextView1);

        String[] list = new String[] {
                "alpha",
                "bravo",
                "charlie",
                "delta",
                "echo"
        };
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(
                this,
                android.R.layout.simple_dropdown_item_1line,
                list);
        mAutoCompleteTextView.setAdapter(adapter);
        mAutoCompleteTextView.setThreshold(1);

        mAutoCompleteTextView.setOnFocusChangeListener(new OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (hasFocus) {
                    mAutoCompleteTextView.showDropDown();
                }
            }
        });
    }

以下のようになります。
ちゃんと候補は登録されていますので、タップすれば入力可能です。
文字色が白、かつバックグラウンドが白なため、候補が見えなくなっちゃってるんですね。

これ、バグというより、デザインが統一されていないために起きた不具合と言えそうです。

というわけで、以下のように ArrayAdapter のコンストラクタに与える引数を android.R.layout.select_dialog_item に変えるとうまくいきます。
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(
                this,
                android.R.layout.select_dialog_item,
                list);


2013/08/13

AutoCompleteTextView をタップした瞬間に候補を表示させる方法

Android で入力候補を表示させる場合、一般的に AutoCompleteTextView を使用すると思います。

AutoCompleteTextView には setThreshold というメソッドがあり、何文字入力した時点から候補を表示するか指定することが出来ます。
しかし、このメソッド、0以下を指定できません。
つまり、最低でも1文字入力しないと候補が出て来ないのです。

多分、大量の候補があった場合に、全部表示してしまうとパフォーマンスが低下するからなのでしょう。
しかし、候補が少ない場合はフォーカスが移った瞬間に候補を表示してあげた方が親切な場合もあります。

そんな場合は、以下のようにしてやれば、フォーカスが移った瞬間に候補を表示させることができます。
    AutoCompleteTextView mAutoCompleteTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mAutoCompleteTextView = (AutoCompleteTextView) findViewById(R.id.autoCompleteTextView1);

        String[] list = new String[] {
                "alpha",
                "bravo",
                "charlie",
                "delta",
                "echo"
        };
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
                android.R.layout.select_dialog_item, list);
        mAutoCompleteTextView.setAdapter(adapter);
        mAutoCompleteTextView.setThreshold(1);

        mAutoCompleteTextView.setOnFocusChangeListener(new OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (hasFocus) {
                    mAutoCompleteTextView.showDropDown();
                }
            }
        });
    }

onFocusChangeListener で、フォーカスが移ったことを検知し、その際に強制的に showDropDown で候補を表示させるわけです。
アプリの作りによっては、他のイベントをトリガにした方が良い場合もあるかもしれません。
2013/06/04

Google Play の Licensing サービスをデバッグする

Google Play の Licensing サービスを使う にて Licensing サービスを利用する準備が整いましたので、実際に動作させ、デバッグしてみます。

テスト用アカウントの種類

まずは、実際に購入していなくても、ライセンス認証に通るアカウントを設定します。
テストレスポンスを受信可能なアカウントは以下のように2種類あります。
用途によって使い分けましょう。

アップロード前にチェック可テストレスポンス受信可テスト設定可
Publisher account
Test account××
Other×××

Publisher Account の設定

Publisher Account はアカウント所有者とほぼ同等の権限を持つアカウントです。
Licensing サービスの設定だけでなく、APK のアップロードやストアの掲載情報の編集、アプリの削除まで出来てしまいますので、お気をつけください。

まず、アカウント所有者で Developer Console にログインし、
設定 → ユーザーアカウントと権限 → 新しいユーザーを招待

Gmail アドレスを入力して、「招待状を送信」をクリックすると、その Gmail アカウントにメールが届きます。
その指示に従うと、Publisher アカウントとして登録されます。

Test account の設定

Test Account は Licensing サービスに特化したテストアカウントです。
Publisher Account のように多くの権限がないため、大人数で開発を行なっている場合には使用しやすいでしょう。
ただ、アップロード前のアプリに対してデバッグが出来ないという制限があります。

Developer Console → 設定 → アカウントの詳細

「テスト用のアクセス権がある Gmail アカウント」 に gmail アドレスを入力。
複数ある場合はカンマ区切りで

デバッグ

「ライセンステスト応答」を設定し、保存します。
先のアプリでライセンス認証を行うと以下の様なレスポンスが返ってきます当然、テストアカウントでログインしている端末で動作させる必要があります。
ServerManagedPolicy を適用している場合の Callback を載せておきましたので参考にして下さい。

ServerManagedPolicy の Callback
LICENSEDallow
LICENSED_OLD_KEYallow
NOT_LICENSEDdontAllow
ERROR_CONTACTING_SERVERdontAllow (Retry)
ERROR_SERVER_FAILUREdontAllow (Retry)
ERROR_INVALID_PACKAGE_NAMEapplicationError
ERROR_NON_MATCHING_UIDapplicationError
ERROR_NOT_MARKET_MANAGEDapplicationError

さらに詳しい情報は以下を参考にして下さい。

以上でデバッグ完了です。

ServerManagedPolicy を使う場合の注意

ServerManagedPolicy を適用した場合、一度 LICENSED で認証が返ると、その情報をローカルキャッシュに保存してしまいます。

キャッシュを使用しているかどうかは、LogCat を見るとわかります。
以下のように Using cached license response が出力されている場合はキャッシュを使っています。
この場合、一定時間たたないと、常に allow が返ってきます。

以下のように Received response. が出力されている場合は通信した結果を使用しています。


2013/06/03

Google Play の Licensing サービスを使う

Google Play には、そのアプリが正規に Google Play からダウンロードされ、そのアカウントに利用権限があるかどうか確認するためのサービスがあります。
これにより、 「有料アプリをダウンロード。その後、すぐ apk をSDカード等にコピーして、購入をキャンセル。すると無料でアプリを使用出来る」 という裏技が(簡単には)使用できなくなるので、有料アプリを公開している方は是非導入すべきでしょう。

ちなみに、LVL (License Verification Library) というと通りが良いようですが、LVL は Licensing Service を Android アプリに組み込む時に使用するライブラリのことです。
2013/06/02

SQLiteDatabase.openDatabase() に OPEN_READONLY 属性を付けているのに "attempt to write a readonly database" が発生することがある

私が GooglePlay に出しているアプリ SQLiteViewer に以下のようなクラッシュレポートが届きました。

java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=0, result=-1, data=Intent { dat=xxxx }} to activity {com.kokufu.android.apps.sqliteviewer.free/com.kokufu.android.apps.sqliteviewer.base.MainActivity}: android.database.sqlite.SQLiteException: attempt to write a readonly database
at android.app.ActivityThread.deliverResults(ActivityThread.java:2532)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:2574)
at android.app.ActivityThread.access$2000(ActivityThread.java:117)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:961)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:130)
at android.app.ActivityThread.main(ActivityThread.java:3683)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:897)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:655)
at dalvik.system.NativeStart.main(Native Method)
Caused by: android.database.sqlite.SQLiteException: attempt to write a readonly database
at android.database.sqlite.SQLiteDatabase.dbopen(Native Method)
at android.database.sqlite.SQLiteDatabase.(SQLiteDatabase.java:1849)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:820)
at com.kokufu.android.apps.sqliteviewer.base.b.a(ProGuard:28)
at com.kokufu.android.apps.sqliteviewer.base.MainActivity.c(ProGuard:631)
at com.kokufu.android.apps.sqliteviewer.base.MainActivity.a(ProGuard:471)
at com.kokufu.android.apps.sqliteviewer.base.MainActivity.onActivityResult(ProGuard:213)
at android.app.Activity.dispatchActivityResult(Activity.java:3935)
at android.app.ActivityThread.deliverResults(ActivityThread.java:2528)
... 11 more

発生したのは SQLiteDatabase.openDatabase()。
この手のアプリは、開こうとしたファイルによってはエラーが出てしまうのは仕方の無いことですとは言え、クラッシュして良いわけではないので、最新版ではクラッシュしないように修正済です。
SQLiteException が RuntimeException なのは間違いだと思う。


ただ、よく見てみると、Caused by のところに、attempt to write a readonly database との文字が。
コードの該当箇所は以下で、OPEN_READONLY 属性がついているのにも関わらずです。

            db = SQLiteDatabase.openDatabase(
                    uri.getPath(),
                    null,
                    SQLiteDatabase.OPEN_READONLY | SQLiteDatabase.NO_LOCALIZED_COLLATORS);

「そんなわけないだろー」と思いながら検索してみると、以下のような情報が。

なるほど。LG-P500 (Android 2.3.3) にあるバグなのね。
で、以下のようにすれば回避可能と。

try {
    db = SQLiteDatabase.openDatabase(
            uri.getPath(),
            null,
            SQLiteDatabase.OPEN_READONLY | SQLiteDatabase.NO_LOCALIZED_COLLATORS);
} catch (SQLiteException e) {
    final String message = e.getMessage();
    if (message == null) {
       throw e;
     }
     if (!message.contains("attempt to write a readonly database")) {
       throw e;
     }
    // データベースを read-only モードで開こうとしたのにも関わらず、
    // 失敗することがあります。
    // これは、LG-P500 Release:2.3.3 Sdk:10. にあるバグのためです。
    // openDatabase メソッドは readonly で開いたデータベースに
    // 書き込みを行おうとします。
    // これは1度だけのようなので、1回 readwrite モードで開いた後、
    // 閉じて、さらにもう1度 readonly で開きます。
    db = SQLiteDatabase.openDatabase(
            uri.getPath(),
            null,
            SQLiteDatabase.OPEN_READWRITE);
    db.close();
    db = SQLiteDatabase.openDatabase(
            uri.getPath(),
            null,
            SQLiteDatabase.OPEN_READONLY | SQLiteDatabase.NO_LOCALIZED_COLLATORS);
}

何か、特定の機種のバグ回避のために、READWRITE でデータベースを開くのは気が進まないんですけど、仕方ありません。
まー、そもそも、クラッシュレポートに機種情報が載っていなかったので、LG-P500 の問題だったのかどうかもわからないのですが…

2013/05/18

Eclipse ADT を Ver. 22 にしたら ClassNotFoundException が発生するようになった

2013/5/18 追記
本件、情報いただきました。
https://twitter.com/droidxav/status/335287826042200064
Google I/O 後に直るそうです。

Eclipse ADT を Ver. 22 にしたら gen ディレクトリ以下が生成されなくなる問題を解決し、ビルドが通るようになったので、これまで動いていたアプリを再ビルドし、実行して見ました。

すると!

FATAL EXCEPTION: main
java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.example.test/com.example.test.MainActivity}: java.lang.ClassNotFoundException: Didn't find class "com.example.test.MainActivity" on path: /data/app/com.example.test-2.apk
 at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2106)
 at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2230)
 at android.app.ActivityThread.access$600(ActivityThread.java:141)
 at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1234)
 at android.os.Handler.dispatchMessage(Handler.java:99)
 at android.os.Looper.loop(Looper.java:137)
 at android.app.ActivityThread.main(ActivityThread.java:5041)
 at java.lang.reflect.Method.invokeNative(Native Method)
 at java.lang.reflect.Method.invoke(Method.java:511)
 at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
 at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.ClassNotFoundException: Didn't find class "com.example.test.MainActivity" on path: /data/app/com.example.test-2.apk
 at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:65)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:501)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:461)
 at android.app.Instrumentation.newActivity(Instrumentation.java:1054)
 at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2097)
 ... 11 more

ClassNotFoundException が発生してしまいました。

ClassNotFoundException が発生するケースは、以下のどちらかであることがほとんどです。

  • Reflection の問題 (Manifest の記述ミスとか)
  • Export の問題(Cで言うリンクの問題)
これまで動いていたアプリなので、Reflection の問題である可能性は低いです。
というわけで、Export設定を見てみました。

Project を右クリック → Property → Java Build Path

Android Private Libraries にチェックがついていません。
このプロジェクト、android-support-v4.jar を使用しているため、Android Private Libraries を Export する必要があります。

しかし、Android SDK Tools Rev. 17 以降、libs ディレクトリ以下は自動的に Export されるはず…。
もちろん、android-support-v4.jar は libs ディレクトリ以下においてあります。

しかし、この手の仕様が変わるのはよくあること。
Android Private Libraries にチェックをつけて、ビルドしなおして、実行してみました。
すると、特に問題なく動きました。

どうも libs 以下ににあるだけではダメで、自分で Export するようにしなければならなくなったようです。
ADT Plugin | Android Developers の General Notes には書いていないようですが…。

ちなみに、新規にプロジェクトを作成した場合は、初期設定で Android Private Libraries にチェックがつくようです。
2013/05/17

Eclipse ADT を Ver. 22 にすると R.java が生成されない

Android SDK rev. 22 がリリースされたので、アップデートしてみました。
いつも通り、Eclipse ADT のアップデートも要求さるがままに実行。

すると、ビルドが通らなくなってしまいました。
どうも、gen 以下が作成されていない様子。

「困ったな」と思って探してみたら、既に解決策を見つけてくれている人がいました。

なるほど、Android SDK Build-tools もインストールしないとだめなのね。
おまけに、一度 Rev. 22 にしないとリストに出てこないときてる。
これははまるわー

2013/04/28

Ubuntu 12.04 LTS に Workrave を入れてみた

1日中座って過ごす危険性と、リスクを防ぐためのたった2つのこと : ライフハッカー[日本版] を読んで、まさに自分の生活がヤバイなぁと思ったので、お勧めのタイマーアプリを入れてみることにしました。
私は Ubuntu で作業していることが多いので、Linux 用もある Workrave を選択。

追記
書いてみてから気づきましたが、Workrave は Ubuntu の APT に含まれているので、 以下のコマンド一発でインストール出来ます
$ sudo apt-get install workrave

このエントリは備忘録のために残しておきますが、以下のような面倒くさいことをやる必要はありません。
2013/03/30

Visual Studio 2012 で TypeScript 開発環境を構築

先日、「Visual Studio 2012 で jQuery 開発環境を構築」というエントリを書いたのですが、このタイミングで Visual Studio 使い始めるなら TypeScript を使うのも手かと思い、TypeScript の開発環境も作ってみました。
使ってみると、TypeScript なかなか良い感じです。
2013/03/21

Android端末 を Ubuntu 12.04 にメディアデバイスとして接続する

昨日、Nexus7 を Ubuntu にメディアデバイスとして接続する というエントリを書いたのですが、このご時世に、いちいち手動でマウントポイントを用意するのも何だなぁと思い、gvfs でマウントできないものか調べてみることに。
すると、結構あっさり以下のサイトにたどり着きました。
なぜ昨日たどり着かなかった、俺…

ふむふむ。Ubuntu 13.04 では gvfs がアップデートされて Android 4.0 の端末MTPアクセスの端末という意味だと思われますにアクセスできるようになったと。
そして、12.04, 12.10 では PPA レポジトリを追加することによって、gvfs をアップデートでき、同じようにアクセス可能になるってことだそうです。

というわけで、早速やって見ました。

まず、以下のコマンドで PPA を追加・更新し、パッケージを更新します。

$ sudo add-apt-repository ppa:langdalepl/gvfs-mtp
$ sudo apt-get update
$ sudo apt-get upgrade

正常に終了したら、再起動。

Nexus 7 を接続すると、Nautilus に自動的に表示されました。
んー、非常に簡単。

2013/03/20

Nexus7 を Ubuntu にメディアデバイスとして接続する

@ Ubuntu 12.04 LTS
2013/3/21 追記
以下の方法で手動マウントするのも良いのですが、gvfs を利用して自動マウントする方が簡単でした。
穀風: Android端末 を Ubuntu 12.04 にメディアデバイスとして接続する

我が家の Ubuntu は Nexus7 を接続しても、うんともすんとも言ってくれません。
これでは少々使い勝手が悪いので、USBメモリのように接続したら自動でマウントして表示されるようにしてみました。
参考にしたのは以下のサイトです以下のサイトでは go-mtpfs を使用していますが、私は標準の mtpfs を使用しました
2013/03/18

OnItemClickListener.onItemClick() の中で isChecked() を呼んではいけない?

穀風: onListItemClick() の中で isChecked() を呼んではいけない? というエントリを書きましたが、同様に OnItemClickListener.onItemClick() の中ではどうなのか、調べてみました。
public class MainActivity extends ListActivity {
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        String[] listItems = { "1", "2", "3" };

        getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
        ListAdapter adapter = new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_multiple_choice, listItems);
        setListAdapter(adapter);

        getListView().setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                CheckedTextView cv = (CheckedTextView) view;
                Log.d(TAG, "isChecked " + cv.isChecked());
            }
        });
    }
}

これを、Android 4.0.4 のエミュレータで動作させると以下の通り
// OFF → ON
isChecked false

// ON → OFF
isChecked true

Android 4.2 のエミュレータで動作させると以下の通り
// OFF → ON
isChecked true

// ON → OFF
isChecked false

やはり逆転してしまいました。
先程も書いたとおり、とりあえずの対策は出来ると思いますが、正しい対処方法がわかりません…

onListItemClick() の中で isChecked() を呼んではいけない?

少し前に、知人に頼まれて作った Androidアプリの動作がおかしいとのこと。
どうも、Galaxy Nexus を 4.0.2 → 4.1.1 にアップデートしてからおかしくなったらしい。

調べてみると、ListActivity.onListItemClick() の中で CheckedTextView.isChecked() を呼んだ時の挙動が変化した模様。

というわけで、以下のようなコードを用意して実験してみました。
public class MainActivity extends ListActivity {
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        String[] listItems = { "1", "2", "3" };

        getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
        ListAdapter adapter = new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_multiple_choice, listItems);
        setListAdapter(adapter);
    }

    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        // ↓ 少なくとも Android 4.2 までは空関数
        // super.onListItemClick(l, v, position, id);
        CheckedTextView cv = (CheckedTextView) v;

        Log.d(TAG, "isChecked " + cv.isChecked());
    }
}

これを、Android 4.0.4 のエミュレータで動作させると以下の通り
// OFF → ON
isChecked false

// ON → OFF
isChecked true

Android 4.2 のエミュレータで動作させると以下の通り
// OFF → ON
isChecked true

// ON → OFF
isChecked false

完全に動作が逆転しています…

そもそも、以前は「チェックしたのに isChecked が false」という直感に反した仕様だったので、正しい動作をするようになったと言えるのかもしれません。

ただ、この変更がどのような意図で行われたのかが不明です。
Android Developersはチェックしたのですが、それらしい記述を見つけることはできませんでした。
バグフィックスなのか、たまたま挙動が変わってしまったのか…
もし、詳しい方がいらっしゃいましたら、コメントやTwitterで是非教えてください。

とりあえずは、以下のように Android のバージョンをチェックして乗り切ることは可能かもしれません。
        CheckedTextView cv = (CheckedTextView) v;

        boolean isChecked = (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) ?
                !cv.isChecked() : cv.isChecked();

ただし、仕様が明確に提示されていない以上、今後もまた変更が入る可能性はあります。
つまり、onListItemClick() の中では isChecked() を呼ばない方が無難だという事になります。

しかし、チェックされた瞬間に動作させたいことってありますよねぇ…

続き: OnItemClickListener.onItemClick() の中で isChecked() を呼んではいけない?
2013/03/17

Visual Studio で Javascript のデバッグ

2013/03/16

Visual Studio 2012 で jQuery 開発環境を構築

Javascript って王道の IDE (統合開発環境)が無いですよね。
ましてや jQuery もサポートしているものとなると微妙です数は結構あるみたいですけど、「とりあえず、これ使っとけ!」みたいなデファクトスタンダードなものが無いという意味です

そんな中、Visual Studio が意外と使えるという話を聞いたので、使って見ることにしました。
結論から言うと、結構良いです。
jQuery用の IntelliSense も使えますし、ステップ実行などのデバッグも可能。
Windows でしか使えないというところが残念ですが。

2013/3/18 追記
「結構良い」と書いたのですが、1点欲しい機能が無いことに気づいてしまいました。
それは、「リファクタリング→名前変更」の機能。
変数名や関数名を一箇所変えると、使用している全ての名前が変わるというものです。
IDE には必須の機能だと思うのですが、Visual Studio Express 2012 の Javascript では使用できないようです。
(C# や C++ ではちゃんと使えます。)
うーん。惜しい。

以下、環境構築と簡単な使用方法です。
2013/03/11

Canon のプリンタドライバ for Linux をインストールしようとしたら "Cannot specify package management system." で失敗した

Canon MP640 のプリンタドライバ for Linux を Ubuntu 12.04 LTS にインストールしようとしたのですが、エラーが出てしまいました。
$ tar zxf cnijfilter-mp640series-3.20-1-i386-deb.tar.gz
$ cd cnijfilter-mp640series-3.20-1-i386-deb
$ sudo ./install.sh
==================================================

Canon Inkjet Printer Driver Ver.3.20-1 for Linux
Copyright CANON INC. 2001-2009
All Rights Reserved.

==================================================
Error! Cannot specify package management system.

install.sh の中でこのエラーを出しているところを探してみると、以下にありました。

install.sh
C_FUNC_get_system()
{
 local c_system_rpm=""
 local c_system_deb=""

 ## Judge is the distribution supporting rpm? ##
 rpm --version 1> /dev/null 2>&1
 c_system_rpm=$?

 ## Judge is the distribution supporting dpkg(debian)? ##
 dpkg --version 1> /dev/null 2>&1
 c_system_deb=$?

 ## rpm and deb are error, or rpm and deb are no error, is error ##
 if [ $c_system_rpm = 0 -a $c_system_deb = 0 ] || [ $c_system_rpm != 0 -a $c_system_deb != 0 ]; then
  echo $C_err_msg1
  return $C_ERR_CODE
 else
  if test $c_system_rpm -eq 0; then
   C_system="rpm"
  else
   C_system="deb"
  fi
 fi
 
 return 0
}

ふむふむ。
rpm と dpkg のうち、一方だけがインストールされていないとだめなのね。
で、以下を実行してみると、
$ rpm --version
RPM version 4.9.1.1
$ dpkg --version
Debian `dpkg' package management program version 1.16.1.2 (amd64).
This is free software; see the GNU General Public License version 2 or
later for copying conditions. There is NO warranty.

両方インストールされてるわ…
記憶にないけど

というわけで、以下のように rpm を削除すると、問題なくインストールできるようになりました。
$ sudo apt-get remove rpm
$ sudo apt-get autoremove
2013/03/06

InstrumentationTestRunner の起動シーケンス図を描いてみた

Android JUnit のテスト実行役である InstrumentationTestRunner の起動シーケンス図を描いてみました。


Application.onCreate()AndroidTestRunner.runTest() は別スレッドで動作しているのです。(赤枠で囲ったとこ)
つまり、「テスト開始時に Application.onCreate() が呼ばれている保証はないよ」ってことですね。

mainスレッド(UIスレッド)を使ってテストを行う場合は問題ありませんが、テスト用のスレッドをそのまま使う場合バックグラウンド処理等は注意が必要です。

2013/03/05

Eclipse の Android JUnit Test は 2回実行される

Android で JUnit Test をしていた時、ちょっと気になる現象を発見してしまいました。
たまたま、android.app.Application を継承した独自実装の Application を使用して、その onCreate() にログを仕込んでいた状態だったので気づいたのです。
以下、その詳細です。

コード

実際のコードをあげるのは大変なので、以下に簡略化したものをあげておきます。

MyApplication.java (Target)
public class MyApplication extends Application {
    private static final String TAG = "MyApplication";

    @Override
    public void onCreate() {
        super.onCreate();

        Log.d(TAG, "onCreate");
    }
}

AndroidManifest.xml (Target)
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.kokufu.android.test.sampletarget"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />

    <application
        android:name=".MyApplication"
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        
        <!-- 省略 -->
        
    </application>

</manifest>


このアプリに対し、以下のようなテストを作成。
(テストとしての役割は全く果たしていませんが…)

MyTest.java (Test)
public class MyTest extends TestCase {
    private static final String TAG = "MyTest";

    public void testA() {
        Log.d(TAG, "testA");
    }

    public void testB() {
        Log.d(TAG, "testB");
    }
}

Application.onCreate() が2度呼ばれている

Eclipse から
Run → Run As → Android JUnit Test
と実行してみると、
以下の様な結果が。
onCreate が2度呼ばれています。
間違えて 2度実行してしまったわけではありません。
たった1度、実行操作を行っただけです。

さらによく見てみると、最初の onCreate だけ PID が異なります。
どうも、別プロセスで 2回実行されているようですが、テスト自体は2回目だけ実行されるようです。
2013/02/25

Nexus10 を工場出荷状態に初期化してみた

Nexus7 を工場出荷状態に初期化してみた に引き続き、Nexus10 も工場出荷状態に戻してみました。
基本はほぼ同じですが。。。
今回も Cygwin on Windows 7 Professional (64bit) で実行しましたが、Linux でも同様に出来ます。

以下、前提条件です。
  • Android SDK がインストールされていること
  • %SDK Location%/platform-tools にパスが通っていること
  • 開発者向けオプション - USBデバッグがONになっていること

ブートローダーのアンロック

Nexus10 を USB で PC に接続し、以下のコマンドを実行します。もしくは、音量UP・DOWNボタン同時押しで電源ON
$ adb.exe reboot bootloader

すると、Nexus10 が再起動し、fastboot モードで立ち上がります。

この状態で以下のコマンドを実行。
$ fastboot oem unlock

ブートローダーをアンロックするか聞かれます。

タッチパネルは効きませんので、音量ボタンで選択し、電源ボタンで決定します。
Yes を選択して少し待つと、以下のようにFASTBOOT STATUS - OKAYoem unlockと表示されます。
これで、ブートローダーのアンロック完了です。

ファクトリーイメージをダウンロード

Nexus10 のファクトリーイメージは以下のサイトからダウンロード出来ます。
Factory Images for Nexus Devices - Android — Google Developers
2013/2/24 時点でのダウンロード先
ダウンロードしたら、解凍しておきます。
$ tar zxf mantaray-jdq39-factory-d79f489e.tgz

ファクトリーイメージの書き込み

解凍したディレクトリにシェルスクリプトが用意されているので、それを実行します。
$ cd mantaray-jdq39
$ ./flash-all

何の音沙汰もなく、少々待たされるので不安になりますが、そのまま待てば自動的に再起動して完了です。