2014年12月 3日

Android

LollipopでのNotification

  • このエントリーをはてなブックマークに追加

Yahoo! JAPAN Tech Advent Calendar 2014の3日目の記事です。一覧はこちら

こんにちは。Androidアプリエンジニアの筒井です。

11月27日の夜にヤフー社内で他社さんも含めたクローズドな合同勉強会を行いました。
その時の自分の発表内容を今日は書こうと思います。

LollipopでのNotification

5.0からのNotificationで自分が興味があるところについて調べてみました。

  • Heads Up Notification
  • ロック画面上での通知の表示
  • その他 Tipsなど

環境

調べるにあたって利用した環境です。

  • Nexus5 (OS: 5.0)
  • ソースコード android-5.0.0_r7

Heads Up Notification

Lollipopから導入された新しい通知の仕組みです。Statusbar, Activityの上にかぶる感じで表示されます。

Heads Up Notification

WindowManager.LayoutParams.TYPE_STATUS_BAR_PANELなど指定されていました。
参考)com.android.systemui.statusbar.phone.PhoneStatusBar#addHeadsUpView

private void addHeadsUpView() {
    int headsUpHeight = mContext.getResources()
            .getDimensionPixelSize(R.dimen.heads_up_window_height);
    WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
            LayoutParams.MATCH_PARENT, headsUpHeight,
            WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, // above the status bar!
            WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
                | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
            PixelFormat.TRANSLUCENT);
    lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
    lp.gravity = Gravity.TOP;
    lp.setTitle("Heads Up");
    lp.packageName = mContext.getPackageName();
    lp.windowAnimations = R.style.Animation_StatusBar_HeadsUp;

    mWindowManager.addView(mHeadsUpNotificationView, lp);
}

どうやって表示するんだろう?

まずは、Javadocを見てみよう。heads-upで検索、検索。
Javadoc

どうもSystem側で判断して表示するらしい。どういうロジックでHeads-Upするんだろうか?

まずはサンプルを見てみよう。

SamplesにあるLNotificationsを利用。
LNotifications

問題なく、Heads Up Notificationが表示されました。では、次にコードも見てみましょう。

サンプル内のコード

Notification createNotification(boolean makeHeadsUpNotification) {
  …
  if (makeHeadsUpNotification) {
      Intent push = new Intent();
      push.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
      push.setClass(getActivity(), LNotificationActivity.class);
      PendingIntent fullScreenPendingIntent = PendingIntent.getActivity(getActivity(), 0,
              push, PendingIntent.FLAG_CANCEL_CURRENT);
      notificationBuilder
              .setContentText("Heads-Up Notification on Android L or above.")
              .setFullScreenIntent(fullScreenPendingIntent, true);
  }
  return notificationBuilder.build();
}

なるほど、setFullScreenIntent()でHeads Upされるようだ。
※ setFullScreenIntentの第二引数をfalseにしてもHeads Up Notification表示されました。
実際のロジックはどうなんだろう。気になりますね。
よし本体のソースも見てみよう!

本体側のソースコードの探検

まずは、HeadsUpでgrep。いくつか出てきましたが、それっぽいのが見つかりました!
frameworks/base/packages/SystemUI/src/com/android/systemui/PhoneStatusBar.java

public void addNotification(StatusBarNotification notification, ...) {
    if (mUseHeadsUp && shouldInterrupt(notification)) { 
       ...
        if (inflateViewsForHeadsUp(interruptionCandidate, holder)) {
          // 1. Populate mHeadsUpNotificationView
          mHeadsUpNotificationView.showNotification(interruptionCandidate);
          return;
        }
    ...

このshouldInterrupt() がtrueを返せば、mHeadsUpNotificationView.showNotification() をコールしそうだ。
mHeadsUpNotificationViewの名前通り、これがHeads Upに関連しそうです。
もう少し見ていきましょう。

frameworks/base/packages/SystemUI/src/com/android/systemui/BaseStatusBar.java
※ PhoneStatubarの親クラスがBaseStatusBarです。

protected boolean shouldInterrupt(StatusBarNotification sbn) {
    ...
    boolean isNoisy = (notification.defaults & Notification.DEFAULT_SOUND) != 0
            || (notification.defaults & Notification.DEFAULT_VIBRATE) != 0
            || notification.sound != null
            || notification.vibrate != null;
    boolean isHighPriority = sbn.getScore() >= INTERRUPTION_THRESHOLD;
    boolean isFullscreen = notification.fullScreenIntent != null;
    boolean hasTicker = mHeadsUpTicker && !TextUtils.isEmpty(notification.tickerText);
    boolean interrupt = (isFullscreen || (isHighPriority && (isNoisy || hasTicker)))
    ...
    return interrupt;
}

fullScreenIntentの設定、もしくは、high priorityでかつ、noisy(音やバイブ)かtickerTextを持っている時にtrueを返すようだ。そして、ここでtrueを返すと、Heads Upされそう。

メモ: sbn.getScore()はpriorityの設定で決定されます。
詳しくは、frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java 内をScoringで検索すると、内部のロジックが分かります。

サンプルを修正して確認してみよう

Notification createNotification(boolean makeHeadsUpNotification) {
   …
   if (makeHeadsUpNotification) {

       notificationBuilder
               .setVibrate(new long[]{100, 0, 100, 0, 100, 0})
               .setPriority(Notification.PRIORITY_HIGH)
               .setContentText("Heads-Up Notification on Android L or above.");
               //.setFullScreenIntent(fullScreenPendingIntent, false);
   }
   return notificationBuilder.build();
}

本体側のソースを見る限り、Notification.PRIORITY_HIGH と vibrate の組み合わせで Heads Upされるはず…。

LNotification2

表示されました:)
なんとなくわかってきた感じがする。実際のコミットログも見てみよう。

Heads Up Notificationに関するコミットログ

コミットログもいくつか出てきたのですが、わりとずばりのものが見つかりました。
一部を抜粋します。https://android.googlesource.com/platform/frameworks/base/+/47c20a1%5E!/

The heads up notification is influences by full screen, sound, vibration and priority.

うん、ソースコードのロジックとマッチしている内容ですね。

改めてドキュメントを見なおしてみる

ソースコードも見て理解が深まってきたので、改めてドキュメントを見なおしてみました…。
わりと近いことが小さくポロッと書いてました。
Notification Docs
ただ、ここには、soundやvibrationのことは書いてないなぁ…。
そもそも、High Priorityだけだと表示されなかったので、少しドキュメントが足りていない感じがします。

ここまででわかったこと

Heads Up Notificationを表示したいときは、

  • fullScreenIntentを設定
  • high priority以上で音・バイブ・tickerTextのいずれかを設定

注意: tickerText は試したのですが、自分の環境ではHeads Upされませんでした。コード上ではそういうロジックだったのですが…仕様かバグかまでは時間が足りず調べていません。

ロック画面上での通知の表示

さて、Lollipopからロック画面上で通知が表示されるようになっています。
こちらも興味深いですね。

LockScreen Notification

上のスクリーンショットでは、メッセージの内容も表示されています。ここは、非表示にしたい人もいそうですね。設定を見てみましょう。

通知の表示の設定

「プライベートな通知内容を非表示にする」に変更してみよう。

LockScreen Notification2

上の設定は、ロック画面のPINやパターンを設定したら、表示されます。

Visibilityについて

設定でも、ある程度コントロールできるが、アプリ側からは表示のコントロールできないんだろうか? という疑問が出てきますが…APIあります!

setVisibility

サンプルにあるので、実際に試してみましょう。とりあえず、Public,Private,Secret それぞれ送ります。

LNotification visiblity

public, private, secretの3種類とロック画面の通知の表示設定の関連性

ロック画面の表示設定とvisibilityの設定の関連性を調べてみました。「すべての通知内容を表示にする」の場合は、Secretにしても表示される仕様のようです。

  • 「すべての通知内容を表示にする」
    LNotification visibility2

  • 「プライベートな通知内容を非表示にする」
    LNotification visibility3

SmartLockについて

LollipopからSmartLockが導入されました。設定している場合、ロック画面の認証をスキップできます。例えば、Android Wearを設定していて、そのAndroid Wearがスマホと接続しているとロック画面認証は不要になります。

設定画面は、以下の様なものです。

LNotification visibility4

SmartLockの設定していた場合は、安全と見なされるので、VisibilityをSecretにしていても、ロック画面に表示されます。
この点は注意が必要です。

Android Wearへの通知について

LNotification setLocal

サンプル使って、テストで通知を出していたら、Android Wearにも通知がどんどん来ました…。テストの時はよいとしても、実際のアプリ開発については、setLocalOnly(true)をして、必要なものだけWear側に通知した方が親切ですね。

まとめ

  • 通知はロック画面にも表示される。
  • 設定項目が複雑、設定によって動作も変わる。 Secretにしていても、ユーザーが「すべての通知内容を表示する」にしていたらロック画面にも表示されたりとか。
  • Heads Upしたいときは、fullScreenIntentを設定(もしくは、High Priority以上にしてバイブ・通知音の設定)

関連情報

参考ページなど

まだ、あまり日本語情報が少ないので、オフィシャルの情報が参考になります。

参考にしたソース

  • BaseStatusBar.java (com.android.systemui.statusbar)
  • NotificationData.java (com.android.systemui.statusbar)
  • PhoneStatusBar.java (com.android.systemui.statusbar.phone)
  • NotificationManagerService.java (com.android.server.notification)

※ 引用したAndroidのフレームワーク、サンプルのコードのライセンスは、どちらも Apache 2.0 に準じます。
https://source.android.com/source/licenses.html

※ 引用したJavadocのドキュメントのライセンスは、Apache 2.0 に準じます。
http://developer.android.com/reference/android/app/Notification.html


さいごに

Lollipopで通知は大きく改善しているので、上手に使っていきたいですね。
本記事もNotificationの導入にあたって参考になればうれしく思います。
読んでくれた、みなさまありがとうございます!

 

-------
※ Google、Nexus、Androidは、Google Inc.の登録商標または商標です。 

Yahoo! JAPANでは情報技術を駆使して人々や社会の課題を一緒に解決していける方を募集しています。詳しくは採用情報をご覧ください。

  • このエントリーをはてなブックマークに追加