• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2021 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.layout
18 
19 import android.annotation.Px
20 import android.content.Context
21 import android.content.res.Resources
22 import android.graphics.Insets
23 import android.graphics.Point
24 import android.graphics.Rect
25 import android.util.LruCache
26 import android.util.Pair
27 import android.view.Display.DEFAULT_DISPLAY
28 import android.view.DisplayCutout
29 import android.view.Surface
30 import androidx.annotation.VisibleForTesting
31 import com.android.app.tracing.traceSection
32 import com.android.internal.policy.SystemBarUtils
33 import com.android.systemui.BottomMarginCommand
34 import com.android.systemui.Dumpable
35 import com.android.systemui.StatusBarInsetsCommand
36 import com.android.systemui.SysUICutoutInformation
37 import com.android.systemui.SysUICutoutProvider
38 import com.android.systemui.dump.DumpManager
39 import com.android.systemui.res.R
40 import com.android.systemui.statusbar.commandline.CommandRegistry
41 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
42 import com.android.systemui.statusbar.policy.CallbackController
43 import com.android.systemui.statusbar.policy.ConfigurationController
44 import com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE
45 import com.android.systemui.util.leak.RotationUtils.ROTATION_NONE
46 import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE
47 import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN
48 import com.android.systemui.util.leak.RotationUtils.Rotation
49 import com.android.systemui.util.leak.RotationUtils.getExactRotation
50 import com.android.systemui.util.leak.RotationUtils.getResourcesForRotation
51 import dagger.assisted.Assisted
52 import dagger.assisted.AssistedFactory
53 import dagger.assisted.AssistedInject
54 import java.io.PrintWriter
55 import java.lang.Math.max
56 import java.util.concurrent.CopyOnWriteArraySet
57 
58 /**
59  * Encapsulates logic that can solve for the left/right insets required for the status bar contents.
60  * Takes into account:
61  * 1. rounded_corner_content_padding
62  * 2. status_bar_padding_start, status_bar_padding_end
63  * 2. display cutout insets from left or right
64  * 3. waterfall insets
65  *
66  * Importantly, these functions can determine status bar content left/right insets for any rotation
67  * before having done a layout pass in that rotation.
68  *
69  * NOTE: This class is not threadsafe
70  */
71 interface StatusBarContentInsetsProvider :
72     CallbackController<StatusBarContentInsetsChangedListener> {
73 
74     /**
75      * Called when the [StatusBarContentInsetsProvider] should start doing its work and allocate its
76      * resources.
77      */
78     fun start()
79 
80     /**
81      * Called when the [StatusBarContentInsetsProvider] should stop and do any required clean up.
82      */
83     fun stop()
84 
85     /**
86      * Some views may need to care about whether or not the current top display cutout is located in
87      * the corner rather than somewhere in the center. In the case of a corner cutout, the status
88      * bar area is contiguous.
89      */
90     fun currentRotationHasCornerCutout(): Boolean
91 
92     /**
93      * Calculates the maximum bounding rectangle for the privacy chip animation + ongoing privacy
94      * dot in the coordinates relative to the given rotation.
95      *
96      * @param rotation the rotation for which the bounds are required. This is an absolute value
97      *   (i.e., ROTATION_NONE will always return the same bounds regardless of the context from
98      *   which this method is called)
99      */
100     fun getBoundingRectForPrivacyChipForRotation(
101         @Rotation rotation: Int,
102         displayCutout: DisplayCutout?,
103     ): Rect
104 
105     /**
106      * Calculate the distance from the left, right and top edges of the screen to the status bar
107      * content area. This differs from the content area rects in that these values can be used
108      * directly as padding.
109      *
110      * @param rotation the target rotation for which to calculate insets
111      */
112     fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Insets
113 
114     /**
115      * Calculate the insets for the status bar content in the device's current rotation
116      *
117      * @see getStatusBarContentAreaForRotation
118      */
119     fun getStatusBarContentInsetsForCurrentRotation(): Insets
120 
121     /**
122      * Calculates the area of the status bar contents invariant of the current device rotation, in
123      * the target rotation's coordinates
124      *
125      * @param rotation the rotation for which the bounds are required. This is an absolute value
126      *   (i.e., ROTATION_NONE will always return the same bounds regardless of the context from
127      *   which this method is called)
128      */
129     fun getStatusBarContentAreaForRotation(@Rotation rotation: Int): Rect
130 
131     /** Get the status bar content area for the given rotation, in absolute bounds */
132     fun getStatusBarContentAreaForCurrentRotation(): Rect
133 
134     fun getStatusBarPaddingTop(@Rotation rotation: Int? = null): Int
135 
136     interface Factory {
137         fun create(
138             context: Context,
139             configurationController: ConfigurationController,
140             sysUICutoutProvider: SysUICutoutProvider,
141         ): StatusBarContentInsetsProvider
142     }
143 }
144 
145 class StatusBarContentInsetsProviderImpl
146 @AssistedInject
147 constructor(
148     @Assisted val context: Context,
149     @Assisted val configurationController: ConfigurationController,
150     val dumpManager: DumpManager,
151     val commandRegistry: CommandRegistry,
152     @Assisted val sysUICutoutProvider: SysUICutoutProvider,
153 ) : StatusBarContentInsetsProvider, ConfigurationController.ConfigurationListener, Dumpable {
154 
155     @AssistedFactory
156     interface Factory : StatusBarContentInsetsProvider.Factory {
createnull157         override fun create(
158             context: Context,
159             configurationController: ConfigurationController,
160             sysUICutoutProvider: SysUICutoutProvider,
161         ): StatusBarContentInsetsProviderImpl
162     }
163 
164     // Limit cache size as potentially we may connect large number of displays
165     // (e.g. network displays)
166     private val insetsCache = LruCache<CacheKey, Rect>(MAX_CACHE_SIZE)
167     private val listeners = CopyOnWriteArraySet<StatusBarContentInsetsChangedListener>()
168     private val isPrivacyDotEnabled: Boolean by
169         lazy(LazyThreadSafetyMode.PUBLICATION) {
170             context.resources.getBoolean(R.bool.config_enablePrivacyDot)
171         }
172 
173     private val nameSuffix =
174         if (context.displayId == DEFAULT_DISPLAY) "" else context.displayId.toString()
175     private val dumpableName = TAG + nameSuffix
176     private val commandName = StatusBarInsetsCommand.NAME + nameSuffix
177 
178     init {
179         if (!StatusBarConnectedDisplays.isEnabled) {
180             // Call start(), since it is not called when the flag is disabled, to keep the old
181             // behavior as it was.
182             start()
183         }
184     }
185 
startnull186     override fun start() {
187         configurationController.addCallback(this)
188         dumpManager.registerNormalDumpable(dumpableName, this)
189         commandRegistry.registerCommand(commandName) {
190             StatusBarInsetsCommand(
191                 object : StatusBarInsetsCommand.Callback {
192                     override fun onExecute(
193                         command: StatusBarInsetsCommand,
194                         printWriter: PrintWriter,
195                     ) {
196                         executeCommand(command, printWriter)
197                     }
198                 }
199             )
200         }
201     }
202 
stopnull203     override fun stop() {
204         StatusBarConnectedDisplays.unsafeAssertInNewMode()
205         configurationController.removeCallback(this)
206         dumpManager.unregisterDumpable(dumpableName)
207         commandRegistry.unregisterCommand(commandName)
208     }
209 
addCallbacknull210     override fun addCallback(listener: StatusBarContentInsetsChangedListener) {
211         listeners.add(listener)
212     }
213 
removeCallbacknull214     override fun removeCallback(listener: StatusBarContentInsetsChangedListener) {
215         listeners.remove(listener)
216     }
217 
onDensityOrFontScaleChangednull218     override fun onDensityOrFontScaleChanged() {
219         clearCachedInsets()
220     }
221 
onThemeChangednull222     override fun onThemeChanged() {
223         clearCachedInsets()
224     }
225 
onMaxBoundsChangednull226     override fun onMaxBoundsChanged() {
227         notifyInsetsChanged()
228     }
229 
clearCachedInsetsnull230     private fun clearCachedInsets() {
231         insetsCache.evictAll()
232         notifyInsetsChanged()
233     }
234 
notifyInsetsChangednull235     private fun notifyInsetsChanged() {
236         listeners.forEach { it.onStatusBarContentInsetsChanged() }
237     }
238 
currentRotationHasCornerCutoutnull239     override fun currentRotationHasCornerCutout(): Boolean {
240         val cutout = checkNotNull(context.display).cutout ?: return false
241         val topBounds = cutout.boundingRectTop
242 
243         val point = Point()
244         checkNotNull(context.display).getRealSize(point)
245 
246         return topBounds.left <= 0 || topBounds.right >= point.x
247     }
248 
getBoundingRectForPrivacyChipForRotationnull249     override fun getBoundingRectForPrivacyChipForRotation(
250         @Rotation rotation: Int,
251         displayCutout: DisplayCutout?,
252     ): Rect {
253         val key = getCacheKey(rotation, displayCutout)
254         var insets = insetsCache[key]
255         if (insets == null) {
256             insets = getStatusBarContentAreaForRotation(rotation)
257         }
258 
259         val rotatedResources = getResourcesForRotation(rotation, context)
260 
261         val dotWidth = rotatedResources.getDimensionPixelSize(R.dimen.ongoing_appops_dot_diameter)
262         val chipWidth =
263             rotatedResources.getDimensionPixelSize(R.dimen.ongoing_appops_chip_max_width)
264 
265         val isRtl = configurationController.isLayoutRtl
266         return getPrivacyChipBoundingRectForInsets(insets, dotWidth, chipWidth, isRtl)
267     }
268 
getStatusBarContentInsetsForRotationnull269     override fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Insets =
270         traceSection(tag = "StatusBarContentInsetsProvider.getStatusBarContentInsetsForRotation") {
271             val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplayAndRotation()
272             val displayCutout = sysUICutout?.cutout
273             val key = getCacheKey(rotation, displayCutout)
274 
275             val screenBounds = context.resources.configuration.windowConfiguration.maxBounds
276             val point = Point(screenBounds.width(), screenBounds.height())
277 
278             // Target rotation can be a different orientation than the current device rotation
279             point.orientToRotZero(getExactRotation(context))
280             val width = point.logicalWidth(rotation)
281 
282             val area =
283                 insetsCache[key]
284                     ?: getAndSetCalculatedAreaForRotation(
285                         rotation,
286                         sysUICutout,
287                         getResourcesForRotation(rotation, context),
288                         key,
289                     )
290 
291             Insets.of(area.left, area.top, /* right= */ width - area.right, /* bottom= */ 0)
292         }
293 
getStatusBarContentInsetsForCurrentRotationnull294     override fun getStatusBarContentInsetsForCurrentRotation(): Insets {
295         return getStatusBarContentInsetsForRotation(getExactRotation(context))
296     }
297 
getStatusBarContentAreaForRotationnull298     override fun getStatusBarContentAreaForRotation(@Rotation rotation: Int): Rect {
299         val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplayAndRotation()
300         val displayCutout = sysUICutout?.cutout
301         val key = getCacheKey(rotation, displayCutout)
302         return insetsCache[key]
303             ?: getAndSetCalculatedAreaForRotation(
304                 rotation,
305                 sysUICutout,
306                 getResourcesForRotation(rotation, context),
307                 key,
308             )
309     }
310 
getStatusBarContentAreaForCurrentRotationnull311     override fun getStatusBarContentAreaForCurrentRotation(): Rect {
312         val rotation = getExactRotation(context)
313         return getStatusBarContentAreaForRotation(rotation)
314     }
315 
getAndSetCalculatedAreaForRotationnull316     private fun getAndSetCalculatedAreaForRotation(
317         @Rotation targetRotation: Int,
318         sysUICutout: SysUICutoutInformation?,
319         rotatedResources: Resources,
320         key: CacheKey,
321     ): Rect {
322         return getCalculatedAreaForRotation(sysUICutout, targetRotation, rotatedResources).also {
323             insetsCache.put(key, it)
324         }
325     }
326 
getCalculatedAreaForRotationnull327     private fun getCalculatedAreaForRotation(
328         sysUICutout: SysUICutoutInformation?,
329         @Rotation targetRotation: Int,
330         rotatedResources: Resources,
331     ): Rect {
332         val currentRotation = getExactRotation(context)
333 
334         val roundedCornerPadding =
335             rotatedResources.getDimensionPixelSize(R.dimen.rounded_corner_content_padding)
336         val minDotPadding =
337             if (isPrivacyDotEnabled)
338                 rotatedResources.getDimensionPixelSize(R.dimen.ongoing_appops_dot_min_padding)
339             else 0
340         val dotWidth =
341             if (isPrivacyDotEnabled)
342                 rotatedResources.getDimensionPixelSize(R.dimen.ongoing_appops_dot_diameter)
343             else 0
344 
345         val minLeft: Int
346         val minRight: Int
347         if (configurationController.isLayoutRtl) {
348             minLeft = max(minDotPadding, roundedCornerPadding)
349             minRight = roundedCornerPadding
350         } else {
351             minLeft = roundedCornerPadding
352             minRight = max(minDotPadding, roundedCornerPadding)
353         }
354 
355         val bottomAlignedMargin = getBottomAlignedMargin(targetRotation, rotatedResources)
356         val statusBarContentHeight =
357             rotatedResources.getDimensionPixelSize(R.dimen.status_bar_icon_size_sp)
358 
359         return calculateInsetsForRotationWithRotatedResources(
360             currentRotation,
361             targetRotation,
362             sysUICutout,
363             context.resources.configuration.windowConfiguration.maxBounds,
364             SystemBarUtils.getStatusBarHeightForRotation(context, targetRotation),
365             minLeft,
366             minRight,
367             configurationController.isLayoutRtl,
368             dotWidth,
369             bottomAlignedMargin,
370             statusBarContentHeight,
371         )
372     }
373 
executeCommandnull374     private fun executeCommand(command: StatusBarInsetsCommand, printWriter: PrintWriter) {
375         command.bottomMargin?.let { executeBottomMarginCommand(it, printWriter) }
376     }
377 
executeBottomMarginCommandnull378     private fun executeBottomMarginCommand(command: BottomMarginCommand, printWriter: PrintWriter) {
379         val rotation = command.rotationValue
380         if (rotation == null) {
381             printWriter.println(
382                 "Rotation should be one of ${BottomMarginCommand.ROTATION_DEGREES_OPTIONS}"
383             )
384             return
385         }
386         val marginBottomDp = command.marginBottomDp
387         if (marginBottomDp == null) {
388             printWriter.println("Margin bottom not set.")
389             return
390         }
391         setBottomMarginOverride(rotation, marginBottomDp)
392     }
393 
394     private val marginBottomOverrides = mutableMapOf<Int, Int>()
395 
setBottomMarginOverridenull396     private fun setBottomMarginOverride(rotation: Int, marginBottomDp: Float) {
397         insetsCache.evictAll()
398         val marginBottomPx = (marginBottomDp * context.resources.displayMetrics.density).toInt()
399         marginBottomOverrides[rotation] = marginBottomPx
400         notifyInsetsChanged()
401     }
402 
403     @Px
getBottomAlignedMarginnull404     private fun getBottomAlignedMargin(targetRotation: Int, resources: Resources): Int {
405         val override = marginBottomOverrides[targetRotation]
406         if (override != null) {
407             return override
408         }
409         val dimenRes =
410             when (targetRotation) {
411                 Surface.ROTATION_0 -> R.dimen.status_bar_bottom_aligned_margin_rotation_0
412                 Surface.ROTATION_90 -> R.dimen.status_bar_bottom_aligned_margin_rotation_90
413                 Surface.ROTATION_180 -> R.dimen.status_bar_bottom_aligned_margin_rotation_180
414                 Surface.ROTATION_270 -> R.dimen.status_bar_bottom_aligned_margin_rotation_270
415                 else -> throw IllegalStateException("Unknown rotation: $targetRotation")
416             }
417         return resources.getDimensionPixelSize(dimenRes)
418     }
419 
getStatusBarPaddingTopnull420     override fun getStatusBarPaddingTop(@Rotation rotation: Int?): Int {
421         val res = rotation?.let { it -> getResourcesForRotation(it, context) } ?: context.resources
422         return res.getDimensionPixelSize(R.dimen.status_bar_padding_top)
423     }
424 
dumpnull425     override fun dump(pw: PrintWriter, args: Array<out String>) {
426         insetsCache.snapshot().forEach { (key, rect) -> pw.println("$key -> $rect") }
427         pw.println(insetsCache)
428         pw.println("Bottom margin overrides: $marginBottomOverrides")
429     }
430 
getCacheKeynull431     private fun getCacheKey(@Rotation rotation: Int, displayCutout: DisplayCutout?): CacheKey =
432         CacheKey(
433             rotation = rotation,
434             displaySize = Rect(context.resources.configuration.windowConfiguration.maxBounds),
435             displayCutout = displayCutout,
436         )
437 
438     private data class CacheKey(
439         @Rotation val rotation: Int,
440         val displaySize: Rect,
441         val displayCutout: DisplayCutout?,
442     )
443 }
444 
445 interface StatusBarContentInsetsChangedListener {
446     fun onStatusBarContentInsetsChanged()
447 }
448 
449 private const val TAG = "StatusBarInsetsProvider"
450 private const val MAX_CACHE_SIZE = 16
451 
getRotationZeroDisplayBoundsnull452 private fun getRotationZeroDisplayBounds(bounds: Rect, @Rotation exactRotation: Int): Rect {
453     if (exactRotation == ROTATION_NONE || exactRotation == ROTATION_UPSIDE_DOWN) {
454         return bounds
455     }
456 
457     // bounds are horizontal, swap height and width
458     return Rect(0, 0, bounds.bottom, bounds.right)
459 }
460 
461 @VisibleForTesting
getPrivacyChipBoundingRectForInsetsnull462 fun getPrivacyChipBoundingRectForInsets(
463     contentRect: Rect,
464     dotWidth: Int,
465     chipWidth: Int,
466     isRtl: Boolean,
467 ): Rect {
468     return if (isRtl) {
469         Rect(
470             contentRect.left - dotWidth,
471             contentRect.top,
472             contentRect.left + chipWidth,
473             contentRect.bottom,
474         )
475     } else {
476         Rect(
477             contentRect.right - chipWidth,
478             contentRect.top,
479             contentRect.right + dotWidth,
480             contentRect.bottom,
481         )
482     }
483 }
484 
485 /**
486  * Calculates the exact left and right positions for the status bar contents for the given rotation
487  *
488  * @param currentRotation current device rotation
489  * @param targetRotation rotation for which to calculate the status bar content rect
490  * @param displayCutout [DisplayCutout] for the current display. possibly null
491  * @param maxBounds the display bounds in our current rotation
492  * @param statusBarHeight height of the status bar for the target rotation
493  * @param minLeft the minimum padding to enforce on the left
494  * @param minRight the minimum padding to enforce on the right
495  * @param isRtl current layout direction is Right-To-Left or not
496  * @param dotWidth privacy dot image width (0 if privacy dot is disabled)
497  * @param bottomAlignedMargin the bottom margin that the status bar content should have. -1 if none,
498  *   and content should be centered vertically.
499  * @param statusBarContentHeight the height of the status bar contents (icons, text, etc)
500  * @see [RotationUtils#getResourcesForRotation]
501  */
502 @VisibleForTesting
calculateInsetsForRotationWithRotatedResourcesnull503 fun calculateInsetsForRotationWithRotatedResources(
504     @Rotation currentRotation: Int,
505     @Rotation targetRotation: Int,
506     sysUICutout: SysUICutoutInformation?,
507     maxBounds: Rect,
508     statusBarHeight: Int,
509     minLeft: Int,
510     minRight: Int,
511     isRtl: Boolean,
512     dotWidth: Int,
513     bottomAlignedMargin: Int,
514     statusBarContentHeight: Int,
515 ): Rect {
516     /*
517     TODO: Check if this is ever used for devices with no rounded corners
518     val left = if (isRtl) paddingEnd else paddingStart
519     val right = if (isRtl) paddingStart else paddingEnd
520      */
521 
522     val rotZeroBounds = getRotationZeroDisplayBounds(maxBounds, currentRotation)
523 
524     return getStatusBarContentBounds(
525         sysUICutout,
526         statusBarHeight,
527         rotZeroBounds.right,
528         rotZeroBounds.bottom,
529         maxBounds.width(),
530         maxBounds.height(),
531         minLeft,
532         minRight,
533         isRtl,
534         dotWidth,
535         targetRotation,
536         currentRotation,
537         bottomAlignedMargin,
538         statusBarContentHeight,
539     )
540 }
541 
542 /**
543  * Calculate the insets needed from the left and right edges for the given rotation.
544  *
545  * @param displayCutout Device display cutout
546  * @param sbHeight appropriate status bar height for this rotation
547  * @param width display width calculated for ROTATION_NONE
548  * @param height display height calculated for ROTATION_NONE
549  * @param cWidth display width in our current rotation
550  * @param cHeight display height in our current rotation
551  * @param minLeft the minimum padding to enforce on the left
552  * @param minRight the minimum padding to enforce on the right
553  * @param isRtl current layout direction is Right-To-Left or not
554  * @param dotWidth privacy dot image width (0 if privacy dot is disabled)
555  * @param targetRotation the rotation for which to calculate margins
556  * @param currentRotation the rotation from which the display cutout was generated
557  * @return a Rect which exactly calculates the Status Bar's content rect relative to the target
558  *   rotation
559  */
getStatusBarContentBoundsnull560 private fun getStatusBarContentBounds(
561     sysUICutout: SysUICutoutInformation?,
562     sbHeight: Int,
563     width: Int,
564     height: Int,
565     cWidth: Int,
566     cHeight: Int,
567     minLeft: Int,
568     minRight: Int,
569     isRtl: Boolean,
570     dotWidth: Int,
571     @Rotation targetRotation: Int,
572     @Rotation currentRotation: Int,
573     bottomAlignedMargin: Int,
574     statusBarContentHeight: Int,
575 ): Rect {
576     val insetTop = getInsetTop(bottomAlignedMargin, statusBarContentHeight, sbHeight)
577 
578     val logicalDisplayWidth = if (targetRotation.isHorizontal()) height else width
579 
580     // Exclude the bottom rect, as it doesn't intersect with the status bar.
581     val cutoutRects = sysUICutout?.cutout?.boundingRectsLeftRightTop
582     if (cutoutRects.isNullOrEmpty()) {
583         return Rect(minLeft, insetTop, logicalDisplayWidth - minRight, sbHeight)
584     }
585 
586     val relativeRotation =
587         if (currentRotation - targetRotation < 0) {
588             currentRotation - targetRotation + 4
589         } else {
590             currentRotation - targetRotation
591         }
592 
593     // Size of the status bar window for the given rotation relative to our exact rotation
594     val sbRect = sbRect(relativeRotation, sbHeight, Pair(cWidth, cHeight))
595 
596     var leftMargin = minLeft
597     var rightMargin = minRight
598     for (cutoutRect in cutoutRects) {
599         val protectionRect = sysUICutout.cameraProtection?.bounds
600         val actualCutoutRect =
601             if (protectionRect?.intersects(cutoutRect) == true) {
602                 rectUnion(cutoutRect, protectionRect)
603             } else {
604                 cutoutRect
605             }
606         // There is at most one non-functional area per short edge of the device. So if the status
607         // bar doesn't share a short edge with the cutout, we can ignore its insets because there
608         // will be no letter-boxing to worry about
609         if (!shareShortEdge(sbRect, actualCutoutRect, cWidth, cHeight)) {
610             continue
611         }
612 
613         if (actualCutoutRect.touchesLeftEdge(relativeRotation, cWidth, cHeight)) {
614             var logicalWidth = actualCutoutRect.logicalWidth(relativeRotation)
615             if (isRtl) logicalWidth += dotWidth
616             leftMargin = max(logicalWidth, leftMargin)
617         } else if (actualCutoutRect.touchesRightEdge(relativeRotation, cWidth, cHeight)) {
618             var logicalWidth = actualCutoutRect.logicalWidth(relativeRotation)
619             if (!isRtl) logicalWidth += dotWidth
620             rightMargin = max(rightMargin, logicalWidth)
621         }
622         // TODO(b/203626889): Fix the scenario when config_mainBuiltInDisplayCutoutRectApproximation
623         //                    is very close to but not directly touch edges.
624     }
625 
626     return Rect(leftMargin, insetTop, logicalDisplayWidth - rightMargin, sbHeight)
627 }
628 
<lambda>null629 private fun rectUnion(first: Rect, second: Rect) = Rect(first).apply { union(second) }
630 
Rectnull631 private fun Rect.intersects(other: Rect): Boolean =
632     intersects(other.left, other.top, other.right, other.bottom)
633 
634 private val DisplayCutout.boundingRectsLeftRightTop
635     get() = listOf(boundingRectLeft, boundingRectRight, boundingRectTop).filter { !it.isEmpty }
636 
637 /*
638  * Returns the inset top of the status bar.
639  *
640  * Only greater than 0, when we want the content to be bottom aligned.
641  *
642  * Common case when we want content to be vertically centered within the status bar.
643  * Example dimensions:
644  * - Status bar height: 50dp
645  * - Content height: 20dp
646  *  _______________________________________________
647  *  |                                             |
648  *  |                                             |
649  *  | 09:00                            5G [] 74%  |  20dp Content CENTER_VERTICAL gravity
650  *  |                                             |
651  *  |_____________________________________________|
652  *
653  *  Case when we want bottom alignment and a bottom margin of 10dp.
654  *  We need to make the status bar height artificially smaller using top padding/inset.
655  *  - Status bar height: 50dp
656  *  - Content height: 20dp
657  *  - Bottom margin: 10dp
658  *   ______________________________________________
659  *  |_____________________________________________| 10dp top inset/padding
660  *  |                                             | 40dp new artificial status bar height
661  *  | 09:00                            5G [] 74%  | 20dp Content CENTER_VERTICAL gravity
662  *  |_____________________________________________| 10dp bottom margin
663  */
664 @Px
getInsetTopnull665 private fun getInsetTop(
666     bottomAlignedMargin: Int,
667     statusBarContentHeight: Int,
668     statusBarHeight: Int,
669 ): Int {
670     val bottomAlignmentEnabled = bottomAlignedMargin >= 0
671     if (!bottomAlignmentEnabled) {
672         return 0
673     }
674     val newArtificialStatusBarHeight = bottomAlignedMargin * 2 + statusBarContentHeight
675     return statusBarHeight - newArtificialStatusBarHeight
676 }
677 
sbRectnull678 private fun sbRect(
679     @Rotation relativeRotation: Int,
680     sbHeight: Int,
681     displaySize: Pair<Int, Int>,
682 ): Rect {
683     val w = displaySize.first
684     val h = displaySize.second
685     return when (relativeRotation) {
686         ROTATION_NONE -> Rect(0, 0, w, sbHeight)
687         ROTATION_LANDSCAPE -> Rect(0, 0, sbHeight, h)
688         ROTATION_UPSIDE_DOWN -> Rect(0, h - sbHeight, w, h)
689         else -> Rect(w - sbHeight, 0, w, h)
690     }
691 }
692 
shareShortEdgenull693 private fun shareShortEdge(
694     sbRect: Rect,
695     cutoutRect: Rect,
696     currentWidth: Int,
697     currentHeight: Int,
698 ): Boolean {
699     if (currentWidth < currentHeight) {
700         // Check top/bottom edges by extending the width of the display cutout rect and checking
701         // for intersections
702         return sbRect.intersects(0, cutoutRect.top, currentWidth, cutoutRect.bottom)
703     } else if (currentWidth > currentHeight) {
704         // Short edge is the height, extend that one this time
705         return sbRect.intersects(cutoutRect.left, 0, cutoutRect.right, currentHeight)
706     }
707 
708     return false
709 }
710 
touchesRightEdgenull711 private fun Rect.touchesRightEdge(@Rotation rot: Int, width: Int, height: Int): Boolean {
712     return when (rot) {
713         ROTATION_NONE -> right >= width
714         ROTATION_LANDSCAPE -> top <= 0
715         ROTATION_UPSIDE_DOWN -> left <= 0
716         else /* SEASCAPE */ -> bottom >= height
717     }
718 }
719 
Rectnull720 private fun Rect.touchesLeftEdge(@Rotation rot: Int, width: Int, height: Int): Boolean {
721     return when (rot) {
722         ROTATION_NONE -> left <= 0
723         ROTATION_LANDSCAPE -> bottom >= height
724         ROTATION_UPSIDE_DOWN -> right >= width
725         else /* SEASCAPE */ -> top <= 0
726     }
727 }
728 
Rectnull729 private fun Rect.logicalTop(@Rotation rot: Int): Int {
730     return when (rot) {
731         ROTATION_NONE -> top
732         ROTATION_LANDSCAPE -> left
733         ROTATION_UPSIDE_DOWN -> bottom
734         else /* SEASCAPE */ -> right
735     }
736 }
737 
Rectnull738 private fun Rect.logicalRight(@Rotation rot: Int): Int {
739     return when (rot) {
740         ROTATION_NONE -> right
741         ROTATION_LANDSCAPE -> top
742         ROTATION_UPSIDE_DOWN -> left
743         else /* SEASCAPE */ -> bottom
744     }
745 }
746 
Rectnull747 private fun Rect.logicalLeft(@Rotation rot: Int): Int {
748     return when (rot) {
749         ROTATION_NONE -> left
750         ROTATION_LANDSCAPE -> bottom
751         ROTATION_UPSIDE_DOWN -> right
752         else /* SEASCAPE */ -> top
753     }
754 }
755 
Rectnull756 private fun Rect.logicalWidth(@Rotation rot: Int): Int {
757     return when (rot) {
758         ROTATION_NONE,
759         ROTATION_UPSIDE_DOWN -> width()
760         else /* LANDSCAPE, SEASCAPE */ -> height()
761     }
762 }
763 
Intnull764 private fun Int.isHorizontal(): Boolean {
765     return this == ROTATION_LANDSCAPE || this == ROTATION_SEASCAPE
766 }
767 
Pointnull768 private fun Point.orientToRotZero(@Rotation rot: Int) {
769     when (rot) {
770         ROTATION_NONE,
771         ROTATION_UPSIDE_DOWN -> return
772         else -> {
773             // swap width and height to zero-orient bounds
774             val yTmp = y
775             y = x
776             x = yTmp
777         }
778     }
779 }
780 
Pointnull781 private fun Point.logicalWidth(@Rotation rot: Int): Int {
782     return when (rot) {
783         ROTATION_NONE,
784         ROTATION_UPSIDE_DOWN -> x
785         else -> y
786     }
787 }
788