diff options
| author | 2023-06-22 16:28:24 +0000 | |
|---|---|---|
| committer | 2023-06-26 20:33:41 +0000 | |
| commit | cfd20f728ea5544771245a7a201df3bc203c6c0e (patch) | |
| tree | 4e77ec85b0a9de8ce6981e7e5d04e3e934cd8f78 | |
| parent | 3214adb648f49cbce4b4ce51f8282ae4a337c7a2 (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
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 + } } |