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.content.Context
21 import android.graphics.Rect
22 import android.view.InsetsFlags
23 import android.view.ViewDebug
24 import android.view.WindowInsetsController
25 import android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS
26 import android.view.WindowInsetsController.Appearance
27 import com.android.internal.statusbar.LetterboxDetails
28 import com.android.internal.util.ContrastColorUtil
29 import com.android.internal.view.AppearanceRegion
30 import com.android.systemui.Dumpable
31 import com.android.systemui.dagger.SysUISingleton
32 import com.android.systemui.dump.DumpManager
33 import java.io.PrintWriter
34 import javax.inject.Inject
35
36 data class LetterboxAppearance(
37 @Appearance val appearance: Int,
38 val appearanceRegions: List<AppearanceRegion>,
39 ) {
40 override fun toString(): String {
41 val appearanceString =
42 ViewDebug.flagsToString(InsetsFlags::class.java, "appearance", appearance)
43 return "LetterboxAppearance{$appearanceString, $appearanceRegions}"
44 }
45 }
46
47 /**
48 * Responsible for calculating the [Appearance] and [AppearanceRegion] for the status bar when apps
49 * are letterboxed.
50 */
51 @SysUISingleton
52 class LetterboxAppearanceCalculator
53 @Inject
54 constructor(
55 context: Context,
56 dumpManager: DumpManager,
57 private val letterboxBackgroundProvider: LetterboxBackgroundProvider,
58 ) : Dumpable {
59
60 private val darkAppearanceIconColor = context.getColor(
61 // For a dark background status bar, use a *light* icon color.
62 com.android.settingslib.R.color.light_mode_icon_color_single_tone
63 )
64 private val lightAppearanceIconColor = context.getColor(
65 // For a light background status bar, use a *dark* icon color.
66 com.android.settingslib.R.color.dark_mode_icon_color_single_tone
67 )
68
69 init {
70 dumpManager.registerCriticalDumpable(this)
71 }
72
73 private var lastAppearance: Int? = null
74 private var lastAppearanceRegions: List<AppearanceRegion>? = null
75 private var lastLetterboxes: List<LetterboxDetails>? = null
76 private var lastLetterboxAppearance: LetterboxAppearance? = null
77
getLetterboxAppearancenull78 fun getLetterboxAppearance(
79 @Appearance originalAppearance: Int,
80 originalAppearanceRegions: List<AppearanceRegion>,
81 letterboxes: List<LetterboxDetails>,
82 statusBarBounds: BoundsPair,
83 ): LetterboxAppearance {
84 lastAppearance = originalAppearance
85 lastAppearanceRegions = originalAppearanceRegions
86 lastLetterboxes = letterboxes
87 return getLetterboxAppearanceInternal(
88 letterboxes, originalAppearance, originalAppearanceRegions, statusBarBounds)
89 .also { lastLetterboxAppearance = it }
90 }
91
getLetterboxAppearanceInternalnull92 private fun getLetterboxAppearanceInternal(
93 letterboxes: List<LetterboxDetails>,
94 originalAppearance: Int,
95 originalAppearanceRegions: List<AppearanceRegion>,
96 statusBarBounds: BoundsPair,
97 ): LetterboxAppearance {
98 if (isScrimNeeded(letterboxes, statusBarBounds)) {
99 return originalAppearanceWithScrim(originalAppearance, originalAppearanceRegions)
100 }
101 val appearance = appearanceWithoutScrim(originalAppearance)
102 val appearanceRegions = getAppearanceRegions(originalAppearanceRegions, letterboxes)
103 return LetterboxAppearance(appearance, appearanceRegions)
104 }
105
isScrimNeedednull106 private fun isScrimNeeded(
107 letterboxes: List<LetterboxDetails>,
108 statusBarBounds: BoundsPair,
109 ): Boolean {
110 if (isOuterLetterboxMultiColored()) {
111 return true
112 }
113 return letterboxes.any { letterbox ->
114 letterbox.letterboxInnerBounds.overlapsWith(statusBarBounds.start) ||
115 letterbox.letterboxInnerBounds.overlapsWith(statusBarBounds.end)
116 }
117 }
118
getAppearanceRegionsnull119 private fun getAppearanceRegions(
120 originalAppearanceRegions: List<AppearanceRegion>,
121 letterboxes: List<LetterboxDetails>
122 ): List<AppearanceRegion> {
123 return sanitizeAppearanceRegions(originalAppearanceRegions, letterboxes) +
124 getAllOuterAppearanceRegions(letterboxes)
125 }
126
sanitizeAppearanceRegionsnull127 private fun sanitizeAppearanceRegions(
128 originalAppearanceRegions: List<AppearanceRegion>,
129 letterboxes: List<LetterboxDetails>
130 ): List<AppearanceRegion> =
131 originalAppearanceRegions.map { appearanceRegion ->
132 val matchingLetterbox =
133 letterboxes.find { it.letterboxFullBounds == appearanceRegion.bounds }
134 if (matchingLetterbox == null) {
135 appearanceRegion
136 } else {
137 // When WindowManager sends appearance regions for an app, it sends them for the
138 // full bounds of its window.
139 // Here we want the bounds to be only for the inner bounds of the letterboxed app.
140 AppearanceRegion(
141 appearanceRegion.appearance, matchingLetterbox.letterboxInnerBounds)
142 }
143 }
144
originalAppearanceWithScrimnull145 private fun originalAppearanceWithScrim(
146 @Appearance originalAppearance: Int,
147 originalAppearanceRegions: List<AppearanceRegion>
148 ): LetterboxAppearance {
149 return LetterboxAppearance(
150 originalAppearance or APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS,
151 originalAppearanceRegions)
152 }
153
154 @Appearance
appearanceWithoutScrimnull155 private fun appearanceWithoutScrim(@Appearance originalAppearance: Int): Int =
156 originalAppearance and APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS.inv()
157
158 private fun getAllOuterAppearanceRegions(
159 letterboxes: List<LetterboxDetails>
160 ): List<AppearanceRegion> = letterboxes.map(this::getOuterAppearanceRegions).flatten()
161
162 private fun getOuterAppearanceRegions(
163 letterboxDetails: LetterboxDetails
164 ): List<AppearanceRegion> {
165 @Appearance val outerAppearance = getOuterAppearance()
166 return getVisibleOuterBounds(letterboxDetails).map { bounds ->
167 AppearanceRegion(outerAppearance, bounds)
168 }
169 }
170
getVisibleOuterBoundsnull171 private fun getVisibleOuterBounds(letterboxDetails: LetterboxDetails): List<Rect> {
172 val inner = letterboxDetails.letterboxInnerBounds
173 val outer = letterboxDetails.letterboxFullBounds
174 val top = Rect(outer.left, outer.top, outer.right, inner.top)
175 val left = Rect(outer.left, outer.top, inner.left, outer.bottom)
176 val right = Rect(inner.right, outer.top, outer.right, outer.bottom)
177 val bottom = Rect(outer.left, inner.bottom, outer.right, outer.bottom)
178 return listOf(left, top, right, bottom).filter { !it.isEmpty }
179 }
180
181 @Appearance
getOuterAppearancenull182 private fun getOuterAppearance(): Int {
183 val backgroundColor = outerLetterboxBackgroundColor()
184 val darkAppearanceContrast =
185 ContrastColorUtil.calculateContrast(darkAppearanceIconColor, backgroundColor)
186 val lightAppearanceContrast =
187 ContrastColorUtil.calculateContrast(lightAppearanceIconColor, backgroundColor)
188 return if (lightAppearanceContrast > darkAppearanceContrast) {
189 WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
190 } else {
191 0 // APPEARANCE_DEFAULT
192 }
193 }
194
195 @ColorInt
outerLetterboxBackgroundColornull196 private fun outerLetterboxBackgroundColor(): Int {
197 return letterboxBackgroundProvider.letterboxBackgroundColor
198 }
199
isOuterLetterboxMultiColorednull200 private fun isOuterLetterboxMultiColored(): Boolean {
201 return letterboxBackgroundProvider.isLetterboxBackgroundMultiColored
202 }
203
Rectnull204 private fun Rect.overlapsWith(other: Rect): Boolean {
205 if (this.contains(other) || other.contains(this)) {
206 return false
207 }
208 return this.intersects(other.left, other.top, other.right, other.bottom)
209 }
210
dumpnull211 override fun dump(pw: PrintWriter, args: Array<out String>) {
212 pw.println(
213 """
214 lastAppearance: ${lastAppearance?.toAppearanceString()}
215 lastAppearanceRegion: $lastAppearanceRegions,
216 lastLetterboxes: $lastLetterboxes,
217 lastLetterboxAppearance: $lastLetterboxAppearance
218 """.trimIndent())
219 }
220 }
221
toAppearanceStringnull222 private fun Int.toAppearanceString(): String =
223 ViewDebug.flagsToString(InsetsFlags::class.java, "appearance", this)
224