2011年04月15日

Notificationのカスタマイズ

先日のエントリーをもう少し具体的に、「壊滅日誌」らしく書こうかな、と思います。
他のちゃんとしたサイトは「こうすればできますよ」ということを書いてくださっている訳ですが、ここでは「こうしてもやっぱりできなかった」ということを愚痴る場所なので。(笑)
(他で情報があまり見つからないので、この内容で万が一検索結果上位に表示されると少々困るのですけどね。あ、ちなみに駄目な見本にする場合も英語力とプログラム基礎知識がないので、多分に誤認識が含まれていると思われます。くれぐれも「参考」程度に見てください)
カスタマイズしないNotificationの使い方は多くの参考書や他サイトに記載があるので割愛するとしまして、Notificationの表示をカスタイズするには
http://developer.android.com/intl/ja/guide/topics/ui/notifiers/notifications.html
にある通り、RemoteViewsを使うことになる訳ですね。

最初にxmlファイルを作成。
custom_notification_layout.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="horizontal"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent"
              android:padding="3dp"
              >
    <ImageView android:id="@+id/image"
              android:layout_width="wrap_content"
              android:layout_height="fill_parent"
              android:layout_marginRight="10dp"
              />
    <TextView android:id="@+id/text"
              android:layout_width="wrap_content"
              android:layout_height="fill_parent"
              android:textColor="#000"
              />
</LinearLayout>

次にクラスで下記のような記述をしてNotificationをカスタマイズすることになるようです。

String ns = Context.NOTIFICATION_SERVICE;
NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns);

// Notification の初期化(ここは必要でないと思いますが、外したらエラーになってしまったので入れておきます)
int icon = R.drawable.icon;
CharSequence tickerText = "";
long when = System.currentTimeMillis();
Notification notification = new Notification(icon,tickerText,when);

//カスタマイズしたxmlの適用(ここでRemoveViewsメソッドを使うとドキュメントに書いてあるが、
//どう使えばいいのか分かりません。誤植?)
RemoteViews contentView = new RemoteViews(getPackageName(), R.layout.custom_notification_layout);
contentView.setImageViewResource(R.id.image, R.drawable.notification_image); //xmlで指定した画像
contentView.setTextViewText(R.id.text, "Hello, this message is in a custom expanded view"); //表示したいテキスト
notification.contentView = contentView;

//setLatestEventInfo()不要で、代わりに以下の記述
Intent notificationIntent = new Intent(this, MyClass.class); // 第2引数は起動するActivity
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.contentIntent = contentIntent;

mNotificationManager.notify(0, notification);

※なお、Serviceを使用する場合AndroidManifest.xmlのアプリケーションタブ、Application Nodesに追加が必要です。

しかし、ここで問題発生。
説明を分かりづらくするとは思いますが、自分としてはWindowsの常駐ソフトのイメージで特定の画面を持たないアプリをイメージしていたので、いきなりNotificationの表示だけにしたいと思いました。そこで、

notification.flags = Notification.FLAG_ONGOING_EVENT;

を加えると常駐するようなので、追記して実行。
しかし、いきなり強制終了です。
少し悩んでから参考書などを見てみましたが、すべてActivityからServiceを呼び出すような形になっていて、Serviceをいきなり起動しているものは見つかりませんでした。
手持ちの参考書(リファレンス的な書き方のものが多い)ではその辺り明示されているものがなかったのですが、Simejiの作者であるadamrockerさんが日経ソフトウェアに連載されていた時の資料を見直したところ、やはりServiceを起動する主体はActivityであるとの記述がありました。
ググるとブロードキャストレシーバーから起動するという方法もあるようですが、とりあえずここは素直に適当なActivity(例えばServiceCallActivity.java)を作ってIntentを設定してstartService()で呼び出すとのことです。

Intent intent = new Intent(this,MyService.class);
startService(intent);

クラスMySerivce.javaはServiceをextendsして、前段のNotificationの記述をonCreate()またはonStart()のいずれか適切な箇所に記述。

さて、ここからが本題。
ここまででカスタムNotificationはなんとかしてできた訳です。
「うまくできた」というのは「壊滅日誌」の趣旨ではありません。(笑)
英文のニュアンスがよく分からなかったとはいえ、ほぼコピペに近い形でできましたが、いままではベースの部分。本来の目的はこの先にあります。

シャープ製のAndroid端末はNotificationエリアに画像ボタンがあって、各種の操作が可能です。
たとえば、IS01(OS1.6)だと伝言メモの起動や設定画面の呼び出しができ、IS03(OS2.1)に至ってはペールビューやマナーモードの切り替え、電波オフモードの設定画面の呼び出しなどができます。
IS01の場合横長の画面なのでNotificationエリアが左右別々に表示されるので、これが本来のNotificationなのかは疑問の残るところではありますが、「複数のクリッカブルなボタンが並ぶ通知エリア」が達成されていることにはなります。

これは確かに他のアプリで見かけない機能なので、ガリガリにいじっている、もしくは非公開クラスなどを使用している可能性がありますが、「なんか簡単にできそう」な機能に思えたので、確かめたかった次第です。
そのため、まずは複数のボタンをNotificationに設置して、それぞれ別のActivityを呼び出そうとしました。
言語の仕組み自体が分かっていない昼行灯的頭脳の持ち主たる自分は、この段階でIntentとPendingIntentのインスタンスを複数作ればうまく行く!くらいに考えていたのですが、それがあまりに甘かった。
そもそもPendingIntentというクラス名で思い当たれよ!というところだったのですが、プラグラマーはまったく何も考えてなかったのです。(笑)

こう書いた以上Intent notificationIntent;Intent notificationIntent2...などとやってもNGだったということは分かってもらえると思いますが、その前に躓いたのが、これまでの書き方で実行するとNotificationに表示はされますが、タップするとnotificationIntentに割り当てたMyClass.classがWindow already focusedなどとエラーが出て、起動しません。
どこが問題かとさんざん悩んだ結果、Activityから呼び出したNotificationである以上呼び出し元のActivityしか起動できないようなのです。
つまりあくまでもPending(保留)していたActivityを再開するというイメージなんですね。

解釈が正しくないかも知れませんが、
Intent notificationIntent = new Intent(this, MyClass.class);

Intent notificationIntent = new Intent(this, ServiceCallActivity.class);
と、呼び出し元のクラス名を記述すれば元のActivityが開きました。

すると、そもそも複数のActivityを開くことができない、つまりボタンを複数設置しても無意味ではないかということになります。
ただ、ごちゃごちゃやっているうちに、他のActivityを開けたよな?と思い出し、また長い試行錯誤……
ようやく分かったのは、呼び出し元のActivityで直接NotificationManagerを呼び出すと、notificationIntentに別クラスのActivityを設定しても開くということでした。
要はServiceをExtendsしたクラスを別途設けずに、最初のActivity(上記の場合はServiceCallActivity.class)にNotificationの記述をすべて書いてしまうということです。
そうすれば、ServiceCallActivity.classからNotificationを表示し(ServiceCallActivity.classはfinish()してしまう)、MyClass.classを呼び出せます。
でも、これはなんだかおかしいような気が……
ただ、想定通り動いたことは動いたので、これでひとつ解決?(したような、しないような)

いずれにしても、別Activityは呼び出せるようになったので、今度は先ほど書いたようにIntentのインスタンスを複数設定し、ボタンを2つ作って、ボタン毎に別な画面を呼び出すようにしてみました。
しかし、NG。
そもそも、ボタンが反応しない(クリッカブルでない)のです。
試したこととしては

・OnClickListenerを登録してPendingIntentを使わず、通常のIntentを直接割り当てる
・xml記述中のButtonに直接android:onClick="Method1"を記述 → Notificationがエラーで表示されなくなる
・RemoteViewsのメソッドにあるsetBitmap(int viewId, String methodName, Bitmap value)を意味が分からないまま使ってみる
・ホームスクリーンウィジェットの作成方法を参考に、(ACTION_BTNCLICK)contentView.setOnClickIntent(R.id.button,pendingIntent2);などとしてみる

当然どれもNGだった訳ですが、RemoteViewsクラスにあるメソッドがどうもすべて使える訳ではなさそうなのが不思議。
なんだかそれっぽいメソッドがRemoteViewsにあるので、できそうで、できないもどかしさのまま、長い間試行錯誤してしまいました。
Inflatorクラスとか使えるのかなあ、などと思ってもみましたが良く分かりませんし、結局少なくとも自分の技量では無理であるということで「できない」という判断に至りました。
HoneyCombのSDKではなにやらできるようになるみたいなので、将来的にはスマートフォンでも使えるようになるのでしょう。
でも、残念!

posted by 白虹 at 22:21| Comment(0) | TrackBack(0) | Android開発
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

この記事へのトラックバックURL
http://blog.sakura.ne.jp/tb/44325999

この記事へのトラックバック