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