Loading packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContent.kt +2 −2 Original line number Diff line number Diff line Loading @@ -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() }, ) } packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModel.kt +190 −183 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -91,29 +93,36 @@ 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 contentManager = createContentManager() contentManager.bind(view) contentManager.start() updateDetailsUI(context = view.context, dialog = null) } } /** Shows the bluetooth dialog. */ fun showDialog(expandable: Expandable?) { // If `QsDetailedView` is enabled, it should show the details view. QsDetailedView.assertInLegacyMode() cancelJob() if (view == null) { // Render with dialog job = coroutineScope.launch(context = mainDispatcher) { val dialogDelegate = createBluetoothTileDialog() dialog = dialogDelegate.createDialog() context = dialog.context val dialog = dialogDelegate.createDialog() val controller = expandable?.dialogTransitionController( Loading @@ -123,41 +132,41 @@ constructor( ) ) controller?.let { dialogTransitionAnimator.show( dialog, it, animateBackgroundBoundsChange = true, ) 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() 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 -> combine(deviceItemInteractor.deviceItemUpdate, deviceItemInteractor.showSeeAllUpdate) { deviceItem, showSeeAll -> updateDialogUiJob?.cancel() updateDialogUiJob = launch { contentManager.apply { onDeviceItemUpdated( deviceItem, showSeeAll, showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled(), showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled(), ) animateProgressBar(false) } Loading Loading @@ -217,7 +226,7 @@ constructor( } } } .launchIn(this@launch) .launchIn(this@coroutineScope) launch { activate() } } } Loading Loading @@ -264,8 +273,7 @@ constructor( } DeviceItemClick.Target.ACTION_ICON -> { deviceItemActionInteractor.onActionIconClick(it.deviceItem) { intent -> deviceItemActionInteractor.onActionIconClick(it.deviceItem) { intent -> startSettingsActivity(intent, it.clickedView) } } Loading Loading @@ -303,7 +311,6 @@ constructor( .onEach { bluetoothAutoOnInteractor.setEnabled(it) } .launchIn(this) } produce<Unit> { awaitClose { dialog?.cancel() } } } } Loading packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt +1 −1 Original line number Diff line number Diff line Loading @@ -51,7 +51,7 @@ constructor( initialUiProperties: BluetoothDetailsContentViewModel.UiProperties, cachedContentHeight: Int, dialogCallback: BluetoothTileDialogCallback, dimissListener: Runnable, dismissListener: Runnable, ): BluetoothTileDialogDelegate } Loading packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +1 −3 Original line number Diff line number Diff line Loading @@ -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); Loading packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModelTest.kt +48 −13 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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()) Loading @@ -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()) Loading @@ -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) Loading @@ -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()) Loading @@ -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) Loading @@ -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 Loading @@ -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) Loading Loading
packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContent.kt +2 −2 Original line number Diff line number Diff line Loading @@ -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() }, ) }
packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModel.kt +190 −183 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -91,29 +93,36 @@ 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 contentManager = createContentManager() contentManager.bind(view) contentManager.start() updateDetailsUI(context = view.context, dialog = null) } } /** Shows the bluetooth dialog. */ fun showDialog(expandable: Expandable?) { // If `QsDetailedView` is enabled, it should show the details view. QsDetailedView.assertInLegacyMode() cancelJob() if (view == null) { // Render with dialog job = coroutineScope.launch(context = mainDispatcher) { val dialogDelegate = createBluetoothTileDialog() dialog = dialogDelegate.createDialog() context = dialog.context val dialog = dialogDelegate.createDialog() val controller = expandable?.dialogTransitionController( Loading @@ -123,41 +132,41 @@ constructor( ) ) controller?.let { dialogTransitionAnimator.show( dialog, it, animateBackgroundBoundsChange = true, ) 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() 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 -> combine(deviceItemInteractor.deviceItemUpdate, deviceItemInteractor.showSeeAllUpdate) { deviceItem, showSeeAll -> updateDialogUiJob?.cancel() updateDialogUiJob = launch { contentManager.apply { onDeviceItemUpdated( deviceItem, showSeeAll, showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled(), showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled(), ) animateProgressBar(false) } Loading Loading @@ -217,7 +226,7 @@ constructor( } } } .launchIn(this@launch) .launchIn(this@coroutineScope) launch { activate() } } } Loading Loading @@ -264,8 +273,7 @@ constructor( } DeviceItemClick.Target.ACTION_ICON -> { deviceItemActionInteractor.onActionIconClick(it.deviceItem) { intent -> deviceItemActionInteractor.onActionIconClick(it.deviceItem) { intent -> startSettingsActivity(intent, it.clickedView) } } Loading Loading @@ -303,7 +311,6 @@ constructor( .onEach { bluetoothAutoOnInteractor.setEnabled(it) } .launchIn(this) } produce<Unit> { awaitClose { dialog?.cancel() } } } } Loading
packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt +1 −1 Original line number Diff line number Diff line Loading @@ -51,7 +51,7 @@ constructor( initialUiProperties: BluetoothDetailsContentViewModel.UiProperties, cachedContentHeight: Int, dialogCallback: BluetoothTileDialogCallback, dimissListener: Runnable, dismissListener: Runnable, ): BluetoothTileDialogDelegate } Loading
packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +1 −3 Original line number Diff line number Diff line Loading @@ -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); Loading
packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModelTest.kt +48 −13 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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()) Loading @@ -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()) Loading @@ -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) Loading @@ -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()) Loading @@ -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) Loading @@ -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 Loading @@ -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) Loading