Android L Preview のデバイスで Heads-up Notification を表示する方法

注:この記事は 2014年 7 月上旬現在の Android L Preview の ROM イメージおよび SDK の内容に基づいて書かれています。Android L が正式バージョンとしてリリースされるまでの間に、この記事の内容は obsolete になるかもしれません。


Android L Preview では新しい通知の種類として Heads-up Notification が追加されました。
Heads-up Notification についての詳細は以下のページ内で "Heads-up Notification" を検索してみてください。
https://developer.android.com/preview/notifications.html

さて、この Heads-up Notification は電話着信時に出ることがわかっていますが、自分のアプリからも出したい場合どうすればよいかということがまだ公式のドキュメントには書かれていないようです。
いろいろやって調べた結果、そもそも必要な定数が @hide されていたり API リファレンスに載っているメソッド名が間違っていたりしたので大変でしたが 自分のアプリから Heads-up Notification を出すことができましたのでその方法を紹介します。

手順としては、以下の様な感じになります。

  1. 実行環境の API Level から、そもそも Heads-up Notification を出せるかどうかを調べる。
  2. Notification.Builder で Notification を構築する。このとき、設定しなければならないパラメータに一定の決まりがある。
  3. NotificationManager で表示する。

大事なのは、Notification を構築するときに与えるオプションですね。
今のところ、Notification.Builder で以下のメソッドの呼び出しが必須であることがわかっています。

  • 通常の Notification を出すために最低限必要なオプション
    • setContentTitle()
    • setContentText()
    • setSmallIcon()
  • Heads-up Notification を出すために最低限必要なオプション
    • setPriority()
    • setFullScreenIntent()
    • setOngoing()
    • setExtras()

この中で、手強いのは setPriority() と setExtras() です。setFullScreenIntent() に指定するのは空のインテントでも良いようです。

まず、setPriority() には Notification.PRIORITY_HIGH を指定する必要があるのですが、どうも Eclipse ADT で開発しているとこのような値は認識されていないようでビルドできません。SDK も ADT も最新バージョン(r23.0.2)に上げているのですが・・・
仕方ないので AOSP のソースを見るとその値が 1 であることがわかるのでそれを直接指定します。

次に setExtras() ですが、これはこのようなメソッドは無いと言われますのでリフレクションで呼び出します。(ちなみに、API リファレンスには addExtras() という違う名前のメソッドが載っています。これも Eclipse からは認識されていません)
setExtras() の引数には Bundle を指定します。Bundle の中身としては、Notification.EXTRA_AS_HEADS_UP をキーとして int 型の値 Notification.HEADS_UP_REQUESTED を指定します。しかし、これらの値もお察しの通り存在しないと言われてしまいます(これは AOSP を見ると @hide が指定されているので明示的に隠されています)のでこれまた AOSP のソースを見てそれぞれ "headsup" および 2 という値を知り、それらを直接指定します。

@SuppressLint("NewApi")
private void showHeadsupNotification(int id) {
    if (Build.VERSION.SDK_INT < 20) {
        Log.i(TAG, "Heads-up Notification is not supported.");
        return;
    }

    Bundle b = new Bundle();
    b.putInt(/* Notification.EXTRA_AS_HEADS_UP */"headsup", /* HEADS_UP_REQUESTED */2);
    Notification.Builder nb = new Notification.Builder(getApplicationContext());
    try {
        Method m = Notification.Builder.class.getMethod("setExtras", Bundle.class);
        m.invoke(nb, b);
    } catch (NoSuchMethodException e) {
        Log.e(TAG, e.getMessage(), e);
    } catch (IllegalArgumentException e) {
        Log.e(TAG, e.getMessage(), e);
    } catch (IllegalAccessException e) {
        Log.e(TAG, e.getMessage(), e);
    } catch (InvocationTargetException e) {
        Log.e(TAG, e.getMessage(), e);
    }

    Notification n = nb
            .setContentTitle("Test Title " + Integer.toString(id))
            .setContentText("test test test")
            .setSmallIcon(R.drawable.ic_launcher)
            .setPriority(1 /* Notification.PRIORITY_HiGH */)
            .setAutoCancel(true)
            .setFullScreenIntent(
                    PendingIntent.getActivity(getApplicationContext(), 0, new Intent(), 0),
                    true).setOngoing(true).build();
    NotificationManager nm = (NotificationManager) getApplicationContext().getSystemService(
            Context.NOTIFICATION_SERVICE);
    nm.notify(id, n);
}    

setExtras() を呼ぶところが不必要に複雑に見えますが、それ以外は特に難しいことはありません。

上記の関数を呼んで表示させた Heads-up Notification がこちら:
f:id:bearmini:20140711152303p:plain


Heads-up Notification は同時に表示されるのは 1 つだけで、あとから表示したほうが勝つようです。(先に表示されていたものは、ステータスバーに引っ込みます)

たとえば電話がかかってきて着信の Heads-up Notification が表示されているときに自分のアプリから Heads-up Notification を出すと、着信のほうが消えて(ステータスバーに格納されて)自分のものが表示されます。

自分のアプリ内で2個以上出そうとしても同様です。最後に表示したものだけが表示されています。
プルダウンするとすべての通知を見ることができます。


ちなみに Android L Preview は Nexus 5 に入れて試しました。