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

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

アプリを常に表示するとバッテリー駆動時間に大きな影響があるため、この機能を追加する場合は電力への影響を考慮してください。

主な概念

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

  • インタラクティブ: 画面の明るさが最大で、ユーザーが完全に操作できる高電力の状態。
  • 常に画面表示: 電力を節約するためにディスプレイが暗くなる省電力状態。この状態の場合、アプリの 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 分ごとに自動的に更新されます。

画面オンの時間を管理する

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

進行中のアクティビティでウォッチフェイスに戻らないようにする

通常、アンビエント状態が一定時間経過すると(タイムアウト #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 ウィンドウ フラグを使用します。これはウェイクロックとして機能し、デバイスをインタラクティブ状態に保ちます。バッテリー駆動時間に大きく影響するため、使用には十分な注意が必要です。

アンビエント モードに関する推奨事項

優れたユーザー エクスペリエンスを提供しながら、アンビエント モードで電力を節約するには、次の設計ガイドラインに従ってください。

  • ミニマルで低電力のディスプレイを使用する
    • 画面の 85% 以上を黒くします。
    • 大きなアイコンやボタンには、塗りつぶしではなく輪郭を使用します。
    • 最も重要な情報のみを表示し、セカンダリ ディテールをインタラクティブ ディスプレイに移動します。
    • 単色の大きなブロックや、機能性のないブランドや背景画像は避けてください。
  • コンテンツが適切に更新されるようにする
    • ストップウォッチ、ワークアウトの距離、時間など、頻繁に変更されるデータの場合は、-- などのプレースホルダ コンテンツを表示して、コンテンツが最新であるという印象を与えないようにします。
    • カウントダウン リングやメディア セッションなど、継続的に更新される進行状況インジケーターを削除します。
    • onUpdateAmbient() コールバックは、重要な更新にのみ使用してください(通常は 1 分間に 1 回)。
  • レイアウトの整合性を維持する
    • インタラクティブ モードと常に画面表示モードで要素の位置を同じに保ち、スムーズな遷移を実現します。
    • 時刻を常に表示する。
  • コンテキストに応じて対応する
    • デバイスが常に画面表示モードになったときに、ユーザーが設定画面または構成画面にいた場合は、設定ビューではなく、アプリのより関連性の高い画面を表示することを検討してください。
  • デバイス固有の要件に対応する
    • 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 でアンビエント モードの処理を簡素化する方法も示しています。