From ea7cb6fd68c773a90a9a9291fcd9a298d32506d4 Mon Sep 17 00:00:00 2001 From: Will Date: Fri, 18 Feb 2022 16:07:52 -0800 Subject: [PATCH] Add logic to show all status bar icons. This change adds all the logic to show/hide the dream overlay status bar icons. Test: atest DreamOverlayStatusBarControllerTest Bug: 216831310 Change-Id: I6e138c3f7e1cabbb92ebcb5032e057ef31f41f66 --- .../layout/dream_overlay_status_bar_view.xml | 3 +- packages/SystemUI/res/values/strings.xml | 7 +- .../dreams/DreamOverlayStatusBarView.java | 64 ++++- .../DreamOverlayStatusBarViewController.java | 206 +++++++++++--- ...eamOverlayStatusBarViewControllerTest.java | 269 ++++++++++++++++-- 5 files changed, 479 insertions(+), 70 deletions(-) diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml index 813787e8f9d02..1cbc3c2848748 100644 --- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml +++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml @@ -29,7 +29,6 @@ android:layout_width="@dimen/dream_overlay_notification_indicator_size" android:layout_height="@dimen/dream_overlay_notification_indicator_size" android:visibility="gone" - android:contentDescription="@string/dream_overlay_status_bar_notification_indicator" app:dotColor="@android:color/white" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" @@ -68,7 +67,7 @@ android:layout_width="@dimen/dream_overlay_status_bar_icon_size" android:layout_height="match_parent" android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin" - android:src="@drawable/ic_remove_circle" + android:src="@drawable/ic_qs_dnd_on" android:tint="@android:color/white" android:visibility="gone" android:contentDescription="@string/dream_overlay_status_bar_priority_mode" /> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index df16b0d45228d..6ec6c23849626 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2421,6 +2421,9 @@ Assistant guest mode enabled Camera and mic are off - - There are notifications + + {count, plural, + =1 {# notification} + other {# notifications} + } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java index 2d969206b4685..d2ab61149d261 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java @@ -16,23 +16,44 @@ package com.android.systemui.dreams; +import android.annotation.IntDef; +import android.annotation.Nullable; import android.content.Context; import android.util.AttributeSet; import android.view.View; -import android.widget.ImageView; import androidx.constraintlayout.widget.ConstraintLayout; -import com.android.internal.util.Preconditions; import com.android.systemui.R; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + /** * {@link DreamOverlayStatusBarView} is the view responsible for displaying the status bar in a * dream. The status bar displays conditional status icons such as "priority mode" and "no wifi". */ public class DreamOverlayStatusBarView extends ConstraintLayout { - private ImageView mWifiStatusView; + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "STATUS_ICON_" }, value = { + STATUS_ICON_NOTIFICATIONS, + STATUS_ICON_WIFI_UNAVAILABLE, + STATUS_ICON_ALARM_SET, + STATUS_ICON_MIC_CAMERA_DISABLED, + STATUS_ICON_PRIORITY_MODE_ON + }) + public @interface StatusIconType {} + public static final int STATUS_ICON_NOTIFICATIONS = 0; + public static final int STATUS_ICON_WIFI_UNAVAILABLE = 1; + public static final int STATUS_ICON_ALARM_SET = 2; + public static final int STATUS_ICON_MIC_CAMERA_DISABLED = 3; + public static final int STATUS_ICON_PRIORITY_MODE_ON = 4; + + private final Map mStatusIcons = new HashMap<>(); public DreamOverlayStatusBarView(Context context) { this(context, null); @@ -55,16 +76,35 @@ public class DreamOverlayStatusBarView extends ConstraintLayout { protected void onFinishInflate() { super.onFinishInflate(); - mWifiStatusView = Preconditions.checkNotNull(findViewById(R.id.dream_overlay_wifi_status), - "R.id.dream_overlay_wifi_status must not be null"); + mStatusIcons.put(STATUS_ICON_WIFI_UNAVAILABLE, + fetchStatusIconForResId(R.id.dream_overlay_wifi_status)); + mStatusIcons.put(STATUS_ICON_ALARM_SET, + fetchStatusIconForResId(R.id.dream_overlay_alarm_set)); + mStatusIcons.put(STATUS_ICON_MIC_CAMERA_DISABLED, + fetchStatusIconForResId(R.id.dream_overlay_camera_mic_off)); + mStatusIcons.put(STATUS_ICON_NOTIFICATIONS, + fetchStatusIconForResId(R.id.dream_overlay_notification_indicator)); + mStatusIcons.put(STATUS_ICON_PRIORITY_MODE_ON, + fetchStatusIconForResId(R.id.dream_overlay_priority_mode)); } - /** - * Whether to show the wifi status icon. - * @param show True if the wifi status icon should be shown. - */ - void showWifiStatus(boolean show) { - // Only show the wifi status icon when wifi isn't available. - mWifiStatusView.setVisibility(show ? View.VISIBLE : View.GONE); + void showIcon(@StatusIconType int iconType, boolean show) { + showIcon(iconType, show, null); + } + + void showIcon(@StatusIconType int iconType, boolean show, @Nullable String contentDescription) { + View icon = mStatusIcons.get(iconType); + if (icon == null) { + return; + } + if (show && contentDescription != null) { + icon.setContentDescription(contentDescription); + } + icon.setVisibility(show ? View.VISIBLE : View.GONE); + } + + private View fetchStatusIconForResId(int resId) { + final View statusIcon = findViewById(resId); + return Objects.requireNonNull(statusIcon); } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java index 32b2309ee83b9..a25a7423770ec 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java @@ -16,19 +16,35 @@ package com.android.systemui.dreams; -import android.annotation.IntDef; +import android.app.AlarmManager; +import android.content.res.Resources; +import android.hardware.SensorPrivacyManager; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; +import android.os.UserHandle; +import android.provider.Settings; +import android.service.notification.NotificationListenerService.RankingMap; +import android.service.notification.StatusBarNotification; +import android.text.format.DateFormat; +import android.util.PluralsMessageFormatter; +import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dreams.dagger.DreamOverlayComponent; +import com.android.systemui.statusbar.NotificationListener; +import com.android.systemui.statusbar.NotificationListener.NotificationHandler; +import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; +import com.android.systemui.statusbar.policy.NextAlarmController; +import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.touch.TouchInsetManager; import com.android.systemui.util.ViewController; +import com.android.systemui.util.time.DateFormatUtil; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; +import java.util.Locale; +import java.util.Map; import javax.inject.Inject; @@ -37,19 +53,15 @@ import javax.inject.Inject; */ @DreamOverlayComponent.DreamOverlayScope public class DreamOverlayStatusBarViewController extends ViewController { - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = { "WIFI_STATUS_" }, value = { - WIFI_STATUS_UNKNOWN, - WIFI_STATUS_UNAVAILABLE, - WIFI_STATUS_AVAILABLE - }) - private @interface WifiStatus {} - private static final int WIFI_STATUS_UNKNOWN = 0; - private static final int WIFI_STATUS_UNAVAILABLE = 1; - private static final int WIFI_STATUS_AVAILABLE = 2; - private final ConnectivityManager mConnectivityManager; private final TouchInsetManager.TouchInsetSession mTouchInsetSession; + private final NextAlarmController mNextAlarmController; + private final AlarmManager mAlarmManager; + private final Resources mResources; + private final DateFormatUtil mDateFormatUtil; + private final IndividualSensorPrivacyController mSensorPrivacyController; + private final NotificationListener mNotificationListener; + private final ZenModeController mZenModeController; private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder() .clearCapabilities() @@ -59,61 +71,183 @@ public class DreamOverlayStatusBarViewController extends ViewController updateMicCameraBlockedStatusIcon(); + + private final NextAlarmController.NextAlarmChangeCallback mNextAlarmCallback = + nextAlarm -> updateAlarmStatusIcon(); + + private final NotificationHandler mNotificationHandler = new NotificationHandler() { + @Override + public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) { + updateNotificationsStatusIcon(); + } + + @Override + public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) { + updateNotificationsStatusIcon(); + } + + @Override + public void onNotificationRemoved( + StatusBarNotification sbn, + RankingMap rankingMap, + int reason) { + updateNotificationsStatusIcon(); + } + + @Override + public void onNotificationRankingUpdate(RankingMap rankingMap) { + } + + @Override + public void onNotificationsInitialized() { + updateNotificationsStatusIcon(); + } + }; + + private final ZenModeController.Callback mZenModeCallback = new ZenModeController.Callback() { + @Override + public void onZenChanged(int zen) { + updatePriorityModeStatusIcon(); + } + }; @Inject public DreamOverlayStatusBarViewController( DreamOverlayStatusBarView view, + @Main Resources resources, ConnectivityManager connectivityManager, - TouchInsetManager.TouchInsetSession touchInsetSession) { + TouchInsetManager.TouchInsetSession touchInsetSession, + AlarmManager alarmManager, + NextAlarmController nextAlarmController, + DateFormatUtil dateFormatUtil, + IndividualSensorPrivacyController sensorPrivacyController, + NotificationListener notificationListener, + ZenModeController zenModeController) { super(view); + mResources = resources; mConnectivityManager = connectivityManager; mTouchInsetSession = touchInsetSession; + mAlarmManager = alarmManager; + mNextAlarmController = nextAlarmController; + mDateFormatUtil = dateFormatUtil; + mSensorPrivacyController = sensorPrivacyController; + mNotificationListener = notificationListener; + mZenModeController = zenModeController; + + // Handlers can be added to NotificationListener, but apparently they can't be removed. So + // add the handler here in the constructor rather than in onViewAttached to avoid confusion. + mNotificationListener.addNotificationHandler(mNotificationHandler); } @Override protected void onViewAttached() { - mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback); + updateNotificationsStatusIcon(); + + mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback); + updateWifiUnavailableStatusIcon(); + + mNextAlarmController.addCallback(mNextAlarmCallback); + updateAlarmStatusIcon(); + + mSensorPrivacyController.addCallback(mSensorCallback); + updateMicCameraBlockedStatusIcon(); + + mZenModeController.addCallback(mZenModeCallback); + updatePriorityModeStatusIcon(); - NetworkCapabilities capabilities = - mConnectivityManager.getNetworkCapabilities( - mConnectivityManager.getActiveNetwork()); - onWifiAvailabilityChanged( - capabilities != null - && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)); mTouchInsetSession.addViewToTracking(mView); } @Override protected void onViewDetached() { + mZenModeController.removeCallback(mZenModeCallback); + mSensorPrivacyController.removeCallback(mSensorCallback); + mNextAlarmController.removeCallback(mNextAlarmCallback); mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); mTouchInsetSession.clear(); } - /** - * Wifi availability has changed. Update the wifi status icon as appropriate. - * @param available Whether wifi is available. - */ - private void onWifiAvailabilityChanged(boolean available) { - final int newWifiStatus = available ? WIFI_STATUS_AVAILABLE : WIFI_STATUS_UNAVAILABLE; - if (mWifiStatus != newWifiStatus) { - mWifiStatus = newWifiStatus; - mView.showWifiStatus(mWifiStatus == WIFI_STATUS_UNAVAILABLE); + private void updateWifiUnavailableStatusIcon() { + final NetworkCapabilities capabilities = + mConnectivityManager.getNetworkCapabilities( + mConnectivityManager.getActiveNetwork()); + final boolean available = capabilities != null + && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI); + mView.showIcon(DreamOverlayStatusBarView.STATUS_ICON_WIFI_UNAVAILABLE, !available); + } + + private void updateAlarmStatusIcon() { + final AlarmManager.AlarmClockInfo alarm = + mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT); + final boolean hasAlarm = alarm != null && alarm.getTriggerTime() > 0; + mView.showIcon( + DreamOverlayStatusBarView.STATUS_ICON_ALARM_SET, + hasAlarm, + hasAlarm ? buildAlarmContentDescription(alarm) : null); + } + + private String buildAlarmContentDescription(AlarmManager.AlarmClockInfo alarm) { + final String skeleton = mDateFormatUtil.is24HourFormat() ? "EHm" : "Ehma"; + final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton); + final String dateString = DateFormat.format(pattern, alarm.getTriggerTime()).toString(); + + return mResources.getString(R.string.accessibility_quick_settings_alarm, dateString); + } + + private void updateMicCameraBlockedStatusIcon() { + final boolean micBlocked = mSensorPrivacyController + .isSensorBlocked(SensorPrivacyManager.Sensors.MICROPHONE); + final boolean cameraBlocked = mSensorPrivacyController + .isSensorBlocked(SensorPrivacyManager.Sensors.CAMERA); + mView.showIcon( + DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED, + micBlocked && cameraBlocked); + } + + private void updateNotificationsStatusIcon() { + if (mView == null) { + // It is possible for this method to be called before the view is attached, which makes + // null-checking necessary. + return; } + + final StatusBarNotification[] notifications = + mNotificationListener.getActiveNotifications(); + final int notificationCount = notifications != null ? notifications.length : 0; + mView.showIcon( + DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS, + notificationCount > 0, + notificationCount > 0 + ? buildNotificationsContentDescription(notificationCount) + : null); + } + + private String buildNotificationsContentDescription(int notificationCount) { + return PluralsMessageFormatter.format( + mResources, + Map.of("count", notificationCount), + R.string.dream_overlay_status_bar_notification_indicator); + } + + private void updatePriorityModeStatusIcon() { + mView.showIcon( + DreamOverlayStatusBarView.STATUS_ICON_PRIORITY_MODE_ON, + mZenModeController.getZen() != Settings.Global.ZEN_MODE_OFF); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java index ad8d44d62a5c6..a32ff801e8248 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java @@ -17,19 +17,34 @@ package com.android.systemui.dreams; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.AlarmManager; +import android.content.res.Resources; +import android.hardware.SensorPrivacyManager; import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; +import android.provider.Settings; +import android.service.notification.NotificationListenerService; +import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.NotificationListener; +import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; +import com.android.systemui.statusbar.policy.NextAlarmController; +import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.touch.TouchInsetManager; +import com.android.systemui.util.time.DateFormatUtil; import org.junit.Before; import org.junit.Test; @@ -41,6 +56,8 @@ import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidTestingRunner.class) public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { + private static final String NOTIFICATION_INDICATOR_FORMATTER_STRING = + "{count, plural, =1 {# notification} other {# notifications}}"; @Mock DreamOverlayStatusBarView mView; @@ -52,14 +69,55 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { Network mNetwork; @Mock TouchInsetManager.TouchInsetSession mTouchSession; + @Mock + Resources mResources; + @Mock + AlarmManager mAlarmManager; + @Mock + AlarmManager.AlarmClockInfo mAlarmClockInfo; + @Mock + NextAlarmController mNextAlarmController; + @Mock + DateFormatUtil mDateFormatUtil; + @Mock + IndividualSensorPrivacyController mSensorPrivacyController; + @Mock + StatusBarNotification mStatusBarNotification; + @Mock + NotificationListenerService.RankingMap mRankingMap; + @Mock + NotificationListener mNotificationListener; + @Mock + ZenModeController mZenModeController; DreamOverlayStatusBarViewController mController; @Before public void setup() { MockitoAnnotations.initMocks(this); - mController = new DreamOverlayStatusBarViewController(mView, mConnectivityManager, - mTouchSession); + + when(mResources.getString(R.string.dream_overlay_status_bar_notification_indicator)) + .thenReturn(NOTIFICATION_INDICATOR_FORMATTER_STRING); + + mController = new DreamOverlayStatusBarViewController( + mView, + mResources, + mConnectivityManager, + mTouchSession, + mAlarmManager, + mNextAlarmController, + mDateFormatUtil, + mSensorPrivacyController, + mNotificationListener, + mZenModeController); + } + + @Test + public void testOnViewAttachedAddsCallbacks() { + mController.onViewAttached(); + verify(mNextAlarmController).addCallback(any()); + verify(mSensorPrivacyController).addCallback(any()); + verify(mZenModeController).addCallback(any()); } @Test @@ -71,28 +129,97 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { } @Test - public void testOnViewAttachedShowsWifiStatusWhenWifiUnavailable() { + public void testOnViewAttachedShowsWifiIconWhenWifiUnavailable() { when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) .thenReturn(false); when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities); mController.onViewAttached(); - verify(mView).showWifiStatus(true); + verify(mView).showIcon(DreamOverlayStatusBarView.STATUS_ICON_WIFI_UNAVAILABLE, true); } @Test - public void testOnViewAttachedHidesWifiStatusWhenWifiAvailable() { + public void testOnViewAttachedHidesWifiIconWhenWifiAvailable() { when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) .thenReturn(true); when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities); mController.onViewAttached(); - verify(mView).showWifiStatus(false); + verify(mView).showIcon(DreamOverlayStatusBarView.STATUS_ICON_WIFI_UNAVAILABLE, false); } @Test - public void testOnViewAttachedShowsWifiStatusWhenNetworkCapabilitiesUnavailable() { + public void testOnViewAttachedShowsWifiIconWhenNetworkCapabilitiesUnavailable() { when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(null); mController.onViewAttached(); - verify(mView).showWifiStatus(true); + } + + @Test + public void testOnViewAttachedShowsAlarmIconWhenAlarmExists() { + when(mAlarmClockInfo.getTriggerTime()).thenReturn(1L); + when(mAlarmManager.getNextAlarmClock(anyInt())).thenReturn(mAlarmClockInfo); + mController.onViewAttached(); + verify(mView).showIcon( + eq(DreamOverlayStatusBarView.STATUS_ICON_ALARM_SET), eq(true), any()); + } + + @Test + public void testOnViewAttachedHidesAlarmIconWhenNoAlarmExists() { + when(mAlarmManager.getNextAlarmClock(anyInt())).thenReturn(null); + mController.onViewAttached(); + verify(mView).showIcon( + eq(DreamOverlayStatusBarView.STATUS_ICON_ALARM_SET), eq(false), isNull()); + } + + @Test + public void testOnViewAttachedShowsMicCameraIconWhenDisabled() { + when(mSensorPrivacyController.isSensorBlocked(SensorPrivacyManager.Sensors.MICROPHONE)) + .thenReturn(true); + when(mSensorPrivacyController.isSensorBlocked(SensorPrivacyManager.Sensors.CAMERA)) + .thenReturn(true); + mController.onViewAttached(); + verify(mView).showIcon(DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED, true); + } + + @Test + public void testOnViewAttachedHidesMicCameraIconWhenEnabled() { + when(mSensorPrivacyController.isSensorBlocked(SensorPrivacyManager.Sensors.MICROPHONE)) + .thenReturn(false); + when(mSensorPrivacyController.isSensorBlocked(SensorPrivacyManager.Sensors.CAMERA)) + .thenReturn(false); + mController.onViewAttached(); + verify(mView).showIcon(DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED, false); + } + + @Test + public void testOnViewAttachedShowsNotificationsIconWhenNotificationsExist() { + StatusBarNotification[] notifications = { mStatusBarNotification }; + when(mNotificationListener.getActiveNotifications()).thenReturn(notifications); + mController.onViewAttached(); + verify(mView).showIcon( + eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any()); + } + + @Test + public void testOnViewAttachedHidesNotificationsIconWhenNoNotificationsExist() { + when(mNotificationListener.getActiveNotifications()).thenReturn(null); + mController.onViewAttached(); + verify(mView).showIcon( + eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(false), isNull()); + } + + @Test + public void testOnViewAttachedShowsPriorityModeIconWhenEnabled() { + when(mZenModeController.getZen()).thenReturn( + Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS); + mController.onViewAttached(); + verify(mView).showIcon(DreamOverlayStatusBarView.STATUS_ICON_PRIORITY_MODE_ON, true); + } + + @Test + public void testOnViewAttachedHidesPriorityModeIconWhenDisabled() { + when(mZenModeController.getZen()).thenReturn( + Settings.Global.ZEN_MODE_OFF); + mController.onViewAttached(); + verify(mView).showIcon(DreamOverlayStatusBarView.STATUS_ICON_PRIORITY_MODE_ON, false); } @Test @@ -103,10 +230,19 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { } @Test - public void testWifiStatusHiddenWhenWifiBecomesAvailable() { - // Make sure wifi starts out unavailable when onViewAttached is called. + public void testOnViewDetachedRemovesCallbacks() { + mController.onViewDetached(); + verify(mNextAlarmController).removeCallback(any()); + verify(mSensorPrivacyController).removeCallback(any()); + verify(mZenModeController).removeCallback(any()); + } + + @Test + public void testWifiIconHiddenWhenWifiBecomesAvailable() { + // Make sure wifi starts out unavailable when onViewAttached is called, and then returns + // true on the second query. when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) - .thenReturn(false); + .thenReturn(false).thenReturn(true); when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities); mController.onViewAttached(); @@ -114,14 +250,15 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class); verify(mConnectivityManager).registerNetworkCallback(any(), callbackCapture.capture()); callbackCapture.getValue().onAvailable(mNetwork); - verify(mView).showWifiStatus(false); + verify(mView).showIcon(DreamOverlayStatusBarView.STATUS_ICON_WIFI_UNAVAILABLE, false); } @Test - public void testWifiStatusShownWhenWifiBecomesUnavailable() { - // Make sure wifi starts out available when onViewAttached is called. + public void testWifiIconShownWhenWifiBecomesUnavailable() { + // Make sure wifi starts out available when onViewAttached is called, then returns false + // on the second query. when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) - .thenReturn(true); + .thenReturn(true).thenReturn(false); when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities); mController.onViewAttached(); @@ -129,11 +266,11 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class); verify(mConnectivityManager).registerNetworkCallback(any(), callbackCapture.capture()); callbackCapture.getValue().onLost(mNetwork); - verify(mView).showWifiStatus(true); + verify(mView).showIcon(DreamOverlayStatusBarView.STATUS_ICON_WIFI_UNAVAILABLE, true); } @Test - public void testWifiStatusHiddenWhenCapabilitiesChange() { + public void testWifiIconHiddenWhenCapabilitiesChange() { // Make sure wifi starts out unavailable when onViewAttached is called. when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) .thenReturn(false); @@ -146,6 +283,102 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) .thenReturn(true); callbackCapture.getValue().onCapabilitiesChanged(mNetwork, mNetworkCapabilities); - verify(mView).showWifiStatus(false); + verify(mView).showIcon(DreamOverlayStatusBarView.STATUS_ICON_WIFI_UNAVAILABLE, false); + } + + @Test + public void testNotificationsIconShownWhenNotificationAdded() { + mController.onViewAttached(); + + StatusBarNotification[] notifications = { mStatusBarNotification }; + when(mNotificationListener.getActiveNotifications()).thenReturn(notifications); + + final ArgumentCaptor callbackCapture = + ArgumentCaptor.forClass(NotificationListener.NotificationHandler.class); + verify(mNotificationListener).addNotificationHandler(callbackCapture.capture()); + callbackCapture.getValue().onNotificationPosted(mStatusBarNotification, mRankingMap); + + verify(mView).showIcon( + eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any()); + } + + @Test + public void testNotificationsIconHiddenWhenLastNotificationRemoved() { + StatusBarNotification[] notifications = { mStatusBarNotification }; + when(mNotificationListener.getActiveNotifications()).thenReturn(notifications) + .thenReturn(null); + mController.onViewAttached(); + + final ArgumentCaptor callbackCapture = + ArgumentCaptor.forClass(NotificationListener.NotificationHandler.class); + verify(mNotificationListener).addNotificationHandler(callbackCapture.capture()); + callbackCapture.getValue().onNotificationPosted(mStatusBarNotification, mRankingMap); + + verify(mView).showIcon( + eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(false), any()); + } + + @Test + public void testMicCameraIconShownWhenSensorsBlocked() { + mController.onViewAttached(); + + when(mSensorPrivacyController.isSensorBlocked(SensorPrivacyManager.Sensors.MICROPHONE)) + .thenReturn(true); + when(mSensorPrivacyController.isSensorBlocked(SensorPrivacyManager.Sensors.CAMERA)) + .thenReturn(true); + + final ArgumentCaptor callbackCapture = + ArgumentCaptor.forClass(IndividualSensorPrivacyController.Callback.class); + verify(mSensorPrivacyController).addCallback(callbackCapture.capture()); + callbackCapture.getValue().onSensorBlockedChanged( + SensorPrivacyManager.Sensors.MICROPHONE, true); + + verify(mView).showIcon(DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED, true); + } + + @Test + public void testMicCameraIconHiddenWhenSensorsNotBlocked() { + when(mSensorPrivacyController.isSensorBlocked(SensorPrivacyManager.Sensors.MICROPHONE)) + .thenReturn(true).thenReturn(false); + when(mSensorPrivacyController.isSensorBlocked(SensorPrivacyManager.Sensors.CAMERA)) + .thenReturn(true).thenReturn(false); + mController.onViewAttached(); + + final ArgumentCaptor callbackCapture = + ArgumentCaptor.forClass(IndividualSensorPrivacyController.Callback.class); + verify(mSensorPrivacyController).addCallback(callbackCapture.capture()); + callbackCapture.getValue().onSensorBlockedChanged( + SensorPrivacyManager.Sensors.MICROPHONE, false); + + verify(mView).showIcon(DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED, false); + } + + @Test + public void testPriorityModeIconShownWhenZenModeEnabled() { + mController.onViewAttached(); + + when(mZenModeController.getZen()).thenReturn(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS); + + final ArgumentCaptor callbackCapture = + ArgumentCaptor.forClass(ZenModeController.Callback.class); + verify(mZenModeController).addCallback(callbackCapture.capture()); + callbackCapture.getValue().onZenChanged(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS); + + verify(mView).showIcon(DreamOverlayStatusBarView.STATUS_ICON_PRIORITY_MODE_ON, true); + } + + @Test + public void testPriorityModeIconHiddenWhenZenModeDisabled() { + when(mZenModeController.getZen()).thenReturn(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS) + .thenReturn(Settings.Global.ZEN_MODE_OFF); + mController.onViewAttached(); + + + final ArgumentCaptor callbackCapture = + ArgumentCaptor.forClass(ZenModeController.Callback.class); + verify(mZenModeController).addCallback(callbackCapture.capture()); + callbackCapture.getValue().onZenChanged(Settings.Global.ZEN_MODE_OFF); + + verify(mView).showIcon(DreamOverlayStatusBarView.STATUS_ICON_PRIORITY_MODE_ON, false); } }