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
+ }
}