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