進行中のアクティビティ

コレクションでコンテンツを整理 必要に応じて、コンテンツの保存と分類を行います。

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 自体が通信メカニズムとして使用されます。

進行中のアクティビティ

進行中の通知があれば、進行中のアクティビティを始めるのは簡単です。

次のコードサンプルには、各プロパティの意味をわかりやすくするためにコメントを記載しています。

Kotlin

var builder = 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)
        // In our case, 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 builder = 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)
        // In our case, 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.TimerPart や、ストップウォッチを表す 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: 書式なしテキストまたはクロノメーター。アプリ ランチャーの履歴セクションに表示されます。設定されていない場合は、通知「コンテキスト テキスト」が使用されます。

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

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

  • 進行中のアクティビティの ID: アプリに進行中のアクティビティが複数ある場合に、fromExistingOngoingActivityfromExistingOngoingActivity() の呼び出しを明確にするために使用される 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 を使用して進行中のアクティビティに関連付けてください。