Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 44efafd6 authored by Caitlin Shkuratov's avatar Caitlin Shkuratov
Browse files

[CS] 5/ Move status bar mode & appearance to repository.

This change majorly involves:
1) Replacing the methods inside SystemBarAttributesListener to be in
flows form instead.
2) Moving CentralSurface's bar mode calculations to the repo.

This change also minorly involves:
1) Adding an OngoingCallRepository that has the ongoing call status.
2) Updating StatusBarBoundsProvider to have an #addListener method
   instead of injecting the listeners directly.
3) Updating LetterboxAppearanceCalculator to have the status bar bounds
   passed to it instead of fetching them.

Bug: 300519002
Fixes: 301605450

Test: Assortment of letterboxing tests, including:
 - On landscape tablet, open landscaped app. Move app to be center,
   left, and right, and verify status bar icons are visible in all
   locations. Verify newly added status bar icons get the right color.
 - Without rotating device at all (aka ensure #onStatusBarBoundsChanged
   is never called), letterbox an app and verify it looks correct.
 - Enter split screen in landscape with letterboxed app on left, typical app on
   right. Verify icons are correct in all split screen sizes. Rotate to
   portrait then rotate back. Verify icons get correct color again.
-  Enter split screen in portrait with typical light app on top. Rotate to
   landscape then rotate back. Verify system icons are still dark
   colored afer rotation.

Test: Assortment of ongoing call tests, including:
 - Start ongoing call then enter immersive mode in app. Verify status
   bar still displays with semi-transparent background. Verify status
   bar can be swiped away.
 - General smoke test

Test: Assortment of bar mode tests, including:
 - Use app in low profile mode -> verify notif icons disappear (replaced
   by dot) and only battery icon shows on the right and is dimmed
 - Verify status bar is transparent on homescreen background with
   sufficient contrast
 - Verify status bar has gradient scrim on homescreen background that
   needs extra contrast
 - Verify status bar appears correctly with apps that request specific
   background color

Test: Verify dumps of StatusBarModeRepositoryImpl and
LetterboxAppearanceCalculator

Test: atest StatusBarModeRepositoryImplTest OngoingCallControllerTest
LetterboxAppearanceCalculatorTest LightBarControllerTest
Test: atest SystemUITests

Change-Id: I5b084356691fca03411461748f56fe6841f1b83e
parent 90884dbc
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -17,13 +17,16 @@
package com.android.systemui.statusbar.dagger

import com.android.systemui.CoreStartable
import com.android.systemui.statusbar.core.StatusBarInitializer
import com.android.systemui.statusbar.data.repository.StatusBarModeRepository
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryImpl
import com.android.systemui.statusbar.phone.LightBarController
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import dagger.multibindings.IntoSet

/**
 * A module for **only** classes related to the status bar **UI element**. This module specifically
@@ -49,4 +52,15 @@ abstract class StatusBarModule {
    @IntoMap
    @ClassKey(OngoingCallController::class)
    abstract fun bindOngoingCallController(impl: OngoingCallController): CoreStartable

    @Binds
    @IntoMap
    @ClassKey(LightBarController::class)
    abstract fun bindLightBarController(impl: LightBarController): CoreStartable

    @Binds
    @IntoSet
    abstract fun statusBarInitializedListener(
        statusBarModeRepository: StatusBarModeRepository,
    ): StatusBarInitializer.OnStatusBarViewInitializedListener
}
+42 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.statusbar.data.model

import com.android.internal.view.AppearanceRegion
import com.android.systemui.statusbar.phone.BoundsPair

/** Keeps track of various parameters coordinating the appearance of the status bar. */
data class StatusBarAppearance(
    /** The current mode of the status bar. */
    val mode: StatusBarMode,
    /** The current bounds of the status bar. */
    val bounds: BoundsPair,
    /**
     * A list of appearance regions for the appearance of the status bar background. Used to
     * determine the correct coloring of status bar icons to ensure contrast. See
     * [com.android.systemui.statusbar.phone.LightBarController].
     */
    val appearanceRegions: List<AppearanceRegion>,
    /**
     * The navigation bar color as set by
     * [com.android.systemui.statusbar.CommandQueue.onSystemBarAttributesChanged].
     *
     * TODO(b/277764509): This likely belongs in a "NavigationBarAppearance"-type class, not a
     *   status bar class.
     */
    val navbarColorManagedByIme: Boolean,
)
+60 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.statusbar.data.model

import com.android.systemui.statusbar.phone.BarTransitions
import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT
import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT
import com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE
import com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT
import com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT
import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode

/**
 * The possible status bar modes.
 *
 * See the associated [BarTransitions] mode documentation for information about each of the modes
 * and how they're used.
 */
enum class StatusBarMode {
    /** Use a semi-transparent (aka translucent) background for the status bar. */
    SEMI_TRANSPARENT,
    /**
     * A mode where notification icons in the status bar are hidden and replaced by a dot (this mode
     * can be requested by apps). See
     * [com.android.systemui.statusbar.phone.LightsOutNotifController].
     */
    LIGHTS_OUT,
    /** Similar to [LIGHTS_OUT], but also with a transparent background for the status bar. */
    LIGHTS_OUT_TRANSPARENT,
    /** Use an opaque background for the status bar. */
    OPAQUE,
    /** Use a transparent background for the status bar. */
    TRANSPARENT;

    /** Converts a [StatusBarMode] to its [BarTransitions] integer. */
    @TransitionMode
    fun toTransitionModeInt(): Int {
        return when (this) {
            SEMI_TRANSPARENT -> MODE_SEMI_TRANSPARENT
            LIGHTS_OUT -> MODE_LIGHTS_OUT
            LIGHTS_OUT_TRANSPARENT -> MODE_LIGHTS_OUT_TRANSPARENT
            OPAQUE -> MODE_OPAQUE
            TRANSPARENT -> MODE_TRANSPARENT
        }
    }
}
+172 −2
Original line number Diff line number Diff line
@@ -16,8 +16,13 @@

package com.android.systemui.statusbar.data.repository

import android.graphics.Rect
import android.view.WindowInsets
import android.view.WindowInsetsController
import android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS
import android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS
import android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS
import android.view.WindowInsetsController.Appearance
import com.android.internal.statusbar.LetterboxDetails
import com.android.internal.view.AppearanceRegion
import com.android.systemui.CoreStartable
@@ -25,12 +30,22 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.DisplayId
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.core.StatusBarInitializer
import com.android.systemui.statusbar.data.model.StatusBarAppearance
import com.android.systemui.statusbar.data.model.StatusBarMode
import com.android.systemui.statusbar.phone.BoundsPair
import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator
import com.android.systemui.statusbar.phone.StatusBarBoundsProvider
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

@@ -41,7 +56,7 @@ import kotlinx.coroutines.flow.stateIn
 * Note: These status bar modes are status bar *window* states that are sent to us from
 * WindowManager, not determined internally.
 */
interface StatusBarModeRepository {
interface StatusBarModeRepository : StatusBarInitializer.OnStatusBarViewInitializedListener {
    /**
     * True if the status bar window is showing transiently and will disappear soon, and false
     * otherwise. ("Otherwise" in this case means the status bar is persistently hidden OR
@@ -61,6 +76,16 @@ interface StatusBarModeRepository {
     */
    val isInFullscreenMode: StateFlow<Boolean>

    /**
     * The current status bar appearance parameters.
     *
     * Null at system startup, but non-null once the first system callback has been received.
     */
    val statusBarAppearance: StateFlow<StatusBarAppearance?>

    /** The current mode of the status bar. */
    val statusBarMode: StateFlow<StatusBarMode>

    /**
     * Requests for the status bar to be shown transiently.
     *
@@ -85,6 +110,8 @@ constructor(
    @Application scope: CoroutineScope,
    @DisplayId thisDisplayId: Int,
    private val commandQueue: CommandQueue,
    private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator,
    ongoingCallRepository: OngoingCallRepository,
) : StatusBarModeRepository, CoreStartable {

    private val commandQueueCallback =
@@ -114,7 +141,7 @@ constructor(

            override fun onSystemBarAttributesChanged(
                displayId: Int,
                @WindowInsetsController.Appearance appearance: Int,
                @Appearance appearance: Int,
                appearanceRegions: Array<AppearanceRegion>,
                navbarColorManagedByIme: Boolean,
                @WindowInsetsController.Behavior behavior: Int,
@@ -125,7 +152,11 @@ constructor(
                if (displayId != thisDisplayId) return
                _originalStatusBarAttributes.value =
                    StatusBarAttributes(
                        appearance,
                        appearanceRegions.toList(),
                        navbarColorManagedByIme,
                        requestedVisibleTypes,
                        letterboxDetails.toList(),
                    )
            }
        }
@@ -139,6 +170,19 @@ constructor(

    private val _originalStatusBarAttributes = MutableStateFlow<StatusBarAttributes?>(null)

    private val _statusBarBounds = MutableStateFlow(BoundsPair(Rect(), Rect()))

    override fun onStatusBarViewInitialized(component: StatusBarFragmentComponent) {
        val statusBarBoundsProvider = component.boundsProvider
        val listener =
            object : StatusBarBoundsProvider.BoundsChangeListener {
                override fun onStatusBarBoundsChanged(bounds: BoundsPair) {
                    _statusBarBounds.value = bounds
                }
            }
        statusBarBoundsProvider.addChangeListener(listener)
    }

    override val isInFullscreenMode: StateFlow<Boolean> =
        _originalStatusBarAttributes
            .map { params ->
@@ -148,6 +192,89 @@ constructor(
            }
            .stateIn(scope, SharingStarted.Eagerly, false)

    /** Modifies the raw [StatusBarAttributes] if letterboxing is needed. */
    private val modifiedStatusBarAttributes: StateFlow<ModifiedStatusBarAttributes?> =
        combine(
                _originalStatusBarAttributes,
                _statusBarBounds,
            ) { originalAttributes, statusBarBounds ->
                if (originalAttributes == null) {
                    null
                } else {
                    val (newAppearance, newAppearanceRegions) =
                        modifyAppearanceIfNeeded(
                            originalAttributes.appearance,
                            originalAttributes.appearanceRegions,
                            originalAttributes.letterboxDetails,
                            statusBarBounds,
                        )
                    ModifiedStatusBarAttributes(
                        newAppearance,
                        newAppearanceRegions,
                        originalAttributes.navbarColorManagedByIme,
                        statusBarBounds,
                    )
                }
            }
            .stateIn(scope, SharingStarted.Eagerly, initialValue = null)

    override val statusBarAppearance: StateFlow<StatusBarAppearance?> =
        combine(
                modifiedStatusBarAttributes,
                isTransientShown,
                isInFullscreenMode,
                ongoingCallRepository.hasOngoingCall,
            ) { modifiedAttributes, isTransientShown, isInFullscreenMode, hasOngoingCall ->
                if (modifiedAttributes == null) {
                    null
                } else {
                    val statusBarMode =
                        toBarMode(
                            modifiedAttributes.appearance,
                            isTransientShown,
                            isInFullscreenMode,
                            hasOngoingCall,
                        )
                    StatusBarAppearance(
                        statusBarMode,
                        modifiedAttributes.statusBarBounds,
                        modifiedAttributes.appearanceRegions,
                        modifiedAttributes.navbarColorManagedByIme,
                    )
                }
            }
            .stateIn(scope, SharingStarted.Eagerly, initialValue = null)

    override val statusBarMode: StateFlow<StatusBarMode> =
        statusBarAppearance
            .map { it?.mode ?: StatusBarMode.TRANSPARENT }
            .stateIn(scope, SharingStarted.Eagerly, initialValue = StatusBarMode.TRANSPARENT)

    private fun toBarMode(
        appearance: Int,
        isTransientShown: Boolean,
        isInFullscreenMode: Boolean,
        hasOngoingCall: Boolean,
    ): StatusBarMode {
        return when {
            hasOngoingCall && isInFullscreenMode -> StatusBarMode.SEMI_TRANSPARENT
            isTransientShown -> StatusBarMode.SEMI_TRANSPARENT
            else -> appearance.toBarMode()
        }
    }

    @Appearance
    private fun Int.toBarMode(): StatusBarMode {
        val lightsOutOpaque = APPEARANCE_LOW_PROFILE_BARS or APPEARANCE_OPAQUE_STATUS_BARS
        return when {
            this and lightsOutOpaque == lightsOutOpaque -> StatusBarMode.LIGHTS_OUT
            this and APPEARANCE_LOW_PROFILE_BARS != 0 -> StatusBarMode.LIGHTS_OUT_TRANSPARENT
            this and APPEARANCE_OPAQUE_STATUS_BARS != 0 -> StatusBarMode.OPAQUE
            this and APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS != 0 -> StatusBarMode.SEMI_TRANSPARENT
            else -> StatusBarMode.TRANSPARENT
        }
    }

    override fun showTransient() {
        _isTransientShown.value = true
    }
@@ -156,11 +283,54 @@ constructor(
        _isTransientShown.value = false
    }

    private fun modifyAppearanceIfNeeded(
        appearance: Int,
        appearanceRegions: List<AppearanceRegion>,
        letterboxDetails: List<LetterboxDetails>,
        statusBarBounds: BoundsPair,
    ): Pair<Int, List<AppearanceRegion>> =
        if (shouldUseLetterboxAppearance(letterboxDetails)) {
            val letterboxAppearance =
                letterboxAppearanceCalculator.getLetterboxAppearance(
                    appearance,
                    appearanceRegions,
                    letterboxDetails,
                    statusBarBounds,
                )
            Pair(letterboxAppearance.appearance, letterboxAppearance.appearanceRegions)
        } else {
            Pair(appearance, appearanceRegions)
        }

    private fun shouldUseLetterboxAppearance(letterboxDetails: List<LetterboxDetails>) =
        letterboxDetails.isNotEmpty()

    override fun dump(pw: PrintWriter, args: Array<out String>) {
        pw.println("originalStatusBarAttributes: ${_originalStatusBarAttributes.value}")
        pw.println("modifiedStatusBarAttributes: ${modifiedStatusBarAttributes.value}")
        pw.println("statusBarMode: ${statusBarMode.value}")
    }

    /**
     * Internal class keeping track of the raw status bar attributes received from the callback.
     * Should never be exposed.
     */
    private data class StatusBarAttributes(
        @Appearance val appearance: Int,
        val appearanceRegions: List<AppearanceRegion>,
        val navbarColorManagedByIme: Boolean,
        @WindowInsets.Type.InsetsType val requestedVisibleTypes: Int,
        val letterboxDetails: List<LetterboxDetails>,
    )

    /**
     * Internal class keeping track of how [StatusBarAttributes] were transformed into new
     * attributes based on letterboxing and other factors. Should never be exposed.
     */
    private data class ModifiedStatusBarAttributes(
        @Appearance val appearance: Int,
        val appearanceRegions: List<AppearanceRegion>,
        val navbarColorManagedByIme: Boolean,
        val statusBarBounds: BoundsPair,
    )
}
+0 −4
Original line number Diff line number Diff line
@@ -290,10 +290,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner {

    void acquireGestureWakeLock(long time);

    boolean setAppearance(int appearance);

    int getBarMode();

    void resendMessage(int msg);

    void resendMessage(Object msg);
Loading