diff options
4 files changed, 109 insertions, 28 deletions
diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml index 0534c6ee466f..a0f916c6827a 100644 --- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml +++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml @@ -17,6 +17,7 @@ <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:id="@+id/root" style="@style/Widget.SliceView.Panel" android:layout_width="wrap_content" @@ -53,17 +54,40 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_title" /> + <View + android:id="@+id/bluetooth_tile_dialog_progress_background" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintEnd_toEndOf="@id/bluetooth_tile_dialog_progress_animation" + app:layout_constraintStart_toStartOf="@id/bluetooth_tile_dialog_progress_animation" + app:layout_constraintTop_toTopOf="@id/bluetooth_tile_dialog_progress_animation" + app:layout_constraintBottom_toBottomOf="@id/bluetooth_tile_dialog_progress_animation" + android:background="?androidprv:attr/colorSurfaceVariant" /> + + <ProgressBar + android:id="@+id/bluetooth_tile_dialog_progress_animation" + android:layout_width="152dp" + android:layout_height="4dp" + android:layout_marginTop="16dp" + style="@style/TrimmedHorizontalProgressBar" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_subtitle" + android:visibility="invisible" + android:indeterminate="true" /> + <androidx.core.widget.NestedScrollView android:id="@+id/scroll_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="21dp" + android:minHeight="145dp" android:fillViewport="true" app:layout_constrainedHeight="true" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_subtitle"> + app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_progress_animation"> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/scroll_layout" @@ -81,7 +105,7 @@ android:paddingStart="36dp" android:text="@string/turn_on_bluetooth" android:clickable="false" - android:textAppearance="@style/TextAppearance.Dialog.Body.Message" + android:textAppearance="@style/TextAppearance.Dialog.Title" android:textSize="16sp" app:layout_constraintEnd_toStartOf="@+id/bluetooth_toggle" app:layout_constraintStart_toStartOf="parent" @@ -90,8 +114,7 @@ <Switch android:id="@+id/bluetooth_toggle" android:layout_width="wrap_content" - android:layout_height="48dp" - android:paddingTop="10dp" + android:layout_height="64dp" android:gravity="start|center_vertical" android:paddingEnd="40dp" android:contentDescription="@string/turn_on_bluetooth" @@ -107,7 +130,6 @@ android:id="@+id/device_list" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="20dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/bluetooth_toggle" diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt index 1805eb182cb1..1a06c38803af 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt @@ -22,12 +22,14 @@ import android.view.LayoutInflater import android.view.View import android.view.View.AccessibilityDelegate import android.view.View.GONE +import android.view.View.INVISIBLE import android.view.View.VISIBLE import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.accessibility.AccessibilityNodeInfo import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction import android.widget.ImageView +import android.widget.ProgressBar import android.widget.Switch import android.widget.TextView import androidx.recyclerview.widget.AsyncListDiffer @@ -92,6 +94,8 @@ constructor( private lateinit var pairNewDeviceButton: View private lateinit var deviceListView: RecyclerView private lateinit var scrollViewContent: View + private lateinit var progressBarAnimation: ProgressBar + private lateinit var progressBarBackground: View override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -122,6 +126,8 @@ constructor( scrollViewContent = this layoutParams.height = cachedContentHeight } + progressBarAnimation = requireViewById(R.id.bluetooth_tile_dialog_progress_animation) + progressBarBackground = requireViewById(R.id.bluetooth_tile_dialog_progress_background) } override fun start() { @@ -135,6 +141,17 @@ constructor( super.dismiss() } + internal suspend fun animateProgressBar(animate: Boolean) { + withContext(mainDispatcher) { + if (animate) { + showProgressBar() + } else { + delay(PROGRESS_BAR_ANIMATION_DURATION_MS) + hideProgressBar() + } + } + } + internal suspend fun onDeviceItemUpdated( deviceItem: List<DeviceItem>, showSeeAll: Boolean, @@ -190,6 +207,28 @@ constructor( } } + private fun showProgressBar() { + if ( + ::progressBarAnimation.isInitialized && + ::progressBarBackground.isInitialized && + progressBarAnimation.visibility != VISIBLE + ) { + progressBarAnimation.visibility = VISIBLE + progressBarBackground.visibility = INVISIBLE + } + } + + private fun hideProgressBar() { + if ( + ::progressBarAnimation.isInitialized && + ::progressBarBackground.isInitialized && + progressBarAnimation.visibility != INVISIBLE + ) { + progressBarAnimation.visibility = INVISIBLE + progressBarBackground.visibility = VISIBLE + } + } + internal inner class Adapter(private val onClickCallback: BluetoothTileDialogCallback) : RecyclerView.Adapter<Adapter.DeviceItemViewHolder>() { @@ -300,6 +339,7 @@ constructor( const val ACTION_PAIR_NEW_DEVICE = "android.settings.BLUETOOTH_PAIRING_SETTINGS" const val DISABLED_ALPHA = 0.3f const val ENABLED_ALPHA = 1f + const val PROGRESS_BAR_ANIMATION_DURATION_MS = 1500L private fun Boolean.toInt(): Int { return if (this) 1 else 0 diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt index 6d08f591690f..194e7bc955c9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt @@ -105,22 +105,29 @@ constructor( deviceItemInteractor.updateDeviceItems(context, DeviceFetchTrigger.FIRST_LOAD) } - bluetoothStateInteractor.bluetoothStateUpdate - .filterNotNull() + // deviceItemUpdate is emitted when device item list is done fetching, update UI and + // stop the progress bar. + deviceItemInteractor.deviceItemUpdate .onEach { - dialog.onBluetoothStateUpdated(it, getSubtitleResId(it)) - updateDeviceItemJob?.cancel() - updateDeviceItemJob = launch { - deviceItemInteractor.updateDeviceItems( - context, - DeviceFetchTrigger.BLUETOOTH_STATE_CHANGE_RECEIVED - ) + updateDialogUiJob?.cancel() + updateDialogUiJob = launch { + dialog.apply { + onDeviceItemUpdated( + it.take(MAX_DEVICE_ITEM_ENTRY), + showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY, + showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled + ) + animateProgressBar(false) + } } } .launchIn(this) + // deviceItemUpdateRequest is emitted when a bluetooth callback is called, re-fetch + // the device item list and animiate the progress bar. deviceItemInteractor.deviceItemUpdateRequest .onEach { + dialog.animateProgressBar(true) updateDeviceItemJob?.cancel() updateDeviceItemJob = launch { deviceItemInteractor.updateDeviceItems( @@ -131,27 +138,37 @@ constructor( } .launchIn(this) - deviceItemInteractor.deviceItemUpdate + // bluetoothStateUpdate is emitted when bluetooth on/off state is changed, re-fetch + // the device item list. + bluetoothStateInteractor.bluetoothStateUpdate + .filterNotNull() .onEach { - updateDialogUiJob?.cancel() - updateDialogUiJob = launch { - dialog.onDeviceItemUpdated( - it.take(MAX_DEVICE_ITEM_ENTRY), - showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY, - showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled + dialog.onBluetoothStateUpdated(it, getSubtitleResId(it)) + updateDeviceItemJob?.cancel() + updateDeviceItemJob = launch { + deviceItemInteractor.updateDeviceItems( + context, + DeviceFetchTrigger.BLUETOOTH_STATE_CHANGE_RECEIVED ) } } .launchIn(this) + // bluetoothStateToggle is emitted when user toggles the bluetooth state switch, + // send the new value to the bluetoothStateInteractor and animate the progress bar. dialog.bluetoothStateToggle - .onEach { bluetoothStateInteractor.isBluetoothEnabled = it } + .onEach { + dialog.animateProgressBar(true) + bluetoothStateInteractor.isBluetoothEnabled = it + } .launchIn(this) + // deviceItemClick is emitted when user clicked on a device item. dialog.deviceItemClick .onEach { deviceItemInteractor.updateDeviceItemOnClick(it) } .launchIn(this) + // contentHeight is emitted when the dialog is dismissed. dialog.contentHeight .onEach { withContext(backgroundDispatcher) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt index 33066d2092b8..9563cebf898b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt @@ -162,13 +162,15 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { @Test fun testStartSettingsActivity_activityLaunched_dialogDismissed() { - `when`(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice) - bluetoothTileDialogViewModel.showDialog(context, null) + testScope.runTest { + `when`(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice) + bluetoothTileDialogViewModel.showDialog(context, null) - val clickedView = View(context) - bluetoothTileDialogViewModel.onPairNewDeviceClicked(clickedView) + val clickedView = View(context) + bluetoothTileDialogViewModel.onPairNewDeviceClicked(clickedView) - verify(uiEventLogger).log(BluetoothTileDialogUiEvent.PAIR_NEW_DEVICE_CLICKED) - verify(activityStarter).postStartActivityDismissingKeyguard(any(), anyInt(), nullable()) + verify(uiEventLogger).log(BluetoothTileDialogUiEvent.PAIR_NEW_DEVICE_CLICKED) + verify(activityStarter).postStartActivityDismissingKeyguard(any(), anyInt(), nullable()) + } } } |