参考
概要
- スマホと 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

