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.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
20 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
21 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
22 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
23 
24 import android.app.ActivityManager;
25 import android.app.ActivityManager.StackInfo;
26 import android.app.AlarmManager;
27 import android.app.AlarmManager.AlarmClockInfo;
28 import android.app.AppGlobals;
29 import android.app.Notification;
30 import android.app.Notification.Action;
31 import android.app.NotificationManager;
32 import android.app.PendingIntent;
33 import android.app.SynchronousUserSwitchObserver;
34 import android.content.BroadcastReceiver;
35 import android.content.ComponentName;
36 import android.content.Context;
37 import android.content.Intent;
38 import android.content.IntentFilter;
39 import android.content.pm.ApplicationInfo;
40 import android.content.pm.IPackageManager;
41 import android.content.pm.PackageManager;
42 import android.graphics.drawable.Icon;
43 import android.media.AudioManager;
44 import android.net.Uri;
45 import android.os.Bundle;
46 import android.os.Handler;
47 import android.os.RemoteException;
48 import android.os.UserHandle;
49 import android.os.UserManager;
50 import android.provider.Settings;
51 import android.provider.Settings.Global;
52 import android.service.notification.StatusBarNotification;
53 import android.service.notification.ZenModeConfig;
54 import android.telecom.TelecomManager;
55 import android.text.format.DateFormat;
56 import android.util.ArraySet;
57 import android.util.Log;
58 import android.util.Pair;
59 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
60 import com.android.internal.telephony.IccCardConstants;
61 import com.android.internal.telephony.TelephonyIntents;
62 import com.android.systemui.Dependency;
63 import com.android.systemui.DockedStackExistsListener;
64 import com.android.systemui.R;
65 import com.android.systemui.SysUiServiceProvider;
66 import com.android.systemui.UiOffloadThread;
67 import com.android.systemui.qs.tiles.DndTile;
68 import com.android.systemui.qs.tiles.RotationLockTile;
69 import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
70 import com.android.systemui.shared.system.ActivityManagerWrapper;
71 import com.android.systemui.statusbar.CommandQueue;
72 import com.android.systemui.statusbar.CommandQueue.Callbacks;
73 import com.android.systemui.statusbar.policy.BluetoothController;
74 import com.android.systemui.statusbar.policy.BluetoothController.Callback;
75 import com.android.systemui.statusbar.policy.CastController;
76 import com.android.systemui.statusbar.policy.CastController.CastDevice;
77 import com.android.systemui.statusbar.policy.DataSaverController;
78 import com.android.systemui.statusbar.policy.DataSaverController.Listener;
79 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
80 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
81 import com.android.systemui.statusbar.policy.HotspotController;
82 import com.android.systemui.statusbar.policy.KeyguardMonitor;
83 import com.android.systemui.statusbar.policy.LocationController;
84 import com.android.systemui.statusbar.policy.LocationController.LocationChangeCallback;
85 import com.android.systemui.statusbar.policy.NextAlarmController;
86 import com.android.systemui.statusbar.policy.RotationLockController;
87 import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback;
88 import com.android.systemui.statusbar.policy.UserInfoController;
89 import com.android.systemui.statusbar.policy.ZenModeController;
90 import com.android.systemui.util.NotificationChannels;
91 
92 import java.util.List;
93 import java.util.Locale;
94 
95 /**
96  * This class contains all of the policy about which icons are installed in the status
97  * bar at boot time.  It goes through the normal API for icons, even though it probably
98  * strictly doesn't need to.
99  */
100 public class PhoneStatusBarPolicy implements Callback, Callbacks,
101         RotationLockControllerCallback, Listener, LocationChangeCallback,
102         ZenModeController.Callback, DeviceProvisionedListener, KeyguardMonitor.Callback {
103     private static final String TAG = "PhoneStatusBarPolicy";
104     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
105 
106     public static final int LOCATION_STATUS_ICON_ID = R.drawable.stat_sys_location;
107     public static final int NUM_TASKS_FOR_INSTANT_APP_INFO = 5;
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 mSlotVolume;
115     private final String mSlotAlarmClock;
116     private final String mSlotManagedProfile;
117     private final String mSlotRotate;
118     private final String mSlotHeadset;
119     private final String mSlotDataSaver;
120     private final String mSlotLocation;
121 
122     private final Context mContext;
123     private final Handler mHandler = new Handler();
124     private final CastController mCast;
125     private final HotspotController mHotspot;
126     private final NextAlarmController mNextAlarmController;
127     private final AlarmManager mAlarmManager;
128     private final UserInfoController mUserInfoController;
129     private final UserManager mUserManager;
130     private final StatusBarIconController mIconController;
131     private final RotationLockController mRotationLockController;
132     private final DataSaverController mDataSaver;
133     private final ZenModeController mZenController;
134     private final DeviceProvisionedController mProvisionedController;
135     private final KeyguardMonitor mKeyguardMonitor;
136     private final LocationController mLocationController;
137     private final ArraySet<Pair<String, Integer>> mCurrentNotifs = new ArraySet<>();
138     private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
139 
140     // Assume it's all good unless we hear otherwise.  We don't always seem
141     // to get broadcasts that it *is* there.
142     IccCardConstants.State mSimState = IccCardConstants.State.READY;
143 
144     private boolean mZenVisible;
145     private boolean mVolumeVisible;
146     private boolean mCurrentUserSetup;
147     private boolean mDockedStackExists;
148 
149     private boolean mManagedProfileIconVisible = false;
150 
151     private BluetoothController mBluetooth;
152     private AlarmManager.AlarmClockInfo mNextAlarm;
153 
PhoneStatusBarPolicy(Context context, StatusBarIconController iconController)154     public PhoneStatusBarPolicy(Context context, StatusBarIconController iconController) {
155         mContext = context;
156         mIconController = iconController;
157         mCast = Dependency.get(CastController.class);
158         mHotspot = Dependency.get(HotspotController.class);
159         mBluetooth = Dependency.get(BluetoothController.class);
160         mNextAlarmController = Dependency.get(NextAlarmController.class);
161         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
162         mUserInfoController = Dependency.get(UserInfoController.class);
163         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
164         mRotationLockController = Dependency.get(RotationLockController.class);
165         mDataSaver = Dependency.get(DataSaverController.class);
166         mZenController = Dependency.get(ZenModeController.class);
167         mProvisionedController = Dependency.get(DeviceProvisionedController.class);
168         mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
169         mLocationController = Dependency.get(LocationController.class);
170 
171         mSlotCast = context.getString(com.android.internal.R.string.status_bar_cast);
172         mSlotHotspot = context.getString(com.android.internal.R.string.status_bar_hotspot);
173         mSlotBluetooth = context.getString(com.android.internal.R.string.status_bar_bluetooth);
174         mSlotTty = context.getString(com.android.internal.R.string.status_bar_tty);
175         mSlotZen = context.getString(com.android.internal.R.string.status_bar_zen);
176         mSlotVolume = context.getString(com.android.internal.R.string.status_bar_volume);
177         mSlotAlarmClock = context.getString(com.android.internal.R.string.status_bar_alarm_clock);
178         mSlotManagedProfile = context.getString(
179                 com.android.internal.R.string.status_bar_managed_profile);
180         mSlotRotate = context.getString(com.android.internal.R.string.status_bar_rotate);
181         mSlotHeadset = context.getString(com.android.internal.R.string.status_bar_headset);
182         mSlotDataSaver = context.getString(com.android.internal.R.string.status_bar_data_saver);
183         mSlotLocation = context.getString(com.android.internal.R.string.status_bar_location);
184 
185         // listen for broadcasts
186         IntentFilter filter = new IntentFilter();
187         filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
188         filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
189         filter.addAction(AudioManager.ACTION_HEADSET_PLUG);
190         filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
191         filter.addAction(TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED);
192         filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
193         filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
194         filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
195         mContext.registerReceiver(mIntentReceiver, filter, null, mHandler);
196 
197         // listen for user / profile change.
198         try {
199             ActivityManager.getService().registerUserSwitchObserver(mUserSwitchListener, TAG);
200         } catch (RemoteException e) {
201             // Ignore
202         }
203 
204         // TTY status
205         updateTTY();
206 
207         // bluetooth status
208         updateBluetooth();
209 
210         // Alarm clock
211         mIconController.setIcon(mSlotAlarmClock, R.drawable.stat_sys_alarm, null);
212         mIconController.setIconVisibility(mSlotAlarmClock, false);
213 
214         // zen
215         mIconController.setIcon(mSlotZen, R.drawable.stat_sys_zen_important, null);
216         mIconController.setIconVisibility(mSlotZen, false);
217 
218         // volume
219         mIconController.setIcon(mSlotVolume, R.drawable.stat_sys_ringer_vibrate, null);
220         mIconController.setIconVisibility(mSlotVolume, false);
221         updateVolumeZen();
222 
223         // cast
224         mIconController.setIcon(mSlotCast, R.drawable.stat_sys_cast, null);
225         mIconController.setIconVisibility(mSlotCast, false);
226 
227         // hotspot
228         mIconController.setIcon(mSlotHotspot, R.drawable.stat_sys_hotspot,
229                 mContext.getString(R.string.accessibility_status_bar_hotspot));
230         mIconController.setIconVisibility(mSlotHotspot, mHotspot.isHotspotEnabled());
231 
232         // managed profile
233         mIconController.setIcon(mSlotManagedProfile, R.drawable.stat_sys_managed_profile_status,
234                 mContext.getString(R.string.accessibility_managed_profile));
235         mIconController.setIconVisibility(mSlotManagedProfile, mManagedProfileIconVisible);
236 
237         // data saver
238         mIconController.setIcon(mSlotDataSaver, R.drawable.stat_sys_data_saver,
239                 context.getString(R.string.accessibility_data_saver_on));
240         mIconController.setIconVisibility(mSlotDataSaver, false);
241 
242         mRotationLockController.addCallback(this);
243         mBluetooth.addCallback(this);
244         mProvisionedController.addCallback(this);
245         mZenController.addCallback(this);
246         mCast.addCallback(mCastCallback);
247         mHotspot.addCallback(mHotspotCallback);
248         mNextAlarmController.addCallback(mNextAlarmCallback);
249         mDataSaver.addCallback(this);
250         mKeyguardMonitor.addCallback(this);
251         mLocationController.addCallback(this);
252 
253         SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallbacks(this);
254         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskListener);
255 
256         // Clear out all old notifications on startup (only present in the case where sysui dies)
257         NotificationManager noMan = mContext.getSystemService(NotificationManager.class);
258         for (StatusBarNotification notification : noMan.getActiveNotifications()) {
259             if (notification.getId() == SystemMessage.NOTE_INSTANT_APPS) {
260                 noMan.cancel(notification.getTag(), notification.getId());
261             }
262         }
263         DockedStackExistsListener.register(exists -> {
264             mDockedStackExists = exists;
265             updateForegroundInstantApps();
266         });
267     }
268 
destroy()269     public void destroy() {
270         mRotationLockController.removeCallback(this);
271         mBluetooth.removeCallback(this);
272         mProvisionedController.removeCallback(this);
273         mZenController.removeCallback(this);
274         mCast.removeCallback(mCastCallback);
275         mHotspot.removeCallback(mHotspotCallback);
276         mNextAlarmController.removeCallback(mNextAlarmCallback);
277         mDataSaver.removeCallback(this);
278         mKeyguardMonitor.removeCallback(this);
279         mLocationController.removeCallback(this);
280         SysUiServiceProvider.getComponent(mContext, CommandQueue.class).removeCallbacks(this);
281         mContext.unregisterReceiver(mIntentReceiver);
282 
283         NotificationManager noMan = mContext.getSystemService(NotificationManager.class);
284         mCurrentNotifs.forEach(v -> noMan.cancelAsUser(v.first, SystemMessage.NOTE_INSTANT_APPS,
285                 new UserHandle(v.second)));
286     }
287 
288     @Override
onZenChanged(int zen)289     public void onZenChanged(int zen) {
290         updateVolumeZen();
291     }
292 
293     @Override
onConfigChanged(ZenModeConfig config)294     public void onConfigChanged(ZenModeConfig config) {
295         updateVolumeZen();
296     }
297 
298     @Override
onLocationActiveChanged(boolean active)299     public void onLocationActiveChanged(boolean active) {
300         updateLocation();
301     }
302 
303     // Updates the status view based on the current state of location requests.
updateLocation()304     private void updateLocation() {
305         if (mLocationController.isLocationActive()) {
306             mIconController.setIcon(mSlotLocation, LOCATION_STATUS_ICON_ID,
307                     mContext.getString(R.string.accessibility_location_active));
308         } else {
309             mIconController.removeAllIconsForSlot(mSlotLocation);
310         }
311     }
312 
updateAlarm()313     private void updateAlarm() {
314         final AlarmClockInfo alarm = mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT);
315         final boolean hasAlarm = alarm != null && alarm.getTriggerTime() > 0;
316         int zen = mZenController.getZen();
317         final boolean zenNone = zen == Global.ZEN_MODE_NO_INTERRUPTIONS;
318         mIconController.setIcon(mSlotAlarmClock, zenNone ? R.drawable.stat_sys_alarm_dim
319                 : R.drawable.stat_sys_alarm, buildAlarmContentDescription());
320         mIconController.setIconVisibility(mSlotAlarmClock, mCurrentUserSetup && hasAlarm);
321     }
322 
buildAlarmContentDescription()323     private String buildAlarmContentDescription() {
324         if (mNextAlarm == null) {
325             return mContext.getString(R.string.status_bar_alarm);
326         }
327         return formatNextAlarm(mNextAlarm, mContext);
328     }
329 
formatNextAlarm(AlarmManager.AlarmClockInfo info, Context context)330     private static String formatNextAlarm(AlarmManager.AlarmClockInfo info, Context context) {
331         if (info == null) {
332             return "";
333         }
334         String skeleton = DateFormat.is24HourFormat(
335                 context, ActivityManager.getCurrentUser()) ? "EHm" : "Ehma";
336         String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
337         String dateString = DateFormat.format(pattern, info.getTriggerTime()).toString();
338 
339         return context.getString(R.string.accessibility_quick_settings_alarm, dateString);
340     }
341 
updateSimState(Intent intent)342     private final void updateSimState(Intent intent) {
343         String stateExtra = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE);
344         if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) {
345             mSimState = IccCardConstants.State.ABSENT;
346         } else if (IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR.equals(stateExtra)) {
347             mSimState = IccCardConstants.State.CARD_IO_ERROR;
348         } else if (IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED.equals(stateExtra)) {
349             mSimState = IccCardConstants.State.CARD_RESTRICTED;
350         } else if (IccCardConstants.INTENT_VALUE_ICC_READY.equals(stateExtra)) {
351             mSimState = IccCardConstants.State.READY;
352         } else if (IccCardConstants.INTENT_VALUE_ICC_LOCKED.equals(stateExtra)) {
353             final String lockedReason =
354                     intent.getStringExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON);
355             if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PIN.equals(lockedReason)) {
356                 mSimState = IccCardConstants.State.PIN_REQUIRED;
357             } else if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PUK.equals(lockedReason)) {
358                 mSimState = IccCardConstants.State.PUK_REQUIRED;
359             } else {
360                 mSimState = IccCardConstants.State.NETWORK_LOCKED;
361             }
362         } else {
363             mSimState = IccCardConstants.State.UNKNOWN;
364         }
365     }
366 
updateVolumeZen()367     private final void updateVolumeZen() {
368         AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
369 
370         boolean zenVisible = false;
371         int zenIconId = 0;
372         String zenDescription = null;
373 
374         boolean volumeVisible = false;
375         int volumeIconId = 0;
376         String volumeDescription = null;
377         int zen = mZenController.getZen();
378 
379         if (DndTile.isVisible(mContext) || DndTile.isCombinedIcon(mContext)) {
380             zenVisible = zen != Global.ZEN_MODE_OFF;
381             zenIconId = R.drawable.stat_sys_dnd;
382             zenDescription = mContext.getString(R.string.quick_settings_dnd_label);
383         } else if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) {
384             zenVisible = true;
385             zenIconId = R.drawable.stat_sys_zen_none;
386             zenDescription = mContext.getString(R.string.interruption_level_none);
387         } else if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
388             zenVisible = true;
389             zenIconId = R.drawable.stat_sys_zen_important;
390             zenDescription = mContext.getString(R.string.interruption_level_priority);
391         }
392 
393         if (!ZenModeConfig.isZenOverridingRinger(zen, mZenController.getConfig())) {
394             if (audioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE) {
395                 volumeVisible = true;
396                 volumeIconId = R.drawable.stat_sys_ringer_vibrate;
397                 volumeDescription = mContext.getString(R.string.accessibility_ringer_vibrate);
398             } else if (audioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT) {
399                 volumeVisible = true;
400                 volumeIconId = R.drawable.stat_sys_ringer_silent;
401                 volumeDescription = mContext.getString(R.string.accessibility_ringer_silent);
402             }
403         }
404 
405         if (zenVisible) {
406             mIconController.setIcon(mSlotZen, zenIconId, zenDescription);
407         }
408         if (zenVisible != mZenVisible) {
409             mIconController.setIconVisibility(mSlotZen, zenVisible);
410             mZenVisible = zenVisible;
411         }
412 
413         if (volumeVisible) {
414             mIconController.setIcon(mSlotVolume, volumeIconId, volumeDescription);
415         }
416         if (volumeVisible != mVolumeVisible) {
417             mIconController.setIconVisibility(mSlotVolume, volumeVisible);
418             mVolumeVisible = volumeVisible;
419         }
420         updateAlarm();
421     }
422 
423     @Override
onBluetoothDevicesChanged()424     public void onBluetoothDevicesChanged() {
425         updateBluetooth();
426     }
427 
428     @Override
onBluetoothStateChange(boolean enabled)429     public void onBluetoothStateChange(boolean enabled) {
430         updateBluetooth();
431     }
432 
updateBluetooth()433     private final void updateBluetooth() {
434         int iconId = R.drawable.stat_sys_data_bluetooth;
435         String contentDescription =
436                 mContext.getString(R.string.accessibility_quick_settings_bluetooth_on);
437         boolean bluetoothVisible = false;
438         if (mBluetooth != null) {
439             if (mBluetooth.isBluetoothConnected()) {
440                 iconId = R.drawable.stat_sys_data_bluetooth_connected;
441                 contentDescription = mContext.getString(R.string.accessibility_bluetooth_connected);
442                 bluetoothVisible = mBluetooth.isBluetoothEnabled();
443             }
444         }
445 
446         mIconController.setIcon(mSlotBluetooth, iconId, contentDescription);
447         mIconController.setIconVisibility(mSlotBluetooth, bluetoothVisible);
448     }
449 
updateTTY()450     private final void updateTTY() {
451         TelecomManager telecomManager =
452                 (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
453         if (telecomManager == null) {
454             updateTTY(TelecomManager.TTY_MODE_OFF);
455         } else {
456             updateTTY(telecomManager.getCurrentTtyMode());
457         }
458     }
459 
updateTTY(int currentTtyMode)460     private final void updateTTY(int currentTtyMode) {
461         boolean enabled = currentTtyMode != TelecomManager.TTY_MODE_OFF;
462 
463         if (DEBUG) Log.v(TAG, "updateTTY: enabled: " + enabled);
464 
465         if (enabled) {
466             // TTY is on
467             if (DEBUG) Log.v(TAG, "updateTTY: set TTY on");
468             mIconController.setIcon(mSlotTty, R.drawable.stat_sys_tty_mode,
469                     mContext.getString(R.string.accessibility_tty_enabled));
470             mIconController.setIconVisibility(mSlotTty, true);
471         } else {
472             // TTY is off
473             if (DEBUG) Log.v(TAG, "updateTTY: set TTY off");
474             mIconController.setIconVisibility(mSlotTty, false);
475         }
476     }
477 
updateCast()478     private void updateCast() {
479         boolean isCasting = false;
480         for (CastDevice device : mCast.getCastDevices()) {
481             if (device.state == CastDevice.STATE_CONNECTING
482                     || device.state == CastDevice.STATE_CONNECTED) {
483                 isCasting = true;
484                 break;
485             }
486         }
487         if (DEBUG) Log.v(TAG, "updateCast: isCasting: " + isCasting);
488         mHandler.removeCallbacks(mRemoveCastIconRunnable);
489         if (isCasting) {
490             mIconController.setIcon(mSlotCast, R.drawable.stat_sys_cast,
491                     mContext.getString(R.string.accessibility_casting));
492             mIconController.setIconVisibility(mSlotCast, true);
493         } else {
494             // don't turn off the screen-record icon for a few seconds, just to make sure the user
495             // has seen it
496             if (DEBUG) Log.v(TAG, "updateCast: hiding icon in 3 sec...");
497             mHandler.postDelayed(mRemoveCastIconRunnable, 3000);
498         }
499     }
500 
updateManagedProfile()501     private void updateManagedProfile() {
502         // getLastResumedActivityUserId needds to acquire the AM lock, which may be contended in
503         // some cases. Since it doesn't really matter here whether it's updated in this frame
504         // or in the next one, we call this method from our UI offload thread.
505         mUiOffloadThread.submit(() -> {
506             final int userId;
507             try {
508                 userId = ActivityManager.getService().getLastResumedActivityUserId();
509                 boolean isManagedProfile = mUserManager.isManagedProfile(userId);
510                 mHandler.post(() -> {
511                     final boolean showIcon;
512                     if (isManagedProfile &&
513                             (!mKeyguardMonitor.isShowing() || mKeyguardMonitor.isOccluded())) {
514                         showIcon = true;
515                         mIconController.setIcon(mSlotManagedProfile,
516                                 R.drawable.stat_sys_managed_profile_status,
517                                 mContext.getString(R.string.accessibility_managed_profile));
518                     } else {
519                         showIcon = false;
520                     }
521                     if (mManagedProfileIconVisible != showIcon) {
522                         mIconController.setIconVisibility(mSlotManagedProfile, showIcon);
523                         mManagedProfileIconVisible = showIcon;
524                     }
525                 });
526             } catch (RemoteException e) {
527                 Log.w(TAG, "updateManagedProfile: ", e);
528             }
529         });
530     }
531 
updateForegroundInstantApps()532     private void updateForegroundInstantApps() {
533         NotificationManager noMan = mContext.getSystemService(NotificationManager.class);
534         ArraySet<Pair<String, Integer>> notifs = new ArraySet<>(mCurrentNotifs);
535         IPackageManager pm = AppGlobals.getPackageManager();
536         mCurrentNotifs.clear();
537         mUiOffloadThread.submit(() -> {
538             try {
539                 final StackInfo focusedStack = ActivityManager.getService().getFocusedStackInfo();
540                 if (focusedStack != null) {
541                     final int windowingMode =
542                             focusedStack.configuration.windowConfiguration.getWindowingMode();
543                     if (windowingMode == WINDOWING_MODE_FULLSCREEN
544                             || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
545                         checkStack(focusedStack, notifs, noMan, pm);
546                     }
547                 }
548                 if (mDockedStackExists) {
549                     checkStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED,
550                             notifs, noMan, pm);
551                 }
552             } catch (RemoteException e) {
553                 e.rethrowFromSystemServer();
554             }
555             // Cancel all the leftover notifications that don't have a foreground process anymore.
556             notifs.forEach(v -> noMan.cancelAsUser(v.first, SystemMessage.NOTE_INSTANT_APPS,
557                     new UserHandle(v.second)));
558         });
559     }
560 
checkStack(int windowingMode, int activityType, ArraySet<Pair<String, Integer>> notifs, NotificationManager noMan, IPackageManager pm)561     private void checkStack(int windowingMode, int activityType,
562             ArraySet<Pair<String, Integer>> notifs, NotificationManager noMan, IPackageManager pm) {
563         try {
564             final StackInfo info =
565                     ActivityManager.getService().getStackInfo(windowingMode, activityType);
566             checkStack(info, notifs, noMan, pm);
567         } catch (RemoteException e) {
568             e.rethrowFromSystemServer();
569         }
570     }
checkStack(StackInfo info, ArraySet<Pair<String, Integer>> notifs, NotificationManager noMan, IPackageManager pm)571     private void checkStack(StackInfo info, ArraySet<Pair<String, Integer>> notifs,
572             NotificationManager noMan, IPackageManager pm) {
573         try {
574             if (info == null || info.topActivity == null) return;
575             String pkg = info.topActivity.getPackageName();
576             if (!hasNotif(notifs, pkg, info.userId)) {
577                 // TODO: Optimize by not always needing to get application info.
578                 // Maybe cache non-ephemeral packages?
579                 ApplicationInfo appInfo = pm.getApplicationInfo(pkg,
580                         PackageManager.MATCH_UNINSTALLED_PACKAGES, info.userId);
581                 if (appInfo.isInstantApp()) {
582                     postEphemeralNotif(pkg, info.userId, appInfo, noMan, info.taskIds[info.taskIds.length - 1]);
583                 }
584             }
585         } catch (RemoteException e) {
586             e.rethrowFromSystemServer();
587         }
588     }
589 
postEphemeralNotif(String pkg, int userId, ApplicationInfo appInfo, NotificationManager noMan, int taskId)590     private void postEphemeralNotif(String pkg, int userId, ApplicationInfo appInfo,
591             NotificationManager noMan, int taskId) {
592         final Bundle extras = new Bundle();
593         extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
594                 mContext.getString(R.string.instant_apps));
595         mCurrentNotifs.add(new Pair<>(pkg, userId));
596         String message = mContext.getString(R.string.instant_apps_message);
597         PendingIntent appInfoAction = PendingIntent.getActivity(mContext, 0,
598                 new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
599                         .setData(Uri.fromParts("package", pkg, null)), 0);
600         Action action = new Notification.Action.Builder(null, mContext.getString(R.string.app_info),
601                 appInfoAction).build();
602 
603         Intent browserIntent = getTaskIntent(taskId, userId);
604         Notification.Builder builder = new Notification.Builder(mContext, NotificationChannels.GENERAL);
605         if (browserIntent != null && browserIntent.isWebIntent()) {
606             // Make sure that this doesn't resolve back to an instant app
607             browserIntent.setComponent(null)
608                     .setPackage(null)
609                     .addFlags(Intent.FLAG_IGNORE_EPHEMERAL)
610                     .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
611 
612             PendingIntent pendingIntent = PendingIntent.getActivity(mContext,
613                     0 /* requestCode */, browserIntent, 0 /* flags */);
614             ComponentName aiaComponent = null;
615             try {
616                 aiaComponent = AppGlobals.getPackageManager().getInstantAppInstallerComponent();
617             } catch (RemoteException e) {
618                 e.rethrowFromSystemServer();
619             }
620             Intent goToWebIntent = new Intent()
621                     .setComponent(aiaComponent)
622                     .setAction(Intent.ACTION_VIEW)
623                     .addCategory(Intent.CATEGORY_BROWSABLE)
624                     .addCategory("unique:" + System.currentTimeMillis())
625                     .putExtra(Intent.EXTRA_PACKAGE_NAME, appInfo.packageName)
626                     .putExtra(Intent.EXTRA_VERSION_CODE, (int) (appInfo.versionCode & 0x7fffffff))
627                     .putExtra(Intent.EXTRA_LONG_VERSION_CODE, appInfo.versionCode)
628                     .putExtra(Intent.EXTRA_EPHEMERAL_FAILURE, pendingIntent)
629                     .putExtra(Intent.EXTRA_INSTANT_APP_FAILURE, pendingIntent);
630 
631             PendingIntent webPendingIntent = PendingIntent.getActivity(mContext, 0, goToWebIntent, 0);
632             Action webAction = new Notification.Action.Builder(null, mContext.getString(R.string.go_to_web),
633                     webPendingIntent).build();
634             builder.addAction(webAction);
635         }
636 
637         noMan.notifyAsUser(pkg, SystemMessage.NOTE_INSTANT_APPS, builder
638                         .addExtras(extras)
639                         .addAction(action)
640                         .setContentIntent(appInfoAction)
641                         .setColor(mContext.getColor(R.color.instant_apps_color))
642                         .setContentTitle(appInfo.loadLabel(mContext.getPackageManager()))
643                         .setLargeIcon(Icon.createWithResource(pkg, appInfo.icon))
644                         .setSmallIcon(Icon.createWithResource(mContext.getPackageName(),
645                                 R.drawable.instant_icon))
646                         .setContentText(message)
647                         .setOngoing(true)
648                         .build(),
649                 new UserHandle(userId));
650     }
651 
getTaskIntent(int taskId, int userId)652     private Intent getTaskIntent(int taskId, int userId) {
653         try {
654             final List<ActivityManager.RecentTaskInfo> tasks =
655                     ActivityManager.getService().getRecentTasks(
656                             NUM_TASKS_FOR_INSTANT_APP_INFO, 0, userId).getList();
657             for (int i = 0; i < tasks.size(); i++) {
658                 if (tasks.get(i).id == taskId) {
659                     return tasks.get(i).baseIntent;
660                 }
661             }
662         } catch (RemoteException e) {
663             // Fall through
664         }
665         return null;
666     }
667 
hasNotif(ArraySet<Pair<String, Integer>> notifs, String pkg, int userId)668     private boolean hasNotif(ArraySet<Pair<String, Integer>> notifs, String pkg, int userId) {
669         Pair<String, Integer> key = new Pair<>(pkg, userId);
670         if (notifs.remove(key)) {
671             mCurrentNotifs.add(key);
672             return true;
673         }
674         return false;
675     }
676 
677     private final SynchronousUserSwitchObserver mUserSwitchListener =
678             new SynchronousUserSwitchObserver() {
679                 @Override
680                 public void onUserSwitching(int newUserId) throws RemoteException {
681                     mHandler.post(() -> mUserInfoController.reloadUserInfo());
682                 }
683 
684                 @Override
685                 public void onUserSwitchComplete(int newUserId) throws RemoteException {
686                     mHandler.post(() -> {
687                         updateAlarm();
688                         updateManagedProfile();
689                         updateForegroundInstantApps();
690                     });
691                 }
692             };
693 
694     private final HotspotController.Callback mHotspotCallback = new HotspotController.Callback() {
695         @Override
696         public void onHotspotChanged(boolean enabled, int numDevices) {
697             mIconController.setIconVisibility(mSlotHotspot, enabled);
698         }
699     };
700 
701     private final CastController.Callback mCastCallback = new CastController.Callback() {
702         @Override
703         public void onCastDevicesChanged() {
704             updateCast();
705         }
706     };
707 
708     private final NextAlarmController.NextAlarmChangeCallback mNextAlarmCallback =
709             new NextAlarmController.NextAlarmChangeCallback() {
710                 @Override
711                 public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
712                     mNextAlarm = nextAlarm;
713                     updateAlarm();
714                 }
715             };
716 
717     @Override
appTransitionStarting(long startTime, long duration, boolean forced)718     public void appTransitionStarting(long startTime, long duration, boolean forced) {
719         updateManagedProfile();
720         updateForegroundInstantApps();
721     }
722 
723     @Override
onKeyguardShowingChanged()724     public void onKeyguardShowingChanged() {
725         updateManagedProfile();
726         updateForegroundInstantApps();
727     }
728 
729     @Override
onUserSetupChanged()730     public void onUserSetupChanged() {
731         boolean userSetup = mProvisionedController.isUserSetup(
732                 mProvisionedController.getCurrentUser());
733         if (mCurrentUserSetup == userSetup) return;
734         mCurrentUserSetup = userSetup;
735         updateAlarm();
736     }
737 
738     @Override
preloadRecentApps()739     public void preloadRecentApps() {
740         updateForegroundInstantApps();
741     }
742 
743     @Override
onRotationLockStateChanged(boolean rotationLocked, boolean affordanceVisible)744     public void onRotationLockStateChanged(boolean rotationLocked, boolean affordanceVisible) {
745         boolean portrait = RotationLockTile.isCurrentOrientationLockPortrait(
746                 mRotationLockController, mContext);
747         if (rotationLocked) {
748             if (portrait) {
749                 mIconController.setIcon(mSlotRotate, R.drawable.stat_sys_rotate_portrait,
750                         mContext.getString(R.string.accessibility_rotation_lock_on_portrait));
751             } else {
752                 mIconController.setIcon(mSlotRotate, R.drawable.stat_sys_rotate_landscape,
753                         mContext.getString(R.string.accessibility_rotation_lock_on_landscape));
754             }
755             mIconController.setIconVisibility(mSlotRotate, true);
756         } else {
757             mIconController.setIconVisibility(mSlotRotate, false);
758         }
759     }
760 
updateHeadsetPlug(Intent intent)761     private void updateHeadsetPlug(Intent intent) {
762         boolean connected = intent.getIntExtra("state", 0) != 0;
763         boolean hasMic = intent.getIntExtra("microphone", 0) != 0;
764         if (connected) {
765             String contentDescription = mContext.getString(hasMic
766                     ? R.string.accessibility_status_bar_headset
767                     : R.string.accessibility_status_bar_headphones);
768             mIconController.setIcon(mSlotHeadset, hasMic ? R.drawable.ic_headset_mic
769                     : R.drawable.ic_headset, contentDescription);
770             mIconController.setIconVisibility(mSlotHeadset, true);
771         } else {
772             mIconController.setIconVisibility(mSlotHeadset, false);
773         }
774     }
775 
776     @Override
onDataSaverChanged(boolean isDataSaving)777     public void onDataSaverChanged(boolean isDataSaving) {
778         mIconController.setIconVisibility(mSlotDataSaver, isDataSaving);
779     }
780 
781     private final SysUiTaskStackChangeListener mTaskListener = new SysUiTaskStackChangeListener() {
782         @Override
783         public void onTaskStackChanged() {
784             // Listen for changes to stacks and then check which instant apps are foreground.
785             updateForegroundInstantApps();
786         }
787     };
788 
789     private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
790         @Override
791         public void onReceive(Context context, Intent intent) {
792             String action = intent.getAction();
793             switch (action) {
794                 case AudioManager.RINGER_MODE_CHANGED_ACTION:
795                 case AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION:
796                     updateVolumeZen();
797                     break;
798                 case TelephonyIntents.ACTION_SIM_STATE_CHANGED:
799                     // Avoid rebroadcast because SysUI is direct boot aware.
800                     if (intent.getBooleanExtra(TelephonyIntents.EXTRA_REBROADCAST_ON_UNLOCK,
801                             false)) {
802                         break;
803                     }
804                     updateSimState(intent);
805                     break;
806                 case TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED:
807                     updateTTY(intent.getIntExtra(TelecomManager.EXTRA_CURRENT_TTY_MODE,
808                             TelecomManager.TTY_MODE_OFF));
809                     break;
810                 case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:
811                 case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:
812                 case Intent.ACTION_MANAGED_PROFILE_REMOVED:
813                     updateManagedProfile();
814                     break;
815                 case AudioManager.ACTION_HEADSET_PLUG:
816                     updateHeadsetPlug(intent);
817                     break;
818             }
819         }
820     };
821 
822     private Runnable mRemoveCastIconRunnable = new Runnable() {
823         @Override
824         public void run() {
825             if (DEBUG) Log.v(TAG, "updateCast: hiding icon NOW");
826             mIconController.setIconVisibility(mSlotCast, false);
827         }
828     };
829 }
830