• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.statusbar.data.repository
18 
19 import android.graphics.Rect
20 import android.view.InsetsFlags
21 import android.view.ViewDebug
22 import android.view.WindowInsets
23 import android.view.WindowInsetsController
24 import android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS
25 import android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS
26 import android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS
27 import android.view.WindowInsetsController.Appearance
28 import com.android.internal.statusbar.LetterboxDetails
29 import com.android.internal.view.AppearanceRegion
30 import com.android.systemui.Dumpable
31 import com.android.systemui.dagger.qualifiers.Application
32 import com.android.systemui.statusbar.CommandQueue
33 import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
34 import com.android.systemui.statusbar.data.model.StatusBarAppearance
35 import com.android.systemui.statusbar.data.model.StatusBarMode
36 import com.android.systemui.statusbar.phone.BoundsPair
37 import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator
38 import com.android.systemui.statusbar.phone.StatusBarBoundsProvider
39 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
40 import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
41 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
42 import dagger.assisted.Assisted
43 import dagger.assisted.AssistedFactory
44 import dagger.assisted.AssistedInject
45 import java.io.PrintWriter
46 import kotlinx.coroutines.CoroutineScope
47 import kotlinx.coroutines.flow.MutableStateFlow
48 import kotlinx.coroutines.flow.SharingStarted
49 import kotlinx.coroutines.flow.StateFlow
50 import kotlinx.coroutines.flow.asStateFlow
51 import kotlinx.coroutines.flow.combine
52 import kotlinx.coroutines.flow.map
53 import kotlinx.coroutines.flow.stateIn
54 
55 /**
56  * A repository for the current mode of the status bar on the homescreen (translucent, transparent,
57  * opaque, lights out, hidden, etc.).
58  *
59  * Note: These status bar modes are status bar *window* states that are sent to us from
60  * WindowManager, not determined internally.
61  */
62 interface StatusBarModePerDisplayRepository {
63     /**
64      * True if the status bar window is showing transiently and will disappear soon, and false
65      * otherwise. ("Otherwise" in this case means the status bar is persistently hidden OR
66      * persistently shown.)
67      *
68      * This behavior is controlled by WindowManager via
69      * [android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE], *not* calculated
70      * internally. SysUI merely obeys the behavior sent to us.
71      */
72     val isTransientShown: StateFlow<Boolean>
73 
74     /**
75      * True the focused window is fullscreen (aka immersive) and false otherwise.
76      *
77      * Typically, the only time the status bar window is hidden is when the focused window is
78      * fullscreen.
79      */
80     val isInFullscreenMode: StateFlow<Boolean>
81 
82     /**
83      * The current status bar appearance parameters.
84      *
85      * Null at system startup, but non-null once the first system callback has been received.
86      */
87     val statusBarAppearance: StateFlow<StatusBarAppearance?>
88 
89     /** The current mode of the status bar. */
90     val statusBarMode: StateFlow<StatusBarMode>
91 
92     /**
93      * Requests for the status bar to be shown transiently.
94      *
95      * TODO(b/277764509): Don't allow [CentralSurfaces] to set the transient mode; have it
96      *   determined internally instead.
97      */
98     fun showTransient()
99 
100     /**
101      * Requests for the status bar to be no longer showing transiently.
102      *
103      * TODO(b/277764509): Don't allow [CentralSurfaces] to set the transient mode; have it
104      *   determined internally instead.
105      */
106     fun clearTransient()
107 }
108 
109 class StatusBarModePerDisplayRepositoryImpl
110 @AssistedInject
111 constructor(
112     @Application scope: CoroutineScope,
113     @Assisted("displayId") thisDisplayId: Int,
114     private val commandQueue: CommandQueue,
115     private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator,
116     ongoingCallRepository: OngoingCallRepository,
117 ) : StatusBarModePerDisplayRepository, OnStatusBarViewInitializedListener, Dumpable {
118 
119     private val commandQueueCallback =
120         object : CommandQueue.Callbacks {
showTransientnull121             override fun showTransient(
122                 displayId: Int,
123                 @WindowInsets.Type.InsetsType types: Int,
124                 isGestureOnSystemBar: Boolean,
125             ) {
126                 if (isTransientRelevant(displayId, types)) {
127                     _isTransientShown.value = true
128                 }
129             }
130 
abortTransientnull131             override fun abortTransient(displayId: Int, @WindowInsets.Type.InsetsType types: Int) {
132                 if (isTransientRelevant(displayId, types)) {
133                     _isTransientShown.value = false
134                 }
135             }
136 
isTransientRelevantnull137             private fun isTransientRelevant(
138                 displayId: Int,
139                 @WindowInsets.Type.InsetsType types: Int,
140             ): Boolean {
141                 return displayId == thisDisplayId && (types and WindowInsets.Type.statusBars() != 0)
142             }
143 
onSystemBarAttributesChangednull144             override fun onSystemBarAttributesChanged(
145                 displayId: Int,
146                 @Appearance appearance: Int,
147                 appearanceRegions: Array<AppearanceRegion>,
148                 navbarColorManagedByIme: Boolean,
149                 @WindowInsetsController.Behavior behavior: Int,
150                 @WindowInsets.Type.InsetsType requestedVisibleTypes: Int,
151                 packageName: String,
152                 letterboxDetails: Array<LetterboxDetails>,
153             ) {
154                 if (displayId != thisDisplayId) return
155                 _originalStatusBarAttributes.value =
156                     StatusBarAttributes(
157                         appearance,
158                         appearanceRegions.toList(),
159                         navbarColorManagedByIme,
160                         requestedVisibleTypes,
161                         letterboxDetails.toList(),
162                     )
163             }
164         }
165 
startnull166     fun start() {
167         commandQueue.addCallback(commandQueueCallback)
168     }
169 
170     private val _isTransientShown = MutableStateFlow(false)
171     override val isTransientShown: StateFlow<Boolean> = _isTransientShown.asStateFlow()
172 
173     private val _originalStatusBarAttributes = MutableStateFlow<StatusBarAttributes?>(null)
174 
175     private val _statusBarBounds = MutableStateFlow(BoundsPair(Rect(), Rect()))
176 
onStatusBarViewInitializednull177     override fun onStatusBarViewInitialized(component: StatusBarFragmentComponent) {
178         val statusBarBoundsProvider = component.boundsProvider
179         val listener =
180             object : StatusBarBoundsProvider.BoundsChangeListener {
181                 override fun onStatusBarBoundsChanged(bounds: BoundsPair) {
182                     _statusBarBounds.value = bounds
183                 }
184             }
185         statusBarBoundsProvider.addChangeListener(listener)
186     }
187 
188     override val isInFullscreenMode: StateFlow<Boolean> =
189         _originalStatusBarAttributes
paramsnull190             .map { params ->
191                 val requestedVisibleTypes = params?.requestedVisibleTypes ?: return@map false
192                 // When the status bar is not requested visible, we assume we're in fullscreen mode.
193                 requestedVisibleTypes and WindowInsets.Type.statusBars() == 0
194             }
195             .stateIn(scope, SharingStarted.Eagerly, false)
196 
197     /** Modifies the raw [StatusBarAttributes] if letterboxing is needed. */
198     private val modifiedStatusBarAttributes: StateFlow<ModifiedStatusBarAttributes?> =
199         combine(
200                 _originalStatusBarAttributes,
201                 _statusBarBounds,
statusBarBoundsnull202             ) { originalAttributes, statusBarBounds ->
203                 if (originalAttributes == null) {
204                     null
205                 } else {
206                     val (newAppearance, newAppearanceRegions) =
207                         modifyAppearanceIfNeeded(
208                             originalAttributes.appearance,
209                             originalAttributes.appearanceRegions,
210                             originalAttributes.letterboxDetails,
211                             statusBarBounds,
212                         )
213                     ModifiedStatusBarAttributes(
214                         newAppearance,
215                         newAppearanceRegions,
216                         originalAttributes.navbarColorManagedByIme,
217                         statusBarBounds,
218                     )
219                 }
220             }
221             .stateIn(scope, SharingStarted.Eagerly, initialValue = null)
222 
223     override val statusBarAppearance: StateFlow<StatusBarAppearance?> =
224         combine(
225                 modifiedStatusBarAttributes,
226                 isTransientShown,
227                 isInFullscreenMode,
228                 ongoingCallRepository.ongoingCallState,
isTransientShownnull229             ) { modifiedAttributes, isTransientShown, isInFullscreenMode, ongoingCallState ->
230                 if (modifiedAttributes == null) {
231                     null
232                 } else {
233                     val statusBarMode =
234                         toBarMode(
235                             modifiedAttributes.appearance,
236                             isTransientShown,
237                             isInFullscreenMode,
238                             hasOngoingCall = ongoingCallState is OngoingCallModel.InCall,
239                         )
240                     StatusBarAppearance(
241                         statusBarMode,
242                         modifiedAttributes.statusBarBounds,
243                         modifiedAttributes.appearanceRegions,
244                         modifiedAttributes.navbarColorManagedByIme,
245                     )
246                 }
247             }
248             .stateIn(scope, SharingStarted.Eagerly, initialValue = null)
249 
250     override val statusBarMode: StateFlow<StatusBarMode> =
251         statusBarAppearance
<lambda>null252             .map { it?.mode ?: StatusBarMode.TRANSPARENT }
253             .stateIn(scope, SharingStarted.Eagerly, initialValue = StatusBarMode.TRANSPARENT)
254 
toBarModenull255     private fun toBarMode(
256         appearance: Int,
257         isTransientShown: Boolean,
258         isInFullscreenMode: Boolean,
259         hasOngoingCall: Boolean,
260     ): StatusBarMode {
261         return when {
262             hasOngoingCall && isInFullscreenMode -> StatusBarMode.SEMI_TRANSPARENT
263             isTransientShown -> StatusBarMode.SEMI_TRANSPARENT
264             else -> appearance.toBarMode()
265         }
266     }
267 
268     @Appearance
toBarModenull269     private fun Int.toBarMode(): StatusBarMode {
270         val lightsOutOpaque = APPEARANCE_LOW_PROFILE_BARS or APPEARANCE_OPAQUE_STATUS_BARS
271         return when {
272             this and lightsOutOpaque == lightsOutOpaque -> StatusBarMode.LIGHTS_OUT
273             this and APPEARANCE_LOW_PROFILE_BARS != 0 -> StatusBarMode.LIGHTS_OUT_TRANSPARENT
274             this and APPEARANCE_OPAQUE_STATUS_BARS != 0 -> StatusBarMode.OPAQUE
275             this and APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS != 0 -> StatusBarMode.SEMI_TRANSPARENT
276             else -> StatusBarMode.TRANSPARENT
277         }
278     }
279 
showTransientnull280     override fun showTransient() {
281         _isTransientShown.value = true
282     }
283 
clearTransientnull284     override fun clearTransient() {
285         _isTransientShown.value = false
286     }
287 
modifyAppearanceIfNeedednull288     private fun modifyAppearanceIfNeeded(
289         appearance: Int,
290         appearanceRegions: List<AppearanceRegion>,
291         letterboxDetails: List<LetterboxDetails>,
292         statusBarBounds: BoundsPair,
293     ): Pair<Int, List<AppearanceRegion>> =
294         if (shouldUseLetterboxAppearance(letterboxDetails)) {
295             val letterboxAppearance =
296                 letterboxAppearanceCalculator.getLetterboxAppearance(
297                     appearance,
298                     appearanceRegions,
299                     letterboxDetails,
300                     statusBarBounds,
301                 )
302             Pair(letterboxAppearance.appearance, letterboxAppearance.appearanceRegions)
303         } else {
304             Pair(appearance, appearanceRegions)
305         }
306 
shouldUseLetterboxAppearancenull307     private fun shouldUseLetterboxAppearance(letterboxDetails: List<LetterboxDetails>) =
308         letterboxDetails.isNotEmpty()
309 
310     override fun dump(pw: PrintWriter, args: Array<out String>) {
311         pw.println("${_originalStatusBarAttributes.value}")
312         pw.println("${modifiedStatusBarAttributes.value}")
313         pw.println("statusBarMode: ${statusBarMode.value}")
314     }
315 
316     /**
317      * Internal class keeping track of the raw status bar attributes received from the callback.
318      * Should never be exposed.
319      */
320     private data class StatusBarAttributes(
321         @Appearance val appearance: Int,
322         val appearanceRegions: List<AppearanceRegion>,
323         val navbarColorManagedByIme: Boolean,
324         @WindowInsets.Type.InsetsType val requestedVisibleTypes: Int,
325         val letterboxDetails: List<LetterboxDetails>,
326     ) {
toStringnull327         override fun toString(): String {
328             return """
329                 StatusBarAttributes(
330                     appearance=${appearance.toAppearanceString()},
331                     appearanceRegions=$appearanceRegions,
332                     navbarColorManagedByIme=$navbarColorManagedByIme,
333                     requestedVisibleTypes=${requestedVisibleTypes.toWindowInsetsString()},
334                     letterboxDetails=$letterboxDetails
335                     )
336                     """
337                 .trimIndent()
338         }
339     }
340 
341     /**
342      * Internal class keeping track of how [StatusBarAttributes] were transformed into new
343      * attributes based on letterboxing and other factors. Should never be exposed.
344      */
345     private data class ModifiedStatusBarAttributes(
346         @Appearance val appearance: Int,
347         val appearanceRegions: List<AppearanceRegion>,
348         val navbarColorManagedByIme: Boolean,
349         val statusBarBounds: BoundsPair,
350     ) {
toStringnull351         override fun toString(): String {
352             return """
353                 ModifiedStatusBarAttributes(
354                     appearance=${appearance.toAppearanceString()},
355                     appearanceRegions=$appearanceRegions,
356                     navbarColorManagedByIme=$navbarColorManagedByIme,
357                     statusBarBounds=$statusBarBounds
358                     )
359                     """
360                 .trimIndent()
361         }
362     }
363 }
364 
toWindowInsetsStringnull365 private fun @receiver:WindowInsets.Type.InsetsType Int.toWindowInsetsString() =
366     "[${WindowInsets.Type.toString(this).replace(" ", ", ")}]"
367 
368 private fun @receiver:Appearance Int.toAppearanceString() =
369     if (this == 0) {
370         "NONE"
371     } else {
372         ViewDebug.flagsToString(InsetsFlags::class.java, "appearance", this)
373     }
374 
375 @AssistedFactory
376 interface StatusBarModePerDisplayRepositoryFactory {
createnull377     fun create(@Assisted("displayId") displayId: Int): StatusBarModePerDisplayRepositoryImpl
378 }
379