1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.statusbar.phone; 18 19 import android.app.ActivityManager; 20 import android.app.ActivityManager.StackId; 21 import android.app.ActivityManager.StackInfo; 22 import android.app.AlarmManager; 23 import android.app.AlarmManager.AlarmClockInfo; 24 import android.app.AppGlobals; 25 import android.app.Notification; 26 import android.app.Notification.Action; 27 import android.app.NotificationManager; 28 import android.app.PendingIntent; 29 import android.app.SynchronousUserSwitchObserver; 30 import android.content.BroadcastReceiver; 31 import android.content.ComponentName; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.content.IntentFilter; 35 import android.content.pm.ApplicationInfo; 36 import android.content.pm.IPackageManager; 37 import android.content.pm.PackageManager; 38 import android.content.pm.UserInfo; 39 import android.graphics.drawable.Icon; 40 import android.media.AudioManager; 41 import android.net.Uri; 42 import android.os.Bundle; 43 import android.os.Handler; 44 import android.os.RemoteException; 45 import android.os.UserHandle; 46 import android.os.UserManager; 47 import android.provider.Settings; 48 import android.provider.Settings.Global; 49 import android.service.notification.StatusBarNotification; 50 import android.telecom.TelecomManager; 51 import android.util.ArraySet; 52 import android.util.Log; 53 import android.util.Pair; 54 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; 55 import com.android.internal.telephony.IccCardConstants; 56 import com.android.internal.telephony.TelephonyIntents; 57 import com.android.systemui.Dependency; 58 import com.android.systemui.DockedStackExistsListener; 59 import com.android.systemui.R; 60 import com.android.systemui.SysUiServiceProvider; 61 import com.android.systemui.UiOffloadThread; 62 import com.android.systemui.qs.tiles.DndTile; 63 import com.android.systemui.qs.tiles.RotationLockTile; 64 import com.android.systemui.recents.misc.SystemServicesProxy; 65 import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener; 66 import com.android.systemui.statusbar.CommandQueue; 67 import com.android.systemui.statusbar.CommandQueue.Callbacks; 68 import com.android.systemui.statusbar.policy.BluetoothController; 69 import com.android.systemui.statusbar.policy.BluetoothController.Callback; 70 import com.android.systemui.statusbar.policy.CastController; 71 import com.android.systemui.statusbar.policy.CastController.CastDevice; 72 import com.android.systemui.statusbar.policy.DataSaverController; 73 import com.android.systemui.statusbar.policy.DataSaverController.Listener; 74 import com.android.systemui.statusbar.policy.DeviceProvisionedController; 75 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; 76 import com.android.systemui.statusbar.policy.HotspotController; 77 import com.android.systemui.statusbar.policy.KeyguardMonitor; 78 import com.android.systemui.statusbar.policy.LocationController; 79 import com.android.systemui.statusbar.policy.LocationController.LocationChangeCallback; 80 import com.android.systemui.statusbar.policy.NextAlarmController; 81 import com.android.systemui.statusbar.policy.RotationLockController; 82 import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback; 83 import com.android.systemui.statusbar.policy.UserInfoController; 84 import com.android.systemui.statusbar.policy.ZenModeController; 85 import com.android.systemui.util.NotificationChannels; 86 87 import 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 */ 94 public 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