• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2022 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.phone
18 
19 import android.annotation.ColorInt
20 import android.graphics.Rect
21 import android.view.InsetsFlags
22 import android.view.ViewDebug
23 import android.view.WindowInsetsController
24 import android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS
25 import android.view.WindowInsetsController.Appearance
26 import com.android.internal.statusbar.LetterboxDetails
27 import com.android.internal.util.ContrastColorUtil
28 import com.android.internal.view.AppearanceRegion
29 import com.android.systemui.dump.DumpManager
30 import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
31 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
32 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
33 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
34 import java.io.PrintWriter
35 import java.util.Arrays
36 import javax.inject.Inject
37 
38 class LetterboxAppearance(
39     @Appearance val appearance: Int,
40     val appearanceRegions: Array<AppearanceRegion>
41 ) {
42     override fun toString(): String {
43         val appearanceString =
44                 ViewDebug.flagsToString(InsetsFlags::class.java, "appearance", appearance)
45         return "LetterboxAppearance{$appearanceString, ${appearanceRegions.contentToString()}}"
46     }
47 }
48 
49 /**
50  * Responsible for calculating the [Appearance] and [AppearanceRegion] for the status bar when apps
51  * are letterboxed.
52  */
53 @CentralSurfacesScope
54 class LetterboxAppearanceCalculator
55 @Inject
56 constructor(
57     private val lightBarController: LightBarController,
58     private val dumpManager: DumpManager,
59     private val letterboxBackgroundProvider: LetterboxBackgroundProvider,
60 ) : OnStatusBarViewInitializedListener, CentralSurfacesComponent.Startable {
61 
62     private var statusBarBoundsProvider: StatusBarBoundsProvider? = null
63 
startnull64     override fun start() {
65         dumpManager.registerCriticalDumpable(javaClass.simpleName) { pw, _ -> dump(pw) }
66     }
67 
stopnull68     override fun stop() {
69         dumpManager.unregisterDumpable(javaClass.simpleName)
70     }
71 
72     private var lastAppearance: Int? = null
73     private var lastAppearanceRegions: Array<AppearanceRegion>? = null
74     private var lastLetterboxes: Array<LetterboxDetails>? = null
75     private var lastLetterboxAppearance: LetterboxAppearance? = null
76 
getLetterboxAppearancenull77     fun getLetterboxAppearance(
78         @Appearance originalAppearance: Int,
79         originalAppearanceRegions: Array<AppearanceRegion>,
80         letterboxes: Array<LetterboxDetails>
81     ): LetterboxAppearance {
82         lastAppearance = originalAppearance
83         lastAppearanceRegions = originalAppearanceRegions
84         lastLetterboxes = letterboxes
85         return getLetterboxAppearanceInternal(
86                 letterboxes, originalAppearance, originalAppearanceRegions)
87             .also { lastLetterboxAppearance = it }
88     }
89 
getLetterboxAppearanceInternalnull90     private fun getLetterboxAppearanceInternal(
91         letterboxes: Array<LetterboxDetails>,
92         originalAppearance: Int,
93         originalAppearanceRegions: Array<AppearanceRegion>
94     ): LetterboxAppearance {
95         if (isScrimNeeded(letterboxes)) {
96             return originalAppearanceWithScrim(originalAppearance, originalAppearanceRegions)
97         }
98         val appearance = appearanceWithoutScrim(originalAppearance)
99         val appearanceRegions = getAppearanceRegions(originalAppearanceRegions, letterboxes)
100         return LetterboxAppearance(appearance, appearanceRegions.toTypedArray())
101     }
102 
isScrimNeedednull103     private fun isScrimNeeded(letterboxes: Array<LetterboxDetails>): Boolean {
104         if (isOuterLetterboxMultiColored()) {
105             return true
106         }
107         return letterboxes.any { letterbox ->
108             letterbox.letterboxInnerBounds.overlapsWith(getStartSideIconBounds()) ||
109                 letterbox.letterboxInnerBounds.overlapsWith(getEndSideIconsBounds())
110         }
111     }
112 
getAppearanceRegionsnull113     private fun getAppearanceRegions(
114         originalAppearanceRegions: Array<AppearanceRegion>,
115         letterboxes: Array<LetterboxDetails>
116     ): List<AppearanceRegion> {
117         return sanitizeAppearanceRegions(originalAppearanceRegions, letterboxes) +
118             getAllOuterAppearanceRegions(letterboxes)
119     }
120 
sanitizeAppearanceRegionsnull121     private fun sanitizeAppearanceRegions(
122         originalAppearanceRegions: Array<AppearanceRegion>,
123         letterboxes: Array<LetterboxDetails>
124     ): List<AppearanceRegion> =
125         originalAppearanceRegions.map { appearanceRegion ->
126             val matchingLetterbox =
127                 letterboxes.find { it.letterboxFullBounds == appearanceRegion.bounds }
128             if (matchingLetterbox == null) {
129                 appearanceRegion
130             } else {
131                 // When WindowManager sends appearance regions for an app, it sends them for the
132                 // full bounds of its window.
133                 // Here we want the bounds to be only for the inner bounds of the letterboxed app.
134                 AppearanceRegion(
135                     appearanceRegion.appearance, matchingLetterbox.letterboxInnerBounds)
136             }
137         }
138 
originalAppearanceWithScrimnull139     private fun originalAppearanceWithScrim(
140         @Appearance originalAppearance: Int,
141         originalAppearanceRegions: Array<AppearanceRegion>
142     ): LetterboxAppearance {
143         return LetterboxAppearance(
144             originalAppearance or APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS,
145             originalAppearanceRegions)
146     }
147 
148     @Appearance
appearanceWithoutScrimnull149     private fun appearanceWithoutScrim(@Appearance originalAppearance: Int): Int =
150         originalAppearance and APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS.inv()
151 
152     private fun getAllOuterAppearanceRegions(
153         letterboxes: Array<LetterboxDetails>
154     ): List<AppearanceRegion> = letterboxes.map(this::getOuterAppearanceRegions).flatten()
155 
156     private fun getOuterAppearanceRegions(
157         letterboxDetails: LetterboxDetails
158     ): List<AppearanceRegion> {
159         @Appearance val outerAppearance = getOuterAppearance()
160         return getVisibleOuterBounds(letterboxDetails).map { bounds ->
161             AppearanceRegion(outerAppearance, bounds)
162         }
163     }
164 
getVisibleOuterBoundsnull165     private fun getVisibleOuterBounds(letterboxDetails: LetterboxDetails): List<Rect> {
166         val inner = letterboxDetails.letterboxInnerBounds
167         val outer = letterboxDetails.letterboxFullBounds
168         val top = Rect(outer.left, outer.top, outer.right, inner.top)
169         val left = Rect(outer.left, outer.top, inner.left, outer.bottom)
170         val right = Rect(inner.right, outer.top, outer.right, outer.bottom)
171         val bottom = Rect(outer.left, inner.bottom, outer.right, outer.bottom)
172         return listOf(left, top, right, bottom).filter { !it.isEmpty }
173     }
174 
175     @Appearance
getOuterAppearancenull176     private fun getOuterAppearance(): Int {
177         val backgroundColor = outerLetterboxBackgroundColor()
178         val darkAppearanceContrast =
179             ContrastColorUtil.calculateContrast(
180                 lightBarController.darkAppearanceIconColor, backgroundColor)
181         val lightAppearanceContrast =
182             ContrastColorUtil.calculateContrast(
183                 lightBarController.lightAppearanceIconColor, backgroundColor)
184         return if (lightAppearanceContrast > darkAppearanceContrast) {
185             WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
186         } else {
187             0 // APPEARANCE_DEFAULT
188         }
189     }
190 
191     @ColorInt
outerLetterboxBackgroundColornull192     private fun outerLetterboxBackgroundColor(): Int {
193         return letterboxBackgroundProvider.letterboxBackgroundColor
194     }
195 
isOuterLetterboxMultiColorednull196     private fun isOuterLetterboxMultiColored(): Boolean {
197         return letterboxBackgroundProvider.isLetterboxBackgroundMultiColored
198     }
199 
getEndSideIconsBoundsnull200     private fun getEndSideIconsBounds(): Rect {
201         return statusBarBoundsProvider?.visibleEndSideBounds ?: Rect()
202     }
203 
getStartSideIconBoundsnull204     private fun getStartSideIconBounds(): Rect {
205         return statusBarBoundsProvider?.visibleStartSideBounds ?: Rect()
206     }
207 
onStatusBarViewInitializednull208     override fun onStatusBarViewInitialized(component: StatusBarFragmentComponent) {
209         statusBarBoundsProvider = component.boundsProvider
210     }
211 
Rectnull212     private fun Rect.overlapsWith(other: Rect): Boolean {
213         if (this.contains(other) || other.contains(this)) {
214             return false
215         }
216         return this.intersect(other)
217     }
218 
dumpnull219     private fun dump(printWriter: PrintWriter) {
220         printWriter.println(
221             """
222            lastAppearance: ${lastAppearance?.toAppearanceString()}
223            lastAppearanceRegion: ${Arrays.toString(lastAppearanceRegions)},
224            lastLetterboxes: ${Arrays.toString(lastLetterboxes)},
225            lastLetterboxAppearance: $lastLetterboxAppearance
226        """.trimIndent())
227     }
228 }
229 
toAppearanceStringnull230 private fun Int.toAppearanceString(): String =
231     ViewDebug.flagsToString(InsetsFlags::class.java, "appearance", this)
232