• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2023 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 
18 package com.android.systemui.keyboard.backlight.ui.view
19 
20 import android.annotation.ColorInt
21 import android.app.Dialog
22 import android.content.Context
23 import android.graphics.drawable.ShapeDrawable
24 import android.graphics.drawable.shapes.RoundRectShape
25 import android.os.Bundle
26 import android.view.Gravity
27 import android.view.Window
28 import android.view.WindowManager
29 import android.widget.FrameLayout
30 import android.widget.ImageView
31 import android.widget.LinearLayout
32 import android.widget.LinearLayout.LayoutParams
33 import android.widget.LinearLayout.LayoutParams.WRAP_CONTENT
34 import com.android.systemui.R
35 import com.android.systemui.util.children
36 
37 class KeyboardBacklightDialog(
38     context: Context,
39     initialCurrentLevel: Int,
40     initialMaxLevel: Int,
41 ) : Dialog(context) {
42 
43     private data class RootProperties(
44         val cornerRadius: Float,
45         val verticalPadding: Int,
46         val horizontalPadding: Int,
47     )
48 
49     private data class BacklightIconProperties(
50         val width: Int,
51         val height: Int,
52         val leftMargin: Int,
53     )
54 
55     private data class StepViewProperties(
56         val width: Int,
57         val height: Int,
58         val horizontalMargin: Int,
59         val smallRadius: Float,
60         val largeRadius: Float,
61     )
62 
63     private var currentLevel: Int = 0
64     private var maxLevel: Int = 0
65 
66     private lateinit var rootView: LinearLayout
67 
68     private var dialogBottomMargin = 208
69     private lateinit var rootProperties: RootProperties
70     private lateinit var iconProperties: BacklightIconProperties
71     private lateinit var stepProperties: StepViewProperties
72     @ColorInt var filledRectangleColor: Int = 0
73     @ColorInt var emptyRectangleColor: Int = 0
74     @ColorInt var backgroundColor: Int = 0
75 
76     init {
77         currentLevel = initialCurrentLevel
78         maxLevel = initialMaxLevel
79     }
80 
81     override fun onCreate(savedInstanceState: Bundle?) {
82         setUpWindowProperties(this)
83         setWindowTitle()
84         updateResources()
85         rootView = buildRootView()
86         setContentView(rootView)
87         super.onCreate(savedInstanceState)
88         updateState(currentLevel, maxLevel, forceRefresh = true)
89     }
90 
91     private fun updateResources() {
92         context.resources.apply {
93             filledRectangleColor = getColor(R.color.backlight_indicator_step_filled)
94             emptyRectangleColor = getColor(R.color.backlight_indicator_step_empty)
95             backgroundColor = getColor(R.color.backlight_indicator_background)
96             rootProperties =
97                 RootProperties(
98                     cornerRadius =
99                         getDimensionPixelSize(R.dimen.backlight_indicator_root_corner_radius)
100                             .toFloat(),
101                     verticalPadding =
102                         getDimensionPixelSize(R.dimen.backlight_indicator_root_vertical_padding),
103                     horizontalPadding =
104                         getDimensionPixelSize(R.dimen.backlight_indicator_root_horizontal_padding)
105                 )
106             iconProperties =
107                 BacklightIconProperties(
108                     width = getDimensionPixelSize(R.dimen.backlight_indicator_icon_width),
109                     height = getDimensionPixelSize(R.dimen.backlight_indicator_icon_height),
110                     leftMargin =
111                         getDimensionPixelSize(R.dimen.backlight_indicator_icon_left_margin),
112                 )
113             stepProperties =
114                 StepViewProperties(
115                     width = getDimensionPixelSize(R.dimen.backlight_indicator_step_width),
116                     height = getDimensionPixelSize(R.dimen.backlight_indicator_step_height),
117                     horizontalMargin =
118                         getDimensionPixelSize(R.dimen.backlight_indicator_step_horizontal_margin),
119                     smallRadius =
120                         getDimensionPixelSize(R.dimen.backlight_indicator_step_small_radius)
121                             .toFloat(),
122                     largeRadius =
123                         getDimensionPixelSize(R.dimen.backlight_indicator_step_large_radius)
124                             .toFloat(),
125                 )
126         }
127     }
128 
129     fun updateState(current: Int, max: Int, forceRefresh: Boolean = false) {
130         if (maxLevel != max || forceRefresh) {
131             maxLevel = max
132             rootView.removeAllViews()
133             buildStepViews().forEach { rootView.addView(it) }
134         }
135         currentLevel = current
136         updateLevel()
137     }
138 
139     private fun updateLevel() {
140         rootView.children.forEachIndexed(
141             action = { index, v ->
142                 val drawable = v.background as ShapeDrawable
143                 if (index <= currentLevel) {
144                     updateColor(drawable, filledRectangleColor)
145                 } else {
146                     updateColor(drawable, emptyRectangleColor)
147                 }
148             }
149         )
150     }
151 
152     private fun updateColor(drawable: ShapeDrawable, @ColorInt color: Int) {
153         if (drawable.paint.color != color) {
154             drawable.paint.color = color
155             drawable.invalidateSelf()
156         }
157     }
158 
159     private fun buildRootView(): LinearLayout {
160         val linearLayout =
161             LinearLayout(context).apply {
162                 orientation = LinearLayout.HORIZONTAL
163                 layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
164                 setPadding(
165                     /* left= */ rootProperties.horizontalPadding,
166                     /* top= */ rootProperties.verticalPadding,
167                     /* right= */ rootProperties.horizontalPadding,
168                     /* bottom= */ rootProperties.verticalPadding
169                 )
170             }
171         val drawable =
172             ShapeDrawable(
173                 RoundRectShape(
174                     /* outerRadii= */ FloatArray(8) { rootProperties.cornerRadius },
175                     /* inset= */ null,
176                     /* innerRadii= */ null
177                 )
178             )
179         drawable.paint.color = backgroundColor
180         linearLayout.background = drawable
181         return linearLayout
182     }
183 
184     private fun buildStepViews(): List<FrameLayout> {
185         val stepViews = (0..maxLevel).map { i -> createStepViewAt(i) }
186         stepViews[0].addView(createBacklightIconView())
187         return stepViews
188     }
189 
190     private fun createStepViewAt(i: Int): FrameLayout {
191         return FrameLayout(context).apply {
192             layoutParams =
193                 FrameLayout.LayoutParams(stepProperties.width, stepProperties.height).apply {
194                     setMargins(
195                         /* left= */ stepProperties.horizontalMargin,
196                         /* top= */ 0,
197                         /* right= */ stepProperties.horizontalMargin,
198                         /* bottom= */ 0
199                     )
200                 }
201             val drawable =
202                 ShapeDrawable(
203                     RoundRectShape(
204                         /* outerRadii= */ radiiForIndex(i, maxLevel),
205                         /* inset= */ null,
206                         /* innerRadii= */ null
207                     )
208                 )
209             drawable.paint.color = emptyRectangleColor
210             background = drawable
211         }
212     }
213 
214     private fun createBacklightIconView(): ImageView {
215         return ImageView(context).apply {
216             setImageResource(R.drawable.ic_keyboard_backlight)
217             layoutParams =
218                 FrameLayout.LayoutParams(iconProperties.width, iconProperties.height).apply {
219                     gravity = Gravity.CENTER
220                     leftMargin = iconProperties.leftMargin
221                 }
222         }
223     }
224 
225     private fun setWindowTitle() {
226         val attrs = window.attributes
227         // TODO(b/271796169): check if title needs to be a translatable resource.
228         attrs.title = "KeyboardBacklightDialog"
229         attrs?.y = dialogBottomMargin
230         window.attributes = attrs
231     }
232 
233     private fun setUpWindowProperties(dialog: Dialog) {
234         val window = dialog.window
235         window.requestFeature(Window.FEATURE_NO_TITLE) // otherwise fails while creating actionBar
236         window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
237         window.addFlags(
238             WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM or
239                 WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
240         )
241         window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
242         window.setBackgroundDrawableResource(android.R.color.transparent)
243         window.setGravity(Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)
244         setCanceledOnTouchOutside(true)
245     }
246 
247     private fun radiiForIndex(i: Int, last: Int): FloatArray {
248         val smallRadius = stepProperties.smallRadius
249         val largeRadius = stepProperties.largeRadius
250         return when (i) {
251             0 -> // left radii bigger
252             floatArrayOf(
253                     largeRadius,
254                     largeRadius,
255                     smallRadius,
256                     smallRadius,
257                     smallRadius,
258                     smallRadius,
259                     largeRadius,
260                     largeRadius
261                 )
262             last -> // right radii bigger
263             floatArrayOf(
264                     smallRadius,
265                     smallRadius,
266                     largeRadius,
267                     largeRadius,
268                     largeRadius,
269                     largeRadius,
270                     smallRadius,
271                     smallRadius
272                 )
273             else -> FloatArray(8) { smallRadius } // all radii equal
274         }
275     }
276 }
277