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