Loading res/drawable/bluetooth_details_banner_background.xml 0 → 100644 +32 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- Copyright (C) 2025 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?android:colorControlHighlight"> <item android:start="16dp" android:end="16dp" android:top="16dp" android:bottom="16dp"> <shape android:shape="rectangle"> <solid android:color="@color/settingslib_materialColorSurfaceVariant" /> <corners android:radius="28dp" /> </shape> </item> </ripple> No newline at end of file res/layout/bluetooth_details_banner.xml 0 → 100644 +50 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- Copyright (C) 2025 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="36dp" android:orientation="vertical" android:background="@drawable/bluetooth_details_banner_background"> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="start|top" android:orientation="horizontal" android:paddingBottom="8dp"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/settingslib_ic_info_outline_24" android:tint="@color/settingslib_materialColorOnSurfaceVariant" android:importantForAccessibility="no" /> </LinearLayout> <TextView android:id="@+id/bluetooth_details_banner_message" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="start" android:textAlignment="viewStart" android:textColor="@color/settingslib_materialColorOnSurfaceVariant" android:hyphenationFrequency="normalFast" android:lineBreakWordStyle="phrase" android:ellipsize="marquee" /> </LinearLayout> res/xml/bluetooth_device_details_fragment.xml +7 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,13 @@ xmlns:settings="http://schemas.android.com/apk/res-auto" android:title="@string/device_details_title"> <com.android.settingslib.widget.LayoutPreference android:key="bluetooth_details_banner" android:layout="@layout/bluetooth_details_banner" android:selectable="false" settings:allowDividerBelow="true" settings:searchable="false"/> <com.android.settingslib.widget.LayoutPreference android:key="bluetooth_device_header" android:layout="@layout/settings_entity_header" Loading src/com/android/settings/bluetooth/BluetoothDetailsBannerController.kt 0 → 100644 +54 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.bluetooth import android.content.Context import android.widget.TextView import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceScreen import com.android.settings.R import com.android.settingslib.bluetooth.BluetoothUtils import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.core.lifecycle.Lifecycle import com.android.settingslib.widget.LayoutPreference class BluetoothDetailsBannerController( private val context: Context, fragment: PreferenceFragmentCompat, private val cachedDevice: CachedBluetoothDevice, lifecycle: Lifecycle, ) : BluetoothDetailsController(context, fragment, cachedDevice, lifecycle) { private lateinit var pref: LayoutPreference override fun getPreferenceKey(): String = KEY_BLUETOOTH_DETAILS_BANNER override fun init(screen: PreferenceScreen) { pref = screen.findPreference(KEY_BLUETOOTH_DETAILS_BANNER) ?: return } override fun refresh() { pref.findViewById<TextView>(R.id.bluetooth_details_banner_message).text = context.getString(R.string.device_details_key_missing_title, cachedDevice.name) } override fun isAvailable(): Boolean = BluetoothUtils.getKeyMissingCount(cachedDevice.device)?.let { it > 0 } ?: false private companion object { const val KEY_BLUETOOTH_DETAILS_BANNER: String = "bluetooth_details_banner" } } src/com/android/settings/bluetooth/BluetoothDetailsConfigurableFragment.kt 0 → 100644 +95 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.bluetooth import android.os.Bundle import android.os.UserManager import android.view.View import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.PreferenceGroup import com.android.settings.dashboard.RestrictedDashboardFragment /** Base class for bluetooth settings which makes the preference visibility/order configurable. */ abstract class BluetoothDetailsConfigurableFragment : RestrictedDashboardFragment(UserManager.DISALLOW_CONFIG_BLUETOOTH) { private var displayOrder: List<String>? = null fun setPreferenceDisplayOrder(prefKeyOrder: List<String>?) { if (displayOrder == prefKeyOrder) { return } displayOrder = prefKeyOrder updatePreferenceOrder() } private val invisiblePrefCategory: PreferenceGroup by lazy { preferenceScreen.findPreference<PreferenceGroup>(INVISIBLE_CATEGORY) ?: run { PreferenceCategory(requireContext()) .apply { key = INVISIBLE_CATEGORY isVisible = false isOrderingAsAdded = true } .also { preferenceScreen.addPreference(it) } } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) updatePreferenceOrder() } private fun updatePreferenceOrder() { val order = displayOrder?: return if (preferenceScreen == null) { return } preferenceScreen.isOrderingAsAdded = true val allPrefs = (invisiblePrefCategory.getAndRemoveAll() + preferenceScreen.getAndRemoveAll()).filter { it != invisiblePrefCategory } allPrefs.forEach { it.order = Preference.DEFAULT_ORDER } val visiblePrefs = allPrefs.filter { order.contains(it.key) }.sortedBy { order.indexOf(it.key) } val invisiblePrefs = allPrefs.filter { !order.contains(it.key) } preferenceScreen.addPreferences(visiblePrefs) preferenceScreen.addPreference(invisiblePrefCategory) invisiblePrefCategory.addPreferences(invisiblePrefs) } private fun PreferenceGroup.getAndRemoveAll(): List<Preference> { val prefs = mutableListOf<Preference>() for (i in 0..<preferenceCount) { prefs.add(getPreference(i)) } removeAll() return prefs } private fun PreferenceGroup.addPreferences(prefs: List<Preference>) { for (pref in prefs) { addPreference(pref) } } private companion object { const val INVISIBLE_CATEGORY = "invisible_profile_category" } } Loading
res/drawable/bluetooth_details_banner_background.xml 0 → 100644 +32 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- Copyright (C) 2025 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?android:colorControlHighlight"> <item android:start="16dp" android:end="16dp" android:top="16dp" android:bottom="16dp"> <shape android:shape="rectangle"> <solid android:color="@color/settingslib_materialColorSurfaceVariant" /> <corners android:radius="28dp" /> </shape> </item> </ripple> No newline at end of file
res/layout/bluetooth_details_banner.xml 0 → 100644 +50 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- Copyright (C) 2025 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="36dp" android:orientation="vertical" android:background="@drawable/bluetooth_details_banner_background"> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="start|top" android:orientation="horizontal" android:paddingBottom="8dp"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/settingslib_ic_info_outline_24" android:tint="@color/settingslib_materialColorOnSurfaceVariant" android:importantForAccessibility="no" /> </LinearLayout> <TextView android:id="@+id/bluetooth_details_banner_message" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="start" android:textAlignment="viewStart" android:textColor="@color/settingslib_materialColorOnSurfaceVariant" android:hyphenationFrequency="normalFast" android:lineBreakWordStyle="phrase" android:ellipsize="marquee" /> </LinearLayout>
res/xml/bluetooth_device_details_fragment.xml +7 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,13 @@ xmlns:settings="http://schemas.android.com/apk/res-auto" android:title="@string/device_details_title"> <com.android.settingslib.widget.LayoutPreference android:key="bluetooth_details_banner" android:layout="@layout/bluetooth_details_banner" android:selectable="false" settings:allowDividerBelow="true" settings:searchable="false"/> <com.android.settingslib.widget.LayoutPreference android:key="bluetooth_device_header" android:layout="@layout/settings_entity_header" Loading
src/com/android/settings/bluetooth/BluetoothDetailsBannerController.kt 0 → 100644 +54 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.bluetooth import android.content.Context import android.widget.TextView import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceScreen import com.android.settings.R import com.android.settingslib.bluetooth.BluetoothUtils import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.core.lifecycle.Lifecycle import com.android.settingslib.widget.LayoutPreference class BluetoothDetailsBannerController( private val context: Context, fragment: PreferenceFragmentCompat, private val cachedDevice: CachedBluetoothDevice, lifecycle: Lifecycle, ) : BluetoothDetailsController(context, fragment, cachedDevice, lifecycle) { private lateinit var pref: LayoutPreference override fun getPreferenceKey(): String = KEY_BLUETOOTH_DETAILS_BANNER override fun init(screen: PreferenceScreen) { pref = screen.findPreference(KEY_BLUETOOTH_DETAILS_BANNER) ?: return } override fun refresh() { pref.findViewById<TextView>(R.id.bluetooth_details_banner_message).text = context.getString(R.string.device_details_key_missing_title, cachedDevice.name) } override fun isAvailable(): Boolean = BluetoothUtils.getKeyMissingCount(cachedDevice.device)?.let { it > 0 } ?: false private companion object { const val KEY_BLUETOOTH_DETAILS_BANNER: String = "bluetooth_details_banner" } }
src/com/android/settings/bluetooth/BluetoothDetailsConfigurableFragment.kt 0 → 100644 +95 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.bluetooth import android.os.Bundle import android.os.UserManager import android.view.View import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.PreferenceGroup import com.android.settings.dashboard.RestrictedDashboardFragment /** Base class for bluetooth settings which makes the preference visibility/order configurable. */ abstract class BluetoothDetailsConfigurableFragment : RestrictedDashboardFragment(UserManager.DISALLOW_CONFIG_BLUETOOTH) { private var displayOrder: List<String>? = null fun setPreferenceDisplayOrder(prefKeyOrder: List<String>?) { if (displayOrder == prefKeyOrder) { return } displayOrder = prefKeyOrder updatePreferenceOrder() } private val invisiblePrefCategory: PreferenceGroup by lazy { preferenceScreen.findPreference<PreferenceGroup>(INVISIBLE_CATEGORY) ?: run { PreferenceCategory(requireContext()) .apply { key = INVISIBLE_CATEGORY isVisible = false isOrderingAsAdded = true } .also { preferenceScreen.addPreference(it) } } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) updatePreferenceOrder() } private fun updatePreferenceOrder() { val order = displayOrder?: return if (preferenceScreen == null) { return } preferenceScreen.isOrderingAsAdded = true val allPrefs = (invisiblePrefCategory.getAndRemoveAll() + preferenceScreen.getAndRemoveAll()).filter { it != invisiblePrefCategory } allPrefs.forEach { it.order = Preference.DEFAULT_ORDER } val visiblePrefs = allPrefs.filter { order.contains(it.key) }.sortedBy { order.indexOf(it.key) } val invisiblePrefs = allPrefs.filter { !order.contains(it.key) } preferenceScreen.addPreferences(visiblePrefs) preferenceScreen.addPreference(invisiblePrefCategory) invisiblePrefCategory.addPreferences(invisiblePrefs) } private fun PreferenceGroup.getAndRemoveAll(): List<Preference> { val prefs = mutableListOf<Preference>() for (i in 0..<preferenceCount) { prefs.add(getPreference(i)) } removeAll() return prefs } private fun PreferenceGroup.addPreferences(prefs: List<Preference>) { for (pref in prefs) { addPreference(pref) } } private companion object { const val INVISIBLE_CATEGORY = "invisible_profile_category" } }