summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Nicolo' Mazzucato <nicomazz@google.com> 2023-06-22 16:28:24 +0000
committer Nicolo' Mazzucato <nicomazz@google.com> 2023-06-26 20:33:41 +0000
commitcfd20f728ea5544771245a7a201df3bc203c6c0e (patch)
tree4e77ec85b0a9de8ce6981e7e5d04e3e934cd8f78
parent3214adb648f49cbce4b4ce51f8282ae4a337c7a2 (diff)
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
-rw-r--r--core/res/res/values/config.xml2
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--packages/SystemUI/res/drawable/stat_sys_connected_display.xml25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt79
5 files changed, 135 insertions, 2 deletions
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index f27535c65fe4..3be0d7ff06af 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -46,6 +46,7 @@
<item><xliff:g id="id">@string/status_bar_secure</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_managed_profile</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_cast</xliff:g></item>
+ <item><xliff:g id="id">@string/status_bar_connected_display</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_screen_record</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_vpn</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_bluetooth</xliff:g></item>
@@ -72,6 +73,7 @@
<string translatable="false" name="status_bar_sync_failing">sync_failing</string>
<string translatable="false" name="status_bar_sync_active">sync_active</string>
<string translatable="false" name="status_bar_cast">cast</string>
+ <string translatable="false" name="status_bar_connected_display">connected_display</string>
<string translatable="false" name="status_bar_hotspot">hotspot</string>
<string translatable="false" name="status_bar_location">location</string>
<string translatable="false" name="status_bar_bluetooth">bluetooth</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 2e5da3353752..83e3cb078708 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3099,6 +3099,7 @@
<java-symbol type="string" name="status_bar_sync_failing" />
<java-symbol type="string" name="status_bar_sync_active" />
<java-symbol type="string" name="status_bar_cast" />
+ <java-symbol type="string" name="status_bar_connected_display" />
<java-symbol type="string" name="status_bar_hotspot" />
<java-symbol type="string" name="status_bar_location" />
<java-symbol type="string" name="status_bar_bluetooth" />
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 000000000000..3f3d6f573f44
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_connected_display.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:fillColor="@android:color/white"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M320,840L320,760L400,760L400,680L160,680Q127,680 103.5,656.5Q80,633 80,600L80,200Q80,167 103.5,143.5Q127,120 160,120L800,120Q833,120 856.5,143.5Q880,167 880,200L880,600Q880,633 856.5,656.5Q833,680 800,680L560,680L560,760L640,760L640,840L320,840ZM160,600L800,600Q800,600 800,600Q800,600 800,600L800,200Q800,200 800,200Q800,200 800,200L160,200Q160,200 160,200Q160,200 160,200L160,600Q160,600 160,600Q160,600 160,600ZM160,600Q160,600 160,600Q160,600 160,600L160,200Q160,200 160,200Q160,200 160,200L160,200Q160,200 160,200Q160,200 160,200L160,600Q160,600 160,600Q160,600 160,600L160,600Z" />
+</vector> \ 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 e6b76ad0e00c..3b5aaeac6c21 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 6b18169bcd86..85fbef0d7bb6 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<NextAlarmController.NextAlarmChangeCallback>
+ 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<State>()
+ suspend fun emit(value: State) = flow.emit(value)
+ override val connectedDisplayState: Flow<State>
+ get() = flow
+ }
}