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 19 import android.content.Context 20 import android.content.res.ColorStateList 21 import android.view.DisplayCutout 22 import android.view.Gravity 23 import android.view.Surface 24 import android.view.View 25 import android.view.ViewGroup 26 import android.widget.FrameLayout 27 import android.widget.ImageView 28 import com.android.systemui.R 29 30 class RoundedCornerDecorProviderImpl( 31 override val viewId: Int, 32 @DisplayCutout.BoundsPosition override val alignedBound1: Int, 33 @DisplayCutout.BoundsPosition override val alignedBound2: Int, 34 private val roundedCornerResDelegate: RoundedCornerResDelegate 35 ) : CornerDecorProvider() { 36 37 private val isTop = alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_TOP) 38 39 override fun inflateView( 40 context: Context, 41 parent: ViewGroup, 42 @Surface.Rotation rotation: Int, 43 tintColor: Int 44 ): View { 45 return ImageView(context).also { view -> 46 // View 47 view.id = viewId 48 initView(view, rotation, tintColor) 49 50 // LayoutParams 51 val layoutSize = if (isTop) { 52 roundedCornerResDelegate.topRoundedSize 53 } else { 54 roundedCornerResDelegate.bottomRoundedSize 55 } 56 val params = FrameLayout.LayoutParams( 57 layoutSize.width, 58 layoutSize.height, 59 alignedBound1.toLayoutGravity(rotation) or alignedBound2.toLayoutGravity(rotation) 60 ) 61 62 // AddView 63 parent.addView(view, params) 64 } 65 } 66 67 private fun initView( 68 view: ImageView, 69 @Surface.Rotation rotation: Int, 70 tintColor: Int 71 ) { 72 view.setRoundedCornerImage(roundedCornerResDelegate, isTop) 73 view.adjustRotation(alignedBounds, rotation) 74 view.imageTintList = ColorStateList.valueOf(tintColor) 75 } 76 77 override fun onReloadResAndMeasure( 78 view: View, 79 reloadToken: Int, 80 @Surface.Rotation rotation: Int, 81 tintColor: Int, 82 displayUniqueId: String? 83 ) { 84 roundedCornerResDelegate.updateDisplayUniqueId(displayUniqueId, reloadToken) 85 86 initView((view as ImageView), rotation, tintColor) 87 88 val layoutSize = if (isTop) { 89 roundedCornerResDelegate.topRoundedSize 90 } else { 91 roundedCornerResDelegate.bottomRoundedSize 92 } 93 (view.layoutParams as FrameLayout.LayoutParams).let { 94 it.width = layoutSize.width 95 it.height = layoutSize.height 96 it.gravity = alignedBound1.toLayoutGravity(rotation) or 97 alignedBound2.toLayoutGravity(rotation) 98 view.setLayoutParams(it) 99 } 100 } 101 } 102 103 @DisplayCutout.BoundsPosition 104 private fun Int.toLayoutGravity(@Surface.Rotation rotation: Int): Int = when (rotation) { 105 Surface.ROTATION_0 -> when (this) { 106 DisplayCutout.BOUNDS_POSITION_LEFT -> Gravity.LEFT 107 DisplayCutout.BOUNDS_POSITION_TOP -> Gravity.TOP 108 DisplayCutout.BOUNDS_POSITION_RIGHT -> Gravity.RIGHT 109 else /* DisplayCutout.BOUNDS_POSITION_BOTTOM */ -> Gravity.BOTTOM 110 } 111 Surface.ROTATION_90 -> when (this) { 112 DisplayCutout.BOUNDS_POSITION_LEFT -> Gravity.BOTTOM 113 DisplayCutout.BOUNDS_POSITION_TOP -> Gravity.LEFT 114 DisplayCutout.BOUNDS_POSITION_RIGHT -> Gravity.TOP 115 else /* DisplayCutout.BOUNDS_POSITION_BOTTOM */ -> Gravity.RIGHT 116 } 117 Surface.ROTATION_270 -> when (this) { 118 DisplayCutout.BOUNDS_POSITION_LEFT -> Gravity.TOP 119 DisplayCutout.BOUNDS_POSITION_TOP -> Gravity.RIGHT 120 DisplayCutout.BOUNDS_POSITION_RIGHT -> Gravity.BOTTOM 121 else /* DisplayCutout.BOUNDS_POSITION_BOTTOM */ -> Gravity.LEFT 122 } 123 else /* Surface.ROTATION_180 */ -> when (this) { 124 DisplayCutout.BOUNDS_POSITION_LEFT -> Gravity.RIGHT 125 DisplayCutout.BOUNDS_POSITION_TOP -> Gravity.BOTTOM 126 DisplayCutout.BOUNDS_POSITION_RIGHT -> Gravity.LEFT 127 else /* DisplayCutout.BOUNDS_POSITION_BOTTOM */ -> Gravity.TOP 128 } 129 } 130 131 private fun ImageView.setRoundedCornerImage( 132 resDelegate: RoundedCornerResDelegate, 133 isTop: Boolean 134 ) { 135 val drawable = if (isTop) 136 resDelegate.topRoundedDrawable 137 else 138 resDelegate.bottomRoundedDrawable 139 140 if (drawable != null) { 141 setImageDrawable(drawable) 142 } else { 143 setImageResource( 144 if (isTop) 145 R.drawable.rounded_corner_top 146 else 147 R.drawable.rounded_corner_bottom 148 ) 149 } 150 } 151 152 /** 153 * Configures the rounded corner drawable's view matrix based on the gravity. 154 * 155 * The gravity describes which corner to configure for, and the drawable we are rotating is assumed 156 * to be oriented for the top-left corner of the device regardless of the target corner. 157 * Therefore we need to rotate 180 degrees to get a bottom-left corner, and mirror in the x- or 158 * y-axis for the top-right and bottom-left corners. 159 */ 160 private fun ImageView.adjustRotation(alignedBounds: List<Int>, @Surface.Rotation rotation: Int) { 161 var newRotation = 0F 162 var newScaleX = 1F 163 var newScaleY = 1F 164 165 val isTop = alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_TOP) 166 val isLeft = alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_LEFT) 167 when (rotation) { 168 Surface.ROTATION_0 -> when { 169 isTop && isLeft -> {} 170 isTop && !isLeft -> { newScaleX = -1F } 171 !isTop && isLeft -> { newScaleY = -1F } 172 else /* !isTop && !isLeft */ -> { newRotation = 180F } 173 } 174 Surface.ROTATION_90 -> when { 175 isTop && isLeft -> { newScaleY = -1F } 176 isTop && !isLeft -> {} 177 !isTop && isLeft -> { newRotation = 180F } 178 else /* !isTop && !isLeft */ -> { newScaleX = -1F } 179 } 180 Surface.ROTATION_270 -> when { 181 isTop && isLeft -> { newScaleX = -1F } 182 isTop && !isLeft -> { newRotation = 180F } 183 !isTop && isLeft -> {} 184 else /* !isTop && !isLeft */ -> { newScaleY = -1F } 185 } 186 else /* Surface.ROTATION_180 */ -> when { 187 isTop && isLeft -> { newRotation = 180F } 188 isTop && !isLeft -> { newScaleY = -1F } 189 !isTop && isLeft -> { newScaleX = -1F } 190 else /* !isTop && !isLeft */ -> {} 191 } 192 } 193 194 this.rotation = newRotation 195 this.scaleX = newScaleX 196 this.scaleY = newScaleY 197 } 198