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