The Android Developer Challenge is back! Submit your idea before December 2.

Wear OS 上で通知を作成する

このページでは、スマートウォッチ用の通知を作成する方法について説明します。また、接続されているスマートフォンからスマートウォッチにブリッジされる各通知にコンテンツを追加する方法についても説明します。

スマートウォッチの通知の構造はスマートフォンと同じです。また、Wear OS by Google では、ユーザー エクスペリエンスを最大限に高めるために、ウェアラブル固有の機能を通知に追加するための API を提供しています。

アプリから発行された各通知は、カードとして通知ストリームに表示されます。

図 1. スマートフォンとスマートウォッチに表示された同じ通知

通知はスマートウォッチとスマートフォンのどちらでも作成できます。ウェアラブル用の通知を作成するには、NotificationCompat.Builder クラスを使用します。このクラスを使用して通知を作成する場合、通知が適切に表示されるようシステムが処理を行います。

注: RemoteViews を使用している通知の場合、カスタム レイアウトが取り除かれ、ウェアラブルにはテキストとアイコンだけが表示されます。ただし、スマートウォッチ上で動作するウェアラブル アプリを作成することで、カスタムのカード レイアウトを使用するカスタム通知を作成できます。

必要なクラスをインポートする

必要なパッケージをインポートするには、次の行を build.gradle ファイルに追加します。

    compile "com.android.support:support-v13:27.0.2"
    

これでプロジェクトから必要なパッケージにアクセスできるようになり、サポート ライブラリから必要なクラスをインポートできます。

Kotlin

    import android.support.v4.app.NotificationCompat
    import android.support.v4.app.NotificationManagerCompat
    import android.support.v4.app.NotificationCompat.WearableExtender
    

Java

    import android.support.v4.app.NotificationCompat;
    import android.support.v4.app.NotificationManagerCompat;
    import android.support.v4.app.NotificationCompat.WearableExtender;
    

通知ビルダーで通知を作成する

v4 サポート ライブラリを使用すると、Android 1.6(API レベル 4)以降との互換性を維持しながら、操作ボタンやアイコンなどの最新の通知機能を備えた通知を作成できます。

注: Android 8.0(API レベル 26)以降の場合、表示する通知のタイプごとに通知チャネルを作成する必要があります。

サポート ライブラリを使用して通知を作成する手順は次のとおりです。

  1. NotificationCompat.Builder のインスタンスを作成します。

    Kotlin

        val notificationId = 1
        // The channel ID of the notification.
        val id = "my_channel_01"
        // Build intent for notification content
        val viewPendingIntent = Intent(this, ViewEventActivity::class.java).let { viewIntent ->
            viewIntent.putExtra(EXTRA_EVENT_ID, eventId)
            PendingIntent.getActivity(this, 0, viewIntent, 0)
        }
    
        // Notification channel ID is ignored for Android 7.1.1
        // (API level 25) and lower.
        val notificationBuilder = NotificationCompat.Builder(this, id)
                .setSmallIcon(R.drawable.ic_event)
                .setContentTitle(eventTitle)
                .setContentText(eventLocation)
                .setContentIntent(viewPendingIntent)
        

    Java

        int notificationId = 001;
        // The channel ID of the notification.
        String id = "my_channel_01";
        // Build intent for notification content
        Intent viewIntent = new Intent(this, ViewEventActivity.class);
        viewIntent.putExtra(EXTRA_EVENT_ID, eventId);
        PendingIntent viewPendingIntent =
                PendingIntent.getActivity(this, 0, viewIntent, 0);
    
        // Notification channel ID is ignored for Android 7.1.1
        // (API level 25) and lower.
        NotificationCompat.Builder notificationBuilder =
            new NotificationCompat.Builder(this, id)
                .setSmallIcon(R.drawable.ic_event)
                .setContentTitle(eventTitle)
                .setContentText(eventLocation)
                .setContentIntent(viewPendingIntent);
        
  2. 通知 ID が指定された通知オブジェクトを notify() に渡すことによって通知を発行します。

    Kotlin

        NotificationManagerCompat.from(this).apply {
            notify(notificationId, notificationBuilder.build())
        }
        

    Java

        // Get an instance of the NotificationManager service
        NotificationManagerCompat notificationManager =
                NotificationManagerCompat.from(this);
    
        // Issue the notification with notification manager.
        notificationManager.notify(notificationId, notificationBuilder.build());
        

この通知がスマートフォンに表示された場合、通知をタップすることで、setContentIntent() メソッドによって指定された PendingIntent を呼び出すことができます。ウェアラブルの場合、この通知は通知ストリームで表示されます。ブリッジ通知では、ユーザーが通知をタップすると、展開可能型通知が表示され、定義済みのアクション(アプリを開くアクションなど)がトリガーされます。通常、これらの操作によってスマートフォン アプリのアクティビティが開始されます。

展開可能な通知を作成する

展開可能な通知では、通知ごとに重要なコンテンツや操作を追加できます。通知に対して指定した追加のコンテンツ ページや操作は、展開可能な通知内に表示されます。展開可能型通知は Wear OS 用のマテリアル デザインに準拠しているため、ユーザーはアプリのように操作することができます。

展開可能型通知の最初のアクションに RemoteInput(返信アクションなど)が含まれている場合、setChoices() を使用して設定した選択肢が、展開可能型通知の最初のアクションの下に表示されます。

以下のいずれかに該当する場合、通知をタップすることで展開可能型通知を表示できます。

  • ペア設定されているスマートフォン上のアプリによって通知が生成され、Wear にブリッジされている。
  • 通知に contentIntent が含まれていない。

注: setColor() メソッドを使用して通知用に設定したアプリ固有の背景色は、通知を展開したときに限り表示されます。

展開可能型通知のベスト プラクティス

展開可能な通知を使用するかどうかを決定する際には、以下のガイドラインを守ってください。

  • ペア設定されているスマートフォンから Wear デバイスにブリッジするすべての通知で、展開可能型通知を使用します。
  • Wear 上でローカルに実行しているアプリによって通知を生成する場合、通知のタップ ターゲットsetContentIntent() を呼び出すことで、通知 ID が指定された通知オブジェクトをアプリ内で起動する必要があります。Wear 上でローカルに実行しているアプリが生成する通知の場合、展開可能型通知を使用しないことをおすすめします。

展開可能な通知を追加する

展開可能な通知では、通知に追加のコンテンツや操作を含めることができます。アプリの通知で提供する詳細情報のレベルは選択できますが、通知に含める詳細情報の量については適切な判断が必要です。

コンテンツを追加する

展開可能型通知内に追加テキストを表示するには、BigTextStyle を使用します。

展開可能型通知内に画像を追加するには、BigPictureStyle を使用します。展開可能型通知に複数の画像を追加する場合は、BigPictureStyle に加えて addPage() メソッドを使用します。

メイン アクション

展開可能型通知には、メイン アクションが 1 つ含まれています。setContentAction() によって別のアクションが指定されている場合を除き、このメイン アクションが通知内の最初のアクションになります。

追加アクション

追加アクションを指定するには、addAction() または addActions() を使用します。利用可能なアクションは、展開可能型通知のアクション ドロワー内にすべて格納されています。

通知のアクションを追加する

setContentIntent() によって定義したメイン コンテンツ アクション以外にも、PendingIntentaddAction() メソッドに渡すことで、アクションを追加できます。

たとえば、下記のコードは上記と同じタイプの通知を示していますが、地図上にイベント会場を表示するアクションを追加しています。

Kotlin

    // Build an intent for an action to view a map
    val mapIntent = Intent(Intent.ACTION_VIEW)
    // The channel ID of the notification.
    val id = "my_channel_01"
    val mapPendingIntent = Intent(Intent.ACTION_VIEW).let { mapIntent ->
        mapIntent.data = Uri.parse("geo:0,0?q=" + Uri.encode(location))
        PendingIntent.getActivity(this, 0, mapIntent, 0)
    }

    val notificationBuilder = NotificationCompat.Builder(this, id)
            .setSmallIcon(R.drawable.ic_event)
            .setContentTitle(eventTitle)
            .setContentText(eventLocation)
            .setContentIntent(viewPendingIntent)
            .addAction(R.drawable.ic_map, getString(R.string.map), mapPendingIntent)
    

Java

    // Build an intent for an action to view a map
    Intent mapIntent = new Intent(Intent.ACTION_VIEW);
    // The channel ID of the notification.
    String id = "my_channel_01";
    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, id)
            .setSmallIcon(R.drawable.ic_event)
            .setContentTitle(eventTitle)
            .setContentText(eventLocation)
            .setContentIntent(viewPendingIntent)
            .addAction(R.drawable.ic_map,
                    getString(R.string.map), mapPendingIntent);
    

スマートフォンでは、通知にアタッチされた追加のボタンとして操作が表示されます。ウェアラブルでは、コンテンツのテキストの後の展開可能な通知内に操作が表示されます。ユーザーが操作をタップすると、関連するインテントがスマートフォン上で呼び出されます。

ヒント: 通知に「返信」操作が含まれている場合(メッセージ アプリなど)、ウェアラブルから音声入力で直接返信できるようにすることで動作を拡張できます。詳しくは、通知の操作として音声入力を追加するをご覧ください。

インライン操作を追加する

インライン操作では、通知のストリーム カード内から通知に対する操作を実行できます。Wear では、インライン操作は通知の一番下に表示される追加のボタンとして表示されます。

インライン操作はオプションですが、通知のストリーム カードにコンテンツが表示された後にユーザーが(展開可能な通知を展開せずに)通知に対する操作を行う可能性が高い場合は、選択することをおすすめします。通知のインライン操作に適したユースケースの例として、テキスト メッセージへの返信、フィットネス アクティビティの停止、メール メッセージのアーカイブなどがあります。

表示できるインライン アクションは、1 つの通知につき 1 つに限られます。インライン アクションを通知内の追加ボタンとして表示するには、setHintDisplayActionInline() メソッドを true に設定します。ユーザーがインライン アクションをタップすると、通知アクションで指定したインテントがシステムによって呼び出されます。

アクションをインラインに表示するためのヒントを追加し、addAction メソッドを使用してインライン アクションを通知に追加するコード スニペットを以下に示します。

Kotlin

    //Wear OS requires a hint to display the reply action inline.
    val actionExtender = NotificationCompat.Action.WearableExtender()
            .setHintLaunchesActivity(true)
            .setHintDisplayActionInline(true)
    wearableExtender.addAction(actionBuilder.extend(actionExtender).build())
    

Java

    //Wear OS requires a hint to display the reply action inline.
    Action.WearableExtender actionExtender =
        new Action.WearableExtender()
            .setHintLaunchesActivity(true)
            .setHintDisplayActionInline(true);
    wearableExtender.addAction(actionBuilder.extend(actionExtender).build());
    

ウェアラブル固有の機能を通知に追加する

ウェアラブル固有の機能(ウェアラブル通知にアプリアイコンを表示しない、ユーザーが音声入力でテキスト応答を指示できるようにするなど)を通知に追加する必要がある場合は、NotificationCompat.WearableExtender クラスを使用してオプションを指定できます。この API を使用する手順は次のとおりです。

  1. WearableExtender のインスタンスを作成し、ウェアラブル固有の通知オプションを設定します。
  2. NotificationCompat.Builder のインスタンスを作成し、上記の説明に沿って、通知プロパティを設定します。
  3. 通知で extend() を呼び出して WearableExtender を渡します。これにより、ウェアラブル用のオプションが通知に適用されます。
  4. build() を呼び出して通知を作成します。

注: フレームワークの NotificationManager を使用する場合、NotificationCompat.WearableExtender の一部の機能が動作しなくなるため、必ず NotificationCompat を使用するようにしてください。

通知の消去操作(キャンセル)はユーザーのデバイス間で同期できます。消去操作の同期を有効にするには、setDismissalId() メソッドを使用します。setDismissalId() メソッドを呼び出すときには、通知ごとにグローバルに一意の ID を文字列として渡します。通知の消去操作が実行されると、同じ消去操作 ID を持つ他のすべての通知がスマートウォッチやコンパニオン スマートフォンから消去されます。消去操作 ID を取得するには、getDismissalId() を使用します。

注: setBackground() メソッドは、Wear 2.0 ではサポートされていません。画像を含む通知の場合は、NotificationCompat.BigPictureStyle を使用してください。

setHintHideIcon() メソッドも、Wear 2.0 ではサポートされていません。

ウェアラブル専用のアクションを指定する

ウェアラブルで使用可能なアクションとスマートフォンで使用可能なアクションを区別したい場合は、WearableExtender.addAction() を使用します。このメソッドでアクションを追加すると、NotificationCompat.Builder.addAction() を使用して追加した他のアクションは、ウェアラブル上では表示されなくなります。WearableExtender.addAction() を使用して追加したアクションはウェアラブル上だけで表示され、スマートフォン上では表示されません。

音声入力を通知アクションとして追加する

音声操作はウェアラブル機能の重要な要素です。音声入力をサポートするアクションを作成するには、通知アクションに追加可能な RemoteInput.Builder のインスタンスを作成します。このクラスのコンストラクタは、システムが音声入力のキーとして使用する文字列を受け入れます。この文字列は、後でスマートフォン アプリ内で入力のテキストを取得する際に使用します。

音声入力プロンプト用のカスタムラベルを提供する RemoteInput オブジェクトの作成方法の例を以下に示します。

Kotlin

    // Key for the string that's delivered in the action's intent
    const val EXTRA_VOICE_REPLY = "extra_voice_reply"
    ...
    val remoteInput = resources.getString(R.string.reply_label).let { replyLabel ->
        RemoteInput.Builder(EXTRA_VOICE_REPLY)
                .setLabel(replyLabel)
                .build()
    }
    

Java

    // Key for the string that's delivered in the action's intent
    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();
    

事前定義済みのテキスト応答を追加する

音声入力の有効化に加え、最大 5 つのテキスト応答を表示してユーザーが選択できるようにすることで、すばやい返信が可能になります。テキスト応答を表示するには、setChoices() を呼び出して、文字列配列を渡します。

たとえば、リソース配列で応答を定義することができます。

res/values/strings.xml

    <?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>
    

次に、文字列配列をインフレートして RemoteInput に追加します。

Kotlin

    // Key for the string that's delivered in the action's intent
    const val EXTRA_VOICE_REPLY = "extra_voice_reply"
    ...
    val remoteInput = resources.getString(R.string.reply_label).let { replyLabel ->
        resources.getStringArray(R.array.reply_choices).let { replyChoices ->
            RemoteInput.Builder(EXTRA_VOICE_REPLY)
                    .setLabel(replyLabel)
                    .setChoices(replyChoices)
                    .build()
        }
    }
    

Java

    public static final String 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();
    

音声入力を文字列として受け取る

ユーザーの音声文字変換後のメッセージを、返信アクションのインテント内で宣言したアクティビティで受け取るには、getResultsFromIntent() を呼び出して、「返信」アクションのインテントを渡します。このメソッドは、テキスト応答を含む Bundle を返します。Bundle に対してクエリを実行すると、応答を取得できます。

注: 音声入力は ClipData として保存されるため、音声結果を取得する際に Intent.getExtras() は使用しないでください。getResultsFromIntent() メソッドを使用すると、文字シーケンスを簡単に受け取ることができます。ClipData を自分で処理する必要もありません。

インテントを受け入れて、上記の例で使用した EXTRA_VOICE_REPLY キーによって参照される音声応答を返す方法を以下のコードに示します。

Kotlin

    /**
     * Obtain the intent that started this activity by calling
     * Activity.getIntent() and pass it into this method to
     * get the associated voice input string.
     */

    private fun getMessageText(intent: Intent): CharSequence? =
            RemoteInput.getResultsFromIntent(intent)?.run {
                getCharSequence(EXTRA_VOICE_REPLY)
            }
    

Java

    /**
     * Obtain the intent that started this activity by calling
     * Activity.getIntent() and pass it into this method to
     * get the associated voice input string.
     */

    private CharSequence getMessageText(Intent intent) {
        Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
        if (remoteInput != null) {
            return remoteInput.getCharSequence(EXTRA_VOICE_REPLY);
        }
        return null;
    }
    

Kotlin

    // Create an intent for the reply action
    val actionPendingIntent = Intent(this, ActionActivity::class.java).let { actionIntent ->
        PendingIntent.getActivity(this, 0, actionIntent,
                PendingIntent.FLAG_UPDATE_CURRENT)
    }
    // Create the action
    val action = NotificationCompat.Action.Builder(
            R.drawable.ic_action,
            getString(R.string.label),
            actionPendingIntent
    ).build()

    // Build the notification and add the action via WearableExtender
    var notification = NotificationCompat.Builder(context)
            .setSmallIcon(R.drawable.ic_message)
            .setContentTitle(getString(R.string.title))
            .setContentText(getString(R.string.content))
            .extend(NotificationCompat.WearableExtender().addAction(action))
            .build()
    

Java

    // Create an intent for the reply action
    Intent actionIntent = new Intent(this, ActionActivity.class);
    PendingIntent actionPendingIntent =
            PendingIntent.getActivity(this, 0, actionIntent,
                    PendingIntent.FLAG_UPDATE_CURRENT);

    // Create the action
    NotificationCompat.Action action =
            new NotificationCompat.Action.Builder(R.drawable.ic_action,
                    getString(R.string.label), actionPendingIntent)
                    .build();

    // Build the notification and add the action via WearableExtender
    Notification notification =
            new NotificationCompat.Builder(context)
                    .setSmallIcon(R.drawable.ic_message)
                    .setContentTitle(getString(R.string.title))
                    .setContentText(getString(R.string.content))
                    .extend(new WearableExtender().addAction(action))
                    .build();
    

スタンドアロンのウェアラブル アプリから通知を開始する

スタンドアロンのスマートウォッチ アプリでの通知の作成方法とブリッジ通知の作成方法に違いはありません。スタンドアロンの Wear アプリで作成された通知はブリッジ通知に似ていますが、動作が少し異なります。contentIntent が設定されていない場合や、ペア設定されているスマートフォンから通知がブリッジされた場合、通知をタップすると展開可能型通知が開きます。一方、通知がスタンドアロンのスマートウォッチ アプリで作成された場合、通知をタップすると contentIntent が起動し、Wear アプリが実行されます。スタンドアロン アプリで通知を作成する方法と、展開可能な通知の動作を模倣する方法については、Wear の通知のサンプルをご覧ください。

デフォルトでは、通知は、コンパニオン スマートフォン上のアプリからペア設定されているスマートウォッチにブリッジされます。スタンドアロンのスマートウォッチ アプリを作成する場合、コンパニオン スマートフォン アプリが存在すると、これらのアプリによって重複する通知が作成される可能性があります。通知の重複に関する問題の対処方法については、通知のブリッジモードをご覧ください。