進行中のアクティビティを表示する

Wear OS デバイスは、ワークアウトのトラッキングなど、長時間実行されるエクスペリエンスによく使用されます。ここでユーザー エクスペリエンスに関する課題が生じます。ユーザーがタスクを開始した後、ウォッチフェイスに移動した場合、どのようにして戻ればよいでしょうか。ランチャーを使用してアプリに戻るのは、特に移動中は難しく、不要な手間がかかります。

この解決策は、進行中の通知を OngoingActivity とペア設定することです。これにより、デバイスは長時間実行されるアクティビティに関する情報をユーザー インターフェース全体に表示できます。ウォッチフェイスの下部にあるタップ可能なアイコンなどの機能が有効になります。これにより、ユーザーはバックグラウンド タスクを認識し、ワンタップでアプリに戻ることができます。

また、進行中のアクティビティにより、アプリが長時間表示されるため、一定期間操作がない場合にシステムがウォッチフェイスに戻るのを防ぐことができます。詳細については、Wear でアプリを表示したままにするをご覧ください。

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

ランニング アイコン

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

進行中の通知は、グローバル アプリ ランチャーの [履歴] セクションにも情報を表示します。これにより、ユーザーはタスクのステータスを確認し、アプリを再開できる便利な場所がもう 1 つ提供されます。

ランチャー

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

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

タイマー

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

地図

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

音楽

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

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

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

設定

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

dependencies {
  implementation "androidx.wear:wear-ongoing:1.1.0"
  implementation "androidx.core:core:1.18.0"
}

進行中のアクティビティを作成する

プロセスは次の 3 つのステップで構成されます。

  1. 標準の NotificationCompat.Builder を作成し、 進行中として構成します。
  2. OngoingActivity オブジェクトを作成して構成し、 通知ビルダーを渡します。
  3. 進行中のアクティビティを通知ビルダーに適用し、結果の通知を送信します。

通知を作成して構成する

まず、NotificationCompat.Builder を作成します。重要なステップは、 setOngoing(true) を呼び出して、進行中の通知としてマークすることです。この段階で、小さいアイコンやカテゴリなど、他の通知プロパティを設定することもできます。

// Create a PendingIntent to pass to the notification builder
val pendingIntent =
    PendingIntent.getActivity(
        this,
        0,
        Intent(this, AlwaysOnActivity::class.java).apply {
            flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
        },
        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
    )

val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
    .setContentTitle("Always On Service")
    .setContentText("Service is running in background")
    .setSmallIcon(R.drawable.animated_walk)
    // Category helps the system prioritize the ongoing activity
    .setCategory(NotificationCompat.CATEGORY_WORKOUT)
    .setContentIntent(pendingIntent)
    .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
    .setOngoing(true) // Important!

OngoingActivity を作成する

次に、ビルダーを使用して OngoingActivity のインスタンスを作成します。OngoingActivity.Builder には、Context、通知 ID、前のステップで作成した NotificationCompat.Builder が必要です。

新しい UI サーフェスに表示されるキー プロパティを構成します。

  • アニメーション アイコンと静的アイコン: アクティブ モードと常に画面表示モードでウォッチ フェイスに表示されるアイコンを指定します。
  • タップ インテント: ユーザーが進行中のアクティビティのアイコンをタップしたときに、アプリに戻るための PendingIntent。前のステップで作成した pendingIndent を再利用できます。

val ongoingActivity =
    OngoingActivity.Builder(applicationContext, NOTIFICATION_ID, notificationBuilder)
        // Sets the icon that appears on the watch face in active mode.
        .setAnimatedIcon(R.drawable.animated_walk)
        // Sets the icon that appears on the watch face in ambient mode.
        .setStaticIcon(R.drawable.ic_walk)
        // Sets the tap target to bring the user back to the app.
        .setTouchIntent(pendingIntent)
        .build()

通知に適用して送信する

最後のステップは、OngoingActivity を通知にリンクして送信することです。ongoingActivity.apply() メソッドは、元の 通知ビルダーを変更し、システムが 追加のサーフェスに表示できるように必要なデータを追加します。適用したら、通常どおり通知を作成して送信できます。

// This call modifies notificationBuilder to include the ongoing activity data.
ongoingActivity.apply(applicationContext)

// Post the notification.
startForeground(NOTIFICATION_ID, notificationBuilder.build())

ランチャーに動的なステータス テキストを追加する

上記のコードでは、タップ可能なアイコンがウォッチフェイスに追加されます。ランチャーの [履歴] セクションでさらに リッチなリアルタイム更新を提供するには、 Status オブジェクトを作成して OngoingActivity にアタッチします。カスタム Status を指定しない場合、システムはデフォルトで通知のコンテンツ テキスト(setContentText() を使用して設定)を使用します。動的テキストを表示するには、Status.Builder を使用します。プレースホルダを含むテンプレート文字列を定義し、Status.Part オブジェクトを指定してプレースホルダを埋めることができます。Status.Part は、ストップウォッチやタイマーなど、動的にできます。

次の例は、「Run for [ストップウォッチ タイマー]」と表示するステータスを作成する方法を示しています。

// Define a template with placeholders for the activity type and the timer.
val statusTemplate = "#type# for #time#"

// Set the start time for a stopwatch.
// Use SystemClock.elapsedRealtime() for time-based parts.
val runStartTime = SystemClock.elapsedRealtime()

val ongoingActivityStatus = Status.Builder()
    // Sets the template string.
    .addTemplate(statusTemplate)
    // Fills the #type# placeholder with a static text part.
    .addPart("type", Status.TextPart("Run"))
    // Fills the #time# placeholder with a stopwatch part.
    .addPart("time", Status.StopwatchPart(runStartTime))
    .build()

最後に、この StatusOngoingActivity にリンクするには、OngoingActivity.BuildersetStatus() を呼び出します。

val ongoingActivity =
    OngoingActivity.Builder(applicationContext, NOTIFICATION_ID, notificationBuilder)
        // ...
        // Add the status to the OngoingActivity.
        .setStatus(ongoingActivityStatus)
        .build()

その他のカスタマイズ

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。

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

ステータスを変更する必要がある場合は、新しい通知と進行中のアクティビティを作成するのではなく、既存の通知の進行中のアクティビティを更新する必要があります。進行中のアクティビティと送信された通知を更新するには、次のように、前に作成したオブジェクトを使用して update() を呼び出します。

ongoingActivity.update(context, newStatus)

進行中のアクティビティへの参照を保存できない場合は、進行中のアクティビティを復元するための静的メソッドがあります。ただし、これはあまり推奨されません。

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

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

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

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

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

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

考慮すべきポイント

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

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

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

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

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

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

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

Media3 を使用している場合、通知は自動的にパブリッシュされます。通知を手動で作成すると、MediaStyleNotificationHelper.MediaStyle が使用されます。それに対応するMediaSession には セッション アクティビティ が入力されているはずです。