Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b1a3482e authored by chelseahao's avatar chelseahao Committed by Chelsea Hao
Browse files

Resolved some TODOs.

Used `AsyncListDiff` to update device list and removed unused root layout. Also used a new background to replace alpha and fixed a subtitle bug.

Test: atest -c com.android.systemui.qs.tiles.dialog.bluetooth
Bug: b/298124674 b/299400510

Change-Id: Iee413b9f53125f4ed9a4bedbf397a4d735426f3a
parent 3526aabf
Loading
Loading
Loading
Loading
+26 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2023 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:attr/colorControlHighlight">
    <item android:id="@android:id/mask">
        <shape android:shape="rectangle">
            <solid android:color="@android:color/white"/>
            <corners android:radius="@dimen/settingslib_switch_bar_radius"/>
        </shape>
    </item>
</ripple>
 No newline at end of file
+25 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2023 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.
  -->

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@android:id/background">
        <shape>
            <solid android:color="?android:attr/colorControlHighlight" />
            <corners android:radius="@dimen/settingslib_switch_bar_radius"/>
        </shape>
    </item>
</layer-list>
 No newline at end of file
+63 −70
Original line number Diff line number Diff line
@@ -14,22 +14,16 @@
  ~ limitations under the License.
  -->

<!-- TODO(b/298124674) remove this root -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/bluetooth_device_list_container"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:layout_marginBottom="4dp">

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/bluetooth_device_row"
    style="@style/BluetoothTileDialog.Device"
    android:layout_width="match_parent"
    android:layout_height="@dimen/bluetooth_dialog_device_height"
    android:paddingEnd="24dp"
    android:paddingStart="20dp"
        android:baselineAligned="false">
    android:layout_marginBottom="4dp">

    <ImageView
        android:id="@+id/bluetooth_device_icon"
@@ -41,15 +35,6 @@
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_gravity="center_vertical" />

        <View
            android:id="@+id/bluetooth_device"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintTop_toTopOf="@+id/bluetooth_device_name"
            app:layout_constraintBottom_toBottomOf="@+id/bluetooth_device_summary"
            app:layout_constraintStart_toStartOf="@+id/bluetooth_device_name"
            app:layout_constraintEnd_toEndOf="@+id/bluetooth_device_name" />

    <TextView
        android:layout_width="0dp"
        android:id="@+id/bluetooth_device_name"
@@ -77,8 +62,17 @@
        app:layout_constraintBottom_toBottomOf="parent"
        android:gravity="center_vertical" />

        <ImageView
    <View
        android:id="@+id/gear_icon"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintStart_toEndOf="@+id/bluetooth_device_name"
        app:layout_constraintEnd_toEndOf="@+id/gear_icon_image"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent" />

    <ImageView
        android:id="@+id/gear_icon_image"
        android:src="@drawable/ic_settings_24dp"
        android:contentDescription="@string/accessibility_bluetooth_device_settings_gear"
        android:layout_width="0dp"
@@ -91,4 +85,3 @@
        android:gravity="center_vertical"
        android:paddingStart="10dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
 No newline at end of file
</LinearLayout>
 No newline at end of file
+1 −1
Original line number Diff line number Diff line
@@ -40,7 +40,7 @@ constructor(
    @Application private val coroutineScope: CoroutineScope,
) {

    internal val updateBluetoothStateFlow: StateFlow<Boolean?> =
    internal val bluetoothStateUpdate: StateFlow<Boolean?> =
        conflatedCallbackFlow {
                val listener =
                    object : BluetoothCallback {
+50 −35
Original line number Diff line number Diff line
@@ -26,6 +26,8 @@ import android.view.ViewGroup
import android.widget.ImageView
import android.widget.Switch
import android.widget.TextView
import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.android.internal.logging.UiEventLogger
@@ -42,24 +44,26 @@ import kotlinx.coroutines.flow.asStateFlow
internal class BluetoothTileDialog
constructor(
    private val bluetoothToggleInitialValue: Boolean,
    private val subtitleResIdInitialValue: Int,
    private val bluetoothTileDialogCallback: BluetoothTileDialogCallback,
    private val uiEventLogger: UiEventLogger,
    context: Context,
) : SystemUIDialog(context, DEFAULT_THEME, DEFAULT_DISMISS_ON_DEVICE_LOCK) {

    private val mutableBluetoothStateSwitchedFlow: MutableStateFlow<Boolean> =
    private val mutableBluetoothStateToggle: MutableStateFlow<Boolean> =
        MutableStateFlow(bluetoothToggleInitialValue)
    internal val bluetoothStateSwitchedFlow
        get() = mutableBluetoothStateSwitchedFlow.asStateFlow()
    internal val bluetoothStateToggle
        get() = mutableBluetoothStateToggle.asStateFlow()

    private val mutableClickedFlow: MutableSharedFlow<Pair<DeviceItem, Int>> =
    private val mutableDeviceItemClick: MutableSharedFlow<DeviceItem> =
        MutableSharedFlow(extraBufferCapacity = 1)
    internal val deviceItemClickedFlow
        get() = mutableClickedFlow.asSharedFlow()
    internal val deviceItemClick
        get() = mutableDeviceItemClick.asSharedFlow()

    private val deviceItemAdapter: Adapter = Adapter(bluetoothTileDialogCallback)

    private lateinit var toggleView: Switch
    private lateinit var subtitleTextView: TextView
    private lateinit var doneButton: View
    private lateinit var seeAllViewGroup: View
    private lateinit var pairNewDeviceViewGroup: View
@@ -74,6 +78,7 @@ constructor(
        setContentView(LayoutInflater.from(context).inflate(R.layout.bluetooth_tile_dialog, null))

        toggleView = requireViewById(R.id.bluetooth_toggle)
        subtitleTextView = requireViewById(R.id.bluetooth_tile_dialog_subtitle) as TextView
        doneButton = requireViewById(R.id.done_button)
        seeAllViewGroup = requireViewById(R.id.see_all_layout_group)
        pairNewDeviceViewGroup = requireViewById(R.id.pair_new_device_layout_group)
@@ -84,6 +89,7 @@ constructor(
        setupToggle()
        setupRecyclerView()

        subtitleTextView.text = context.getString(subtitleResIdInitialValue)
        doneButton.setOnClickListener { dismiss() }
        seeAllText.setOnClickListener { bluetoothTileDialogCallback.onSeeAllClicked(it) }
        pairNewDeviceText.setOnClickListener {
@@ -91,7 +97,6 @@ constructor(
        }
    }

    // TODO(b/298124674): use DiffUtil or AsyncListDiffer to avoid updating the whole list
    internal fun onDeviceItemUpdated(
        deviceItem: List<DeviceItem>,
        showSeeAll: Boolean,
@@ -102,18 +107,15 @@ constructor(
        deviceItemAdapter.refreshDeviceItemList(deviceItem)
    }

    internal fun onDeviceItemUpdatedAtPosition(deviceItem: DeviceItem, position: Int) {
        deviceItemAdapter.refreshDeviceItem(deviceItem, position)
    }

    internal fun onBluetoothStateUpdated(isEnabled: Boolean) {
    internal fun onBluetoothStateUpdated(isEnabled: Boolean, subtitleResId: Int) {
        toggleView.isChecked = isEnabled
        subtitleTextView.text = context.getString(subtitleResId)
    }

    private fun setupToggle() {
        toggleView.isChecked = bluetoothToggleInitialValue
        toggleView.setOnCheckedChangeListener { _, isChecked ->
            mutableBluetoothStateSwitchedFlow.value = isChecked
            mutableBluetoothStateToggle.value = isChecked
            uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_TOGGLE_CLICKED)
        }
    }
@@ -128,7 +130,32 @@ constructor(
    internal inner class Adapter(private val onClickCallback: BluetoothTileDialogCallback) :
        RecyclerView.Adapter<Adapter.DeviceItemViewHolder>() {

        private val deviceItem: MutableList<DeviceItem> = mutableListOf()
        private val diffUtilCallback =
            object : DiffUtil.ItemCallback<DeviceItem>() {
                override fun areItemsTheSame(
                    deviceItem1: DeviceItem,
                    deviceItem2: DeviceItem
                ): Boolean {
                    return deviceItem1.cachedBluetoothDevice == deviceItem2.cachedBluetoothDevice
                }

                override fun areContentsTheSame(
                    deviceItem1: DeviceItem,
                    deviceItem2: DeviceItem
                ): Boolean {
                    return deviceItem1.type == deviceItem2.type &&
                        deviceItem1.cachedBluetoothDevice == deviceItem2.cachedBluetoothDevice &&
                        deviceItem1.deviceName == deviceItem2.deviceName &&
                        deviceItem1.connectionSummary == deviceItem2.connectionSummary &&
                        // Ignored the icon drawable
                        deviceItem1.iconWithDescription?.second ==
                            deviceItem2.iconWithDescription?.second &&
                        deviceItem1.background == deviceItem2.background &&
                        deviceItem1.isEnabled == deviceItem2.isEnabled
                }
            }

        private val asyncListDiffer = AsyncListDiffer(this, diffUtilCallback)

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DeviceItemViewHolder {
            val view =
@@ -137,29 +164,21 @@ constructor(
            return DeviceItemViewHolder(view)
        }

        override fun getItemCount() = deviceItem.size
        override fun getItemCount() = asyncListDiffer.currentList.size

        override fun onBindViewHolder(holder: DeviceItemViewHolder, position: Int) {
            val item = getItem(position)
            holder.bind(item, position, onClickCallback)
            holder.bind(item, onClickCallback)
        }

        internal fun getItem(position: Int) = deviceItem[position]
        internal fun getItem(position: Int) = asyncListDiffer.currentList[position]

        internal fun refreshDeviceItemList(updated: List<DeviceItem>) {
            deviceItem.clear()
            deviceItem.addAll(updated)
            notifyDataSetChanged()
        }

        internal fun refreshDeviceItem(updated: DeviceItem, position: Int) {
            deviceItem[position] = updated
            notifyItemChanged(position)
            asyncListDiffer.submitList(updated)
        }

        internal inner class DeviceItemViewHolder(view: View) : RecyclerView.ViewHolder(view) {
            private val container = view.requireViewById<View>(R.id.bluetooth_device_row)
            private val deviceView = view.requireViewById<View>(R.id.bluetooth_device)
            private val nameView = view.requireViewById<TextView>(R.id.bluetooth_device_name)
            private val summaryView = view.requireViewById<TextView>(R.id.bluetooth_device_summary)
            private val iconView = view.requireViewById<ImageView>(R.id.bluetooth_device_icon)
@@ -167,18 +186,16 @@ constructor(

            internal fun bind(
                item: DeviceItem,
                position: Int,
                deviceItemOnClickCallback: BluetoothTileDialogCallback
            ) {
                container.apply {
                    isEnabled = item.isEnabled
                    alpha = item.alpha
                    background = item.background
                }
                deviceView.setOnClickListener {
                    mutableClickedFlow.tryEmit(Pair(item, position))
                    background = item.background?.let { context.getDrawable(it) }
                    setOnClickListener {
                        mutableDeviceItemClick.tryEmit(item)
                        uiEventLogger.log(BluetoothTileDialogUiEvent.DEVICE_CLICKED)
                    }
                }
                nameView.text = item.deviceName
                summaryView.text = item.connectionSummary
                iconView.apply {
@@ -195,8 +212,6 @@ constructor(
    }

    internal companion object {
        const val ENABLED_ALPHA = 1.0f
        const val DISABLED_ALPHA = 0.3f
        const val MAX_DEVICE_ITEM_ENTRY = 3
        const val ACTION_BLUETOOTH_DEVICE_DETAILS =
            "com.android.settings.BLUETOOTH_DEVICE_DETAIL_SETTINGS"
Loading