ヤフー株式会社は、2023年10月1日にLINEヤフー株式会社になりました。LINEヤフー株式会社の新しいブログはこちらです。LINEヤフー Tech Blog

テクノロジー

LollipopでのNotification

こんにちは。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.の登録商標または商標です。 

こちらの記事のご感想を聞かせください。

  • 学びがある
  • わかりやすい
  • 新しい視点

ご感想ありがとうございました

このページの先頭へ