1 /*
2  * Copyright (C) 2008 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.statusbar.phone;
18 
19 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.STATUS_BAR_WORK_ICON_ACCESSIBILITY;
20 
21 import android.annotation.Nullable;
22 import android.app.ActivityTaskManager;
23 import android.app.AlarmManager;
24 import android.app.AlarmManager.AlarmClockInfo;
25 import android.app.admin.DevicePolicyManager;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.SharedPreferences;
31 import android.content.res.Resources;
32 import android.media.AudioManager;
33 import android.os.Handler;
34 import android.os.Looper;
35 import android.os.RemoteException;
36 import android.os.UserManager;
37 import android.provider.Settings.Global;
38 import android.service.notification.ZenModeConfig;
39 import android.telecom.TelecomManager;
40 import android.text.format.DateFormat;
41 import android.util.Log;
42 import android.view.View;
43 
44 import androidx.lifecycle.Observer;
45 
46 import com.android.systemui.R;
47 import com.android.systemui.broadcast.BroadcastDispatcher;
48 import com.android.systemui.dagger.qualifiers.DisplayId;
49 import com.android.systemui.dagger.qualifiers.Main;
50 import com.android.systemui.dagger.qualifiers.UiBackground;
51 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor;
52 import com.android.systemui.privacy.PrivacyItem;
53 import com.android.systemui.privacy.PrivacyItemController;
54 import com.android.systemui.privacy.PrivacyType;
55 import com.android.systemui.privacy.logging.PrivacyLogger;
56 import com.android.systemui.qs.tiles.DndTile;
57 import com.android.systemui.qs.tiles.RotationLockTile;
58 import com.android.systemui.screenrecord.RecordingController;
59 import com.android.systemui.settings.UserTracker;
60 import com.android.systemui.statusbar.CommandQueue;
61 import com.android.systemui.statusbar.policy.BluetoothController;
62 import com.android.systemui.statusbar.policy.CastController;
63 import com.android.systemui.statusbar.policy.CastController.CastDevice;
64 import com.android.systemui.statusbar.policy.DataSaverController;
65 import com.android.systemui.statusbar.policy.DataSaverController.Listener;
66 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
67 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
68 import com.android.systemui.statusbar.policy.HotspotController;
69 import com.android.systemui.statusbar.policy.KeyguardStateController;
70 import com.android.systemui.statusbar.policy.LocationController;
71 import com.android.systemui.statusbar.policy.NextAlarmController;
72 import com.android.systemui.statusbar.policy.RotationLockController;
73 import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback;
74 import com.android.systemui.statusbar.policy.SensorPrivacyController;
75 import com.android.systemui.statusbar.policy.UserInfoController;
76 import com.android.systemui.statusbar.policy.ZenModeController;
77 import com.android.systemui.util.RingerModeTracker;
78 import com.android.systemui.util.kotlin.JavaAdapter;
79 import com.android.systemui.util.time.DateFormatUtil;
80 
81 import java.io.PrintWriter;
82 import java.io.StringWriter;
83 import java.util.List;
84 import java.util.Locale;
85 import java.util.concurrent.Executor;
86 
87 import javax.inject.Inject;
88 
89 /**
90  * This class contains all of the policy about which icons are installed in the status bar at boot
91  * time. It goes through the normal API for icons, even though it probably strictly doesn't need to.
92  */
93 public class PhoneStatusBarPolicy
94         implements BluetoothController.Callback,
95                 CommandQueue.Callbacks,
96                 RotationLockControllerCallback,
97                 Listener,
98                 ZenModeController.Callback,
99                 DeviceProvisionedListener,
100                 KeyguardStateController.Callback,
101                 PrivacyItemController.Callback,
102                 LocationController.LocationChangeCallback,
103                 RecordingController.RecordingStateChangeCallback {
104     private static final String TAG = "PhoneStatusBarPolicy";
105     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
106 
107     static final int LOCATION_STATUS_ICON_ID = PrivacyType.TYPE_LOCATION.getIconId();
108 
109     private final String mSlotCast;
110     private final String mSlotHotspot;
111     private final String mSlotBluetooth;
112     private final String mSlotTty;
113     private final String mSlotZen;
114     private final String mSlotMute;
115     private final String mSlotVibrate;
116     private final String mSlotAlarmClock;
117     private final String mSlotManagedProfile;
118     private final String mSlotRotate;
119     private final String mSlotHeadset;
120     private final String mSlotDataSaver;
121     private final String mSlotLocation;
122     private final String mSlotMicrophone;
123     private final String mSlotCamera;
124     private final String mSlotSensorsOff;
125     private final String mSlotScreenRecord;
126     private final String mSlotConnectedDisplay;
127     private final int mDisplayId;
128     private final SharedPreferences mSharedPreferences;
129     private final DateFormatUtil mDateFormatUtil;
130     private final JavaAdapter mJavaAdapter;
131     private final ConnectedDisplayInteractor mConnectedDisplayInteractor;
132     private final TelecomManager mTelecomManager;
133 
134     private final Handler mHandler;
135     private final CastController mCast;
136     private final HotspotController mHotspot;
137     private final NextAlarmController mNextAlarmController;
138     private final AlarmManager mAlarmManager;
139     private final UserInfoController mUserInfoController;
140     private final UserManager mUserManager;
141     private final UserTracker mUserTracker;
142     private final DevicePolicyManager mDevicePolicyManager;
143     private final StatusBarIconController mIconController;
144     private final CommandQueue mCommandQueue;
145     private final BroadcastDispatcher mBroadcastDispatcher;
146     private final Resources mResources;
147     private final RotationLockController mRotationLockController;
148     private final DataSaverController mDataSaver;
149     private final ZenModeController mZenController;
150     private final DeviceProvisionedController mProvisionedController;
151     private final KeyguardStateController mKeyguardStateController;
152     private final LocationController mLocationController;
153     private final PrivacyItemController mPrivacyItemController;
154     private final Executor mMainExecutor;
155     private final Executor mUiBgExecutor;
156     private final SensorPrivacyController mSensorPrivacyController;
157     private final RecordingController mRecordingController;
158     private final RingerModeTracker mRingerModeTracker;
159     private final PrivacyLogger mPrivacyLogger;
160 
161     private boolean mZenVisible;
162     private boolean mVibrateVisible;
163     private boolean mMuteVisible;
164     private boolean mCurrentUserSetup;
165 
166     private boolean mManagedProfileIconVisible = false;
167 
168     private BluetoothController mBluetooth;
169     private AlarmManager.AlarmClockInfo mNextAlarm;
170 
171     @Inject
PhoneStatusBarPolicy(StatusBarIconController iconController, CommandQueue commandQueue, BroadcastDispatcher broadcastDispatcher, @Main Executor mainExecutor, @UiBackground Executor uiBgExecutor, @Main Looper looper, @Main Resources resources, CastController castController, HotspotController hotspotController, BluetoothController bluetoothController, NextAlarmController nextAlarmController, UserInfoController userInfoController, RotationLockController rotationLockController, DataSaverController dataSaverController, ZenModeController zenModeController, DeviceProvisionedController deviceProvisionedController, KeyguardStateController keyguardStateController, LocationController locationController, SensorPrivacyController sensorPrivacyController, AlarmManager alarmManager, UserManager userManager, UserTracker userTracker, DevicePolicyManager devicePolicyManager, RecordingController recordingController, @Nullable TelecomManager telecomManager, @DisplayId int displayId, @Main SharedPreferences sharedPreferences, DateFormatUtil dateFormatUtil, RingerModeTracker ringerModeTracker, PrivacyItemController privacyItemController, PrivacyLogger privacyLogger, ConnectedDisplayInteractor connectedDisplayInteractor, JavaAdapter javaAdapter )172     public PhoneStatusBarPolicy(StatusBarIconController iconController,
173             CommandQueue commandQueue, BroadcastDispatcher broadcastDispatcher,
174             @Main Executor mainExecutor, @UiBackground Executor uiBgExecutor, @Main Looper looper,
175             @Main Resources resources, CastController castController,
176             HotspotController hotspotController, BluetoothController bluetoothController,
177             NextAlarmController nextAlarmController, UserInfoController userInfoController,
178             RotationLockController rotationLockController, DataSaverController dataSaverController,
179             ZenModeController zenModeController,
180             DeviceProvisionedController deviceProvisionedController,
181             KeyguardStateController keyguardStateController,
182             LocationController locationController,
183             SensorPrivacyController sensorPrivacyController, AlarmManager alarmManager,
184             UserManager userManager, UserTracker userTracker,
185             DevicePolicyManager devicePolicyManager, RecordingController recordingController,
186             @Nullable TelecomManager telecomManager, @DisplayId int displayId,
187             @Main SharedPreferences sharedPreferences, DateFormatUtil dateFormatUtil,
188             RingerModeTracker ringerModeTracker,
189             PrivacyItemController privacyItemController,
190             PrivacyLogger privacyLogger,
191             ConnectedDisplayInteractor connectedDisplayInteractor,
192             JavaAdapter javaAdapter
193     ) {
194         mIconController = iconController;
195         mCommandQueue = commandQueue;
196         mConnectedDisplayInteractor = connectedDisplayInteractor;
197         mBroadcastDispatcher = broadcastDispatcher;
198         mHandler = new Handler(looper);
199         mResources = resources;
200         mCast = castController;
201         mHotspot = hotspotController;
202         mBluetooth = bluetoothController;
203         mNextAlarmController = nextAlarmController;
204         mAlarmManager = alarmManager;
205         mUserInfoController = userInfoController;
206         mUserManager = userManager;
207         mUserTracker = userTracker;
208         mDevicePolicyManager = devicePolicyManager;
209         mRotationLockController = rotationLockController;
210         mDataSaver = dataSaverController;
211         mZenController = zenModeController;
212         mProvisionedController = deviceProvisionedController;
213         mKeyguardStateController = keyguardStateController;
214         mLocationController = locationController;
215         mPrivacyItemController = privacyItemController;
216         mSensorPrivacyController = sensorPrivacyController;
217         mRecordingController = recordingController;
218         mMainExecutor = mainExecutor;
219         mUiBgExecutor = uiBgExecutor;
220         mTelecomManager = telecomManager;
221         mRingerModeTracker = ringerModeTracker;
222         mPrivacyLogger = privacyLogger;
223         mJavaAdapter = javaAdapter;
224 
225         mSlotCast = resources.getString(com.android.internal.R.string.status_bar_cast);
226         mSlotConnectedDisplay = resources.getString(
227                 com.android.internal.R.string.status_bar_connected_display);
228         mSlotHotspot = resources.getString(com.android.internal.R.string.status_bar_hotspot);
229         mSlotBluetooth = resources.getString(com.android.internal.R.string.status_bar_bluetooth);
230         mSlotTty = resources.getString(com.android.internal.R.string.status_bar_tty);
231         mSlotZen = resources.getString(com.android.internal.R.string.status_bar_zen);
232         mSlotMute = resources.getString(com.android.internal.R.string.status_bar_mute);
233         mSlotVibrate = resources.getString(com.android.internal.R.string.status_bar_volume);
234         mSlotAlarmClock = resources.getString(com.android.internal.R.string.status_bar_alarm_clock);
235         mSlotManagedProfile = resources.getString(
236                 com.android.internal.R.string.status_bar_managed_profile);
237         mSlotRotate = resources.getString(com.android.internal.R.string.status_bar_rotate);
238         mSlotHeadset = resources.getString(com.android.internal.R.string.status_bar_headset);
239         mSlotDataSaver = resources.getString(com.android.internal.R.string.status_bar_data_saver);
240         mSlotLocation = resources.getString(com.android.internal.R.string.status_bar_location);
241         mSlotMicrophone = resources.getString(com.android.internal.R.string.status_bar_microphone);
242         mSlotCamera = resources.getString(com.android.internal.R.string.status_bar_camera);
243         mSlotSensorsOff = resources.getString(com.android.internal.R.string.status_bar_sensors_off);
244         mSlotScreenRecord = resources.getString(
245                 com.android.internal.R.string.status_bar_screen_record);
246 
247         mDisplayId = displayId;
248         mSharedPreferences = sharedPreferences;
249         mDateFormatUtil = dateFormatUtil;
250     }
251 
252     /** Initialize the object after construction. */
init()253     public void init() {
254         // listen for broadcasts
255         IntentFilter filter = new IntentFilter();
256 
257         filter.addAction(AudioManager.ACTION_HEADSET_PLUG);
258         filter.addAction(Intent.ACTION_SIM_STATE_CHANGED);
259         filter.addAction(TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED);
260         filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
261         filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
262         filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
263         mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, mHandler);
264         Observer<Integer> observer = ringer -> mHandler.post(this::updateVolumeZen);
265 
266         mRingerModeTracker.getRingerMode().observeForever(observer);
267         mRingerModeTracker.getRingerModeInternal().observeForever(observer);
268 
269         // listen for user / profile change.
270         mUserTracker.addCallback(mUserSwitchListener, mMainExecutor);
271 
272         // TTY status
273         updateTTY();
274 
275         // bluetooth status
276         updateBluetooth();
277 
278         // Alarm clock
279         mIconController.setIcon(mSlotAlarmClock, R.drawable.stat_sys_alarm, null);
280         mIconController.setIconVisibility(mSlotAlarmClock, false);
281 
282         // zen
283         mIconController.setIcon(mSlotZen, R.drawable.stat_sys_dnd, null);
284         mIconController.setIconVisibility(mSlotZen, false);
285 
286         // vibrate
287         mIconController.setIcon(mSlotVibrate, R.drawable.stat_sys_ringer_vibrate,
288                 mResources.getString(R.string.accessibility_ringer_vibrate));
289         mIconController.setIconVisibility(mSlotVibrate, false);
290         // mute
291         mIconController.setIcon(mSlotMute, R.drawable.stat_sys_ringer_silent,
292                 mResources.getString(R.string.accessibility_ringer_silent));
293         mIconController.setIconVisibility(mSlotMute, false);
294         updateVolumeZen();
295 
296         // cast
297         mIconController.setIcon(mSlotCast, R.drawable.stat_sys_cast, null);
298         mIconController.setIconVisibility(mSlotCast, false);
299 
300         // connected display
301         mIconController.setIcon(mSlotConnectedDisplay, R.drawable.stat_sys_connected_display, null);
302         mIconController.setIconVisibility(mSlotConnectedDisplay, false);
303 
304         // hotspot
305         mIconController.setIcon(mSlotHotspot, R.drawable.stat_sys_hotspot,
306                 mResources.getString(R.string.accessibility_status_bar_hotspot));
307         mIconController.setIconVisibility(mSlotHotspot, mHotspot.isHotspotEnabled());
308 
309         // managed profile
310         updateManagedProfile();
311 
312         // data saver
313         mIconController.setIcon(mSlotDataSaver, R.drawable.stat_sys_data_saver,
314                 mResources.getString(R.string.accessibility_data_saver_on));
315         mIconController.setIconVisibility(mSlotDataSaver, false);
316 
317 
318         // privacy items
319         String microphoneString = mResources.getString(PrivacyType.TYPE_MICROPHONE.getNameId());
320         String microphoneDesc = mResources.getString(
321                 R.string.ongoing_privacy_chip_content_multiple_apps, microphoneString);
322         mIconController.setIcon(mSlotMicrophone, PrivacyType.TYPE_MICROPHONE.getIconId(),
323                 microphoneDesc);
324         mIconController.setIconVisibility(mSlotMicrophone, false);
325 
326         String cameraString = mResources.getString(PrivacyType.TYPE_CAMERA.getNameId());
327         String cameraDesc = mResources.getString(
328                 R.string.ongoing_privacy_chip_content_multiple_apps, cameraString);
329         mIconController.setIcon(mSlotCamera, PrivacyType.TYPE_CAMERA.getIconId(),
330                 cameraDesc);
331         mIconController.setIconVisibility(mSlotCamera, false);
332 
333         mIconController.setIcon(mSlotLocation, LOCATION_STATUS_ICON_ID,
334                 mResources.getString(R.string.accessibility_location_active));
335         mIconController.setIconVisibility(mSlotLocation, false);
336 
337         // sensors off
338         mIconController.setIcon(mSlotSensorsOff, R.drawable.stat_sys_sensors_off,
339                 mResources.getString(R.string.accessibility_sensors_off_active));
340         mIconController.setIconVisibility(mSlotSensorsOff,
341                 mSensorPrivacyController.isSensorPrivacyEnabled());
342 
343         // screen record
344         mIconController.setIcon(mSlotScreenRecord, R.drawable.stat_sys_screen_record, null);
345         mIconController.setIconVisibility(mSlotScreenRecord, false);
346 
347         mRotationLockController.addCallback(this);
348         mBluetooth.addCallback(this);
349         mProvisionedController.addCallback(this);
350         mCurrentUserSetup = mProvisionedController.isCurrentUserSetup();
351         mZenController.addCallback(this);
352         mCast.addCallback(mCastCallback);
353         mHotspot.addCallback(mHotspotCallback);
354         mNextAlarmController.addCallback(mNextAlarmCallback);
355         mDataSaver.addCallback(this);
356         mKeyguardStateController.addCallback(this);
357         mPrivacyItemController.addCallback(this);
358         mSensorPrivacyController.addCallback(mSensorPrivacyListener);
359         mLocationController.addCallback(this);
360         mRecordingController.addCallback(this);
361         mJavaAdapter.alwaysCollectFlow(mConnectedDisplayInteractor.getConnectedDisplayState(),
362                 this::onConnectedDisplayAvailabilityChanged);
363 
364         mCommandQueue.addCallback(this);
365     }
366 
getManagedProfileAccessibilityString()367     private String getManagedProfileAccessibilityString() {
368         return mDevicePolicyManager.getResources().getString(
369                 STATUS_BAR_WORK_ICON_ACCESSIBILITY,
370                 () -> mResources.getString(R.string.accessibility_managed_profile));
371     }
372 
373     @Override
onZenChanged(int zen)374     public void onZenChanged(int zen) {
375         updateVolumeZen();
376     }
377 
378     @Override
onConfigChanged(ZenModeConfig config)379     public void onConfigChanged(ZenModeConfig config) {
380         updateVolumeZen();
381     }
382 
updateAlarm()383     private void updateAlarm() {
384         final AlarmClockInfo alarm = mAlarmManager.getNextAlarmClock(mUserTracker.getUserId());
385         final boolean hasAlarm = alarm != null && alarm.getTriggerTime() > 0;
386         int zen = mZenController.getZen();
387         final boolean zenNone = zen == Global.ZEN_MODE_NO_INTERRUPTIONS;
388         mIconController.setIcon(mSlotAlarmClock, zenNone ? R.drawable.stat_sys_alarm_dim
389                 : R.drawable.stat_sys_alarm, buildAlarmContentDescription());
390         mIconController.setIconVisibility(mSlotAlarmClock, mCurrentUserSetup && hasAlarm);
391     }
392 
buildAlarmContentDescription()393     private String buildAlarmContentDescription() {
394         if (mNextAlarm == null) {
395             return mResources.getString(R.string.status_bar_alarm);
396         }
397 
398         String skeleton = mDateFormatUtil.is24HourFormat() ? "EHm" : "Ehma";
399         String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
400         String dateString = DateFormat.format(pattern, mNextAlarm.getTriggerTime()).toString();
401 
402         return mResources.getString(R.string.accessibility_quick_settings_alarm, dateString);
403     }
404 
updateVolumeZen()405     private final void updateVolumeZen() {
406         boolean zenVisible = false;
407         int zenIconId = 0;
408         String zenDescription = null;
409 
410         boolean vibrateVisible = false;
411         boolean muteVisible = false;
412         int zen = mZenController.getZen();
413 
414         if (DndTile.isVisible(mSharedPreferences) || DndTile.isCombinedIcon(mSharedPreferences)) {
415             zenVisible = zen != Global.ZEN_MODE_OFF;
416             zenIconId = R.drawable.stat_sys_dnd;
417             zenDescription = mResources.getString(R.string.quick_settings_dnd_label);
418         } else if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) {
419             zenVisible = true;
420             zenIconId = R.drawable.stat_sys_dnd;
421             zenDescription = mResources.getString(R.string.interruption_level_none);
422         } else if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
423             zenVisible = true;
424             zenIconId = R.drawable.stat_sys_dnd;
425             zenDescription = mResources.getString(R.string.interruption_level_priority);
426         }
427 
428         if (!ZenModeConfig.isZenOverridingRinger(zen, mZenController.getConsolidatedPolicy())) {
429             final Integer ringerModeInternal =
430                     mRingerModeTracker.getRingerModeInternal().getValue();
431             if (ringerModeInternal != null) {
432                 if (ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {
433                     vibrateVisible = true;
434                 } else if (ringerModeInternal == AudioManager.RINGER_MODE_SILENT) {
435                     muteVisible = true;
436                 }
437             }
438         }
439 
440         if (zenVisible) {
441             mIconController.setIcon(mSlotZen, zenIconId, zenDescription);
442         }
443         if (zenVisible != mZenVisible) {
444             mIconController.setIconVisibility(mSlotZen, zenVisible);
445             mZenVisible = zenVisible;
446         }
447 
448         if (vibrateVisible != mVibrateVisible) {
449             mIconController.setIconVisibility(mSlotVibrate, vibrateVisible);
450             mVibrateVisible = vibrateVisible;
451         }
452 
453         if (muteVisible != mMuteVisible) {
454             mIconController.setIconVisibility(mSlotMute, muteVisible);
455             mMuteVisible = muteVisible;
456         }
457 
458         updateAlarm();
459     }
460 
461     @Override
onBluetoothDevicesChanged()462     public void onBluetoothDevicesChanged() {
463         updateBluetooth();
464     }
465 
466     @Override
onBluetoothStateChange(boolean enabled)467     public void onBluetoothStateChange(boolean enabled) {
468         updateBluetooth();
469     }
470 
updateBluetooth()471     private final void updateBluetooth() {
472         int iconId = R.drawable.stat_sys_data_bluetooth_connected;
473         String contentDescription =
474                 mResources.getString(R.string.accessibility_quick_settings_bluetooth_on);
475         boolean bluetoothVisible = false;
476         if (mBluetooth != null) {
477             if (mBluetooth.isBluetoothConnected()
478                     && (mBluetooth.isBluetoothAudioActive()
479                     || !mBluetooth.isBluetoothAudioProfileOnly())) {
480                 contentDescription = mResources.getString(
481                         R.string.accessibility_bluetooth_connected);
482                 bluetoothVisible = mBluetooth.isBluetoothEnabled();
483             }
484         }
485 
486         mIconController.setIcon(mSlotBluetooth, iconId, contentDescription);
487         mIconController.setIconVisibility(mSlotBluetooth, bluetoothVisible);
488     }
489 
updateTTY()490     private final void updateTTY() {
491         if (mTelecomManager == null) {
492             updateTTY(TelecomManager.TTY_MODE_OFF);
493         } else {
494             updateTTY(mTelecomManager.getCurrentTtyMode());
495         }
496     }
497 
updateTTY(int currentTtyMode)498     private final void updateTTY(int currentTtyMode) {
499         boolean enabled = currentTtyMode != TelecomManager.TTY_MODE_OFF;
500 
501         if (DEBUG) Log.v(TAG, "updateTTY: enabled: " + enabled);
502 
503         if (enabled) {
504             // TTY is on
505             if (DEBUG) Log.v(TAG, "updateTTY: set TTY on");
506             mIconController.setIcon(mSlotTty, R.drawable.stat_sys_tty_mode,
507                     mResources.getString(R.string.accessibility_tty_enabled));
508             mIconController.setIconVisibility(mSlotTty, true);
509         } else {
510             // TTY is off
511             if (DEBUG) Log.v(TAG, "updateTTY: set TTY off");
512             mIconController.setIconVisibility(mSlotTty, false);
513         }
514     }
515 
updateCast()516     private void updateCast() {
517         boolean isCasting = false;
518         for (CastDevice device : mCast.getCastDevices()) {
519             if (device.state == CastDevice.STATE_CONNECTING
520                     || device.state == CastDevice.STATE_CONNECTED) {
521                 isCasting = true;
522                 break;
523             }
524         }
525         if (DEBUG) Log.v(TAG, "updateCast: isCasting: " + isCasting);
526         mHandler.removeCallbacks(mRemoveCastIconRunnable);
527         if (isCasting && !mRecordingController.isRecording()) { // screen record has its own icon
528             mIconController.setIcon(mSlotCast, R.drawable.stat_sys_cast,
529                     mResources.getString(R.string.accessibility_casting));
530             mIconController.setIconVisibility(mSlotCast, true);
531         } else {
532             // don't turn off the screen-record icon for a few seconds, just to make sure the user
533             // has seen it
534             if (DEBUG) Log.v(TAG, "updateCast: hiding icon in 3 sec...");
535             mHandler.postDelayed(mRemoveCastIconRunnable, 3000);
536         }
537     }
538 
updateManagedProfile()539     private void updateManagedProfile() {
540         // getLastResumedActivityUserId needs to acquire the AM lock, which may be contended in
541         // some cases. Since it doesn't really matter here whether it's updated in this frame
542         // or in the next one, we call this method from our UI offload thread.
543         mUiBgExecutor.execute(() -> {
544             final int userId;
545             try {
546                 userId = ActivityTaskManager.getService().getLastResumedActivityUserId();
547                 boolean isManagedProfile = mUserManager.isManagedProfile(userId);
548                 String accessibilityString = getManagedProfileAccessibilityString();
549                 mMainExecutor.execute(() -> {
550                     final boolean showIcon;
551                     if (isManagedProfile && (!mKeyguardStateController.isShowing()
552                             || mKeyguardStateController.isOccluded())) {
553                         showIcon = true;
554                         mIconController.setIcon(mSlotManagedProfile,
555                                 R.drawable.stat_sys_managed_profile_status,
556                                 accessibilityString);
557                     } else {
558                         showIcon = false;
559                     }
560                     if (mManagedProfileIconVisible != showIcon) {
561                         mIconController.setIconVisibility(mSlotManagedProfile, showIcon);
562                         mManagedProfileIconVisible = showIcon;
563                     }
564                 });
565             } catch (RemoteException e) {
566                 Log.w(TAG, "updateManagedProfile: ", e);
567             }
568         });
569     }
570 
571     private final UserTracker.Callback mUserSwitchListener =
572             new UserTracker.Callback() {
573                 @Override
574                 public void onUserChanging(int newUser, Context userContext) {
575                     mHandler.post(() -> mUserInfoController.reloadUserInfo());
576                 }
577 
578                 @Override
579                 public void onUserChanged(int newUser, Context userContext) {
580                     mHandler.post(() -> {
581                         updateAlarm();
582                         updateManagedProfile();
583                         onUserSetupChanged();
584                     });
585                 }
586             };
587 
588     private final HotspotController.Callback mHotspotCallback = new HotspotController.Callback() {
589         @Override
590         public void onHotspotChanged(boolean enabled, int numDevices) {
591             mIconController.setIconVisibility(mSlotHotspot, enabled);
592         }
593     };
594 
595     private final CastController.Callback mCastCallback = new CastController.Callback() {
596         @Override
597         public void onCastDevicesChanged() {
598             updateCast();
599         }
600     };
601 
602     private final NextAlarmController.NextAlarmChangeCallback mNextAlarmCallback =
603             new NextAlarmController.NextAlarmChangeCallback() {
604                 @Override
605                 public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
606                     mNextAlarm = nextAlarm;
607                     updateAlarm();
608                 }
609             };
610 
611     private final SensorPrivacyController.OnSensorPrivacyChangedListener mSensorPrivacyListener =
612             new SensorPrivacyController.OnSensorPrivacyChangedListener() {
613                 @Override
614                 public void onSensorPrivacyChanged(boolean enabled) {
615                     mHandler.post(() -> {
616                         mIconController.setIconVisibility(mSlotSensorsOff, enabled);
617                     });
618                 }
619             };
620 
621     @Override
appTransitionStarting(int displayId, long startTime, long duration, boolean forced)622     public void appTransitionStarting(int displayId, long startTime, long duration,
623             boolean forced) {
624         if (mDisplayId == displayId) {
625             updateManagedProfile();
626         }
627     }
628 
629     @Override
appTransitionFinished(int displayId)630     public void appTransitionFinished(int displayId) {
631         if (mDisplayId == displayId) {
632             updateManagedProfile();
633         }
634     }
635 
636     @Override
onKeyguardShowingChanged()637     public void onKeyguardShowingChanged() {
638         updateManagedProfile();
639     }
640 
641     @Override
onUserSetupChanged()642     public void onUserSetupChanged() {
643         boolean userSetup = mProvisionedController.isCurrentUserSetup();
644         if (mCurrentUserSetup == userSetup) return;
645         mCurrentUserSetup = userSetup;
646         updateAlarm();
647     }
648 
649     @Override
onRotationLockStateChanged(boolean rotationLocked, boolean affordanceVisible)650     public void onRotationLockStateChanged(boolean rotationLocked, boolean affordanceVisible) {
651         boolean portrait = RotationLockTile.isCurrentOrientationLockPortrait(
652                 mRotationLockController, mResources);
653         if (rotationLocked) {
654             if (portrait) {
655                 mIconController.setIcon(mSlotRotate, R.drawable.stat_sys_rotate_portrait,
656                         mResources.getString(R.string.accessibility_rotation_lock_on_portrait));
657             } else {
658                 mIconController.setIcon(mSlotRotate, R.drawable.stat_sys_rotate_landscape,
659                         mResources.getString(R.string.accessibility_rotation_lock_on_landscape));
660             }
661             mIconController.setIconVisibility(mSlotRotate, true);
662         } else {
663             mIconController.setIconVisibility(mSlotRotate, false);
664         }
665     }
666 
updateHeadsetPlug(Intent intent)667     private void updateHeadsetPlug(Intent intent) {
668         boolean connected = intent.getIntExtra("state", 0) != 0;
669         boolean hasMic = intent.getIntExtra("microphone", 0) != 0;
670         if (connected) {
671             String contentDescription = mResources.getString(hasMic
672                     ? R.string.accessibility_status_bar_headset
673                     : R.string.accessibility_status_bar_headphones);
674             mIconController.setIcon(mSlotHeadset, hasMic ? R.drawable.stat_sys_headset_mic
675                     : R.drawable.stat_sys_headset, contentDescription);
676             mIconController.setIconVisibility(mSlotHeadset, true);
677         } else {
678             mIconController.setIconVisibility(mSlotHeadset, false);
679         }
680     }
681 
682     @Override
onDataSaverChanged(boolean isDataSaving)683     public void onDataSaverChanged(boolean isDataSaving) {
684         mIconController.setIconVisibility(mSlotDataSaver, isDataSaving);
685     }
686 
687     @Override  // PrivacyItemController.Callback
onPrivacyItemsChanged(List<PrivacyItem> privacyItems)688     public void onPrivacyItemsChanged(List<PrivacyItem> privacyItems) {
689         updatePrivacyItems(privacyItems);
690     }
691 
updatePrivacyItems(List<PrivacyItem> items)692     private void updatePrivacyItems(List<PrivacyItem> items) {
693         boolean showCamera = false;
694         boolean showMicrophone = false;
695         boolean showLocation = false;
696         for (PrivacyItem item : items) {
697             if (item == null /* b/124234367 */) {
698                 Log.e(TAG, "updatePrivacyItems - null item found");
699                 StringWriter out = new StringWriter();
700                 mPrivacyItemController.dump(new PrintWriter(out), null);
701                 // Throw so we can look into this
702                 throw new NullPointerException(out.toString());
703             }
704             switch (item.getPrivacyType()) {
705                 case TYPE_CAMERA:
706                     showCamera = true;
707                     break;
708                 case TYPE_LOCATION:
709                     showLocation = true;
710                     break;
711                 case TYPE_MICROPHONE:
712                     showMicrophone = true;
713                     break;
714             }
715         }
716 
717         // Disabling for now, but keeping the log
718         /*
719         mIconController.setIconVisibility(mSlotCamera, showCamera);
720         mIconController.setIconVisibility(mSlotMicrophone, showMicrophone);
721         if (mPrivacyItemController.getLocationAvailable()) {
722             mIconController.setIconVisibility(mSlotLocation, showLocation);
723         }
724          */
725         mPrivacyLogger.logStatusBarIconsVisible(showCamera, showMicrophone,  showLocation);
726     }
727 
728     @Override
onLocationActiveChanged(boolean active)729     public void onLocationActiveChanged(boolean active) {
730         if (!mPrivacyItemController.getLocationAvailable()) {
731             updateLocationFromController();
732         }
733     }
734 
735     // Updates the status view based on the current state of location requests.
updateLocationFromController()736     private void updateLocationFromController() {
737         if (mLocationController.isLocationActive()) {
738             mIconController.setIconVisibility(mSlotLocation, true);
739         } else {
740             mIconController.setIconVisibility(mSlotLocation, false);
741         }
742     }
743 
744     private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
745         @Override
746         public void onReceive(Context context, Intent intent) {
747             String action = intent.getAction();
748             switch (action) {
749                 case Intent.ACTION_SIM_STATE_CHANGED:
750                     // Avoid rebroadcast because SysUI is direct boot aware.
751                     if (intent.getBooleanExtra(Intent.EXTRA_REBROADCAST_ON_UNLOCK, false)) {
752                         break;
753                     }
754                     break;
755                 case TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED:
756                     updateTTY(intent.getIntExtra(TelecomManager.EXTRA_CURRENT_TTY_MODE,
757                             TelecomManager.TTY_MODE_OFF));
758                     break;
759                 case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:
760                 case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:
761                 case Intent.ACTION_MANAGED_PROFILE_REMOVED:
762                     updateManagedProfile();
763                     break;
764                 case AudioManager.ACTION_HEADSET_PLUG:
765                     updateHeadsetPlug(intent);
766                     break;
767             }
768         }
769     };
770 
771     private Runnable mRemoveCastIconRunnable = new Runnable() {
772         @Override
773         public void run() {
774             if (DEBUG) Log.v(TAG, "updateCast: hiding icon NOW");
775             mIconController.setIconVisibility(mSlotCast, false);
776         }
777     };
778 
779     // Screen Recording
780     @Override
onCountdown(long millisUntilFinished)781     public void onCountdown(long millisUntilFinished) {
782         if (DEBUG) Log.d(TAG, "screenrecord: countdown " + millisUntilFinished);
783         int countdown = (int) Math.floorDiv(millisUntilFinished + 500, 1000);
784         int resourceId = R.drawable.stat_sys_screen_record;
785         String description = Integer.toString(countdown);
786         switch (countdown) {
787             case 1:
788                 resourceId = R.drawable.stat_sys_screen_record_1;
789                 break;
790             case 2:
791                 resourceId = R.drawable.stat_sys_screen_record_2;
792                 break;
793             case 3:
794                 resourceId = R.drawable.stat_sys_screen_record_3;
795                 break;
796         }
797         mIconController.setIcon(mSlotScreenRecord, resourceId, description);
798         mIconController.setIconVisibility(mSlotScreenRecord, true);
799         // Set as assertive so talkback will announce the countdown
800         mIconController.setIconAccessibilityLiveRegion(mSlotScreenRecord,
801                 View.ACCESSIBILITY_LIVE_REGION_ASSERTIVE);
802     }
803 
804     @Override
onCountdownEnd()805     public void onCountdownEnd() {
806         if (DEBUG) Log.d(TAG, "screenrecord: hiding icon during countdown");
807         mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, false));
808         // Reset talkback priority
809         mHandler.post(() -> mIconController.setIconAccessibilityLiveRegion(mSlotScreenRecord,
810                 View.ACCESSIBILITY_LIVE_REGION_NONE));
811     }
812 
813     @Override
onRecordingStart()814     public void onRecordingStart() {
815         if (DEBUG) Log.d(TAG, "screenrecord: showing icon");
816         mIconController.setIcon(mSlotScreenRecord,
817                 R.drawable.stat_sys_screen_record,
818                 mResources.getString(R.string.screenrecord_ongoing_screen_only));
819         mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, true));
820     }
821 
822     @Override
onRecordingEnd()823     public void onRecordingEnd() {
824         // Ensure this is on the main thread
825         if (DEBUG) Log.d(TAG, "screenrecord: hiding icon");
826         mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, false));
827     }
828 
onConnectedDisplayAvailabilityChanged(ConnectedDisplayInteractor.State state)829     private void onConnectedDisplayAvailabilityChanged(ConnectedDisplayInteractor.State state) {
830         boolean visible = state != ConnectedDisplayInteractor.State.DISCONNECTED;
831 
832         if (DEBUG) {
833             Log.d(TAG, "connected_display: " + (visible ? "showing" : "hiding") + " icon");
834         }
835 
836         mIconController.setIconVisibility(mSlotConnectedDisplay, visible);
837     }
838 }
839