• 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.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