1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.dreams; 18 19 import android.app.AlarmManager; 20 import android.app.StatusBarManager; 21 import android.content.res.Resources; 22 import android.hardware.SensorPrivacyManager; 23 import android.net.ConnectivityManager; 24 import android.net.ConnectivityManager.NetworkCallback; 25 import android.net.Network; 26 import android.net.NetworkCapabilities; 27 import android.net.NetworkRequest; 28 import android.provider.Settings; 29 import android.text.format.DateFormat; 30 import android.util.PluralsMessageFormatter; 31 import android.view.View; 32 33 import androidx.annotation.Nullable; 34 35 import com.android.systemui.R; 36 import com.android.systemui.dagger.qualifiers.Main; 37 import com.android.systemui.dreams.DreamOverlayStatusBarItemsProvider.StatusBarItem; 38 import com.android.systemui.dreams.dagger.DreamOverlayComponent; 39 import com.android.systemui.log.LogBuffer; 40 import com.android.systemui.log.dagger.DreamLog; 41 import com.android.systemui.settings.UserTracker; 42 import com.android.systemui.statusbar.CrossFadeHelper; 43 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; 44 import com.android.systemui.statusbar.policy.NextAlarmController; 45 import com.android.systemui.statusbar.policy.ZenModeController; 46 import com.android.systemui.statusbar.window.StatusBarWindowStateController; 47 import com.android.systemui.touch.TouchInsetManager; 48 import com.android.systemui.util.ViewController; 49 import com.android.systemui.util.time.DateFormatUtil; 50 51 import java.util.ArrayList; 52 import java.util.List; 53 import java.util.Locale; 54 import java.util.Map; 55 import java.util.Optional; 56 import java.util.concurrent.Executor; 57 import java.util.stream.Collectors; 58 59 import javax.inject.Inject; 60 61 /** 62 * View controller for {@link DreamOverlayStatusBarView}. 63 */ 64 @DreamOverlayComponent.DreamOverlayScope 65 public class DreamOverlayStatusBarViewController extends ViewController<DreamOverlayStatusBarView> { 66 private static final String TAG = "DreamStatusBarCtrl"; 67 68 private final ConnectivityManager mConnectivityManager; 69 private final TouchInsetManager.TouchInsetSession mTouchInsetSession; 70 private final NextAlarmController mNextAlarmController; 71 private final AlarmManager mAlarmManager; 72 private final Resources mResources; 73 private final DateFormatUtil mDateFormatUtil; 74 private final IndividualSensorPrivacyController mSensorPrivacyController; 75 private final Optional<DreamOverlayNotificationCountProvider> 76 mDreamOverlayNotificationCountProvider; 77 private final ZenModeController mZenModeController; 78 private final DreamOverlayStateController mDreamOverlayStateController; 79 private final UserTracker mUserTracker; 80 private final StatusBarWindowStateController mStatusBarWindowStateController; 81 private final DreamOverlayStatusBarItemsProvider mStatusBarItemsProvider; 82 private final Executor mMainExecutor; 83 private final List<DreamOverlayStatusBarItemsProvider.StatusBarItem> mExtraStatusBarItems = 84 new ArrayList<>(); 85 private final DreamLogger mLogger; 86 87 private boolean mIsAttached; 88 89 // Whether dream entry animations are finished. 90 private boolean mEntryAnimationsFinished = false; 91 92 private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder() 93 .clearCapabilities() 94 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build(); 95 96 private final NetworkCallback mNetworkCallback = new NetworkCallback() { 97 @Override 98 public void onCapabilitiesChanged( 99 Network network, NetworkCapabilities networkCapabilities) { 100 updateWifiUnavailableStatusIcon(); 101 } 102 103 @Override 104 public void onAvailable(Network network) { 105 updateWifiUnavailableStatusIcon(); 106 } 107 108 @Override 109 public void onLost(Network network) { 110 updateWifiUnavailableStatusIcon(); 111 } 112 }; 113 114 private final DreamOverlayStateController.Callback mDreamOverlayStateCallback = 115 new DreamOverlayStateController.Callback() { 116 @Override 117 public void onStateChanged() { 118 mEntryAnimationsFinished = 119 mDreamOverlayStateController.areEntryAnimationsFinished(); 120 updateVisibility(); 121 updateAssistantAttentionIcon(); 122 } 123 }; 124 125 private final IndividualSensorPrivacyController.Callback mSensorCallback = 126 (sensor, blocked) -> updateMicCameraBlockedStatusIcon(); 127 128 private final NextAlarmController.NextAlarmChangeCallback mNextAlarmCallback = 129 nextAlarm -> updateAlarmStatusIcon(); 130 131 private final ZenModeController.Callback mZenModeCallback = new ZenModeController.Callback() { 132 @Override 133 public void onZenChanged(int zen) { 134 updatePriorityModeStatusIcon(); 135 } 136 }; 137 138 private final DreamOverlayNotificationCountProvider.Callback mNotificationCountCallback = 139 notificationCount -> showIcon( 140 DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS, 141 notificationCount > 0, 142 notificationCount > 0 143 ? buildNotificationsContentDescription(notificationCount) 144 : null); 145 146 private final DreamOverlayStatusBarItemsProvider.Callback mStatusBarItemsProviderCallback = 147 this::onStatusBarItemsChanged; 148 149 @Inject DreamOverlayStatusBarViewController( DreamOverlayStatusBarView view, @Main Resources resources, @Main Executor mainExecutor, ConnectivityManager connectivityManager, TouchInsetManager.TouchInsetSession touchInsetSession, AlarmManager alarmManager, NextAlarmController nextAlarmController, DateFormatUtil dateFormatUtil, IndividualSensorPrivacyController sensorPrivacyController, Optional<DreamOverlayNotificationCountProvider> dreamOverlayNotificationCountProvider, ZenModeController zenModeController, StatusBarWindowStateController statusBarWindowStateController, DreamOverlayStatusBarItemsProvider statusBarItemsProvider, DreamOverlayStateController dreamOverlayStateController, UserTracker userTracker, @DreamLog LogBuffer logBuffer)150 public DreamOverlayStatusBarViewController( 151 DreamOverlayStatusBarView view, 152 @Main Resources resources, 153 @Main Executor mainExecutor, 154 ConnectivityManager connectivityManager, 155 TouchInsetManager.TouchInsetSession touchInsetSession, 156 AlarmManager alarmManager, 157 NextAlarmController nextAlarmController, 158 DateFormatUtil dateFormatUtil, 159 IndividualSensorPrivacyController sensorPrivacyController, 160 Optional<DreamOverlayNotificationCountProvider> dreamOverlayNotificationCountProvider, 161 ZenModeController zenModeController, 162 StatusBarWindowStateController statusBarWindowStateController, 163 DreamOverlayStatusBarItemsProvider statusBarItemsProvider, 164 DreamOverlayStateController dreamOverlayStateController, 165 UserTracker userTracker, 166 @DreamLog LogBuffer logBuffer) { 167 super(view); 168 mResources = resources; 169 mMainExecutor = mainExecutor; 170 mConnectivityManager = connectivityManager; 171 mTouchInsetSession = touchInsetSession; 172 mAlarmManager = alarmManager; 173 mNextAlarmController = nextAlarmController; 174 mDateFormatUtil = dateFormatUtil; 175 mSensorPrivacyController = sensorPrivacyController; 176 mDreamOverlayNotificationCountProvider = dreamOverlayNotificationCountProvider; 177 mStatusBarWindowStateController = statusBarWindowStateController; 178 mStatusBarItemsProvider = statusBarItemsProvider; 179 mZenModeController = zenModeController; 180 mDreamOverlayStateController = dreamOverlayStateController; 181 mUserTracker = userTracker; 182 mLogger = new DreamLogger(logBuffer, TAG); 183 184 // Register to receive show/hide updates for the system status bar. Our custom status bar 185 // needs to hide when the system status bar is showing to ovoid overlapping status bars. 186 statusBarWindowStateController.addListener(this::onSystemStatusBarStateChanged); 187 } 188 189 @Override onViewAttached()190 protected void onViewAttached() { 191 mIsAttached = true; 192 193 mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback); 194 updateWifiUnavailableStatusIcon(); 195 196 mNextAlarmController.addCallback(mNextAlarmCallback); 197 updateAlarmStatusIcon(); 198 199 mSensorPrivacyController.addCallback(mSensorCallback); 200 updateMicCameraBlockedStatusIcon(); 201 202 mZenModeController.addCallback(mZenModeCallback); 203 updatePriorityModeStatusIcon(); 204 205 mDreamOverlayNotificationCountProvider.ifPresent( 206 provider -> provider.addCallback(mNotificationCountCallback)); 207 208 mStatusBarItemsProvider.addCallback(mStatusBarItemsProviderCallback); 209 210 mDreamOverlayStateController.addCallback(mDreamOverlayStateCallback); 211 } 212 213 @Override onViewDetached()214 protected void onViewDetached() { 215 mZenModeController.removeCallback(mZenModeCallback); 216 mSensorPrivacyController.removeCallback(mSensorCallback); 217 mNextAlarmController.removeCallback(mNextAlarmCallback); 218 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); 219 mDreamOverlayNotificationCountProvider.ifPresent( 220 provider -> provider.removeCallback(mNotificationCountCallback)); 221 mStatusBarItemsProvider.removeCallback(mStatusBarItemsProviderCallback); 222 mView.removeAllExtraStatusBarItemViews(); 223 mDreamOverlayStateController.setDreamOverlayStatusBarVisible(false); 224 mDreamOverlayStateController.removeCallback(mDreamOverlayStateCallback); 225 mTouchInsetSession.clear(); 226 227 mIsAttached = false; 228 } 229 230 /** 231 * Sets fade of the dream overlay status bar. 232 * 233 * No-op if the dream overlay status bar should not be shown. 234 */ setFadeAmount(float fadeAmount, boolean fadingOut)235 protected void setFadeAmount(float fadeAmount, boolean fadingOut) { 236 updateVisibility(); 237 238 if (mView.getVisibility() != View.VISIBLE) { 239 return; 240 } 241 242 if (fadingOut) { 243 CrossFadeHelper.fadeOut(mView, 1 - fadeAmount, /* remap= */ false); 244 } else { 245 CrossFadeHelper.fadeIn(mView, fadeAmount, /* remap= */ false); 246 } 247 } 248 249 /** 250 * Sets the y translation of the dream overlay status bar. 251 */ setTranslationY(float translationY)252 public void setTranslationY(float translationY) { 253 mView.setTranslationY(translationY); 254 } 255 shouldShowStatusBar()256 private boolean shouldShowStatusBar() { 257 return !mDreamOverlayStateController.isLowLightActive() 258 && !mStatusBarWindowStateController.windowIsShowing(); 259 } 260 updateWifiUnavailableStatusIcon()261 private void updateWifiUnavailableStatusIcon() { 262 final NetworkCapabilities capabilities = 263 mConnectivityManager.getNetworkCapabilities( 264 mConnectivityManager.getActiveNetwork()); 265 final boolean available = capabilities != null 266 && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI); 267 showIcon(DreamOverlayStatusBarView.STATUS_ICON_WIFI_UNAVAILABLE, !available, 268 R.string.wifi_unavailable_dream_overlay_content_description); 269 } 270 updateAlarmStatusIcon()271 private void updateAlarmStatusIcon() { 272 final AlarmManager.AlarmClockInfo alarm = 273 mAlarmManager.getNextAlarmClock(mUserTracker.getUserId()); 274 final boolean hasAlarm = alarm != null && alarm.getTriggerTime() > 0; 275 showIcon( 276 DreamOverlayStatusBarView.STATUS_ICON_ALARM_SET, 277 hasAlarm, 278 hasAlarm ? buildAlarmContentDescription(alarm) : null); 279 } 280 updateAssistantAttentionIcon()281 private void updateAssistantAttentionIcon() { 282 showIcon(DreamOverlayStatusBarView.STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE, 283 mDreamOverlayStateController.hasAssistantAttention(), 284 R.string.assistant_attention_content_description); 285 } 286 updateVisibility()287 private void updateVisibility() { 288 final int currentVisibility = mView.getVisibility(); 289 final int newVisibility = shouldShowStatusBar() ? View.VISIBLE : View.INVISIBLE; 290 if (currentVisibility == newVisibility) { 291 return; 292 } 293 294 mView.setVisibility(newVisibility); 295 mDreamOverlayStateController.setDreamOverlayStatusBarVisible(newVisibility == View.VISIBLE); 296 } 297 buildAlarmContentDescription(AlarmManager.AlarmClockInfo alarm)298 private String buildAlarmContentDescription(AlarmManager.AlarmClockInfo alarm) { 299 final String skeleton = mDateFormatUtil.is24HourFormat() ? "EHm" : "Ehma"; 300 final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton); 301 final String dateString = DateFormat.format(pattern, alarm.getTriggerTime()).toString(); 302 303 return mResources.getString(R.string.accessibility_quick_settings_alarm, dateString); 304 } 305 updateMicCameraBlockedStatusIcon()306 private void updateMicCameraBlockedStatusIcon() { 307 final boolean micBlocked = mSensorPrivacyController 308 .isSensorBlocked(SensorPrivacyManager.Sensors.MICROPHONE); 309 final boolean cameraBlocked = mSensorPrivacyController 310 .isSensorBlocked(SensorPrivacyManager.Sensors.CAMERA); 311 @DreamOverlayStatusBarView.StatusIconType int iconType = Resources.ID_NULL; 312 showIcon( 313 DreamOverlayStatusBarView.STATUS_ICON_CAMERA_DISABLED, 314 !micBlocked && cameraBlocked, 315 R.string.camera_blocked_dream_overlay_content_description); 316 showIcon( 317 DreamOverlayStatusBarView.STATUS_ICON_MIC_DISABLED, 318 micBlocked && !cameraBlocked, 319 R.string.microphone_blocked_dream_overlay_content_description); 320 showIcon( 321 DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED, 322 micBlocked && cameraBlocked, 323 R.string.camera_and_microphone_blocked_dream_overlay_content_description); 324 } 325 buildNotificationsContentDescription(int notificationCount)326 private String buildNotificationsContentDescription(int notificationCount) { 327 return PluralsMessageFormatter.format( 328 mResources, 329 Map.of("count", notificationCount), 330 R.string.dream_overlay_status_bar_notification_indicator); 331 } 332 updatePriorityModeStatusIcon()333 private void updatePriorityModeStatusIcon() { 334 showIcon( 335 DreamOverlayStatusBarView.STATUS_ICON_PRIORITY_MODE_ON, 336 mZenModeController.getZen() != Settings.Global.ZEN_MODE_OFF, 337 R.string.priority_mode_dream_overlay_content_description); 338 } 339 showIcon(@reamOverlayStatusBarView.StatusIconType int iconType, boolean show, int contentDescriptionResId)340 private void showIcon(@DreamOverlayStatusBarView.StatusIconType int iconType, boolean show, 341 int contentDescriptionResId) { 342 showIcon(iconType, show, mResources.getString(contentDescriptionResId)); 343 } 344 showIcon( @reamOverlayStatusBarView.StatusIconType int iconType, boolean show, @Nullable String contentDescription)345 private void showIcon( 346 @DreamOverlayStatusBarView.StatusIconType int iconType, 347 boolean show, 348 @Nullable String contentDescription) { 349 mMainExecutor.execute(() -> { 350 if (mIsAttached) { 351 mLogger.logShowOrHideStatusBarItem( 352 show, DreamOverlayStatusBarView.getLoggableStatusIconType(iconType)); 353 mView.showIcon(iconType, show, contentDescription); 354 } 355 }); 356 } 357 onSystemStatusBarStateChanged(@tatusBarManager.WindowVisibleState int state)358 private void onSystemStatusBarStateChanged(@StatusBarManager.WindowVisibleState int state) { 359 if (!mIsAttached || !mEntryAnimationsFinished) { 360 return; 361 } 362 363 mMainExecutor.execute(this::updateVisibility); 364 } 365 onStatusBarItemsChanged(List<StatusBarItem> newItems)366 private void onStatusBarItemsChanged(List<StatusBarItem> newItems) { 367 mMainExecutor.execute(() -> { 368 mExtraStatusBarItems.clear(); 369 mExtraStatusBarItems.addAll(newItems); 370 mView.setExtraStatusBarItemViews( 371 newItems 372 .stream() 373 .map(StatusBarItem::getView) 374 .collect(Collectors.toList())); 375 }); 376 } 377 } 378