Home | History | Annotate | Download | only in phone
      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 
    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 
    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
    289     public void onZenChanged(int zen) {
    290         updateVolumeZen();
    291     }
    292 
    293     @Override
    294     public void onConfigChanged(ZenModeConfig config) {
    295         updateVolumeZen();
    296     }
    297 
    298     @Override
    299     public void onLocationActiveChanged(boolean active) {
    300         updateLocation();
    301     }
    302 
    303     // Updates the status view based on the current state of location requests.
    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 
    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 
    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 
    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 
    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 
    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
    424     public void onBluetoothDevicesChanged() {
    425         updateBluetooth();
    426     }
    427 
    428     @Override
    429     public void onBluetoothStateChange(boolean enabled) {
    430         updateBluetooth();
    431     }
    432 
    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 
    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 
    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 
    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 
    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 
    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 
    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     }
    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 
    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 
    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 
    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
    718     public void appTransitionStarting(long startTime, long duration, boolean forced) {
    719         updateManagedProfile();
    720         updateForegroundInstantApps();
    721     }
    722 
    723     @Override
    724     public void onKeyguardShowingChanged() {
    725         updateManagedProfile();
    726         updateForegroundInstantApps();
    727     }
    728 
    729     @Override
    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
    739     public void preloadRecentApps() {
    740         updateForegroundInstantApps();
    741     }
    742 
    743     @Override
    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 
    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
    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