常時オンアプリとシステムのアンビエント モード

このガイドでは、アプリを常にオンにする方法、電源状態の切り替えに対応する方法、バッテリーを節約しながら優れたユーザー エクスペリエンスを提供するためにアプリの動作を管理する方法について説明します。

アプリを常に表示すると電池寿命に大きく影響するため、この機能を追加する場合は電力への影響を考慮してください。

主なコンセプト

Wear OS アプリが全画面表示されている場合、次の 2 つの電源状態のいずれかになります。

  • インタラクティブ: 画面が最大輝度で、ユーザーが完全に操作できる高電力状態。
  • アンビエント: 電力を節約するためにディスプレイが暗くなる低電力状態。この状態では、アプリの UI はまだ全画面を占有していますが、システムがぼかしや時刻などのオーバーレイ コンテンツの表示によって外観を変更する可能性があります。これは、アンビエント モードとも呼ばれます。

オペレーティング システムは、これらの状態間の移行を制御します。

常時オンアプリとは、インタラクティブ状態とアンビエント状態の両方でコンテンツを表示するアプリです。

デバイスが低電力のアンビエント状態のときに、常にオンのアプリが独自の UI を表示し続ける場合、そのアプリはアンビアクティブ モードであると表現されます。

システムの切り替えとデフォルトの動作

アプリがフォアグラウンドにある場合、システムはユーザーの操作がないときにトリガーされる 2 つのタイムアウトに基づいて、電源状態の切り替えを管理します。

  • タイムアウト #1: インタラクティブ状態からアンビエント状態: ユーザーが操作しない状態が一定時間続くと、デバイスはアンビエント状態になります。
  • タイムアウト #2: ウォッチフェイスに戻る: 操作が一定時間行われない場合、システムは現在のアプリを非表示にしてウォッチフェイスを表示することがあります。

システムが最初のトランジションを通過して Ambient 状態になった直後のデフォルトの動作は、Wear OS のバージョンとアプリの構成によって異なります。

  • Wear OS 5 以前の場合、一時停止したアプリのぼかし加工されたスクリーンショットが、時刻が重ねて表示された状態で表示されます。この状態は、次のフローチャートの「AOD Lite」ノードで表されます。
  • Wear OS 6 以降では、アプリが SDK 36 以降を対象としている場合、常にオンと見なされます。ディスプレイは暗くなりますが、アプリケーションは実行を続け、表示されたままになります。(更新は 1 分に 1 回程度になることがあります)。この状態は、次のフローチャートの「Global AOD」ノードで表されます。

アンビエント状態の動作をカスタマイズする

デフォルトのシステム動作に関係なく、すべての Wear OS バージョンで、Wear OS の Jetpack Compose で LocalAmbientModeManager を使用して状態遷移を監視することで、アンビエント状態のアプリの外観や動作をカスタマイズできます。この状態は、次のフローチャートの「Ambiactive Mode」ノードで表されます。

アンビエント状態をモニタリングする

Wear OS の Jetpack Compose でアンビエント モードのイベントに対応するには:

  1. LocalAmbientModeManager を使用して、システムのアンビエント モードのステータスの変更を監視し、それに応じてコンポーザブルを調整します。AmbientMode.Ambient 状態では低電力モードに調整し、AmbientMode.Interactive 状態ではフル インタラクティブ ディスプレイに戻すようにコンポーネントを構成します。

    // In a production application, the AmbientModeManager should be instantiated and provided at
    // the highest level of the Compose hierarchy (typically in the host Activity's setContent
    // block) using a CompositionLocalProvider. This ensures proper lifecycle management and
    // broad accessibility.
    
    // For this self-contained demo, AmbientModeManager is created and provided locally:
    val activityAmbientModeManager = rememberAmbientModeManager()
    CompositionLocalProvider(LocalAmbientModeManager provides activityAmbientModeManager) {
        val ambientModeManager = LocalAmbientModeManager.current
        val ambientMode = ambientModeManager?.currentAmbientMode
    
        if (ambientModeManager != null) {
            ambientModeManager.AmbientTickEffect {
                // While device is in ambient mode, update properties every minute or so
                // ...
            }
        }
    
        // ...
            val ambientModeName =
                when (ambientMode) {
                    is AmbientMode.Interactive -> "Interactive"
                    is AmbientMode.Ambient -> "Ambient"
                    else -> "Unknown"
                }
    
            Text(text = "$ambientModeName Mode")
            // ...
    }

Jetpack Compose を使用するデベロッパーは、このパターンの実装を簡素化する LocalAmbientModeManager を使用します。

アンビエント動作のフローチャート

次のフローチャートは、デバイスの Wear OS のバージョン、アプリの targetSdkVersionAmbientLifecycleCallback を実装しているかどうかに基づいて、システムがアンビエント動作を決定する仕組みを示しています。

Wear OS のアンビエント モードの決定ロジックを示すフローチャート。デバイスの OS バージョンとアプリの構成によって、3 つの結果(ぼかしオーバーレイ、グローバル AOD、アプリ管理のアンビエント モード)のいずれかが決まる仕組みを示します。
図 1.: Wear OS のアンビエント モードの決定ロジックを示すフローチャート。

画面をオンにする時間を制御する

以降のセクションでは、アプリが画面に表示される時間を管理する方法について説明します。

進行中のアクティビティまたはライブ アップデートでウォッチフェイスに戻れない

アンビエント状態(タイムアウト #2)が一定時間続くと、通常はウォッチフェイスに戻ります。ユーザーはシステム設定でタイムアウト時間を設定できます。ユーザーがワークアウトをトラッキングする場合など、ユースケースによっては、アプリを長時間表示する必要が生じる場合があります。

Wear OS 5 以降では、進行中のアクティビティを実装することで、これを防ぐことができます。ワークアウト セッションなど、進行中のユーザー タスクに関する情報をアプリで表示している場合は、Ongoing Activity API を使用して、タスクが終了するまでアプリを表示し続けることができます。ユーザーが手動でウォッチフェイスに戻った場合、進行中のアクティビティ インジケーターを使用すると、ワンタップでアプリに戻ることができます。

または、Wear OS 7 以降では、進行中のアクティビティの代わりにライブ更新を使用できます。下位互換性を確保するため、Wear OS 6 以前を搭載したデバイスでは、進行中のアクティビティのサポートを継続してください。

これを実装するには、次のコード スニペットに示すように、継続的通知のタッチ インテントが常時オン アクティビティを指している必要があります。

val activityIntent =
    Intent(this, AlwaysOnActivity::class.java).apply {
        flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
    }

val pendingIntent =
    PendingIntent.getActivity(
        this,
        0,
        activityIntent,
        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
    )

val notificationBuilder =
    NotificationCompat.Builder(this, CHANNEL_ID)
        // ...
        // ...
        .setOngoing(true)

// ...

val ongoingActivity =
    OngoingActivity.Builder(applicationContext, NOTIFICATION_ID, notificationBuilder)
        // ...
        // ...
        .setTouchIntent(pendingIntent)
        .build()

ongoingActivity.apply(applicationContext)

val notification = notificationBuilder.build()

画面をオンのままにしてアンビエント状態を回避する

まれに、デバイスがアンビエント状態に移行することを完全に防ぐ必要がある場合があります。つまり、タイムアウト #1 を回避するためです。これを行うには、keepScreenOn 修飾子を使用します。これは wake lock として機能し、デバイスをインタラクティブ状態に保ちます。バッテリー駆動時間に大きな影響があるため、この機能を使用する際は十分に注意してください。

アンビエント モードのおすすめ

アンビエント モードで最高のユーザー エクスペリエンスを提供し、電力を節約するには、次の設計ガイドラインに沿ってください。これらの推奨事項は、誤解を招く情報を防ぎ、視覚的な混乱を軽減することで、明確なユーザー エクスペリエンスを優先しつつ、ディスプレイの電力を最適化します。

  • 視覚的なノイズを減らし、パワーを表示します。クリーンでミニマルな UI は、アプリが低電力状態であることをユーザーに伝え、明るいピクセルを制限することでバッテリーを大幅に節約します。
    • 画面の 85% 以上を黒くします。
    • 最も重要な情報のみを表示し、二次的な詳細はインタラクティブ ディスプレイに移動します。
    • 大きなアイコンやボタンには、塗りつぶしではなくアウトラインを使用します。
    • 単色の大きなブロックや、機能しないブランディングや背景画像は避けてください。
  • 古い動的データを処理する
    • AmbientTickEffect コールバックは、バッテリーの消耗を抑えるために定期的に(通常は 1 分に 1 回)のみ呼び出されます。この制限により、ストップウォッチ、心拍数、ワークアウトの距離など、頻繁に変化するデータは、更新の間に古くなります。誤解を招く不正確な情報が表示されないように、これらのライブ値を -- などの静的なプレースホルダ コンテンツに置き換えます。
  • レイアウトの一貫性を維持する
    • インタラクティブ モードとアンビエント モードで要素の位置を同じに保ち、スムーズな切り替えを実現します。
    • 時刻を常に表示します。
  • コンテキストを認識する
    • デバイスがアンビエント モードに入ったときにユーザーが設定画面または構成画面を表示していた場合は、設定ビューではなく、アプリのより関連性の高い画面を表示することを検討してください。
  • デバイス固有の要件を処理する
    • currentAmbientMode から返される AmbientMode オブジェクトで、次のようにします。
      • isLowBitAmbientSupportedtrue の場合、可能であればアンチ エイリアスを無効にします。
      • isBurnInProtectionRequiredtrue の場合、画面の焼き付きを防ぐため、UI 要素を定期的にわずかにシフトし、白く塗りつぶされた領域がないようにします。

デバッグとテスト

これらの adb コマンドは、デバイスがアンビエント モードのときにアプリがどのように動作するかを開発またはテストする場合に役立ちます。

# put device in ambient mode if the always on display is enabled in settings
# (and not disabled by other settings, such as theatre mode)
$ adb shell input keyevent KEYCODE_SLEEP

# put device in interactive mode
$ adb shell input keyevent KEYCODE_WAKEUP

例: ワークアウト アプリ

ワークアウト アプリで、エクササイズ セッションの全期間にわたってユーザーに指標を表示する必要がある場合を考えてみましょう。アプリは、アンビエント状態の切り替えを通じて表示されたままになり、文字盤に置き換えられないようにする必要があります。

これを実現するには、デベロッパーは次のことを行う必要があります。

  1. LocalAmbientModeManager を使用して、インタラクティブ状態とアンビエント状態の間の UI の変更(画面の暗転や不要なデータの削除など)を処理します。
  2. ベスト プラクティスに沿って、アンビエント状態用の新しい低電力レイアウトを作成します。
  3. ワークアウト中は Ongoing Activity API(または Wear OS 7 以降の Live Updates)を使用して、システムがウォッチフェイスに戻らないようにします。

完全な実装については、GitHub の Compose ベースのエクササイズのサンプルをご覧ください。このサンプルでは、LocalAmbientModeManager コンポーザブルを使用して、Compose でアンビエント モードの処理を簡素化する方法も示します。