2011年8月7日

[widget]AppWidgetManager

過去記事:2010/08/22 17:22
話の流れ上、こちらを先に扱わねばなるまい。
今回作ったAppWidgetではAppWidgetManagerを使っているのだが、まだ何をしているのかよくわかっていない。。
ちゃんと把握しよう!

まず、起動後すぐに呼ばれるのはonUpdate()。
そのまま載せよう。
    @Override

    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {

        Log.v(TAG, "onUpdate");



        final int APP_NUM = appWidgetIds.length;

        for(int i=0; i<APP_NUM; i++) {

            RemoteViews remoteView = getClickRemoteView(context);

            updateIcon(remoteView);

            appWidgetManager.updateAppWidget(appWidgetIds[i], remoteView);

        }

    }
    private RemoteViews getClickRemoteView(Context context) {

        Log.d(TAG, "getClickRemoteView");

        

        Intent intent = new Intent();

        intent.setAction(ACTION_WIDGET_CONTROL);

        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

        

        // register click event

        RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.layout.main);

        remoteView.setOnClickPendingIntent(R.id.ImageView, pendingIntent);

        return remoteView;

    }
updateIcon()は、アイコンを変更するだけの処理だから省く。
ここでは、各AppWidgetに対してupdateAppWidget()しているように見える。
しかし、印象ではいかんな。
appWidgetManagerは、引数だ。
ドキュメントによると、「updateAppWidget()を呼び出せるオブジェクトだ」とのこと。
オブジェクトなのかインスタンスなのかとか、そんなのはまあいいとしよう(私の中では、オブジェクトが具象化したものがインスタンス、としているので、引数はインスタンス扱いだ)。
第3引数のappWidgetIdsは、「アップデートが必要なappWidgetIds」とのこと。appWidgetIdsの説明に、それはなかろう・・・。
AppWidgetにはそれぞれIDがあるようなので、それは後で調べよう。
とにかく、onUpdate()が呼び出される場合には、対象となるWidgetについていろいろとやってやらなくてはならないということらしい。
そういうことからすると、掲載したonUpdate()でそれぞれのappWidgetIds[]に対してRemoteViewsをnewして割り当ててやるのは、正しいと思われる。
ここ以外では、RemoteViewsを割り当てる機会はなさそうだ。
割り当てる?
AppWidgetManager::updateAppWidget()は何だろうか。
Set the RemoteViews to use for the specified appWidgetId.
「RemoteViewsを指定したappWidgetIdに設定する」でいいのかな。
applyではなく、setか。
どこで呼んでもいいわけではなく、ACTION_APPWIDGET_UPDATEか、"outside of the handler"らしい。handlerの外部? the?
ACTION_APPWIDGET_UPDATEのハンドラ外だったら、領域UとUということになり、U∩Uなので全領域になるのではないか?
このメソッドは、UIDがAppWidget providerを所有(owner)しているところから呼ばれないと動作しないらしい。
「呼ぶことはできるけど、動作するには制限がありますよ」ということなのか。
今までを見ていると、zygoteが起動しているから、ownerはzygoteなのだろう。
とにかく、アプリレベルで呼び出せるものでないことだけは確かだ。
自然?に呼び出されるACTION_APPWIDGET_UPDATEは、ownerから呼び出されているとおもってもよさそうだ。
自分で強制的にACTION_APPWIDGET_UPDATEを使っても動かないよ、ということでよいのかな。

もう1つの呼び出し方がある。
これはonUpdate()ではないブロードキャストを受信しての動作だ。
            RemoteViews remoteView = getClickRemoteView(context);

            remoteView.setImageViewResource(R.id.ImageView, R.drawable.mounted);



            // update AppWidget

            ComponentName thisWidget = new ComponentName(context, EjectSD.class);

            AppWidgetManager.getInstance(context).updateAppWidget(thisWidget, remoteView);
前半はアイコン描画なので、どうでもいい。
ポイントは後半の2行だ。
>AppWidgetManager.getInstance(context)
これは、contextが提供するAppWidgetManagerを取得するものだ。
contextは、onReceive()の引数でもらったものになる。
ドキュメントによると、receiverが動作しているcontextらしい。
取得できるのはAppWidgetManagerのインスタンスなので、これも結局のところはAppWidgetManager::updateAppWidget()を呼び出すことになる。
さっきの「ownerから呼び出されないと」は、この意味なのか。
そうであれば、AppWidgetManagerのルールはこうなる。
  • onUpdate()の場合は、引数を使う
  • onReceive()の場合は、AppWidgetManager.getInstance(context)を使う
たぶん、これ以外では呼び出せないのだろう。

あとは、updateAppWidget()の引数だけか。
onUpdate()の場合は、各AppWidgetに対して(AppWidgetのIDに対して)RemoteViewsを設定する必要がある。
ここでは、ImageViewをタッチしたときに飛ばすPendingIntentを設定している。
不思議なのは、onReceive()の場合だ。
今回のAppWidgetだが、1つタッチすると全部のAppWidgetアイコンがちゃんと変化してくれる。
AppWidgetのIDなど何も気にしていないのに、だ。
これをどう解釈するとよいだろうか?
一番あっさりした解釈としては、複数のAppWidgetがあったとしても、全部同じRemoteViewsを参照している、だろう。
位置情報は別々に持っていて、その中身だけは一緒、と。
そうでなければ、updateAppWidget()が全AppWidgetのRemoteViewsを更新して回っている、ということになってしまう。
さすがにそれはないだろう。
もしそうなら、onUpdate()ではforの中でRemoteViewsをnewせず、外でnewしたものをupdateAppWidget()で渡してやればいいことになる。
うん、試してみたが、それでもちゃんと動いているようだ。

しかし、疑問は残る。
最初は全AppWidgetに対してnewしたRemoteViewsを設定しているのに、何でタッチすると一斉にアイコンが変化するのか?
推測するに、こうではなかろうか。
onUpdate()内で各AppWidgetごとにnew RemoteViewsしていたのは、こんなイメージ。

1回だけnew RemoteViewsした場合は、こんなイメージ。

これならば、納得できる。
psで見たとき、AppWidgetを増やしてもメモリがそんなに変化してないのも、リソースが増えていないからという理屈になる。
根拠がないのは、リソースが1つ、としているところだ。
各AppWidgetごとに異なる描画を行わせることもできるので、リソースは別々になっているものと思うのだ。
COWか?という気もするが、確証がない。

あ、updateAppWidget()の説明を読んでなかった!
なんと、引数にComponentNameをとる場合は、特定のAppWidgetインスタンスではなく全AppWidgetインスタンスが対象になると書いてある。
そうすると、最初の図が間違っていることになる。
onUpdate()時は、こうなっていたのだ。

今回のAppWidgetでは、個別に設定しなくていいので、onUpdate()はこれでよかろう。
    @Override

    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {

        RemoteViews remoteView = getClickRemoteView(context);

        updateIcon(remoteView);

        ComponentName thisWidget = new ComponentName(context, EjectSD.class);

        appWidgetManager.updateAppWidget(thisWidget, remoteView);

    }
これで、このAppWidget内では参照がこのような関係になる。

うむ、気が晴れた。

[widget]RemoteViews

過去記事:2010/08/21 16:47
"view"は打ちやすいけど、"views"は打ちにくい。
いつも"w"を抜かしてしまう。
と、どうでもいい話はなしにしよう。
AppWidgetで残る大物は、RemoteViewsとPendingIntentだろう。
RemoteViewsを先にやろう。
あー、AppWidgetManagerってのもいるな。まあ、これは気分次第で。

AppWidgetのために何か描画させたい場合、普通のViewではなくRemoteViewsを使う。
RemoteViewsはViewから派生されたわけではない。ParcelableとFilterをimplementsしてはいるが、親クラスはいない(Objectね)。
特徴は、別プロセスから表示させることができる(can be displayed in another process)だろう。
何がどう"another process"なのかがよくわからん。
起動させるのはHomeアプリだから、この場合はHomeアプリプロセスを指しているのだろうか。
違う。訳が違う。
in another process、だから、別のプロセスで表示できる、となるのか。
別プロセスを立ち上げて、そこでRemoteViewsを表示させると言うことか?
psで見ると、AppWidgetの親はzygoteになっていた。USERはapp_XX(XXは数字)。そうなっているのは、emailやmms、inputmethodのsimejiなんかもだ。
起動直後(Homeには登録していない)

USER      PID   PPID  VSIZE  RSS     WCHAN    PC         NAME

root      1772  1     78708  11124 c0108254 afd0dc74 S zygoteapp_45    2151  1772  100056 12484 ffffffff afd0eb08 S jp.typepad.hirokuma.EjectSD



Homeアプリ表示(Homeには登録していない)app_45    2151  1772  100576 12408 ffffffff afd0eb08 S jp.typepad.hirokuma.EjectSD

ちょっと時間経過

app_45    2151  1772  100576 12388 ffffffff afd0eb08 S jp.typepad.hirokuma.EjectSDHome登録時のウィジェット一覧表示中app_45    2151  1772  100576 11428 ffffffff afd0eb08 S jp.typepad.hirokuma.EjectSDHome登録app_45    2151  1772  100576 11120 ffffffff afd0eb08 S jp.typepad.hirokuma.EjectSD

ちょっと時間経過app_45    2151  1772  100576 11292 ffffffff afd0eb08 S jp.typepad.hirokuma.EjectSDHomeにもう1つウィジェット登録app_45    2151  1772  100576 10924 ffffffff afd0eb08 S jp.typepad.hirokuma.EjectSD1回タップしてみたapp_45    2151  1772  100576 10924 ffffffff afd0eb08 S jp.typepad.hirokuma.EjectSD9個置いてみた。app_45    2151  1772  100576 10700 ffffffff afd0eb08 S jp.typepad.hirokuma.EjectSD
RSSってのが変化しているのでちょこちょこpsて見てみたが、RSSは常駐セットの大きさ、らしい。swapされてない物理メモリだとか。
PCはprogram count?
とにかく、増やしても違いが見えない。
これは単に、今回作ったウィジェットがそうだからなのかも。全部同じ見栄えであってほしいので、ウィジェットIDを管理していないのだ。
app_45は、数値だと10045みたいだ。
USER+α、という計算なのかな?

RemoteViewsのコンストラクタは2つあるが、今回使っているのはpackageNameとlayoutIdをとる方。
これは単にメンバ変数に代入するだけで終わっている。
クリックイベントを受けとってもらうPendingIntentを登録するメソッドのsetOnClickPendingIntent()も、内部で保持するだけ。
内部のActionリストに追加するだけなのだ。
コンストラクタで登録したパッケージ名とレイアウトIDはいつ使うのか?
apply()が一番それっぽく使っているようだ。
apply()関係のメソッドがいくつかあるが、呼ぶと例外を投げるらしい。
システムから呼び出されることを前提にしているのだろうか?
うーん、何一つ疑問が解決しない・・・。
ウィジェットの起動はActivityManagerが行っているようなので、そこから見ていくか。
ログはamのもののようだ。services/java/com/android/server/am/ActivityManagerService.java。
まあ、そういうものか。
プロセス起動は、zygoteが行っている。
これは、Homeアプリがウィジェットとして使おうと使うまいと、関係がない。
では、Homeアプリに9つAppWidgetを登録したときの起動時logcatを見てみよう。
I/ActivityManager( 1868): Start proc jp.typepad.hirokuma.EjectSD for broadcast jp.typepad.hirokuma.EjectSD/.EjectSD: pid=2137 uid=10045 gids={}
V/EjectSD ( 2137): onReceive:android.appwidget.action.APPWIDGET_ENABLED
V/EjectSD ( 2137): onReceive:android.appwidget.action.APPWIDGET_UPDATE
V/EjectSD ( 2137): onUpdate
D/EjectSD ( 2137): getClickRemoteView
V/EjectSD ( 2137): updateIcon
D/EjectSD ( 2137): getClickRemoteView
V/EjectSD ( 2137): updateIcon
D/EjectSD ( 2137): getClickRemoteView
V/EjectSD ( 2137): updateIcon
D/EjectSD ( 2137): getClickRemoteView
V/EjectSD ( 2137): updateIcon
D/EjectSD ( 2137): getClickRemoteView
V/EjectSD ( 2137): updateIcon
D/EjectSD ( 2137): getClickRemoteView
V/EjectSD ( 2137): updateIcon
D/EjectSD ( 2137): getClickRemoteView
V/EjectSD ( 2137): updateIcon
D/EjectSD ( 2137): getClickRemoteView
V/EjectSD ( 2137): updateIcon
D/EjectSD ( 2137): getClickRemoteView
V/EjectSD ( 2137): updateIcon

V/EjectSD ( 2137): onReceive:android.intent.action.MEDIA_MOUNTED
D/EjectSD ( 2137): mount
D/EjectSD ( 2137): getClickRemoteView
これだけ見てもわからないと思うが・・・。
最初の1行は、起動。
次にintentとしてENABLED、続いてUPDATEが来ているので、処理している。
実装しているのはonUpdate()で、こんなことをしている。
final int APP_NUM = appWidgetIds.length;
for(int i=0; i<APP_NUM; i++) {
 RemoteViews remoteView = getClickRemoteView(context);
 updateIcon(remoteView);
 appWidgetManager.updateAppWidget(appWidgetIds[i], remoteView);
}

引数でappWidgetIds[]が渡されるので、それぞれにRemoteViewsを生成し(中でnewを呼び、クリック時のインテントを登録している)、アイコンを描画し、反映させている。
9つ分やっているのは、ここだったのだ。
自分で思いついたわけではなく、どこかのサイトからもらってきた処理だが、そういう意味があったのだな。
では、アイコンをタップしたときのlogcatも見ておこう。
V/EjectSD ( 2137): onReceive:jp.typepad.hirokumaEjectSD.WIDGET_CONTROL
D/EjectSD ( 2137): click
D/EjectSD ( 2137): getClickRemoteView
V/EjectSD ( 2137): onReceive:android.intent.action.MEDIA_EJECT
V/EjectSD ( 2137): onReceive:android.intent.action.MEDIA_UNMOUNTED
D/EjectSD ( 2137): umount
D/EjectSD ( 2137): getClickRemoteView

先頭部分がタップ時のintent受信。
ログに出てこないが、ここでMountServiceに対してmount/umount要求を投げている。
要求が帰ってくるまでの間、アイコンを変化させるためにRemoteViewsを作成し、アイコンをセットしている。
アイコンの変更は、こんな処理。
remoteView.setImageViewResource(R.id.ImageView, R.drawable.icon);
remoteViewはRemoteViewsのインスタンスね。
レイアウトXMLでImageViewという名前のImageViewを置いているので、それをiconというリソースにします、というだけだ。
実は、これがわからずに調べ回っていたのだ。View関係だからRemoteViewsの説明を見ればいいのだろうけど、思いつかなかったのだよ。
その後ここでやっているのは、onUpdate()と違ってこんなことになっている。
ComponentName thisWidget = new ComponentName(context, EjectSD.class);
AppWidgetManager.getInstance(context).updateAppWidget(thisWidget, remoteView);

これもどこかからもらってきた処理だ。
しかし、これも処理がわからずに困っていたのだよ。
後半はUNMOUNTEDとなったintentを受信しての処理だ。
やっていることはタップ時とほとんど同じ。
アイコンを変更させ、updateAppWidget()している。
久しぶりにAndroid関係の本を購入。
「AndroidSDK開発のレシピ」。
カバーはいりません、と言ったつもりなのに、されてしまった。。
何となく悔しい。

[widget]intent-filter

過去記事:2010/08/21 11:54
intent-filterのマッチングについては、「他もアクティビティ図にしたので、見たい人は持っていってくだされ。」ということにした。
これは複雑だ!
しかし、Uriだけのマッチングはもう少しシンプルで、hostやportの一致を見ている。
つまり、こう言えよう。
intent-filterの比較は、intent-filterごとに行われる、と。
今回でいえば、
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="jp.typepad.hirokumaEjectSD.WIDGET_CONTROL"/>
</intent-filter>

と、
<intent-filter>
<action android:name="android.intent.action.MEDIA_UNMOUNTED"/>
<action android:name="android.intent.action.MEDIA_EJECT"/>
<action android:name="android.intent.action.MEDIA_REMOVED"/>
<action android:name="android.intent.action.MEDIA_UNMOUNTABLE"/>
<action android:name="android.intent.action.MEDIA_MOUNTED"/>
<data android:scheme="file" />
</intent-filter>

だ。
前者はactionのみのため、match()はどれかのactionと一致し(matchAction)、dataはないのでMATCH_CATEGORY_EMPTY+MATCH_ADJUSTMENT_NORMALとなり(matchData)、categoryもないのでmatchData()の結果がそのまま戻り値となる。
MATCH_ADJUSTMENT_MASK でANDとって非0だからマッチした、となるのかな。
後者はactionとschemeがあるので、まずactionがどれかと一致し(matchAction)、schemeがあるのでどれかと一致してMATCH_CATEGORY_SCHEMEとなり(matchData)、categoryはないのでmatchData()の結果がそのまま戻り値となる。
これは MATCH_CATEGORY_MASK でANDするのか?
match()の戻り値をどう使うか、よくわからん。
が、そこはいいとしよう。
intent-filterの分け方としては、
  • actionのみのintentを受け付けるのであれば、同じintent-filterにまとめてよい
  • actionとschemeがあるintentを受け付けるのであれば、同じintent-filgerにまとめてよい
  • categoryを含む場合は完全一致となるので注意すべし
ということになるだろうか。
試していないので、そうなるかわからんが、そうなるんではないかね。

MOUNT/UNMOUNTなどのintentは、schemeとして"file"が付加されている。
送信されるintentはこんな感じだ。
in = new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + path));
むむ、ということは、schemeだけでなくpathもあることになる。
しかしご安心を。
matchData()でpathが比較されるのは、addDataAuthority()した場合のみだ。
XMLだと、<auth>タグがそれに該当するはずなのだが・・・ドキュメントにはないな。
ちなみに、IntentFilterクラスには、writeToXml()というメソッドがある。
試してないけど、どうやらXMLに吐きだしてくれるみたいだ。
何か使えるかな?

なお、MOUNT/UNMOUNTを受け付けるには許可が必要となる。
AndroidManifest.xmlに、
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
がいるのだ。
まあ、忘れていてもlogcatで例外を投げてくれるので、わかりやすいがね。

[widget]AppWidgetProvider

過去記事:2010/08/20 21:00
AppWidgetProviderは、BroadcastReceiverをextendsしている。
コンストラクタもからっぽ。
特徴としては、onReceive()をオーバーライドして、onUpdate(), onDeleted(), onEnabled(), onDisabled()を呼び出せるようにしているところ。
なので、onReceive()を自分でオーバーライドした場合には、superを呼び出すタイミングを考えないと、うまく動かないことがあるかもしれない。
まあ、onUpdate()だけ派生している分には影響ないのだが。
BroadcastReceiverを派生しているので、普通のブロードキャストも受信できる。
そうなると、onReceive()を派生させないと意味がない。
onReceive()内でintent.getAction()によってswitch ...はできないので、if-elseifしき、どこにも引っかからなかったらsuperに処理させる、ということになるか。

私が処理したかったのは、
  • アイコンのタップ
  • マウント通知
  • アンマウント通知
なので、
  • ACTION_WIDGET_CONTROL (自前で定義)
  • Intent.ACTION_MEDIA_MOUNTED
  • Intent.ACTION_MEDIA_UNMOUNTED
を処理した。それ以外は、super.onReceive()にお任せ。
ソースに書くだけでは受け取ってくれないので、AndroidManifest.xmlに追記。
<receiver android:name=".EjectSD">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="jp.typepad.hirokumaEjectSD.WIDGET_CONTROL"/>
</intent-filter>
<meta-data android:name="android.appwidget.provider" android:resource="@xml/app_widget"/>
</receiver>

<receiver android:name=".EjectSD">
<intent-filter>
<action android:name="android.intent.action.MEDIA_UNMOUNTED"/>
<action android:name="android.intent.action.MEDIA_EJECT"/>
<action android:name="android.intent.action.MEDIA_REMOVED"/>
<action android:name="android.intent.action.MEDIA_UNMOUNTABLE"/>
<action android:name="android.intent.action.MEDIA_MOUNTED"/>
<data android:scheme="file" />
</intent-filter>
</receiver>


前半は、ウィジェット更新とタップを受けるintent-filter、後半は、SDカード関連のintent-filter。
receiverは同じだから、まとめていいのだろうか?
分け方がよくわかっていない。

以下、Android Documentの訳も含めて書いておく。
intent-filterには、action, category, nameの3つのIntent特性(Intent characteristics)がある。
これらは文字列だ。
メソッドでは、setAction(), のような「set」ではなく、addAction(), のような「add」になっている。
つまり、追加していくしくみなのだ。
となると、intent-filterをまとめていいのかどうかは、intent-filterがどのようにintentをマッチさせるかのしくみによることになる。
actionとcategoryはマッチしなくてはならない。
そしてdata(data typeとdata scheme+authority+path)はマッチしなくてはならない。
なら、全部やん。
まあ上記は基本ルールらしいので、もう少し見ていこう。
actionは、Intent actionにマッチする。 "if any of the given values match XXX"とあるので、「もしXXXと一致するなら」くらいの意味になるのだろうか。
それが嫌なら、actionは書くな。
「嫌なら」とは書いてないけど、そんな意味でよかろうか。
次は、Data Type, Data Scheme, Data Authority, Data Pathと続く。
Dataシリーズだろうか。
これらにはそれぞれ、addDataType()やaddDataScheme()のようなメソッドがある。

Data Type。Intent typeにマッチする。MIMEっぽいワイルドカードが使えるらしい。"audio/*"とすると、"audio/mpeg"とか"audio/aiff"などに対応できるとか。大文字・小文字は関係するのだと。小文字使っとけ、と。actionは関係しないのか?

Data Scheme。Intent data's schemeにマッチする。これも大文字・小文字があるので、小文字使っとけ、と。

Data Authority。data authorityと schemeの1つとマッチするか、書かないでおくか。
ならschemeは必ず書く必要があるのかというと、実際はそんなこともない。いるのかいらんのか、表にまとめられんのか、君らは!
これも大文字・小文字あり、と。以下省略。

Data Path。Intentのdata pathと、schemeとauthorityの両方にマッチする。か、書かないでおくか。
そしてCategory。
・・・訳せんかった。

Extra categories in the filter that are not in the Intent will not cause the match to fail.
Intentにないintent-filterの余計なカテゴリーはマッチしない、ということ?
つまり、カテゴリーは完全一致でしか引っかからないということだろうか。
actionがない場合、カテゴリーのないintent-filterは、カテゴリーのないintentしかマッチできない、とある。
悔しいが、よくわからん。

では、intentをマッチさせているソースを見てみたいものだ。
あんまり見てないけど、IntentFilter::match()だろう。
引数が長い方のmatchね。
戻り値は、
  • MATCH_CATEGORY_MASK : 0xfff0000
  • MATCH_ADJUSTMENT_MASK : 0x000ffff
  • MATCH_ADJUSTMENT_NORMAL : 0x8000
  • MATCH_CATEGORY_EMPTY : 0x0100000
  • MATCH_CATEGORY_SCHEME : 0x0200000
  • MATCH_CATEGORY_HOST : 0x0300000
  • MATCH_CATEGORY_PORT : 0x0400000
  • MATCH_CATEGORY_PATH : 0x0500000
  • MATCH_CATEGORY_TYPE : 0x0600000
  • NO_MATCH_TYPE : -1
  • NO_MATCH_DATA : -2
  • NO_MATCH_ACTION : -3
  • NO_MATCH_CATEGORY : -1
とのこと。
actionがある場合には、matchAction()を呼び出し、マッチしなければ終了。
次にmatchData()。戻りが0未満なら、終了。
次にmatchCategories()。非nullなら、終了。
ここまでくぐり抜けたら、matchData()の値を返して終了。
では、順に見ていこう。

matchAction(action)
まず、引数action(おそらくintent.getAction()と同じものが来るだろう)がnullか、mActionsすなわちaddAction()していないか、mActionsの大きさが0の場合は、falseを返す。
そうでなければ、actionがmActionsに含まれればtrue、含まれなければfalseを返す。
実にシンプルだ。
actionに相当するものがあれば比較し、なければ比較しない。
呼び出し元では、引数actionが非nullであることが条件になっている。
つまりIntentにactionがなければ、ここは通らないことになる。
actionがあるならば、!matchAction()なので、trueを返さないとマッチ失敗となる。

matchDAta(type, scheme, data)
ここは処理が長い。ループはないので、上から下に流れるだけなのだが、長いな。
まず、addDataType()とaddDataScheme()をしてない場合。
typeもdataもnullなら、マッチしたことになる。
MATCH_CATEGORY_EMPTY+MATCH_ADJUSTMENT_NORMAL = 0x108000
そうでなければ、NO_MATCH_DATA。
addDataScheme()していた場合・・・・

と書いていこうとしたが、文章ではわかりにくいことがわかった。
図にしよう。
今回は、メインの流れのみ。
(紛失)
他もアクティビティ図にしたので、見たい人は持っていってくだされ(紛失)。

[widget]よくわからんがAppWidgetが動いた

過去記事:2010/08/19 23:36

AndroidのAppWidgetといえば、ネットで検索すると山のようにヒットする。
花形だ(私の中では)。


にもかかわらず、なぜか私はうまく動かすことができない・・・。
おそらく、理屈がわからずに作っているからだろう。
APIがたくさんあると、どうにもめんどくさくなっていかん。
それが慣れない「Java」ということになり、さらに手を抜いてしまっている。


また、自由度が高いところも、調べるのが面倒になった理由に挙げられよう。
ああやってもこうやっても実現できる、といわれると、ではどれがいいんだ?というところで止まってしまうのだ。
私にとって一番いいのは、手間がかからないこと。
特に今回は拡張するような機能もないので、やりたいことができればよかったのだ。

そんなこんなして、何とかできあがったようなので、次回から学習も含めて解説してみよう。
やるのは、

  • アイコンタッチとブロードキャストを受け付ける
  • 受け付けると、アイコンを変化させる
  • アイコンは各AppWidgetではなく、全AppWidgetが変化する

というだけのもの。
前回からの宿題ですな。

[a500]自作EjectSDが動かない

A500の調子がいいうちに、自作のアプリをインストールしてみた。
それが悪い影響を及ぼすかどうか知らんけど、普通に動いてもらわねば。


https://market.android.com/details?id=jp.typepad.hirokuma.EjectSD
うまく動かない。
マウントなどを認識してくれないのだ。

logcatで見ると、アプリは/mnt/sdcardを見ているようだ。
しかしmountで見ると、SDカードは
  • /mnt/external_sd
  • /mnt/secure/asec
あたりにマウントされているようなのだ。
うーむ、巷では聞いたことがあったのだが、自分のところに影響しようとは。
printenvすると、こうなっていた。

SECOND_VOLUME_STORAGE=/mnt/external_sd

EjectSDでは、Environment.getExternalStorageState()でマウント状態を取得している。
パスは、Environment.getExternalStorageDirectory().toString()で取得している。
つまり、getExternalStorageシリーズでは正しく取得できないことになる。
まいったなあ。


http://developer.android.com/guide/topics/data/data-storage.html#filesExternal
getExternalFilesDir(null)、でいいのかな?

ちょろっと試したいところだが、mmでビルドできる環境じゃないと動かないアプリなのだ。
Context.getExternalFilesDir(null)

これは、nullが返ってきた。
W/ApplicationContext(  835): Unable to create external files directory

API9だとandroid.os.storage.StorageManagerがあるので、使えるかもしれん。
が、それは使いたくないので、このまま進めよう。

環境変数を見てみる。

String str3 = System.getenv("THIRD_VOLUME_STORAGE");
String str2 = System.getenv("SECOND_VOLUME_STORAGE");
String str1 = System.getenv("EXTERNAL_STORAGE");
String str = Environment.getExternalStorageDirectory().toString();

結果

D/EjectSD ( 1195): 3 : /mnt/usb_storage
D/EjectSD ( 1195): 2 : /mnt/external_sd
D/EjectSD ( 1195): 1 : /mnt/sdcard
D/EjectSD ( 1195): 0 : /mnt/sdcard


決め手に欠けるので、これはユーザに選んでもらうしかないなぁ。

しかしどうしようもないのは、mount/umountのブロードキャストが飛んできてなさそうなこと。
MountServiceではだめなのかなぁ。
voldやMediaScannerなんかは動いているから、取得する方法はありそうなんだけど。

まだまだ、である。

[a500]今日は調子がよい。。

昨晩、またタッチパネル操作ができなくなったので、放っておいた。
今朝もだめだったんだけど、USBキーボードを使うか、動いた一瞬の隙を突いたか忘れたが、工場出荷時に戻した。

そこから、調子がよい。
あんまり使ってはいないけど、再起動しても、スリープから復帰させても動いている。
最初からこの状態だったら、何も悩まないのに。。。

まだAcerには電話していないので、今週はこれで様子を見てみよう。
だんだんこうやって、初期不良時間が短くなっていく。

[a500]Amazonはこの件についてAcerに問い合わせを要求した

やっぱり、これは初期不良だよな。
なんたって、買った直後ですらタッチパネルが使えなかったし。
でも、使えることもあったしなぁ。。。

そんなわけで、Amazonの返品要求(交換)をしてみた。
理由は、

初回の起動からタッチパネルが反応しないため、 何もできない。ごくまれに、使えることがある。 

と、現状を書いた。
短いのかもしれんが、だらだら書いても同じだしなぁ。


数時間後、メールが来た。

誠に申し訳ありませんが、当サイトでは、お客様よりご連絡いただいた問題が初期不良かどうかを判断できません。
恐れ入りますが、取扱説明書または保証書をご確認のうえ、今回の問題について一度メーカーにお問い合わせください。

まあ、もっともな話だ。
現品を見られるわけでもなく、自社製品というわけでもないので、私個人の判断では相手も納得しないだろう。
「使えることがある」というのも、気になったのかもしれない。
私でもそう言うだろうな。


そんなわけで、Acerに電話せんといかん。
そして、納得してもらえたら、窓口の人の名前を教えてもらい、それをAmazonに通知して、ようやく不良品扱いとなる。

ふー。

2011年8月6日

[a500]返品しそうになっているが、まだ迷いはある

8割方、返品へ気持ちは動いている。
もう一度工場出荷時に戻したんだけど、そこからタッチパネルは反応なし。
USBキーボードを挿すと動くんだろうけど、それは製品仕様を満たしていない。
では、返品だ。


しかし、どうにかしたらタッチパネルが動くかも。
そう思って、あれこれ試している。
そしたら、音量ボタンを押したときに出てくるポップアップ画面がときどきタッチパネルに反応することがわかった。
反応したからといって、アプリ側が反応してくれるわけではない。
タッチパネルのところだけ反応するのだ。


あー、ソフトバグというか、ハードウェア初期化のタイミングがずれてるとか、安定時間が部品交換で変わったとか、そんなんじゃなかろうかね。
適当な推測だけど。

とにかく、今のうちにあるA500は、一番最初の「ようこそ」画面から先に進めないのであった。
やっぱり返品かねぇ。

[a500]Acer A500を買ったが、タッチパネルが反応せん

AcerのA500というタブレット端末を購入した。
それはいいのだが、タッチパネルが反応しないことが多くて困る。

まず、電源をONすると、起動画面が出て、Androidのアニメーションが出て、ロック解除画面が出る。
ここでタッチパネルが使えない。。。
100%かというとそういうわけでもなく、98%くらい。
たまーに、使えるときがある。
95%くらいかも。
時計描画は更新されるし、電源ボタンを短押しするとLCDがON/OFFするし、長押しするとダイアログも出てくる。
adbだって動く。
だから、死んでいるわけではない。

だけど。。。タッチパネルが使えないので、ロック解除できない。
うーむ。
最近わかったのは、USBキーボードを挿すと解除できるということ。
挿すと、ホーム画面が出てくる。

さて、ホーム画面が出てきたらOKかというと、そういうわけではない。
相変わらずタッチパネルが使えない。
このとき、2パターンある。
画面の一番下(ステータスエリア?)にホーム画面へ遷移したりするタッチパネルのボタンがあるのだが、それが使えるときと使えないときだ。
そこが使える場合、タッチするとタッチパネルが復帰して、使えるようになる。
使えない場合は・・・どうしようもない。

まあ、今は「どうしようもない」なんだけどね。
ネットで調べると何人かいるようで、スリープさせて復帰させるとだいたい大丈夫、という人もいた。
うちはそうではないみたい。
参ったなぁ。。。

誰も彼もがそうではないようなので、ソフトバグではないのかもしれない。
でも、ハードバグだったらもうちょっと動きが悪そうな気がするし。
うーん、うーん。。。


RESETボタンがあるので、試しもした。
変わらず。。。
念のため工場設定に戻しもしたが、やはり変わらんかった。


今、再起動させて、反応している。
そう、動いているときはなんの問題もないのよねぇ。。。
ソフトをインストールしてこうなった、とかならソフトを疑うんだけど、買って、電源ONして、最初の入力中に反応しなくなったからなぁ。
メールアドレスの入力中に、急になったような記憶がある。
なので、初期不良・・・?
でも動くときは動くし・・・と、堂々巡りだ。

あ、今まで動いていたのに、急に反応しなくなった。
うーん、さっぱりわからん。

Rotation Lockボタンとサウンドボタンをいじったら、また反応しだした。。。
いったいなんなんだ??