1 /*
2 * 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.decor
18 import android.content.Context
19 import android.view.DisplayCutout
20 import android.view.Surface
21 import android.view.View
22 import android.view.ViewGroup
23
24 /**
25 * An interface for providing view with a specific functionality. Take an example, if privacy dot
26 * is enabled, there are 4 DecorProviders which are used to provide privacy dot views on top-left,
27 * top-right, bottom-left, bottom-right.
28 */
29 abstract class DecorProvider {
30
31 /** Id for the view which is created through inflateView() */
32 abstract val viewId: Int
33
34 /** The number of total aligned bounds */
35 val numOfAlignedBound: Int
36 get() = alignedBounds.size
37
38 /** The aligned bounds for the view which is created through inflateView() */
39 abstract val alignedBounds: List<Int>
40
41 /**
42 * Called when res info changed.
43 * Child provider needs to implement it if its view needs to be updated.
44 */
onReloadResAndMeasurenull45 abstract fun onReloadResAndMeasure(
46 view: View,
47 reloadToken: Int,
48 @Surface.Rotation rotation: Int,
49 tintColor: Int,
50 displayUniqueId: String?
51 )
52
53 /** Inflate view into parent as current rotation */
54 abstract fun inflateView(
55 context: Context,
56 parent: ViewGroup,
57 @Surface.Rotation rotation: Int,
58 tintColor: Int
59 ): View
60
61 override fun toString() = "${javaClass.simpleName}{alignedBounds=$alignedBounds}"
62 }
63
64 /**
65 * A provider for view shown on corner.
66 */
67 abstract class CornerDecorProvider : DecorProvider() {
68 /** The first bound which a corner view is aligned based on rotation 0 */
69 @DisplayCutout.BoundsPosition protected abstract val alignedBound1: Int
70 /** The second bound which a corner view is aligned based on rotation 0 */
71 @DisplayCutout.BoundsPosition protected abstract val alignedBound2: Int
72
73 override val alignedBounds: List<Int> by lazy {
74 listOf(alignedBound1, alignedBound2)
75 }
76 }
77
78 /**
79 * A provider for view shown on bound.
80 */
81 abstract class BoundDecorProvider : DecorProvider() {
82 /** The bound which a view is aligned based on rotation 0 */
83 @DisplayCutout.BoundsPosition protected abstract val alignedBound: Int
84
<lambda>null85 override val alignedBounds: List<Int> by lazy {
86 listOf(alignedBound)
87 }
88 }
89
90 /**
91 * Split list to 2 sub-lists, and return it back as Pair<>. The providers on the first list contains
92 * this alignedBound element. The providers on the second list do not contain this alignedBound
93 * element.
94 */
partitionAlignedBoundnull95 fun List<DecorProvider>.partitionAlignedBound(
96 @DisplayCutout.BoundsPosition alignedBound: Int
97 ): Pair<List<DecorProvider>, List<DecorProvider>> {
98 return partition { it.alignedBounds.contains(alignedBound) }
99 }
100
101 /**
102 * Get the proper bound from DecorProvider list
103 * Time complexity: O(N), N is the number of providers
104 *
105 * Choose order
106 * 1. Return null if list is empty
107 * 2. If list contains BoundDecorProvider, return its alignedBound[0] because it is a must-have
108 * bound
109 * 3. Return the bound with most DecorProviders
110 */
getProperBoundnull111 fun List<DecorProvider>.getProperBound(): Int? {
112 // Return null if list is empty
113 if (isEmpty()) {
114 return null
115 }
116
117 // Choose alignedBounds[0] of BoundDecorProvider if any
118 val singleBoundProvider = firstOrNull { it.numOfAlignedBound == 1 }
119 if (singleBoundProvider != null) {
120 return singleBoundProvider.alignedBounds[0]
121 }
122
123 // Return the bound with most DecorProviders
124 val boundCount = intArrayOf(0, 0, 0, 0)
125 for (provider in this) {
126 for (bound in provider.alignedBounds) {
127 boundCount[bound]++
128 }
129 }
130 var maxCount = 0
131 var maxCountBound: Int? = null
132 val bounds = arrayOf(
133 // Put top and bottom at first to get the highest priority to be chosen
134 DisplayCutout.BOUNDS_POSITION_TOP,
135 DisplayCutout.BOUNDS_POSITION_BOTTOM,
136 DisplayCutout.BOUNDS_POSITION_LEFT,
137 DisplayCutout.BOUNDS_POSITION_RIGHT
138 )
139 for (bound in bounds) {
140 if (boundCount[bound] > maxCount) {
141 maxCountBound = bound
142 maxCount = boundCount[bound]
143 }
144 }
145 return maxCountBound
146 }
147