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