diff options
| author | 2025-02-04 10:13:59 -0800 | |
|---|---|---|
| committer | 2025-02-23 11:31:21 -0800 | |
| commit | 4283472e0249b72058b1631b7d5179f7c0a8a2d6 (patch) | |
| tree | f053feaca62d2adfd9c400ad78b09d57ec043731 | |
| parent | 8dea4977bc267da81874809de279e50d593a927e (diff) | |
Refactor `showDetailsContent` in `BluetoothDetailsContentViewModel`.
Split `showDetailsContent` into two methods. One will be called by the dialog flow to show dialog, the other will be called by the details view flow to bind the view.
Test: BluetoothDetailsContentViewModelTest, BluetoothTileTest
Flag: NONE refactor
Change-Id: I8384bf509a8fd25ff59226ca6a1494626d05c005
6 files changed, 245 insertions, 203 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContent.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContent.kt index c6644562a9cb..8bdfd594711a 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContent.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContent.kt @@ -32,9 +32,9 @@ fun BluetoothDetailsContent(detailsContentViewModel: BluetoothDetailsContentView val view = LayoutInflater.from(context) .inflate(R.layout.bluetooth_tile_dialog, /* root= */ null) - detailsContentViewModel.showDetailsContent(/* expandable= */ null, view) + detailsContentViewModel.bindDetailsView(view) view }, - onRelease = { detailsContentViewModel.contentManager.releaseView() }, + onRelease = { detailsContentViewModel.unbindDetailsView() }, ) } diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModel.kt index 1c9cf8d14f74..7ecd27647238 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModel.kt @@ -44,6 +44,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.qs.flags.QsDetailedView import com.android.systemui.res.R import com.android.systemui.statusbar.phone.SystemUIDialog import javax.inject.Inject @@ -52,6 +53,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.produce +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filterNotNull @@ -91,221 +93,226 @@ constructor( private var job: Job? = null /** - * Shows the details content. + * Binds the bluetooth details view with BluetoothDetailsContentManager. * - * @param view The view from which the dialog is shown. If view is null, it should show the - * bluetooth tile details view. - * - * TODO: b/378513956 Refactor this method into 2. One is called by the dialog to show the - * dialog, another is called by the details view model to bind the view. + * @param view The view from which the bluetooth details content is shown. */ - fun showDetailsContent(expandable: Expandable?, view: View?) { + fun bindDetailsView(view: View) { + // If `QsDetailedView` is not enabled, it should show the dialog. + QsDetailedView.assertInNewMode() + cancelJob() job = coroutineScope.launch(context = mainDispatcher) { - var updateDeviceItemJob: Job? - var updateDialogUiJob: Job? = null - val dialog: SystemUIDialog? - val context: Context - - if (view == null) { - // Render with dialog - val dialogDelegate = createBluetoothTileDialog() - dialog = dialogDelegate.createDialog() - context = dialog.context - - val controller = - expandable?.dialogTransitionController( - DialogCuj( - InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, - INTERACTION_JANK_TAG, - ) - ) - controller?.let { - dialogTransitionAnimator.show( - dialog, - it, - animateBackgroundBoundsChange = true, - ) - } ?: dialog.show() - // contentManager is created after dialog.show - contentManager = dialogDelegate.contentManager - } else { - // Render with tile details view - dialog = null - context = view.context - contentManager = createContentManager() - contentManager.bind(view) - contentManager.start() - } + contentManager = createContentManager() + contentManager.bind(view) + contentManager.start() + updateDetailsUI(context = view.context, dialog = null) + } + } - updateDeviceItemJob = launch { - deviceItemInteractor.updateDeviceItems(context, DeviceFetchTrigger.FIRST_LOAD) - } + /** Shows the bluetooth dialog. */ + fun showDialog(expandable: Expandable?) { + // If `QsDetailedView` is enabled, it should show the details view. + QsDetailedView.assertInLegacyMode() - // deviceItemUpdate is emitted when device item list is done fetching, update UI and - // stop the progress bar. - combine( - deviceItemInteractor.deviceItemUpdate, - deviceItemInteractor.showSeeAllUpdate, - ) { deviceItem, showSeeAll -> - updateDialogUiJob?.cancel() - updateDialogUiJob = launch { - contentManager.apply { - onDeviceItemUpdated( - deviceItem, - showSeeAll, - showPairNewDevice = - bluetoothStateInteractor.isBluetoothEnabled(), - ) - animateProgressBar(false) - } - } - } - .launchIn(this) + cancelJob() - // deviceItemUpdateRequest is emitted when a bluetooth callback is called, re-fetch - // the device item list and animate the progress bar. - merge( - deviceItemInteractor.deviceItemUpdateRequest, - audioModeInteractor.isOngoingCall, - bluetoothDeviceMetadataInteractor.metadataUpdate, - if ( - audioSharingInteractor.audioSharingAvailable() && - audioSharingInteractor.qsDialogImprovementAvailable() - ) { - audioSharingInteractor.audioSourceStateUpdate - } else { - emptyFlow() - }, + job = + coroutineScope.launch(context = mainDispatcher) { + val dialogDelegate = createBluetoothTileDialog() + val dialog = dialogDelegate.createDialog() + + val controller = + expandable?.dialogTransitionController( + DialogCuj( + InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, + INTERACTION_JANK_TAG, + ) ) - .onEach { - contentManager.animateProgressBar(true) - updateDeviceItemJob?.cancel() - updateDeviceItemJob = launch { - deviceItemInteractor.updateDeviceItems( - context, - DeviceFetchTrigger.BLUETOOTH_CALLBACK_RECEIVED, + controller?.let { + dialogTransitionAnimator.show(dialog, it, animateBackgroundBoundsChange = true) + } ?: dialog.show() + // contentManager is created after dialog.show + contentManager = dialogDelegate.contentManager + updateDetailsUI(dialog.context, dialog) + } + } + + /** Unbinds the details view when it goes away. */ + fun unbindDetailsView() { + cancelJob() + contentManager.releaseView() + } + + private suspend fun updateDetailsUI(context: Context, dialog: SystemUIDialog?) { + coroutineScope { + var updateDeviceItemJob: Job? + var updateDialogUiJob: Job? = null + + updateDeviceItemJob = launch { + deviceItemInteractor.updateDeviceItems(context, DeviceFetchTrigger.FIRST_LOAD) + } + + // deviceItemUpdate is emitted when device item list is done fetching, update UI and + // stop the progress bar. + combine(deviceItemInteractor.deviceItemUpdate, deviceItemInteractor.showSeeAllUpdate) { + deviceItem, + showSeeAll -> + updateDialogUiJob?.cancel() + updateDialogUiJob = launch { + contentManager.apply { + onDeviceItemUpdated( + deviceItem, + showSeeAll, + showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled(), ) + animateProgressBar(false) } } - .launchIn(this) - - if (audioSharingInteractor.audioSharingAvailable()) { - if (audioSharingInteractor.qsDialogImprovementAvailable()) { - launch { audioSharingInteractor.handleAudioSourceWhenReady() } + } + .launchIn(this) + + // deviceItemUpdateRequest is emitted when a bluetooth callback is called, re-fetch + // the device item list and animate the progress bar. + merge( + deviceItemInteractor.deviceItemUpdateRequest, + audioModeInteractor.isOngoingCall, + bluetoothDeviceMetadataInteractor.metadataUpdate, + if ( + audioSharingInteractor.audioSharingAvailable() && + audioSharingInteractor.qsDialogImprovementAvailable() + ) { + audioSharingInteractor.audioSourceStateUpdate + } else { + emptyFlow() + }, + ) + .onEach { + contentManager.animateProgressBar(true) + updateDeviceItemJob?.cancel() + updateDeviceItemJob = launch { + deviceItemInteractor.updateDeviceItems( + context, + DeviceFetchTrigger.BLUETOOTH_CALLBACK_RECEIVED, + ) } + } + .launchIn(this) + + if (audioSharingInteractor.audioSharingAvailable()) { + if (audioSharingInteractor.qsDialogImprovementAvailable()) { + launch { audioSharingInteractor.handleAudioSourceWhenReady() } + } - audioSharingButtonViewModelFactory.create().run { - audioSharingButtonStateUpdate - .onEach { - when (it) { - is AudioSharingButtonState.Visible -> { - contentManager.onAudioSharingButtonUpdated( - VISIBLE, - context.getString(it.resId), - it.isActive, - ) - } - is AudioSharingButtonState.Gone -> { - contentManager.onAudioSharingButtonUpdated( - GONE, - label = null, - isActive = false, - ) - } + audioSharingButtonViewModelFactory.create().run { + audioSharingButtonStateUpdate + .onEach { + when (it) { + is AudioSharingButtonState.Visible -> { + contentManager.onAudioSharingButtonUpdated( + VISIBLE, + context.getString(it.resId), + it.isActive, + ) + } + is AudioSharingButtonState.Gone -> { + contentManager.onAudioSharingButtonUpdated( + GONE, + label = null, + isActive = false, + ) } } - .launchIn(this@launch) - launch { activate() } - } + } + .launchIn(this@coroutineScope) + launch { activate() } } + } - // bluetoothStateUpdate is emitted when bluetooth on/off state is changed, re-fetch - // the device item list. - bluetoothStateInteractor.bluetoothStateUpdate - .onEach { - contentManager.onBluetoothStateUpdated( - it, - UiProperties.build(it, isAutoOnToggleFeatureAvailable()), + // bluetoothStateUpdate is emitted when bluetooth on/off state is changed, re-fetch + // the device item list. + bluetoothStateInteractor.bluetoothStateUpdate + .onEach { + contentManager.onBluetoothStateUpdated( + it, + UiProperties.build(it, isAutoOnToggleFeatureAvailable()), + ) + updateDeviceItemJob?.cancel() + updateDeviceItemJob = launch { + deviceItemInteractor.updateDeviceItems( + context, + DeviceFetchTrigger.BLUETOOTH_STATE_CHANGE_RECEIVED, ) - 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. + contentManager.bluetoothStateToggle + .filterNotNull() + .onEach { + contentManager.animateProgressBar(true) + bluetoothStateInteractor.setBluetoothEnabled(it) + } + .launchIn(this) + + // deviceItemClick is emitted when user clicked on a device item. + contentManager.deviceItemClick + .filterNotNull() + .onEach { + when (it.target) { + DeviceItemClick.Target.ENTIRE_ROW -> { + deviceItemActionInteractor.onClick(it.deviceItem, dialog) + logger.logDeviceClick( + it.deviceItem.cachedBluetoothDevice.address, + it.deviceItem.type, ) } - } - .launchIn(this) - - // bluetoothStateToggle is emitted when user toggles the bluetooth state switch, - // send the new value to the bluetoothStateInteractor and animate the progress bar. - contentManager.bluetoothStateToggle - .filterNotNull() - .onEach { - contentManager.animateProgressBar(true) - bluetoothStateInteractor.setBluetoothEnabled(it) - } - .launchIn(this) - - // deviceItemClick is emitted when user clicked on a device item. - contentManager.deviceItemClick - .filterNotNull() - .onEach { - when (it.target) { - DeviceItemClick.Target.ENTIRE_ROW -> { - deviceItemActionInteractor.onClick(it.deviceItem, dialog) - logger.logDeviceClick( - it.deviceItem.cachedBluetoothDevice.address, - it.deviceItem.type, - ) - } - DeviceItemClick.Target.ACTION_ICON -> { - deviceItemActionInteractor.onActionIconClick(it.deviceItem) { intent - -> - startSettingsActivity(intent, it.clickedView) - } + DeviceItemClick.Target.ACTION_ICON -> { + deviceItemActionInteractor.onActionIconClick(it.deviceItem) { intent -> + startSettingsActivity(intent, it.clickedView) } } } - .launchIn(this) + } + .launchIn(this) + + // contentHeight is emitted when the dialog is dismissed. + contentManager.contentHeight + .filterNotNull() + .onEach { + withContext(backgroundDispatcher) { + sharedPreferences.edit().putInt(CONTENT_HEIGHT_PREF_KEY, it).apply() + } + } + .launchIn(this) - // contentHeight is emitted when the dialog is dismissed. - contentManager.contentHeight - .filterNotNull() + if (isAutoOnToggleFeatureAvailable()) { + // bluetoothAutoOnUpdate is emitted when bluetooth auto on on/off state is + // changed. + bluetoothAutoOnInteractor.isEnabled .onEach { - withContext(backgroundDispatcher) { - sharedPreferences.edit().putInt(CONTENT_HEIGHT_PREF_KEY, it).apply() - } + contentManager.onBluetoothAutoOnUpdated( + it, + if (it) R.string.turn_on_bluetooth_auto_info_enabled + else R.string.turn_on_bluetooth_auto_info_disabled, + ) } .launchIn(this) - if (isAutoOnToggleFeatureAvailable()) { - // bluetoothAutoOnUpdate is emitted when bluetooth auto on on/off state is - // changed. - bluetoothAutoOnInteractor.isEnabled - .onEach { - contentManager.onBluetoothAutoOnUpdated( - it, - if (it) R.string.turn_on_bluetooth_auto_info_enabled - else R.string.turn_on_bluetooth_auto_info_disabled, - ) - } - .launchIn(this) - - // bluetoothAutoOnToggle is emitted when user toggles the bluetooth auto on - // switch, send the new value to the bluetoothAutoOnInteractor. - contentManager.bluetoothAutoOnToggle - .filterNotNull() - .onEach { bluetoothAutoOnInteractor.setEnabled(it) } - .launchIn(this) - } - - produce<Unit> { awaitClose { dialog?.cancel() } } + // bluetoothAutoOnToggle is emitted when user toggles the bluetooth auto on + // switch, send the new value to the bluetoothAutoOnInteractor. + contentManager.bluetoothAutoOnToggle + .filterNotNull() + .onEach { bluetoothAutoOnInteractor.setEnabled(it) } + .launchIn(this) } + produce<Unit> { awaitClose { dialog?.cancel() } } + } } private suspend fun createBluetoothTileDialog(): BluetoothTileDialogDelegate { diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt index c55f60587527..bac685419f04 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt @@ -51,7 +51,7 @@ constructor( initialUiProperties: BluetoothDetailsContentViewModel.UiProperties, cachedContentHeight: Int, dialogCallback: BluetoothTileDialogCallback, - dimissListener: Runnable, + dismissListener: Runnable, ): BluetoothTileDialogDelegate } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index 2ac5bd3b1425..4b1c90f0c2ce 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -156,15 +156,13 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { private void handleClickEvent(@Nullable Expandable expandable) { if (mFeatureFlags.isEnabled(Flags.BLUETOOTH_QS_TILE_DIALOG)) { - mDetailsContentViewModel.get().showDetailsContent(expandable, /* view= */ null); + mDetailsContentViewModel.get().showDialog(expandable); } else { // Secondary clicks are header clicks, just toggle. toggleBluetooth(); } } - - @Override public Intent getLongClickIntent() { return new Intent(Settings.ACTION_BLUETOOTH_SETTINGS); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModelTest.kt index f04fc86d7230..dbbdcf7f9bac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModelTest.kt @@ -28,9 +28,11 @@ import com.android.internal.logging.UiEventLogger import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.flags.Flags +import com.android.systemui.Flags.FLAG_QS_TILE_DETAILED_VIEW import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.animation.Expandable +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.ActivityStarter @@ -194,9 +196,9 @@ class BluetoothDetailsContentViewModelTest : SysuiTestCase() { } @Test - fun testShowDetailsContent_noAnimation() { + fun testShowDialog_noAnimation() { testScope.runTest { - bluetoothDetailsContentViewModel.showDetailsContent(null, null) + bluetoothDetailsContentViewModel.showDialog(null) runCurrent() verify(mDialogTransitionAnimator, never()).show(any(), any(), any()) @@ -204,9 +206,9 @@ class BluetoothDetailsContentViewModelTest : SysuiTestCase() { } @Test - fun testShowDetailsContent_animated() { + fun testShowDialog_animated() { testScope.runTest { - bluetoothDetailsContentViewModel.showDetailsContent(expandable, null) + bluetoothDetailsContentViewModel.showDialog(expandable) runCurrent() verify(mDialogTransitionAnimator).show(any(), any(), anyBoolean()) @@ -214,9 +216,11 @@ class BluetoothDetailsContentViewModelTest : SysuiTestCase() { } @Test - fun testShowDetailsContent_animated_inDetailsView() { + @EnableSceneContainer + @EnableFlags(FLAG_QS_TILE_DETAILED_VIEW) + fun testBindDetailsView() { testScope.runTest { - bluetoothDetailsContentViewModel.showDetailsContent(expandable, mockView) + bluetoothDetailsContentViewModel.bindDetailsView(mockView) runCurrent() verify(bluetoothDetailsContentManager).bind(mockView) @@ -225,10 +229,10 @@ class BluetoothDetailsContentViewModelTest : SysuiTestCase() { } @Test - fun testShowDetailsContent_animated_callInBackgroundThread() { + fun testShowDialog_animated_callInBackgroundThread() { testScope.runTest { backgroundExecutor.execute { - bluetoothDetailsContentViewModel.showDetailsContent(expandable, null) + bluetoothDetailsContentViewModel.showDialog(expandable) runCurrent() verify(mDialogTransitionAnimator).show(any(), any(), anyBoolean()) @@ -237,10 +241,12 @@ class BluetoothDetailsContentViewModelTest : SysuiTestCase() { } @Test - fun testShowDetailsContent_animated_callInBackgroundThread_inDetailsView() { + @EnableSceneContainer + @EnableFlags(FLAG_QS_TILE_DETAILED_VIEW) + fun testBindDetailsView_callInBackgroundThread() { testScope.runTest { backgroundExecutor.execute { - bluetoothDetailsContentViewModel.showDetailsContent(expandable, mockView) + bluetoothDetailsContentViewModel.bindDetailsView(mockView) runCurrent() verify(bluetoothDetailsContentManager).bind(mockView) @@ -250,9 +256,21 @@ class BluetoothDetailsContentViewModelTest : SysuiTestCase() { } @Test - fun testShowDetailsContent_fetchDeviceItem() { + fun testShowDialog_fetchDeviceItem() { testScope.runTest { - bluetoothDetailsContentViewModel.showDetailsContent(null, null) + bluetoothDetailsContentViewModel.showDialog(null) + runCurrent() + + verify(deviceItemInteractor).deviceItemUpdate + } + } + + @Test + @EnableSceneContainer + @EnableFlags(FLAG_QS_TILE_DETAILED_VIEW) + fun testBindDetailsView_fetchDeviceItem() { + testScope.runTest { + bluetoothDetailsContentViewModel.bindDetailsView(mockView) runCurrent() verify(deviceItemInteractor).deviceItemUpdate @@ -263,7 +281,24 @@ class BluetoothDetailsContentViewModelTest : SysuiTestCase() { fun testStartSettingsActivity_activityLaunched_dialogDismissed() { testScope.runTest { whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice) - bluetoothDetailsContentViewModel.showDetailsContent(null, null) + bluetoothDetailsContentViewModel.showDialog(null) + runCurrent() + + val clickedView = View(context) + bluetoothDetailsContentViewModel.onPairNewDeviceClicked(clickedView) + + verify(uiEventLogger).log(BluetoothTileDialogUiEvent.PAIR_NEW_DEVICE_CLICKED) + verify(activityStarter).postStartActivityDismissingKeyguard(any(), anyInt(), nullable()) + } + } + + @Test + @EnableSceneContainer + @EnableFlags(FLAG_QS_TILE_DETAILED_VIEW) + fun testStartSettingsActivity_activityLaunched_detailsViewDismissed() { + testScope.runTest { + whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice) + bluetoothDetailsContentViewModel.bindDetailsView(mockView) runCurrent() val clickedView = View(context) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt index e8649df7b409..6f71df5958d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt @@ -96,7 +96,9 @@ class BluetoothTileTest(flags: FlagsParameterization) : SysuiTestCase() { qsLogger, bluetoothController, featureFlags, - ) { bluetoothDetailsContentViewModel } + ) { + bluetoothDetailsContentViewModel + } tile.initialize() testableLooper.processAllMessages() |