From cfd20f728ea5544771245a7a201df3bc203c6c0e Mon Sep 17 00:00:00 2001 From: Nicolo' Mazzucato Date: Thu, 22 Jun 2023 16:28:24 +0000 Subject: [PATCH] Add status bar icon when a connected display is attached Adds a "display" icon to the status bar when there is at least one external display attached. Bug: 286186256 Test: PhoneStatusBarPolicyTest Change-Id: I0eaf74732a5d97a8a694e9cad55ac529154ef47b --- core/res/res/values/config.xml | 2 + core/res/res/values/symbols.xml | 1 + .../drawable/stat_sys_connected_display.xml | 25 ++++++ .../statusbar/phone/PhoneStatusBarPolicy.java | 30 ++++++- .../phone/PhoneStatusBarPolicyTest.kt | 79 ++++++++++++++++++- 5 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 packages/SystemUI/res/drawable/stat_sys_connected_display.xml diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index f27535c65fe49..3be0d7ff06afb 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -46,6 +46,7 @@ @string/status_bar_secure @string/status_bar_managed_profile @string/status_bar_cast + @string/status_bar_connected_display @string/status_bar_screen_record @string/status_bar_vpn @string/status_bar_bluetooth @@ -72,6 +73,7 @@ sync_failing sync_active cast + connected_display hotspot location bluetooth diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 2e5da33537526..83e3cb0787084 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3099,6 +3099,7 @@ + diff --git a/packages/SystemUI/res/drawable/stat_sys_connected_display.xml b/packages/SystemUI/res/drawable/stat_sys_connected_display.xml new file mode 100644 index 0000000000000..3f3d6f573f44c --- /dev/null +++ b/packages/SystemUI/res/drawable/stat_sys_connected_display.xml @@ -0,0 +1,25 @@ + + + + \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index e6b76ad0e00cb..3b5aaeac6c211 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -48,6 +48,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.UiBackground; +import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor; import com.android.systemui.privacy.PrivacyItem; import com.android.systemui.privacy.PrivacyItemController; import com.android.systemui.privacy.PrivacyType; @@ -74,6 +75,7 @@ import com.android.systemui.statusbar.policy.SensorPrivacyController; import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.util.RingerModeTracker; +import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.time.DateFormatUtil; import java.io.PrintWriter; @@ -121,9 +123,12 @@ public class PhoneStatusBarPolicy private final String mSlotCamera; private final String mSlotSensorsOff; private final String mSlotScreenRecord; + private final String mSlotConnectedDisplay; private final int mDisplayId; private final SharedPreferences mSharedPreferences; private final DateFormatUtil mDateFormatUtil; + private final JavaAdapter mJavaAdapter; + private final ConnectedDisplayInteractor mConnectedDisplayInteractor; private final TelecomManager mTelecomManager; private final Handler mHandler; @@ -182,9 +187,13 @@ public class PhoneStatusBarPolicy @Main SharedPreferences sharedPreferences, DateFormatUtil dateFormatUtil, RingerModeTracker ringerModeTracker, PrivacyItemController privacyItemController, - PrivacyLogger privacyLogger) { + PrivacyLogger privacyLogger, + ConnectedDisplayInteractor connectedDisplayInteractor, + JavaAdapter javaAdapter + ) { mIconController = iconController; mCommandQueue = commandQueue; + mConnectedDisplayInteractor = connectedDisplayInteractor; mBroadcastDispatcher = broadcastDispatcher; mHandler = new Handler(looper); mResources = resources; @@ -211,8 +220,11 @@ public class PhoneStatusBarPolicy mTelecomManager = telecomManager; mRingerModeTracker = ringerModeTracker; mPrivacyLogger = privacyLogger; + mJavaAdapter = javaAdapter; mSlotCast = resources.getString(com.android.internal.R.string.status_bar_cast); + mSlotConnectedDisplay = resources.getString( + com.android.internal.R.string.status_bar_connected_display); mSlotHotspot = resources.getString(com.android.internal.R.string.status_bar_hotspot); mSlotBluetooth = resources.getString(com.android.internal.R.string.status_bar_bluetooth); mSlotTty = resources.getString(com.android.internal.R.string.status_bar_tty); @@ -285,6 +297,10 @@ public class PhoneStatusBarPolicy mIconController.setIcon(mSlotCast, R.drawable.stat_sys_cast, null); mIconController.setIconVisibility(mSlotCast, false); + // connected display + mIconController.setIcon(mSlotConnectedDisplay, R.drawable.stat_sys_connected_display, null); + mIconController.setIconVisibility(mSlotConnectedDisplay, false); + // hotspot mIconController.setIcon(mSlotHotspot, R.drawable.stat_sys_hotspot, mResources.getString(R.string.accessibility_status_bar_hotspot)); @@ -342,6 +358,8 @@ public class PhoneStatusBarPolicy mSensorPrivacyController.addCallback(mSensorPrivacyListener); mLocationController.addCallback(this); mRecordingController.addCallback(this); + mJavaAdapter.alwaysCollectFlow(mConnectedDisplayInteractor.getConnectedDisplayState(), + this::onConnectedDisplayAvailabilityChanged); mCommandQueue.addCallback(this); } @@ -800,4 +818,14 @@ public class PhoneStatusBarPolicy if (DEBUG) Log.d(TAG, "screenrecord: hiding icon"); mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, false)); } + + private void onConnectedDisplayAvailabilityChanged(ConnectedDisplayInteractor.State state) { + boolean visible = state != ConnectedDisplayInteractor.State.DISCONNECTED; + + if (DEBUG) { + Log.d(TAG, "connected_display: " + (visible ? "showing" : "hiding") + " icon"); + } + + mIconController.setIconVisibility(mSlotConnectedDisplay, visible); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt index 6b18169bcd864..85fbef0d7bb6a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt @@ -27,6 +27,8 @@ import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor +import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State import com.android.systemui.privacy.PrivacyItemController import com.android.systemui.privacy.logging.PrivacyLogger import com.android.systemui.screenrecord.RecordingController @@ -46,9 +48,17 @@ import com.android.systemui.statusbar.policy.UserInfoController import com.android.systemui.statusbar.policy.ZenModeController import com.android.systemui.util.RingerModeTracker import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.kotlin.JavaAdapter import com.android.systemui.util.mockito.capture import com.android.systemui.util.time.DateFormatUtil import com.android.systemui.util.time.FakeSystemClock +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -57,6 +67,8 @@ import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.anyInt +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.inOrder import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @@ -64,11 +76,13 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @RunWithLooper +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest class PhoneStatusBarPolicyTest : SysuiTestCase() { companion object { private const val ALARM_SLOT = "alarm" + private const val CONNECTED_DISPLAY_SLOT = "connected_display" } @Mock private lateinit var iconController: StatusBarIconController @@ -102,6 +116,9 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { private lateinit var alarmCallbackCaptor: ArgumentCaptor + private val testScope = TestScope(UnconfinedTestDispatcher()) + private val fakeConnectedDisplayStateProvider = FakeConnectedDisplayStateProvider() + private lateinit var executor: FakeExecutor private lateinit var statusBarPolicy: PhoneStatusBarPolicy private lateinit var testableLooper: TestableLooper @@ -164,6 +181,57 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { verify(iconController).setIconVisibility(ALARM_SLOT, true) } + @Test + fun connectedDisplay_connected_iconShown() = + testScope.runTest { + statusBarPolicy.init() + clearInvocations(iconController) + + fakeConnectedDisplayStateProvider.emit(State.CONNECTED) + runCurrent() + + verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true) + } + + @Test + fun connectedDisplay_disconnected_iconHidden() = + testScope.runTest { + statusBarPolicy.init() + clearInvocations(iconController) + + fakeConnectedDisplayStateProvider.emit(State.DISCONNECTED) + + verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, false) + } + + @Test + fun connectedDisplay_disconnectedThenConnected_iconShown() = + testScope.runTest { + statusBarPolicy.init() + clearInvocations(iconController) + + fakeConnectedDisplayStateProvider.emit(State.CONNECTED) + fakeConnectedDisplayStateProvider.emit(State.DISCONNECTED) + fakeConnectedDisplayStateProvider.emit(State.CONNECTED) + + inOrder(iconController).apply { + verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true) + verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, false) + verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true) + } + } + + @Test + fun connectedDisplay_connectSecureDisplay_iconShown() = + testScope.runTest { + statusBarPolicy.init() + clearInvocations(iconController) + + fakeConnectedDisplayStateProvider.emit(State.CONNECTED_SECURE) + + verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true) + } + private fun createAlarmInfo(): AlarmManager.AlarmClockInfo { return AlarmManager.AlarmClockInfo(10L, null) } @@ -200,7 +268,16 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { dateFormatUtil, ringerModeTracker, privacyItemController, - privacyLogger + privacyLogger, + fakeConnectedDisplayStateProvider, + JavaAdapter(testScope.backgroundScope) ) } + + private class FakeConnectedDisplayStateProvider : ConnectedDisplayInteractor { + private val flow = MutableSharedFlow() + suspend fun emit(value: State) = flow.emit(value) + override val connectedDisplayState: Flow + get() = flow + } }