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

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

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

主な概念

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

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

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

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

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

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

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

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

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

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

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

デフォルトのシステム動作に関係なく、すべての Wear OS バージョンで、AmbientLifecycleObserver を使用して状態遷移のコールバックをリッスンすることで、アンビエント状態のアプリの外観や動作をカスタマイズできます。

AmbientLifecycleObserver を使用する

常に画面表示モードのイベントに対応するには、AmbientLifecycleObserver クラスを使用します。

  1. AmbientLifecycleObserver.AmbientLifecycleCallback インターフェースを実装します。onEnterAmbient() メソッドを使用して低電力状態に合わせて UI を調整し、onExitAmbient() を使用してフル インタラクティブ ディスプレイに戻します。

    val ambientCallback = object : AmbientLifecycleObserver.AmbientLifecycleCallback {
        override fun onEnterAmbient(ambientDetails: AmbientLifecycleObserver.AmbientDetails) {
            // ... Called when moving from interactive mode into ambient mode.
            // Adjust UI for low-power state: dim colors, hide non-essential elements.
        }
    
        override fun onExitAmbient() {
            // ... Called when leaving ambient mode, back into interactive mode.
            // Restore full UI.
        }
    
        override fun onUpdateAmbient() {
            // ... Called by the system periodically (typically once per minute)
            // to allow the app to update its display while in ambient mode.
        }
    }
    
  2. AmbientLifecycleObserver を作成し、アクティビティまたはコンポーザブルのライフサイクルに登録します。

    private val ambientObserver = AmbientLifecycleObserver(activity, ambientCallback)
    
    override fun onCreate(savedInstanceState: Bundle) {
        super.onCreate(savedInstanceState)
        lifecycle.addObserver(ambientObserver)
    
        // ...
    }
    
  3. removeObserver() を呼び出して、onDestroy() のオブザーバーを削除します。

Jetpack Compose を使用するデベロッパー向けに、Horologist ライブラリには、このパターンの実装を簡素化する便利なユーティリティである AmbientAware コンポーザブルが用意されています。

アンビエント対応の TimeText

カスタム オブザーバーを必要とする例外として、Wear OS 6 では TimeText ウィジェットはアンビエント対応です。デバイスがアンビエント状態の場合、追加のコードなしで 1 分に 1 回自動的に更新されます。

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

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

進行中のアクティビティでウォッチフェイスに戻るのを防ぐ

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

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

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

private fun createNotification(): Notification {
    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)

    return notificationBuilder.build()
}

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

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

アンビエント モードの推奨事項

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

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

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