2014年7月10日木曜日

Android Wear に Notification で出来ること

参考

概要
  • スマホと Wear が接続されていると、Notification が Wear にも表示(同期)される。
  • Wear では通知はカードとして表示され、このカードが表示されるところを context stream という。
  • これまでの通知でももちろん Wear に表示されるが、Wear 用に Notification を拡張することができる。


Notification を作る

Notification の作成には NotificationCompat.Builder を使う。これで作っておけば、システムがかってにスマホと Wear で通知の見た目を変えてくれる。

通知の発行には NotificationManagerCompat を使う(NotificationManager ではなく)。



■ SmallIcon だけの Notification int notificationId = 001; NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif); // Notification を発行 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.notify(notificationId, notificationBuilder.build()); スマホ


Wear(左: ホーム画面、右: タップした状態)


Wearのカードの右上に表示されるアイコンは setSmallIcon() で指定したものではなく、アプリアイコンになる。 タップしたときの背景色はどこから来てるのかよくわからない。アプリアイコンのカラーパレット?



■ タイトルとメッセージありの Notification int notificationId = 001; NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") // Notification を発行 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.notify(notificationId, notificationBuilder.build()); スマホ


Wear(左: ホーム画面、右: タップした状態)




■ Content Intent ありの Notification int notificationId = 001; // Content Intent 用の PendingIntent を作成 Intent intent = new Intent(this, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent); // Notification を発行 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.notify(notificationId, notificationBuilder.build()); スマホ


Wear(左: ホーム画面、中央: タップした状態、右: タップしたあと左にスワイプ)


setContentIntent() で PendingIntent を指定すると、Open on phone(携帯で開く)が追加され、それをタップすると指定した PendingIntent が実行される。



■ InboxStyle の Notification Intent intent = new Intent(this, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .setStyle(new NotificationCompat.InboxStyle() .addLine("1行目") .addLine("2行目") .setContentTitle("インボックス タイトル") .setSummaryText("+3 more")); // Notification を発行 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.notify(notificationId, notificationBuilder.build()); スマホ


Wear(左: ホーム画面、中央: タップした状態、右: タップしたあと左にスワイプ)




■ BigTextStyle の Notification Intent intent = new Intent(this, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .setStyle(new NotificationCompat.BigTextStyle() .bigText(getString(R.string.long_text) .setContentTitle("ビッグテキスト") .setSummaryText("サマリー")); // Notification を発行 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.notify(notificationId, notificationBuilder.build()); スマホ(上: 閉じてる状態、下: 開いた状態)



Wear(上: ホーム画面、中: タップした状態、下: 中でタップすると全文が見れる)






■ BigPictureStyle の Notification Intent intent = new Intent(this, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .setStyle(new NotificationCompat.BigPictureStyle() .bigPicture(bitmap) .setContentTitle("ビッグピクチャー") .setSummaryText("サマリー")); // Notification を発行 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.notify(notificationId, notificationBuilder.build()); スマホ(上: 閉じてる状態、下: 開いた状態)



Wear(上: ホーム画面、下: タップした状態)



bitPicture() で指定した画像が背景になる。カードがなく背景にセットされた画像を見れるページが追加される。
タイトルには BigPictureStyle.setContentTitle() が表示されるが、その下は setSummaryText() ではなく setContentText() が表示される。




Action ボタンを追加する

Gmail の Notification の Archive に相当するもの。


*アイコンがぼけているように見えますが、実際ぼけています。Open on phone と比較すると明らかにぼけています。

NotificationCompat.Builder の addAction() で、画像、テキスト、タップされたときの PendingIntent を指定する。 Intent intent = new Intent(this, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); // Action 用の PendingIntent を作成 Intent mapIntent = new Intent(Intent.ACTION_VIEW); Uri geoUri = Uri.parse("geo:0,0?q=" + Uri.encode(location)); mapIntent.setData(geoUri); PendingIntent mapPendingIntent = PendingIntent.getActivity(this, 0, mapIntent, 0); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .addAction(R.drawable.ic_map, "Map", mapPendingIntent); // Notification を発行 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.notify(notificationId, notificationBuilder.build()); addAction() の第1引数に指定するアイコンは ActionBar のアイコンと同じサイズにする。
つまり、 32dp x 32dp(内枠 24dp x 24dp)。

Wear 側で表示される画像は、スマホの解像度に一致したリソース。スマホが xxhdpi の場合、hdpi のリソースがあっても、xxhdpi のリソースが Wear に表示される。

スマホ


Wear(上: ホーム画面、下: タップした状態)






Wear だけのアクションを追加

Wear にだけ表示されるアクションを追加できる。スマホの Notification には表示されない。

WearableExtender.addAction() を使う。
このメソッドを Action を追加すると、Wearable では NotificationCompatBuilder.addAction() で指定された Action は表示されない。 そのため、別の機能を WearableExtender.addAction() で与えるというよりは、スマホ用に NotificationCompatBuilder.addAction() で指定した機能を、Wear では Wear 用に適したものに置き換えたい場合に使う。

例えば、Gmail の返信機能は、スマホでは返信画面を開くだけだが、Wear では音声入力の結果を返信できるようにしている。 Intent intent = new Intent(this, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); // Action 用の PendingIntent を作成 Intent mapIntent = new Intent(Intent.ACTION_VIEW); Uri geoUri = Uri.parse("geo:0,0?q=" + Uri.encode(location)); mapIntent.setData(geoUri); PendingIntent mapPendingIntent = PendingIntent.getActivity(this, 0, mapIntent, 0); // Wear 用 Action を作成 NotificationCompat.Action action = new NotificationCompat.Action.Builder(R.drawable.ic_map_for_wear, "Map for Wear", mapPendingIntent) .build(); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .addAction(R.drawable.ic_map, "Map for phone", mapPendingIntent) .extend(new NotificationCompat.WearableExtender().addAction(action)); // Notification を発行 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.notify(notificationId, notificationBuilder.build()); Wear 用のアイコンには Action Bar の2倍、つまり 64dp x 64dp(内枠 48dp x 48dp)の画像を指定する。こうするとNotificationCompat.Builder.addAction() で Wear に表示していたときと違って画像がぼけない。

スマホ


Wear(上: ホーム画面、下: タップした状態)






背景画像を指定する

■ setLargeIcon() を使う

setLargeIcon() を使うと、カードをタップしたときの背景に利用される。 NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .setLargeIcon(bitmap); BigPictureStyle を使った場合、setLargeIcon() を指定していても Wear の背景は BigPictureStyle.bigPicture() で指定した画像になる。

スマホ


Wear(左: ホーム画面、右: タップした状態)


■ WearableExtender.setBackground() を使う

スマホの Notification で LargeIcon を使いたくない場合は、NotificationCompat.WearableExtender の setBackground() を使う。 NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender() .setBackground(BitmapFactory.decodeResource( getResources(), R.drawable.sample)); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .extend(wearableExtender); スマホ


Wear(左: ホーム画面、右: タップした状態)





Wear のカードでアプリアイコンを表示しない

カードの右上に表示されるアプリアイコンを消すには、NotificationCompat.WearableExtender.setHintHideIcon() を使う。 NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender() .setHintHideIcon(true); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .extend(wearableExtender); Wear(左: ホーム画面、右: タップした状態)





Wear のカード内にアイコンを表示する

NotificationCompat.WearableExtender.setContentIcon() を使う。 NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender() .setContentIcon(R.drawable.ic_droid); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .extend(wearableExtender); ここでは、アプリアイコンサイズの画像を指定してみた。
ambient 時は白の tint がかかる。

Wear(上: ホーム画面、下: タップした状態)






Wear のカード内にアイコンを左に表示する

NotificationCompat.WearableExtender. setContentIconGravity() で GravityCompat.START を指定する。 指定できるのは START と END で、デフォルトは END。 NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender() .setContentIcon(R.drawable.ic_droid) .setContentIconGravity(GravityCompat.START); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .extend(wearableExtender); Wear(上: ホーム画面、下: タップした状態)






カードの位置を変える

setGravity() を使う。指定できるのは Gravity.TOP、Gravity.CENTER_VERTICAL、Gravity.BOTTOM。デフォルトは BOTTOM。 NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender() .setGravity(Gravity.TOP); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .extend(wearableExtender); Wear(左: ホーム画面、右: タップした状態)

Gravity.TOP


Gravity.CENTER_VERTICAL





オリジナルのカードを表示する

これは Wear アプリから Notification を発行するときにだけ使えます。
setDisplayIntent() で、カードをタップしたときに表示する Activity を持った PendingIntent を指定します。

Wear 上のコードなので、NotificationCompat ではなく Notification を使っています。 // この MainActivity は Wear アプリの MainActivity Intent intent = new Intent(this, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); // カードをタップしたときに表示される Activity // ここでは ImageView 1つだけのレイアウト Intent displayIntent = new Intent(this, DisplayActivity.class); PendingIntent displayPendingIntent = PendingIntent.getActivity(this, 0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT); NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender() .setDisplayIntent(displayPendingIntent); Notification.Builder notificationBuilder = new Notification.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .extend(wearableExtender); NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); notificationManager.notify(notificationId, notificationBuilder.build()); Wear から Notification を発行する場合、setSmallIcon() で指定したアイコンがカードの右上に表示される。
setContentIntent() で指定した PendingIntent は Open(開く)という Action になる。

Wear(左: ホーム画面、右: タップした状態)





オリジナルのカードの大きさを変える

setCustomSizePreset()を使う。
指定できるのは、SIZE_DEFAULT、SIZE_FULL_SCREEN、SIZE_LEARGE、SIZE_MEDIUM、SIZE_SMALL、SIZE_XSMALL。 NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender() .setDisplayIntent(displayPendingIntent) .setCustomSizePreset(NotificationCompat.WearableExtender.SIZE_FULL_SCREEN); Notification.Builder notificationBuilder = new Notification.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .extend(wearableExtender); Wear(左: ホーム画面、右: タップした状態)

SIZE_FULL_SCREEN


SIZE_LARGE


SIZE_MEDIUM


SIZE_SMALL


SIZE_XSMALL





Notification から音声入力を使う

Notification に返信するなどテキストを入力するアクションがある場合、Wear にはキーボードがない代わりに RemoteInput を使って、音声入力を使うことができる。

*エミュレータには音声入力がないので、AVDの設定で Hardware keyboard present を有効にしておくと、代わりにキーボードで入力できる。

RemoteInput は RemoteInput.Builder を使って作る。コントラクタには音声入力結果に紐づけるキー(文字列)を渡す。 private static final String EXTRA_VOICE_REPLY = "extra_voice_reply"; ... String replyLabel = getResources().getString(R.string.reply_label); RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY) .setLabel(replyLabel) .build(); setLabel() でラベルを指定すると、上部の青色部分に表示される。 複数の項目を入力させたい場合などのに便利。
例えば、"Ok google, remind me" というと、About what? と When? を別々に入力する画面になる。



RemoteInput を Notification に組み込むには、NotificationCompat.Action.Builder の addRemoteInput() を使う。 // 音声入力の結果を受けとるための PendingIntent を作る Intent replyIntent = new Intent(this, ReplyActivity.class); PendingIntent replyPendingIntent = PendingIntent.getActivity(this, 0, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT); // RemoteInput 用の Action を作る NotificationCompat.Action action = new NotificationCompat.Action.Builder(R.drawable.ic_reply, "Reply message", replyPendingIntent) .addRemoteInput(remoteInput) .build(); // WearableExtender に addAction() で RemoteInput の Action を追加 NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender() .addAction(action); Notification.Builder notificationBuilder = new Notification.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("タイトル") .setContentText("メッセージ") .setContentIntent(pendingIntent) .extend(wearableExtender); 音声入力が正しく終ると、NotificationCompat.Action.Builder の第3引数に指定した PendingIntent が実行される。

PendingIntent に指定された Activity もしくは Service では、getIntent() で取得した Intent を RemoteInput.getResultsFromIntent() に渡して Bundle を取得し、RemoteInput に指定したキーで文字列を取り出す。 /** * Activity.getIntent() を渡す */ private CharSequence getMessageText(Intent intent) { Bundle remoteInput = RemoteInput.getResultsFromIntent(intent); if (remoteInput != null) { return remoteInput.getCharSequence(EXTRA_VOICE_REPLY); } return null; } * 音声入力の結果は ClipData として保存されているため、Intent.getExtras() を使わずに getResultsFromIntent() を使うこと。

Wear(上: ホーム画面、中: タップした状態、下: reply actionをタップ&入力した状態)




setLabel() を指定しない場合。





■ 音声入力時に選択肢を与える

選択肢をあらかじめ用意しておくこともできる。選択肢は5つまで。setChoices() で文字列の配列を渡して指定する。 <?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="reply_choices"> <item>Yes</item> <item>No</item> <item>Maybe</item> </string-array> </resources> public static final EXTRA_VOICE_REPLY = "extra_voice_reply"; ... String replyLabel = getResources().getString(R.string.reply_label); // 選択肢 String[] replyChoices = getResources().getStringArray(R.array.reply_choices); RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY) .setLabel(replyLabel) .setChoices(replyChoices) .build();



■ 選択肢だけから選択させる

音声入力を使わず、選択肢だけから選択させることもできる。 そのためには、setAllowFreeFormInput() で false を指定する。
この場合、setChoices() で選択肢を指定しておかないと IllegalArgumentException になる。 public static final EXTRA_VOICE_REPLY = "extra_voice_reply"; ... String replyLabel = getResources().getString(R.string.reply_label); // 選択肢 String[] replyChoices = getResources().getStringArray(R.array.reply_choices); RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY) .setLabel(replyLabel) .setChoices(replyChoices) .setAllowFreeFormInput(false) .build();





Page を追加する

追加の情報を表示したい場合などに、Page を追加することができる。

例えば、ハングアウトのカードでは、メインのカードに最新のメッセージが表示され、次のページに最近の投稿メッセージが表示される。
Google Now の天気カードでは、メインのカードに今日の天気、次のページに明日から四日間の天気が表示される。



Page を追加するには NotificationCompat.WearableExtender の addPage()addPages() を使う。 // メインの Notification の NotificationCompat.Builder を作る NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notif) .setContentTitle("Page 1") .setContentText("吾輩は猫である") .setContentIntent(pendingIntent); // 2ページ目の Notification を作る Notification secondPageNotification = new NotificationCompat.Builder(this) .setContentTitle("Page 2") .setContentText(getString(R.string.long_text)) .build(); // addPage() で2ページ目を追加し、extend() で1ページ目を拡張する Notification twoPageNotification = new NotificationCompat.WearableExtender() .addPage(secondPageNotification) .extend(notificationBuilder) .build(); // Notification を発行 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.notify(notificationId, twoPageNotification); スマホ


Wear(上: ホーム画面、中: タップした状態)







Notification をまとめる

同じような通知はまとめて表示することが推奨されているが、Wearでは個別の通知の中がみれないのは不便。
そこで、1つのカードにグループ化し、カードをタップするとそれぞれの Notification が個別のカードに別れるよう作ることができる。

Gmailのカードはまさにこの形になっている。

カードをグループ化するには NotificationCompat.Builder.setGroup() で同じ文字列を指定する。 final static String GROUP_KEY_EMAILS = "group_key_emails"; // Build the notification, setting the group appropriately Notification notif = new NotificationCompat.Builder(mContext) .setContentTitle("New mail from " + sender1) .setContentText(subject1) .setSmallIcon(R.drawable.new_mail); .setGroup(GROUP_KEY_EMAILS) .build(); // Issue the notification NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.notify(notificationId1, notif); setGroup() に同じ文字列を指定し、notify() で指定する ID は別にする。 Notification notif2 = new NotificationCompat.Builder(mContext) .setContentTitle("New mail from " + sender2) .setContentText(subject2) .setSmallIcon(R.drawable.new_mail); .setGroup(GROUP_KEY_EMAILS) .build(); notificationManager.notify(notificationId2, notif2); Wear(上: ホーム画面、中: タップした状態、下: 中をタップした状態)




グループ化されたカードがスタックされる順番は、新しく発行されたものが上になる(デフォルト)。
setSortKey() を使うと、順番を任意に指定することができる。


setGroup() を指定した Notification はスマホでは表示されないため、Summary Notification を用意する。 Summary Notification では setGroup() に同じ文字列を指定し、setGroupSummry() に true を指定する。この Summary Notification は Wear では表示されない。 Notification summaryNotification = new NotificationCompat.Builder(mContext) .setContentTitle("2 new messages") .setSmallIcon(R.drawable.ic_notif) .setStyle(new NotificationCompat.InboxStyle() .addLine(line1) .addLine(line2) .setBigContentTitle("2 new messages") .setSummaryText(summary)) .setGroup(GROUP_KEY_EMAILS) .setGroupSummary(true) .build(); notificationManager.notify(notificationId3, summaryNotification); スマホ


Summary Notification は Wear に直接表示されることはないが、 NotificationCompat.WearableExtender を使ってスタック全体の背景やスタック全体に対する Action を指定できる。 Bitmap background = BitmapFactory.decodeResource(getResources(), R.drawable.ic_background); NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender() .setBackground(background); // extend() で背景を指定 Notification summaryNotificationWithBackground = new NotificationCompat.Builder(mContext) .setContentTitle("2 new messages") ... .extend(wearableExtender) .setGroup(GROUP_KEY_EMAILS) .setGroupSummary(true) .build(); Wear




0 件のコメント:

コメントを投稿