1 /* 2 * Copyright (C) 2024 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.settingslib.widget 18 19 import android.annotation.SuppressLint 20 import android.os.Handler 21 import android.os.Looper 22 import android.util.TypedValue 23 import androidx.annotation.DrawableRes 24 import androidx.preference.Preference 25 import androidx.preference.PreferenceCategory 26 import androidx.preference.PreferenceGroup 27 import androidx.preference.PreferenceGroupAdapter 28 import androidx.preference.PreferenceViewHolder 29 import com.android.settingslib.widget.theme.R 30 31 /** 32 * A custom adapter for displaying settings preferences in a list, handling rounded corners for 33 * preference items within a group. 34 */ 35 @SuppressLint("RestrictedApi") 36 open class SettingsPreferenceGroupAdapter(preferenceGroup: PreferenceGroup) : 37 PreferenceGroupAdapter(preferenceGroup) { 38 39 private val mPreferenceGroup = preferenceGroup 40 private var mRoundCornerMappingList: ArrayList<Int> = ArrayList() 41 42 private var mNormalPaddingStart = 0 43 private var mGroupPaddingStart = 0 44 private var mNormalPaddingEnd = 0 45 private var mGroupPaddingEnd = 0 46 @DrawableRes private var mLegacyBackgroundRes: Int 47 48 private val mHandler = Handler(Looper.getMainLooper()) 49 <lambda>null50 private val syncRunnable = Runnable { updatePreferences() } 51 52 init { 53 val context = preferenceGroup.context 54 mNormalPaddingStart = 55 context.resources.getDimensionPixelSize(R.dimen.settingslib_expressive_space_small1) 56 mGroupPaddingStart = mNormalPaddingStart * 2 57 mNormalPaddingEnd = 58 context.resources.getDimensionPixelSize(R.dimen.settingslib_expressive_space_small1) 59 mGroupPaddingEnd = mNormalPaddingEnd * 2 60 val outValue = TypedValue() 61 context.theme.resolveAttribute( 62 android.R.attr.selectableItemBackground, 63 outValue, 64 true, /* resolveRefs */ 65 ) 66 mLegacyBackgroundRes = outValue.resourceId 67 updatePreferences() 68 } 69 70 @SuppressLint("RestrictedApi") onPreferenceHierarchyChangenull71 override fun onPreferenceHierarchyChange(preference: Preference) { 72 super.onPreferenceHierarchyChange(preference) 73 74 // Post after super class has posted their sync runnable to update preferences. 75 mHandler.removeCallbacks(syncRunnable) 76 mHandler.post(syncRunnable) 77 } 78 79 @SuppressLint("RestrictedApi") onBindViewHoldernull80 override fun onBindViewHolder(holder: PreferenceViewHolder, position: Int) { 81 super.onBindViewHolder(holder, position) 82 updateBackground(holder, position) 83 } 84 updatePreferencesnull85 private fun updatePreferences() { 86 val oldList = ArrayList(mRoundCornerMappingList) 87 mRoundCornerMappingList = ArrayList() 88 mappingPreferenceGroup(mRoundCornerMappingList, mPreferenceGroup) 89 if (mRoundCornerMappingList != oldList) { 90 notifyDataSetChanged() 91 } 92 } 93 94 @SuppressLint("RestrictedApi") mappingPreferenceGroupnull95 private fun mappingPreferenceGroup(cornerStyles: MutableList<Int>, group: PreferenceGroup) { 96 cornerStyles.clear() 97 cornerStyles.addAll(MutableList(itemCount) { 0 }) 98 99 // the first item in to group 100 var startIndex = -1 101 // the last item in the group 102 var endIndex = -1 103 var currentParent: PreferenceGroup? = group 104 for (i in 0 until itemCount) { 105 when (val pref = getItem(i)) { 106 // the preference has round corner background, so we don't need to handle it. 107 is GroupSectionDividerMixin -> { 108 cornerStyles[i] = 0 109 startIndex = -1 110 endIndex = -1 111 } 112 113 // PreferenceCategory should not have round corner background. 114 is PreferenceCategory -> { 115 cornerStyles[i] = 0 116 startIndex = -1 117 endIndex = -1 118 currentParent = pref 119 } 120 121 // ExpandablePreference is PreferenceGroup but it should handle round corner 122 is Expandable -> { 123 // When ExpandablePreference is expanded, we treat is as the first item. 124 if (pref.isExpanded()) { 125 currentParent = pref as? PreferenceGroup 126 startIndex = i 127 cornerStyles[i] = cornerStyles[i] or ROUND_CORNER_TOP or ROUND_CORNER_CENTER 128 endIndex = -1 129 } 130 } 131 132 else -> { 133 val parent = pref?.parent 134 135 // item in the group should have round corner background. 136 cornerStyles[i] = cornerStyles[i] or ROUND_CORNER_CENTER 137 if (parent === currentParent) { 138 // find the first item in the group 139 if (startIndex == -1) { 140 startIndex = i 141 cornerStyles[i] = cornerStyles[i] or ROUND_CORNER_TOP 142 } 143 144 // find the last item in the group, if we find the new last item, we should 145 // remove the old last item round corner. 146 if (endIndex == -1 || endIndex < i) { 147 if (endIndex != -1) { 148 cornerStyles[endIndex] = 149 cornerStyles[endIndex] and ROUND_CORNER_BOTTOM.inv() 150 } 151 endIndex = i 152 cornerStyles[i] = cornerStyles[i] or ROUND_CORNER_BOTTOM 153 } 154 } else { 155 // this item is new group, we should reset the index. 156 currentParent = parent 157 startIndex = i 158 cornerStyles[i] = cornerStyles[i] or ROUND_CORNER_TOP 159 endIndex = i 160 cornerStyles[i] = cornerStyles[i] or ROUND_CORNER_BOTTOM 161 } 162 } 163 } 164 } 165 } 166 167 /** handle roundCorner background */ updateBackgroundnull168 private fun updateBackground(holder: PreferenceViewHolder, position: Int) { 169 val context = holder.itemView.context 170 @DrawableRes 171 val backgroundRes = 172 when (SettingsThemeHelper.isExpressiveTheme(context)) { 173 true -> getRoundCornerDrawableRes(position, isSelected = false) 174 else -> mLegacyBackgroundRes 175 } 176 177 val v = holder.itemView 178 // Update padding 179 if (SettingsThemeHelper.isExpressiveTheme(context)) { 180 val paddingStart = if (backgroundRes == 0) mNormalPaddingStart else mGroupPaddingStart 181 val paddingEnd = if (backgroundRes == 0) mNormalPaddingEnd else mGroupPaddingEnd 182 v.setPaddingRelative(paddingStart, v.paddingTop, paddingEnd, v.paddingBottom) 183 } 184 // Update background 185 v.setBackgroundResource(backgroundRes) 186 } 187 188 @DrawableRes getRoundCornerDrawableResnull189 protected fun getRoundCornerDrawableRes(position: Int, isSelected: Boolean): Int { 190 return getRoundCornerDrawableRes(position, isSelected, false) 191 } 192 193 @DrawableRes getRoundCornerDrawableResnull194 protected fun getRoundCornerDrawableRes( 195 position: Int, 196 isSelected: Boolean, 197 isHighlighted: Boolean, 198 ): Int { 199 val cornerType = mRoundCornerMappingList[position] 200 201 if ((cornerType and ROUND_CORNER_CENTER) == 0) { 202 return 0 203 } 204 205 return when { 206 (cornerType and ROUND_CORNER_TOP) != 0 && (cornerType and ROUND_CORNER_BOTTOM) == 0 -> { 207 // the first 208 if (isSelected) R.drawable.settingslib_round_background_top_selected 209 else if (isHighlighted) R.drawable.settingslib_round_background_top_highlighted 210 else R.drawable.settingslib_round_background_top 211 } 212 213 (cornerType and ROUND_CORNER_BOTTOM) != 0 && (cornerType and ROUND_CORNER_TOP) == 0 -> { 214 // the last 215 if (isSelected) R.drawable.settingslib_round_background_bottom_selected 216 else if (isHighlighted) R.drawable.settingslib_round_background_bottom_highlighted 217 else R.drawable.settingslib_round_background_bottom 218 } 219 220 (cornerType and ROUND_CORNER_TOP) != 0 && (cornerType and ROUND_CORNER_BOTTOM) != 0 -> { 221 // the only one preference 222 if (isSelected) R.drawable.settingslib_round_background_selected 223 else if (isHighlighted) R.drawable.settingslib_round_background_highlighted 224 else R.drawable.settingslib_round_background 225 } 226 227 else -> { 228 // in the center 229 if (isSelected) R.drawable.settingslib_round_background_center_selected 230 else if (isHighlighted) R.drawable.settingslib_round_background_center_highlighted 231 else R.drawable.settingslib_round_background_center 232 } 233 } 234 } 235 236 companion object { 237 private const val ROUND_CORNER_CENTER: Int = 1 238 private const val ROUND_CORNER_TOP: Int = 1 shl 1 239 private const val ROUND_CORNER_BOTTOM: Int = 1 shl 2 240 } 241 } 242