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