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

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

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

主なコンセプト

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

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

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

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

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

システム トランジションとデフォルトの動作

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

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

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

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

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

デフォルトのシステム動作に関係なく、すべての Wear OS バージョンで、LocalAmbientModeManager を使用して Wear OS の Jetpack Compose で状態遷移を監視することで、アンビエント状態のアプリの外観や動作をカスタマイズできます。この状態は、次のフローチャートの「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")
            // ...
    }

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

次のフローチャートは、デバイスの Wear OS バージョン、アプリの targetSdkVersion、アンビエント状態の遷移を監視するかどうか(LocalAmbientModeManager または AmbientLifecycleCallback を使用)に基づいて、システムがアンビエント動作を決定する方法を示しています。

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% 以上を黒く保ちます(オンピクセルの比率が 15% 以下)。
    • 最も重要な情報のみを表示し、二次的な詳細はインタラクティブ ディスプレイに移動します。
    • 大きなアイコンやボタンには、塗りつぶしではなくアウトラインを使用します。アウトラインのコントラストを調整して、画面のちらつきや焼き付きのリスクを回避しながら、要素を読みやすくします。
    • 単色の大きなブロックや、機能しないブランディングや背景画像は避けてください。
  • 古い動的データとアニメーションを処理する
    • AmbientTickEffect コールバックは、バッテリーの消耗を抑えるために、定期的に(通常は 1 分に 1 回)のみ呼び出されます。この制限により、ストップウォッチ、心拍数、ワークアウトの距離など、頻繁に変化するデータは、更新の間に古くなります。誤解を招く不正確な情報が表示されないように、これらのライブ値を -- などの静的なプレースホルダ コンテンツに置き換えます。
    • 短いメディア セッションの進行状況リングなど、ユーザーの現在のコンテキストや状況で有用な頻繁な更新が必要な進行状況インジケーターを削除します。1 分に 1 回の更新でも価値がある場合(長いタイマーなど)は、進行状況インジケーターを保持できます。
    • アンビエント モードのすべてのアニメーション(アニメーションの進行状況インジケーターやスクロールするマーキー テキストなど)を一時停止または削除します。
  • 一貫したレイアウトを維持する
    • インタラクティブ モードとアンビエント モードで要素の位置を同じに保ち、スムーズな切り替えを実現します。最新の Wear OS デバイスはアンビエント セッション中のタッチ入力をサポートしているため、ボタンなどのインタラクティブな UI 要素は表示されたままになります。
    • 常に現在時刻を表示します。Wear OS 向け Jetpack Compose では、TimeText(ScreenScaffold にデフォルトで含まれています)はアンビエント対応で、アンビエント モードで 1 分に 1 回自動的に更新されます。
  • コンテキストを認識する
    • デバイスが常に画面表示モードに入ったときに、ユーザーが設定画面または構成画面を表示していた場合は、設定ビューではなく、アプリのより関連性の高い画面を表示することを検討してください。
  • デバイス固有の要件に対応する
    • currentAmbientMode が AmbientMode.Ambient を返す場合:
      • isLowBitAmbientSupported が true の場合、ディスプレイはアンビエント モードで色あたりのビット数が少なくなります。アンチエイリアシングを無効にし(カスタムの Canvas または Paint 描画オペレーションなど)、可能な限りカラー グラデーションを避けます。
      • isBurnInProtectionRequired が true の場合、システムはピクセル バーンインを防ぐために、表示コンテンツを定期的にシフトします。アプリの UI 要素を手動で移動しないでください。代わりに、白一色の領域を避け、重要な UI 要素を画面の端から 10 ピクセル以上離して配置し、システムがディスプレイを移動したときに切り取られないようにしてください。(ScreenScaffold などの標準の Wear Compose レイアウトでは、すでに十分なエッジ パディングが提供されています)。

デバッグとテスト

これらの 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 でアンビエント モードの処理を簡素化する方法も示します。