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      */
45     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 
85     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  */
95 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  */
111 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