進行中のアクティビティ

Wear OS では、進行中のアクティビティと進行中の通知をペア設定すると、その通知が Wear OS ユーザー インターフェースの他のサーフェスに追加されます。これにより、ユーザーは長時間のアクティビティにさらに集中できるようになります。

進行中の通知は通常、ユーザーが実際に行っているバックグラウンド タスクや、なんらかの形で保留されているためにデバイスを占有しているバックグラウンド タスクがあることを通知するために使用されます。

たとえば、Wear OS ユーザーがワークアウト アプリを使用してアクティビティからランニングを記録し、そのアプリから離れて他のタスクを開始することがあります。ユーザーがワークアウト アプリから離れると、アプリは一部のバックグラウンド処理に関連付けられた進行中の通知に移行して、引き続きランニングについてユーザーに知らせるようにします。この通知は、ユーザーに最新情報を提供し、簡単にアプリに戻るための方法を提供します。

ただし、通知を表示するには、ユーザーはウォッチフェイスの下にある通知トレイをスワイプして、対応する通知を見つける必要があります。これは、他のサーフェスほど便利ではありません。

Ongoing Activity API を使用すると、アプリの進行中の通知により、Wear OS 上の新しく便利な複数のサーフェスに情報を表示することができるため、ユーザーの操作を妨げることはありません。

たとえば、このワークアウト アプリでは、タップ可能な実行中のアイコンとして情報がユーザーのウォッチフェイスに表示されます。

ランニング アイコン

図 1. アクティビティ インジケーター

進行中のアクティビティは、グローバル アプリ ランチャーの履歴セクションにも表示されます。

ランチャー

図 2. グローバル ランチャー

進行中のアクティビティに関連付けられた進行中の通知を使用するのに適した状況を以下に示します。

タイマー

図 3. タイマー: アクティブに時間をカウントダウンし、タイマーが一時停止または停止されると終了します。

地図

図 4. ターンバイターン ナビゲーション: 目的地までのルートをアナウンスします。ユーザーが目的地に到着するか、ナビゲーションを停止すると終了します。

音楽

図 5. メディア: セッション全体で音楽を再生します。ユーザーがセッションを一時停止した直後に終了します。

Wear は、メディアアプリ用に進行中のアクティビティを自動的に作成します。

他の種類のアプリ用に進行中のアクティビティを作成する詳細な例については、進行中のアクティビティに関する Codelab をご覧ください。

セットアップ

アプリで Ongoing Activity API の使用を開始するには、アプリの build.gradle ファイルに次の依存関係を追加します。

dependencies {
  implementation "androidx.wear:wear-ongoing:1.0.0"
  // Includes LocusIdCompat and new Notification categories for Ongoing Activity.
  implementation "androidx.core:core:1.6.0"
}

進行中のアクティビティを開始する

まず進行中の通知を作成し、その後で進行中のアクティビティを作成します。

進行中の通知を作成する

進行中のアクティビティは進行中の通知と密接に関係しています。これらが連携して、ユーザーが実際に行っているタスクや、なんらかの形で保留されているためにデバイスを占有しているタスクをユーザーに通知します。

進行中のアクティビティと進行中の通知をペア設定する必要があります。 進行中のアクティビティと通知をリンクすることには、以下のように多くの利点があります。

  • 通知は、進行中のアクティビティをサポートしていないデバイスでの代替手段です。通知は、バックグラウンドで実行中のアプリが表示される唯一のサーフェスです。
  • Android 11 以降、Wear OS では、アプリが他のサーフェスで進行中のアクティビティとして表示されると、通知トレイの通知が非表示になります。
  • 現在の実装では、Notification 自体が通信メカニズムとして使用されます。

Notification.Builder.setOngoing を使用して進行中の通知を作成します。

進行中のアクティビティを開始する

進行中の通知を作成したら、次のサンプルに示すように、進行中のアクティビティを作成します。コード内のコメントを確認し、各プロパティの動作を理解してください。

Kotlin

var notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
      …
      .setSmallIcon(..)
      .setOngoing(true)

val ongoingActivityStatus = Status.Builder()
    // Sets the text used across various surfaces.
    .addTemplate(mainText)
    .build()

val ongoingActivity =
    OngoingActivity.Builder(
        applicationContext, NOTIFICATION_ID, notificationBuilder
    )
        // Sets the animated icon that will appear on the watch face in
        // active mode.
        // If it isn't set, the watch face will use the static icon in
        // active mode.
        .setAnimatedIcon(R.drawable.ic_walk)
        // Sets the icon that will appear on the watch face in ambient mode.
        // Falls back to Notification's smallIcon if not set.
        // If neither is set, an Exception is thrown.
        .setStaticIcon(R.drawable.ic_walk)
        // Sets the tap/touch event so users can re-enter your app from the
        // other surfaces.
        // Falls back to Notification's contentIntent if not set.
        // If neither is set, an Exception is thrown.
        .setTouchIntent(activityPendingIntent)
        // Here, sets the text used for the Ongoing Activity (more
        // options are available for timers and stopwatches).
        .setStatus(ongoingActivityStatus)
        .build()

ongoingActivity.apply(applicationContext)

notificationManager.notify(NOTIFICATION_ID, builder.build())

Java

NotificationCompat.Builder notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
      …
      .setSmallIcon(..)
      .setOngoing(true);

OngoingActivityStatus ongoingActivityStatus = OngoingActivityStatus.Builder()
    // Sets the text used across various surfaces.
    .addTemplate(mainText)
    .build();

OngoingActivity ongoingActivity =
    OngoingActivity.Builder(
        applicationContext, NOTIFICATION_ID, notificationBuilder
    )
        // Sets the animated icon that will appear on the watch face in
        // active mode.
        // If it isn't set, the watch face will use the static icon in
        // active mode.
        .setAnimatedIcon(R.drawable.ic_walk)
        // Sets the icon that will appear on the watch face in ambient mode.
        // Falls back to Notification's smallIcon if not set.
        // If neither is set, an Exception is thrown.
        .setStaticIcon(R.drawable.ic_walk)
        // Sets the tap/touch event so users can re-enter your app from the
        // other surfaces.
        // Falls back to Notification's contentIntent if not set.
        // If neither is set, an Exception is thrown.
        .setTouchIntent(activityPendingIntent)
        // Here, sets the text used for the Ongoing Activity (more
        // options are available for timers and stopwatches).
        .setStatus(ongoingActivityStatus)
        .build();

ongoingActivity.apply(applicationContext);

notificationManager.notify(NOTIFICATION_ID, builder.build());

先ほどの例の中で最も重要な部分を示す手順を次に示します。

  1. NotificationCompat.Builder.setOngoing(true) を呼び出し、オプションのフィールドを設定します。

  2. テキストを表す OngoingActivityStatus または別のステータス オプション(次のセクションで説明します)を作成します。

  3. OngoingActivity を作成し、通知 ID を設定します。

  4. コンテキストを指定して OngoingActivityapply() を呼び出します。

  5. notificationManager.notify() を呼び出し、進行中のアクティビティに設定されている通知 ID を渡して、両者を関連付けます。

ステータス

Status を使用して、ランチャーの履歴セクションなどの新しいサーフェスで、OngoingActivity のライブ ステータスをユーザーに公開します。この機能を使用するには、Status.Builder サブクラスを使用します。

ほとんどの場合、アプリ ランチャーの履歴セクションに表示するテキストを表すテンプレートを追加するだけで済みます。

addTemplate() メソッドを使用し、テキストの動的な部分を Status.Part として指定することで、スパンを使用したテキストの表示をカスタマイズできます。

次の例は、「time」という単語を赤色で表示する方法を示しています。この例では、アプリ ランチャーの履歴セクションでストップウォッチを表す Status.StopwatchPart を使用しています。

Kotlin

val htmlStatus =
        "<p>The <font color=\"red\">time</font> on your current #type# is #time#.</p>"

val statusTemplate =
        Html.fromHtml(
                htmlStatus,
                Html.FROM_HTML_MODE_COMPACT
        )

// Creates a 5 minute timer.
// Note the use of SystemClock.elapsedRealtime(), not System.currentTimeMillis().
val runStartTime = SystemClock.elapsedRealtime() + TimeUnit.MINUTES.toMillis(5)

val status = new Status.Builder()
   .addTemplate(statusTemplate)
   .addPart("type", Status.TextPart("run"))
   .addPart("time", Status.StopwatchPart(runStartTime)
   .build()

Java

String htmlStatus =
        "<p>The <font color=\"red\">time</font> on your current #type# is #time#.</p>";

Spanned statusTemplate =
        Html.fromHtml(
                htmlStatus,
                Html.FROM_HTML_MODE_COMPACT
        );

// Creates a 5 minute timer.
// Note the use of SystemClock.elapsedRealtime(), not System.currentTimeMillis().
Long runStartTime = SystemClock.elapsedRealtime() + TimeUnit.MINUTES.toMillis(5);

Status status = new Status.Builder()
   .addTemplate(statusTemplate)
   .addPart("type", new Status.TextPart("run"))
   .addPart("time", new Status.StopwatchPart(runStartTime)
   .build();

テンプレートの一部を参照するには、名前を # で囲みます。出力に # を生成するには、テンプレートで ## を使用します。

この例では、HTMLCompat を使用してテンプレートに渡す CharSequence を生成しています。この方法は、Spannable オブジェクトを手動で定義するよりも簡単です。

その他のカスタマイズ

Status 以外にも、次の方法で進行中のアクティビティまたは通知をカスタマイズできます。ただし、OEM の実装によっては、こうしたカスタマイズが使用されない場合もあります。

進行中の通知

  • 設定したカテゴリによって、進行中のアクティビティの優先度が決まります。
    • CATEGORY_CALL: 音声通話またはビデオ通話の着信、あるいはそれに類する同期を行う通信リクエスト
    • CATEGORY_NAVIGATION: 地図またはターンバイターン ナビゲーション
    • CATEGORY_TRANSPORT: 再生のメディア トランスポート コントロール
    • CATEGORY_ALARM: アラームまたはタイマー
    • CATEGORY_WORKOUT: ワークアウト(新しいカテゴリ)
    • CATEGORY_LOCATION_SHARING: 一時的な現在地の共有(新しいカテゴリ)
    • CATEGORY_STOPWATCH: ストップウォッチ(新しいカテゴリ)

進行中のアクティビティ

  • アニメーション アイコン: 白黒のベクター(背景が透明であることが望ましい)。アクティブ モードのとき、ウォッチフェイスに表示されます。アニメーション アイコンが設定されていない場合、デフォルトの通知アイコンが使用されます(デフォルトの通知アイコンはアプリによって異なります)。

  • 静的アイコン: 背景が透明なベクターアイコン。常に画面表示モードのときウォッチフェイスに表示されます。アニメーション アイコンが設定されていない場合、アクティブ モードのウォッチフェイスには静的アイコンが使用されます。静的アイコンが設定されていない場合、通知アイコンが使用されます。どちらも設定されていない場合は、例外がスローされます(アプリ ランチャーには引き続きアプリアイコンが使用されます)。

  • OngoingActivityStatus: 書式なしテキストまたは Chronometer。アプリ ランチャーの履歴セクションに表示されます。設定されていない場合は、通知「コンテキスト テキスト」が使用されます。

  • タップ インテント: ユーザーが進行中のアクティビティのアイコンをタップしたとき、アプリに戻るために使用される PendingIntent。ウォッチフェイスまたはランチャー アイテムに表示されます。アプリの起動に使用された元のインテントとは異なる場合があります。設定されていない場合は、通知のコンテンツ インテントが使用されます。どちらも設定されていない場合は、例外がスローされます。

  • LocusId: 進行中のアクティビティに対応するランチャー ショートカットを割り当てる ID。アクティビティの進行中は、ランチャーの [履歴] セクションに表示されます。設定されていない場合、ランチャーは、同じパッケージの [履歴] セクションにあるすべてのアプリアイテムを非表示にし、進行中のアクティビティのみを表示します。

  • 進行中のアクティビティの ID: アプリに進行中のアクティビティが複数ある場合に、fromExistingOngoingActivity() の呼び出しを明確にするために使用される ID。

進行中のアクティビティを更新する

デベロッパーはほとんどの場合、画面上のデータを更新する必要があるとき、進行中の通知と進行中のアクティビティを新規作成します。しかし、インスタンスを再作成するのではなく保持する場合、Ongoing Activity API には OngoingActivity を更新するヘルパー メソッドも用意されています。

アプリがバックグラウンドで動作している場合、Ongoing Activity API に更新を送信できます。ただし、更新メソッドは互いに近すぎる呼び出しを無視するため、頻繁に実行しないでください。1 分間に数回の更新が妥当です。

進行中のアクティビティと送信された通知を更新するには、次のように、前に作成したオブジェクトを使用して update() を呼び出します。

Kotlin

ongoingActivity.update(context, newStatus)

Java

ongoingActivity.update(context, newStatus);

便宜上、進行中のアクティビティを作成するための静的メソッドが用意されています。

Kotlin

OngoingActivity.recoverOngoingActivity(context)
               .update(context, newStatus)

Java

OngoingActivity.recoverOngoingActivity(context)
               .update(context, newStatus);

進行中のアクティビティを停止する

アプリが進行中のアクティビティとしての動作を完了するときは、進行中の通知をキャンセルするだけで済みます。

フォアグラウンドになったときに通知または進行中のアクティビティをキャンセルし、バックグラウンドに戻ったときにそれらを再作成することもできますが、これは必須ではありません。

進行中のアクティビティを一時停止する

アプリに明示的な停止アクションがある場合は、一時停止を解除した後も進行中のアクティビティを継続します。明示的な停止アクションがないアプリの場合は、一時停止したときにアクティビティを終了します。

おすすめの方法

Ongoing Activity API を使用する場合は、次の点に注意してください。

  • notificationManager.notify(...) 呼び出す前に ongoingActivity.apply(context) を呼び出してください。
  • 進行中のアクティビティの静的アイコンを明示的に設定するか、通知を介したフォールバックとして設定してください。そうしなかった場合、IllegalArgumentException が発生します。

  • 背景が透明な白黒のベクターアイコンを使用してください。

  • 進行中のアクティビティのタップ インテントを明示的に設定するか、通知を使用したフォールバックとして設定してください。そうしなかった場合、IllegalArgumentException が発生します。

  • NotificationCompat には、LocusIdCompat新しいカテゴリ(ワークアウト、ストップウォッチ、現在地の共有)を含む Core AndroidX ライブラリ core:1.5.0-alpha05+ を使用してください。

  • アプリのマニフェストで MAIN LAUNCHER アクティビティが複数宣言されている場合は、動的ショートカットを公開し、LocusId を使用して進行中のアクティビティに関連付けてください。

Wear OS デバイスでメディアを再生する際にメディア通知をパブリッシュする

メディア コンテンツが Wear OS デバイスで再生されている場合は、メディア通知をパブリッシュします。これにより、対応する進行中のアクティビティをシステムで作成できます。

Media3 を使用している場合、通知は自動的に公開されます。通知を手動で作成する場合は、MediaStyleNotificationHelper.MediaStyle を使用し、対応する MediaSessionセッション アクティビティが設定されている必要があります。