1 /* 2 * Copyright 2021 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.globalactions; 18 19 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 20 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 21 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 22 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 23 import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS; 24 import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN; 25 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON; 26 27 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST; 28 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; 29 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; 30 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING; 31 32 import android.animation.Animator; 33 import android.animation.AnimatorListenerAdapter; 34 import android.annotation.Nullable; 35 import android.app.ActivityManager; 36 import android.app.Dialog; 37 import android.app.IActivityManager; 38 import android.app.StatusBarManager; 39 import android.app.WallpaperManager; 40 import android.app.admin.DevicePolicyManager; 41 import android.app.trust.TrustManager; 42 import android.content.BroadcastReceiver; 43 import android.content.Context; 44 import android.content.DialogInterface; 45 import android.content.Intent; 46 import android.content.IntentFilter; 47 import android.content.pm.PackageManager; 48 import android.content.pm.UserInfo; 49 import android.content.res.ColorStateList; 50 import android.content.res.Configuration; 51 import android.content.res.Resources; 52 import android.database.ContentObserver; 53 import android.graphics.Color; 54 import android.graphics.drawable.Drawable; 55 import android.media.AudioManager; 56 import android.os.Binder; 57 import android.os.Bundle; 58 import android.os.Handler; 59 import android.os.IBinder; 60 import android.os.Message; 61 import android.os.RemoteException; 62 import android.os.SystemProperties; 63 import android.os.UserHandle; 64 import android.os.UserManager; 65 import android.os.Vibrator; 66 import android.provider.Settings; 67 import android.service.dreams.IDreamManager; 68 import android.sysprop.TelephonyProperties; 69 import android.telecom.TelecomManager; 70 import android.telephony.ServiceState; 71 import android.telephony.TelephonyCallback; 72 import android.telephony.TelephonyManager; 73 import android.util.ArraySet; 74 import android.util.Log; 75 import android.view.ContextThemeWrapper; 76 import android.view.GestureDetector; 77 import android.view.IWindowManager; 78 import android.view.LayoutInflater; 79 import android.view.MotionEvent; 80 import android.view.View; 81 import android.view.ViewGroup; 82 import android.view.WindowManager; 83 import android.view.accessibility.AccessibilityEvent; 84 import android.view.accessibility.AccessibilityManager; 85 import android.widget.BaseAdapter; 86 import android.widget.ImageView; 87 import android.widget.ImageView.ScaleType; 88 import android.widget.LinearLayout; 89 import android.widget.ListPopupWindow; 90 import android.widget.TextView; 91 92 import androidx.annotation.NonNull; 93 import androidx.lifecycle.Lifecycle; 94 import androidx.lifecycle.LifecycleOwner; 95 import androidx.lifecycle.LifecycleRegistry; 96 97 import com.android.internal.R; 98 import com.android.internal.annotations.VisibleForTesting; 99 import com.android.internal.colorextraction.ColorExtractor; 100 import com.android.internal.colorextraction.ColorExtractor.GradientColors; 101 import com.android.internal.logging.MetricsLogger; 102 import com.android.internal.logging.UiEvent; 103 import com.android.internal.logging.UiEventLogger; 104 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 105 import com.android.internal.statusbar.IStatusBarService; 106 import com.android.internal.util.EmergencyAffordanceManager; 107 import com.android.internal.util.ScreenshotHelper; 108 import com.android.internal.widget.LockPatternUtils; 109 import com.android.keyguard.KeyguardUpdateMonitor; 110 import com.android.systemui.MultiListLayout; 111 import com.android.systemui.MultiListLayout.MultiListAdapter; 112 import com.android.systemui.animation.DialogLaunchAnimator; 113 import com.android.systemui.broadcast.BroadcastDispatcher; 114 import com.android.systemui.colorextraction.SysuiColorExtractor; 115 import com.android.systemui.dagger.qualifiers.Background; 116 import com.android.systemui.dagger.qualifiers.Main; 117 import com.android.systemui.model.SysUiState; 118 import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; 119 import com.android.systemui.plugins.GlobalActionsPanelPlugin; 120 import com.android.systemui.scrim.ScrimDrawable; 121 import com.android.systemui.statusbar.NotificationShadeWindowController; 122 import com.android.systemui.statusbar.phone.StatusBar; 123 import com.android.systemui.statusbar.phone.SystemUIDialog; 124 import com.android.systemui.statusbar.phone.SystemUIDialogManager; 125 import com.android.systemui.statusbar.policy.ConfigurationController; 126 import com.android.systemui.statusbar.policy.KeyguardStateController; 127 import com.android.systemui.telephony.TelephonyListenerManager; 128 import com.android.systemui.util.EmergencyDialerConstants; 129 import com.android.systemui.util.RingerModeTracker; 130 import com.android.systemui.util.settings.GlobalSettings; 131 import com.android.systemui.util.settings.SecureSettings; 132 133 import java.util.ArrayList; 134 import java.util.List; 135 import java.util.Optional; 136 import java.util.concurrent.Executor; 137 138 import javax.inject.Inject; 139 140 /** 141 * Helper to show the global actions dialog. Each item is an {@link Action} that may show depending 142 * on whether the keyguard is showing, and whether the device is provisioned. 143 */ 144 public class GlobalActionsDialogLite implements DialogInterface.OnDismissListener, 145 DialogInterface.OnShowListener, 146 ConfigurationController.ConfigurationListener, 147 GlobalActionsPanelPlugin.Callbacks, 148 LifecycleOwner { 149 150 public static final String SYSTEM_DIALOG_REASON_KEY = "reason"; 151 public static final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions"; 152 public static final String SYSTEM_DIALOG_REASON_DREAM = "dream"; 153 154 private static final String TAG = "GlobalActionsDialogLite"; 155 156 private static final boolean SHOW_SILENT_TOGGLE = true; 157 158 /* Valid settings for global actions keys. 159 * see config.xml config_globalActionList */ 160 @VisibleForTesting 161 static final String GLOBAL_ACTION_KEY_POWER = "power"; 162 private static final String GLOBAL_ACTION_KEY_AIRPLANE = "airplane"; 163 static final String GLOBAL_ACTION_KEY_BUGREPORT = "bugreport"; 164 private static final String GLOBAL_ACTION_KEY_SILENT = "silent"; 165 private static final String GLOBAL_ACTION_KEY_USERS = "users"; 166 private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings"; 167 static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown"; 168 private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist"; 169 private static final String GLOBAL_ACTION_KEY_ASSIST = "assist"; 170 static final String GLOBAL_ACTION_KEY_RESTART = "restart"; 171 private static final String GLOBAL_ACTION_KEY_LOGOUT = "logout"; 172 static final String GLOBAL_ACTION_KEY_EMERGENCY = "emergency"; 173 static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot"; 174 175 // See NotificationManagerService#scheduleDurationReachedLocked 176 private static final long TOAST_FADE_TIME = 333; 177 // See NotificationManagerService.LONG_DELAY 178 private static final int TOAST_VISIBLE_TIME = 3500; 179 180 private final Context mContext; 181 private final GlobalActionsManager mWindowManagerFuncs; 182 private final AudioManager mAudioManager; 183 private final IDreamManager mDreamManager; 184 private final DevicePolicyManager mDevicePolicyManager; 185 private final LockPatternUtils mLockPatternUtils; 186 private final TelephonyListenerManager mTelephonyListenerManager; 187 private final KeyguardStateController mKeyguardStateController; 188 private final BroadcastDispatcher mBroadcastDispatcher; 189 protected final GlobalSettings mGlobalSettings; 190 protected final SecureSettings mSecureSettings; 191 protected final Resources mResources; 192 private final ConfigurationController mConfigurationController; 193 private final UserManager mUserManager; 194 private final TrustManager mTrustManager; 195 private final IActivityManager mIActivityManager; 196 private final TelecomManager mTelecomManager; 197 private final MetricsLogger mMetricsLogger; 198 private final UiEventLogger mUiEventLogger; 199 private final SysUiState mSysUiState; 200 201 // Used for RingerModeTracker 202 private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this); 203 204 @VisibleForTesting 205 protected final ArrayList<Action> mItems = new ArrayList<>(); 206 @VisibleForTesting 207 protected final ArrayList<Action> mOverflowItems = new ArrayList<>(); 208 @VisibleForTesting 209 protected final ArrayList<Action> mPowerItems = new ArrayList<>(); 210 211 @VisibleForTesting 212 protected ActionsDialogLite mDialog; 213 214 private Action mSilentModeAction; 215 private ToggleAction mAirplaneModeOn; 216 217 protected MyAdapter mAdapter; 218 protected MyOverflowAdapter mOverflowAdapter; 219 protected MyPowerOptionsAdapter mPowerAdapter; 220 221 private boolean mKeyguardShowing = false; 222 private boolean mDeviceProvisioned = false; 223 private ToggleState mAirplaneState = ToggleState.Off; 224 private boolean mIsWaitingForEcmExit = false; 225 private boolean mHasTelephony; 226 private boolean mHasVibrator; 227 private final boolean mShowSilentToggle; 228 private final EmergencyAffordanceManager mEmergencyAffordanceManager; 229 private final ScreenshotHelper mScreenshotHelper; 230 private final SysuiColorExtractor mSysuiColorExtractor; 231 private final IStatusBarService mStatusBarService; 232 protected final NotificationShadeWindowController mNotificationShadeWindowController; 233 private final IWindowManager mIWindowManager; 234 private final Executor mBackgroundExecutor; 235 private final RingerModeTracker mRingerModeTracker; 236 private int mDialogPressDelay = DIALOG_PRESS_DELAY; // ms 237 protected Handler mMainHandler; 238 private int mSmallestScreenWidthDp; 239 private final Optional<StatusBar> mStatusBarOptional; 240 private final SystemUIDialogManager mDialogManager; 241 private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 242 private final DialogLaunchAnimator mDialogLaunchAnimator; 243 244 @VisibleForTesting 245 public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum { 246 @UiEvent(doc = "The global actions / power menu surface became visible on the screen.") 247 GA_POWER_MENU_OPEN(337), 248 249 @UiEvent(doc = "The global actions / power menu surface was dismissed.") 250 GA_POWER_MENU_CLOSE(471), 251 252 @UiEvent(doc = "The global actions bugreport button was pressed.") 253 GA_BUGREPORT_PRESS(344), 254 255 @UiEvent(doc = "The global actions bugreport button was long pressed.") 256 GA_BUGREPORT_LONG_PRESS(345), 257 258 @UiEvent(doc = "The global actions emergency button was pressed.") 259 GA_EMERGENCY_DIALER_PRESS(346), 260 261 @UiEvent(doc = "The global actions screenshot button was pressed.") 262 GA_SCREENSHOT_PRESS(347), 263 264 @UiEvent(doc = "The global actions screenshot button was long pressed.") 265 GA_SCREENSHOT_LONG_PRESS(348), 266 267 @UiEvent(doc = "The global actions power off button was pressed.") 268 GA_SHUTDOWN_PRESS(802), 269 270 @UiEvent(doc = "The global actions power off button was long pressed.") 271 GA_SHUTDOWN_LONG_PRESS(803), 272 273 @UiEvent(doc = "The global actions reboot button was pressed.") 274 GA_REBOOT_PRESS(349), 275 276 @UiEvent(doc = "The global actions reboot button was long pressed.") 277 GA_REBOOT_LONG_PRESS(804), 278 279 @UiEvent(doc = "The global actions lockdown button was pressed.") 280 GA_LOCKDOWN_PRESS(354), // already created by cwren apparently 281 282 @UiEvent(doc = "Power menu was opened via quick settings button.") 283 GA_OPEN_QS(805), 284 285 @UiEvent(doc = "Power menu was opened via power + volume up.") 286 GA_OPEN_POWER_VOLUP(806), 287 288 @UiEvent(doc = "Power menu was opened via long press on power.") 289 GA_OPEN_LONG_PRESS_POWER(807), 290 291 @UiEvent(doc = "Power menu was closed via long press on power.") 292 GA_CLOSE_LONG_PRESS_POWER(808), 293 294 @UiEvent(doc = "Power menu was dismissed by back gesture.") 295 GA_CLOSE_BACK(809), 296 297 @UiEvent(doc = "Power menu was dismissed by tapping outside dialog.") 298 GA_CLOSE_TAP_OUTSIDE(810), 299 300 @UiEvent(doc = "Power menu was closed via power + volume up.") 301 GA_CLOSE_POWER_VOLUP(811); 302 303 private final int mId; 304 GlobalActionsEvent(int id)305 GlobalActionsEvent(int id) { 306 mId = id; 307 } 308 309 @Override getId()310 public int getId() { 311 return mId; 312 } 313 } 314 315 /** 316 * @param context everything needs a context :( 317 */ 318 @Inject GlobalActionsDialogLite( Context context, GlobalActionsManager windowManagerFuncs, AudioManager audioManager, IDreamManager iDreamManager, DevicePolicyManager devicePolicyManager, LockPatternUtils lockPatternUtils, BroadcastDispatcher broadcastDispatcher, TelephonyListenerManager telephonyListenerManager, GlobalSettings globalSettings, SecureSettings secureSettings, @Nullable Vibrator vibrator, @Main Resources resources, ConfigurationController configurationController, KeyguardStateController keyguardStateController, UserManager userManager, TrustManager trustManager, IActivityManager iActivityManager, @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger, SysuiColorExtractor colorExtractor, IStatusBarService statusBarService, NotificationShadeWindowController notificationShadeWindowController, IWindowManager iWindowManager, @Background Executor backgroundExecutor, UiEventLogger uiEventLogger, RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler, PackageManager packageManager, Optional<StatusBar> statusBarOptional, KeyguardUpdateMonitor keyguardUpdateMonitor, DialogLaunchAnimator dialogLaunchAnimator, SystemUIDialogManager dialogManager)319 public GlobalActionsDialogLite( 320 Context context, 321 GlobalActionsManager windowManagerFuncs, 322 AudioManager audioManager, 323 IDreamManager iDreamManager, 324 DevicePolicyManager devicePolicyManager, 325 LockPatternUtils lockPatternUtils, 326 BroadcastDispatcher broadcastDispatcher, 327 TelephonyListenerManager telephonyListenerManager, 328 GlobalSettings globalSettings, 329 SecureSettings secureSettings, 330 @Nullable Vibrator vibrator, 331 @Main Resources resources, 332 ConfigurationController configurationController, 333 KeyguardStateController keyguardStateController, 334 UserManager userManager, 335 TrustManager trustManager, 336 IActivityManager iActivityManager, 337 @Nullable TelecomManager telecomManager, 338 MetricsLogger metricsLogger, 339 SysuiColorExtractor colorExtractor, 340 IStatusBarService statusBarService, 341 NotificationShadeWindowController notificationShadeWindowController, 342 IWindowManager iWindowManager, 343 @Background Executor backgroundExecutor, 344 UiEventLogger uiEventLogger, 345 RingerModeTracker ringerModeTracker, 346 SysUiState sysUiState, 347 @Main Handler handler, 348 PackageManager packageManager, 349 Optional<StatusBar> statusBarOptional, 350 KeyguardUpdateMonitor keyguardUpdateMonitor, 351 DialogLaunchAnimator dialogLaunchAnimator, 352 SystemUIDialogManager dialogManager) { 353 mContext = context; 354 mWindowManagerFuncs = windowManagerFuncs; 355 mAudioManager = audioManager; 356 mDreamManager = iDreamManager; 357 mDevicePolicyManager = devicePolicyManager; 358 mLockPatternUtils = lockPatternUtils; 359 mTelephonyListenerManager = telephonyListenerManager; 360 mKeyguardStateController = keyguardStateController; 361 mBroadcastDispatcher = broadcastDispatcher; 362 mGlobalSettings = globalSettings; 363 mSecureSettings = secureSettings; 364 mResources = resources; 365 mConfigurationController = configurationController; 366 mUserManager = userManager; 367 mTrustManager = trustManager; 368 mIActivityManager = iActivityManager; 369 mTelecomManager = telecomManager; 370 mMetricsLogger = metricsLogger; 371 mUiEventLogger = uiEventLogger; 372 mSysuiColorExtractor = colorExtractor; 373 mStatusBarService = statusBarService; 374 mNotificationShadeWindowController = notificationShadeWindowController; 375 mIWindowManager = iWindowManager; 376 mBackgroundExecutor = backgroundExecutor; 377 mRingerModeTracker = ringerModeTracker; 378 mSysUiState = sysUiState; 379 mMainHandler = handler; 380 mSmallestScreenWidthDp = resources.getConfiguration().smallestScreenWidthDp; 381 mStatusBarOptional = statusBarOptional; 382 mKeyguardUpdateMonitor = keyguardUpdateMonitor; 383 mDialogLaunchAnimator = dialogLaunchAnimator; 384 mDialogManager = dialogManager; 385 386 // receive broadcasts 387 IntentFilter filter = new IntentFilter(); 388 filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 389 filter.addAction(Intent.ACTION_SCREEN_OFF); 390 filter.addAction(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); 391 mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter); 392 393 mHasTelephony = packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY); 394 395 // get notified of phone state changes 396 mTelephonyListenerManager.addServiceStateListener(mPhoneStateListener); 397 mGlobalSettings.registerContentObserver( 398 Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true, 399 mAirplaneModeObserver); 400 mHasVibrator = vibrator != null && vibrator.hasVibrator(); 401 402 mShowSilentToggle = SHOW_SILENT_TOGGLE && !resources.getBoolean( 403 R.bool.config_useFixedVolume); 404 if (mShowSilentToggle) { 405 mRingerModeTracker.getRingerMode().observe(this, ringer -> 406 mHandler.sendEmptyMessage(MESSAGE_REFRESH) 407 ); 408 } 409 410 mEmergencyAffordanceManager = new EmergencyAffordanceManager(context); 411 mScreenshotHelper = new ScreenshotHelper(context); 412 413 mConfigurationController.addCallback(this); 414 } 415 416 /** 417 * Clean up callbacks 418 */ destroy()419 public void destroy() { 420 mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver); 421 mTelephonyListenerManager.removeServiceStateListener(mPhoneStateListener); 422 mGlobalSettings.unregisterContentObserver(mAirplaneModeObserver); 423 mConfigurationController.removeCallback(this); 424 } 425 getContext()426 protected Context getContext() { 427 return mContext; 428 } 429 getEventLogger()430 protected UiEventLogger getEventLogger() { 431 return mUiEventLogger; 432 } 433 getStatusBar()434 protected Optional<StatusBar> getStatusBar() { 435 return mStatusBarOptional; 436 } 437 getKeyguardUpdateMonitor()438 protected KeyguardUpdateMonitor getKeyguardUpdateMonitor() { 439 return mKeyguardUpdateMonitor; 440 } 441 442 /** 443 * Show the global actions dialog (creating if necessary) or hide it if it's already showing. 444 * 445 * @param keyguardShowing True if keyguard is showing 446 * @param isDeviceProvisioned True if device is provisioned 447 * @param view The view from which we should animate the dialog when showing it 448 */ showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned, @Nullable View view)449 public void showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned, 450 @Nullable View view) { 451 mKeyguardShowing = keyguardShowing; 452 mDeviceProvisioned = isDeviceProvisioned; 453 if (mDialog != null && mDialog.isShowing()) { 454 // In order to force global actions to hide on the same affordance press, we must 455 // register a call to onGlobalActionsShown() first to prevent the default actions 456 // menu from showing. This will be followed by a subsequent call to 457 // onGlobalActionsHidden() on dismiss() 458 mWindowManagerFuncs.onGlobalActionsShown(); 459 mDialog.dismiss(); 460 mDialog = null; 461 } else { 462 handleShow(view); 463 } 464 } 465 isKeyguardShowing()466 protected boolean isKeyguardShowing() { 467 return mKeyguardShowing; 468 } 469 isDeviceProvisioned()470 protected boolean isDeviceProvisioned() { 471 return mDeviceProvisioned; 472 } 473 474 /** 475 * Dismiss the global actions dialog, if it's currently shown 476 */ dismissDialog()477 public void dismissDialog() { 478 mHandler.removeMessages(MESSAGE_DISMISS); 479 mHandler.sendEmptyMessage(MESSAGE_DISMISS); 480 } 481 awakenIfNecessary()482 protected void awakenIfNecessary() { 483 if (mDreamManager != null) { 484 try { 485 if (mDreamManager.isDreaming()) { 486 mDreamManager.awaken(); 487 } 488 } catch (RemoteException e) { 489 // we tried 490 } 491 } 492 } 493 handleShow(@ullable View view)494 protected void handleShow(@Nullable View view) { 495 awakenIfNecessary(); 496 mDialog = createDialog(); 497 prepareDialog(); 498 499 WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes(); 500 attrs.setTitle("ActionsDialog"); 501 attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 502 mDialog.getWindow().setAttributes(attrs); 503 // Don't acquire soft keyboard focus, to avoid destroying state when capturing bugreports 504 mDialog.getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM); 505 506 if (view != null) { 507 mDialogLaunchAnimator.showFromView(mDialog, view); 508 } else { 509 mDialog.show(); 510 } 511 mWindowManagerFuncs.onGlobalActionsShown(); 512 } 513 514 @VisibleForTesting shouldShowAction(Action action)515 protected boolean shouldShowAction(Action action) { 516 if (mKeyguardShowing && !action.showDuringKeyguard()) { 517 return false; 518 } 519 if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { 520 return false; 521 } 522 return action.shouldShow(); 523 } 524 525 /** 526 * Returns the maximum number of power menu items to show based on which GlobalActions 527 * layout is being used. 528 */ 529 @VisibleForTesting getMaxShownPowerItems()530 protected int getMaxShownPowerItems() { 531 return mResources.getInteger(com.android.systemui.R.integer.power_menu_lite_max_columns) 532 * mResources.getInteger(com.android.systemui.R.integer.power_menu_lite_max_rows); 533 } 534 535 /** 536 * Add a power menu action item for to either the main or overflow items lists, depending on 537 * whether controls are enabled and whether the max number of shown items has been reached. 538 */ addActionItem(Action action)539 private void addActionItem(Action action) { 540 if (mItems.size() < getMaxShownPowerItems()) { 541 mItems.add(action); 542 } else { 543 mOverflowItems.add(action); 544 } 545 } 546 547 @VisibleForTesting getDefaultActions()548 protected String[] getDefaultActions() { 549 return mResources.getStringArray(R.array.config_globalActionsList); 550 } 551 addIfShouldShowAction(List<Action> actions, Action action)552 private void addIfShouldShowAction(List<Action> actions, Action action) { 553 if (shouldShowAction(action)) { 554 actions.add(action); 555 } 556 } 557 558 @VisibleForTesting createActionItems()559 protected void createActionItems() { 560 // Simple toggle style if there's no vibrator, otherwise use a tri-state 561 if (!mHasVibrator) { 562 mSilentModeAction = new SilentModeToggleAction(); 563 } else { 564 mSilentModeAction = new SilentModeTriStateAction(mAudioManager, mHandler); 565 } 566 mAirplaneModeOn = new AirplaneModeAction(); 567 onAirplaneModeChanged(); 568 569 mItems.clear(); 570 mOverflowItems.clear(); 571 mPowerItems.clear(); 572 String[] defaultActions = getDefaultActions(); 573 574 ShutDownAction shutdownAction = new ShutDownAction(); 575 RestartAction restartAction = new RestartAction(); 576 ArraySet<String> addedKeys = new ArraySet<>(); 577 List<Action> tempActions = new ArrayList<>(); 578 CurrentUserProvider currentUser = new CurrentUserProvider(); 579 580 // make sure emergency affordance action is first, if needed 581 if (mEmergencyAffordanceManager.needsEmergencyAffordance()) { 582 addIfShouldShowAction(tempActions, new EmergencyAffordanceAction()); 583 addedKeys.add(GLOBAL_ACTION_KEY_EMERGENCY); 584 } 585 586 for (int i = 0; i < defaultActions.length; i++) { 587 String actionKey = defaultActions[i]; 588 if (addedKeys.contains(actionKey)) { 589 // If we already have added this, don't add it again. 590 continue; 591 } 592 if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) { 593 addIfShouldShowAction(tempActions, shutdownAction); 594 } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) { 595 addIfShouldShowAction(tempActions, mAirplaneModeOn); 596 } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) { 597 if (shouldDisplayBugReport(currentUser.get())) { 598 addIfShouldShowAction(tempActions, new BugReportAction()); 599 } 600 } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) { 601 if (mShowSilentToggle) { 602 addIfShouldShowAction(tempActions, mSilentModeAction); 603 } 604 } else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) { 605 if (SystemProperties.getBoolean("fw.power_user_switcher", false)) { 606 addUserActions(tempActions, currentUser.get()); 607 } 608 } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) { 609 addIfShouldShowAction(tempActions, getSettingsAction()); 610 } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) { 611 if (shouldDisplayLockdown(currentUser.get())) { 612 addIfShouldShowAction(tempActions, new LockDownAction()); 613 } 614 } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) { 615 addIfShouldShowAction(tempActions, getVoiceAssistAction()); 616 } else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) { 617 addIfShouldShowAction(tempActions, getAssistAction()); 618 } else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) { 619 addIfShouldShowAction(tempActions, restartAction); 620 } else if (GLOBAL_ACTION_KEY_SCREENSHOT.equals(actionKey)) { 621 addIfShouldShowAction(tempActions, new ScreenshotAction()); 622 } else if (GLOBAL_ACTION_KEY_LOGOUT.equals(actionKey)) { 623 // TODO(b/206032495): should call mDevicePolicyManager.getLogoutUserId() instead of 624 // hardcode it to USER_SYSTEM so it properly supports headless system user mode 625 // (and then call mDevicePolicyManager.clearLogoutUser() after switched) 626 if (mDevicePolicyManager.isLogoutEnabled() 627 && currentUser.get() != null 628 && currentUser.get().id != UserHandle.USER_SYSTEM) { 629 addIfShouldShowAction(tempActions, new LogoutAction()); 630 } 631 } else if (GLOBAL_ACTION_KEY_EMERGENCY.equals(actionKey)) { 632 addIfShouldShowAction(tempActions, new EmergencyDialerAction()); 633 } else { 634 Log.e(TAG, "Invalid global action key " + actionKey); 635 } 636 // Add here so we don't add more than one. 637 addedKeys.add(actionKey); 638 } 639 640 // replace power and restart with a single power options action, if needed 641 if (tempActions.contains(shutdownAction) && tempActions.contains(restartAction) 642 && tempActions.size() > getMaxShownPowerItems()) { 643 // transfer shutdown and restart to their own list of power actions 644 int powerOptionsIndex = Math.min(tempActions.indexOf(restartAction), 645 tempActions.indexOf(shutdownAction)); 646 tempActions.remove(shutdownAction); 647 tempActions.remove(restartAction); 648 mPowerItems.add(shutdownAction); 649 mPowerItems.add(restartAction); 650 651 // add the PowerOptionsAction after Emergency, if present 652 tempActions.add(powerOptionsIndex, new PowerOptionsAction()); 653 } 654 for (Action action : tempActions) { 655 addActionItem(action); 656 } 657 } 658 onRefresh()659 protected void onRefresh() { 660 // re-allocate actions between main and overflow lists 661 this.createActionItems(); 662 } 663 initDialogItems()664 protected void initDialogItems() { 665 createActionItems(); 666 mAdapter = new MyAdapter(); 667 mOverflowAdapter = new MyOverflowAdapter(); 668 mPowerAdapter = new MyPowerOptionsAdapter(); 669 } 670 671 /** 672 * Create the global actions dialog. 673 * 674 * @return A new dialog. 675 */ createDialog()676 protected ActionsDialogLite createDialog() { 677 initDialogItems(); 678 679 ActionsDialogLite dialog = new ActionsDialogLite(mContext, 680 com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActionsLite, 681 mAdapter, mOverflowAdapter, mSysuiColorExtractor, 682 mStatusBarService, mNotificationShadeWindowController, 683 mSysUiState, this::onRefresh, mKeyguardShowing, mPowerAdapter, mUiEventLogger, 684 mStatusBarOptional, mKeyguardUpdateMonitor, mLockPatternUtils, 685 mDialogManager); 686 687 dialog.setOnDismissListener(this); 688 dialog.setOnShowListener(this); 689 690 return dialog; 691 } 692 693 @VisibleForTesting shouldDisplayLockdown(UserInfo user)694 boolean shouldDisplayLockdown(UserInfo user) { 695 if (user == null) { 696 return false; 697 } 698 699 int userId = user.id; 700 701 // Lockdown is meaningless without a place to go. 702 if (!mKeyguardStateController.isMethodSecure()) { 703 return false; 704 } 705 706 // Only show the lockdown button if the device isn't locked down (for whatever reason). 707 int state = mLockPatternUtils.getStrongAuthForUser(userId); 708 return (state == STRONG_AUTH_NOT_REQUIRED 709 || state == SOME_AUTH_REQUIRED_AFTER_USER_REQUEST); 710 } 711 712 @VisibleForTesting shouldDisplayBugReport(UserInfo currentUser)713 boolean shouldDisplayBugReport(UserInfo currentUser) { 714 return mGlobalSettings.getInt(Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 715 && (currentUser == null || currentUser.isPrimary()); 716 } 717 718 @Override onConfigChanged(Configuration newConfig)719 public void onConfigChanged(Configuration newConfig) { 720 if (mDialog != null && mDialog.isShowing() 721 && (newConfig.smallestScreenWidthDp != mSmallestScreenWidthDp)) { 722 mSmallestScreenWidthDp = newConfig.smallestScreenWidthDp; 723 mDialog.refreshDialog(); 724 } 725 } 726 727 /** 728 * Implements {@link GlobalActionsPanelPlugin.Callbacks#dismissGlobalActionsMenu()}, which is 729 * called when the quick access wallet requests dismissal. 730 */ 731 @Override dismissGlobalActionsMenu()732 public void dismissGlobalActionsMenu() { 733 dismissDialog(); 734 } 735 736 @VisibleForTesting 737 protected final class PowerOptionsAction extends SinglePressAction { PowerOptionsAction()738 private PowerOptionsAction() { 739 super(com.android.systemui.R.drawable.ic_settings_power, 740 R.string.global_action_power_options); 741 } 742 743 @Override showDuringKeyguard()744 public boolean showDuringKeyguard() { 745 return true; 746 } 747 748 @Override showBeforeProvisioning()749 public boolean showBeforeProvisioning() { 750 return true; 751 } 752 753 @Override onPress()754 public void onPress() { 755 if (mDialog != null) { 756 mDialog.showPowerOptionsMenu(); 757 } 758 } 759 } 760 761 @VisibleForTesting 762 final class ShutDownAction extends SinglePressAction implements LongPressAction { ShutDownAction()763 ShutDownAction() { 764 super(R.drawable.ic_lock_power_off, 765 R.string.global_action_power_off); 766 } 767 768 @Override onLongPress()769 public boolean onLongPress() { 770 mUiEventLogger.log(GlobalActionsEvent.GA_SHUTDOWN_LONG_PRESS); 771 if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { 772 mWindowManagerFuncs.reboot(true); 773 return true; 774 } 775 return false; 776 } 777 778 @Override showDuringKeyguard()779 public boolean showDuringKeyguard() { 780 return true; 781 } 782 783 @Override showBeforeProvisioning()784 public boolean showBeforeProvisioning() { 785 return true; 786 } 787 788 @Override onPress()789 public void onPress() { 790 mUiEventLogger.log(GlobalActionsEvent.GA_SHUTDOWN_PRESS); 791 // shutdown by making sure radio and power are handled accordingly. 792 mWindowManagerFuncs.shutdown(); 793 } 794 } 795 796 @VisibleForTesting 797 protected abstract class EmergencyAction extends SinglePressAction { EmergencyAction(int iconResId, int messageResId)798 EmergencyAction(int iconResId, int messageResId) { 799 super(iconResId, messageResId); 800 } 801 802 @Override shouldBeSeparated()803 public boolean shouldBeSeparated() { 804 return false; 805 } 806 807 @Override create( Context context, View convertView, ViewGroup parent, LayoutInflater inflater)808 public View create( 809 Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { 810 View v = super.create(context, convertView, parent, inflater); 811 int textColor = getEmergencyTextColor(context); 812 int iconColor = getEmergencyIconColor(context); 813 int backgroundColor = getEmergencyBackgroundColor(context); 814 TextView messageView = v.findViewById(R.id.message); 815 messageView.setTextColor(textColor); 816 messageView.setSelected(true); // necessary for marquee to work 817 ImageView icon = v.findViewById(R.id.icon); 818 icon.getDrawable().setTint(iconColor); 819 icon.setBackgroundTintList(ColorStateList.valueOf(backgroundColor)); 820 v.setBackgroundTintList(ColorStateList.valueOf(backgroundColor)); 821 return v; 822 } 823 824 @Override showDuringKeyguard()825 public boolean showDuringKeyguard() { 826 return true; 827 } 828 829 @Override showBeforeProvisioning()830 public boolean showBeforeProvisioning() { 831 return true; 832 } 833 } 834 getEmergencyTextColor(Context context)835 protected int getEmergencyTextColor(Context context) { 836 return context.getResources().getColor( 837 com.android.systemui.R.color.global_actions_lite_text); 838 } 839 getEmergencyIconColor(Context context)840 protected int getEmergencyIconColor(Context context) { 841 return context.getResources().getColor( 842 com.android.systemui.R.color.global_actions_lite_emergency_icon); 843 } 844 getEmergencyBackgroundColor(Context context)845 protected int getEmergencyBackgroundColor(Context context) { 846 return context.getResources().getColor( 847 com.android.systemui.R.color.global_actions_lite_emergency_background); 848 } 849 850 private class EmergencyAffordanceAction extends EmergencyAction { EmergencyAffordanceAction()851 EmergencyAffordanceAction() { 852 super(R.drawable.emergency_icon, 853 R.string.global_action_emergency); 854 } 855 856 @Override onPress()857 public void onPress() { 858 mEmergencyAffordanceManager.performEmergencyCall(); 859 } 860 } 861 862 @VisibleForTesting 863 class EmergencyDialerAction extends EmergencyAction { EmergencyDialerAction()864 private EmergencyDialerAction() { 865 super(com.android.systemui.R.drawable.ic_emergency_star, 866 R.string.global_action_emergency); 867 } 868 869 @Override onPress()870 public void onPress() { 871 mMetricsLogger.action(MetricsEvent.ACTION_EMERGENCY_DIALER_FROM_POWER_MENU); 872 mUiEventLogger.log(GlobalActionsEvent.GA_EMERGENCY_DIALER_PRESS); 873 if (mTelecomManager != null) { 874 // Close shade so user sees the activity 875 mStatusBarOptional.ifPresent(StatusBar::collapseShade); 876 Intent intent = mTelecomManager.createLaunchEmergencyDialerIntent( 877 null /* number */); 878 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK 879 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 880 | Intent.FLAG_ACTIVITY_CLEAR_TOP); 881 intent.putExtra(EmergencyDialerConstants.EXTRA_ENTRY_TYPE, 882 EmergencyDialerConstants.ENTRY_TYPE_POWER_MENU); 883 mContext.startActivityAsUser(intent, UserHandle.CURRENT); 884 } 885 } 886 } 887 888 @VisibleForTesting makeEmergencyDialerActionForTesting()889 EmergencyDialerAction makeEmergencyDialerActionForTesting() { 890 return new EmergencyDialerAction(); 891 } 892 893 @VisibleForTesting 894 final class RestartAction extends SinglePressAction implements LongPressAction { RestartAction()895 RestartAction() { 896 super(R.drawable.ic_restart, R.string.global_action_restart); 897 } 898 899 @Override onLongPress()900 public boolean onLongPress() { 901 mUiEventLogger.log(GlobalActionsEvent.GA_REBOOT_LONG_PRESS); 902 if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { 903 mWindowManagerFuncs.reboot(true); 904 return true; 905 } 906 return false; 907 } 908 909 @Override showDuringKeyguard()910 public boolean showDuringKeyguard() { 911 return true; 912 } 913 914 @Override showBeforeProvisioning()915 public boolean showBeforeProvisioning() { 916 return true; 917 } 918 919 @Override onPress()920 public void onPress() { 921 mUiEventLogger.log(GlobalActionsEvent.GA_REBOOT_PRESS); 922 mWindowManagerFuncs.reboot(false); 923 } 924 } 925 926 @VisibleForTesting 927 class ScreenshotAction extends SinglePressAction { ScreenshotAction()928 ScreenshotAction() { 929 super(R.drawable.ic_screenshot, R.string.global_action_screenshot); 930 } 931 932 @Override onPress()933 public void onPress() { 934 // Add a little delay before executing, to give the 935 // dialog a chance to go away before it takes a 936 // screenshot. 937 // TODO: instead, omit global action dialog layer 938 mHandler.postDelayed(new Runnable() { 939 @Override 940 public void run() { 941 mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, true, true, 942 SCREENSHOT_GLOBAL_ACTIONS, mHandler, null); 943 mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU); 944 mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_PRESS); 945 } 946 }, mDialogPressDelay); 947 } 948 949 @Override showDuringKeyguard()950 public boolean showDuringKeyguard() { 951 return true; 952 } 953 954 @Override showBeforeProvisioning()955 public boolean showBeforeProvisioning() { 956 return false; 957 } 958 959 @Override shouldShow()960 public boolean shouldShow() { 961 // Include screenshot in power menu for legacy nav because it is not accessible 962 // through Recents in that mode 963 return is2ButtonNavigationEnabled(); 964 } 965 is2ButtonNavigationEnabled()966 boolean is2ButtonNavigationEnabled() { 967 return NAV_BAR_MODE_2BUTTON == mContext.getResources().getInteger( 968 com.android.internal.R.integer.config_navBarInteractionMode); 969 } 970 } 971 972 @VisibleForTesting makeScreenshotActionForTesting()973 ScreenshotAction makeScreenshotActionForTesting() { 974 return new ScreenshotAction(); 975 } 976 977 @VisibleForTesting 978 class BugReportAction extends SinglePressAction implements LongPressAction { 979 BugReportAction()980 BugReportAction() { 981 super(R.drawable.ic_lock_bugreport, R.string.bugreport_title); 982 } 983 984 @Override onPress()985 public void onPress() { 986 // don't actually trigger the bugreport if we are running stability 987 // tests via monkey 988 if (ActivityManager.isUserAMonkey()) { 989 return; 990 } 991 // Add a little delay before executing, to give the 992 // dialog a chance to go away before it takes a 993 // screenshot. 994 mHandler.postDelayed(new Runnable() { 995 @Override 996 public void run() { 997 try { 998 // Take an "interactive" bugreport. 999 mMetricsLogger.action( 1000 MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE); 1001 mUiEventLogger.log(GlobalActionsEvent.GA_BUGREPORT_PRESS); 1002 if (!mIActivityManager.launchBugReportHandlerApp()) { 1003 Log.w(TAG, "Bugreport handler could not be launched"); 1004 mIActivityManager.requestInteractiveBugReport(); 1005 } 1006 // Close shade so user sees the activity 1007 mStatusBarOptional.ifPresent(StatusBar::collapseShade); 1008 } catch (RemoteException e) { 1009 } 1010 } 1011 }, mDialogPressDelay); 1012 } 1013 1014 @Override onLongPress()1015 public boolean onLongPress() { 1016 // don't actually trigger the bugreport if we are running stability 1017 // tests via monkey 1018 if (ActivityManager.isUserAMonkey()) { 1019 return false; 1020 } 1021 try { 1022 // Take a "full" bugreport. 1023 mMetricsLogger.action(MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL); 1024 mUiEventLogger.log(GlobalActionsEvent.GA_BUGREPORT_LONG_PRESS); 1025 mIActivityManager.requestFullBugReport(); 1026 // Close shade so user sees the activity 1027 mStatusBarOptional.ifPresent(StatusBar::collapseShade); 1028 } catch (RemoteException e) { 1029 } 1030 return false; 1031 } 1032 showDuringKeyguard()1033 public boolean showDuringKeyguard() { 1034 return true; 1035 } 1036 1037 @Override showBeforeProvisioning()1038 public boolean showBeforeProvisioning() { 1039 return false; 1040 } 1041 } 1042 1043 @VisibleForTesting makeBugReportActionForTesting()1044 BugReportAction makeBugReportActionForTesting() { 1045 return new BugReportAction(); 1046 } 1047 1048 private final class LogoutAction extends SinglePressAction { LogoutAction()1049 private LogoutAction() { 1050 super(R.drawable.ic_logout, R.string.global_action_logout); 1051 } 1052 1053 @Override showDuringKeyguard()1054 public boolean showDuringKeyguard() { 1055 return true; 1056 } 1057 1058 @Override showBeforeProvisioning()1059 public boolean showBeforeProvisioning() { 1060 return false; 1061 } 1062 1063 @Override onPress()1064 public void onPress() { 1065 // Add a little delay before executing, to give the dialog a chance to go away before 1066 // switching user 1067 mHandler.postDelayed(() -> { 1068 try { 1069 int currentUserId = getCurrentUser().id; 1070 mIActivityManager.switchUser(UserHandle.USER_SYSTEM); 1071 mIActivityManager.stopUser(currentUserId, true /*force*/, null); 1072 } catch (RemoteException re) { 1073 Log.e(TAG, "Couldn't logout user " + re); 1074 } 1075 }, mDialogPressDelay); 1076 } 1077 } 1078 getSettingsAction()1079 private Action getSettingsAction() { 1080 return new SinglePressAction(R.drawable.ic_settings, 1081 R.string.global_action_settings) { 1082 1083 @Override 1084 public void onPress() { 1085 Intent intent = new Intent(Settings.ACTION_SETTINGS); 1086 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); 1087 mContext.startActivity(intent); 1088 } 1089 1090 @Override 1091 public boolean showDuringKeyguard() { 1092 return true; 1093 } 1094 1095 @Override 1096 public boolean showBeforeProvisioning() { 1097 return true; 1098 } 1099 }; 1100 } 1101 1102 private Action getAssistAction() { 1103 return new SinglePressAction(R.drawable.ic_action_assist_focused, 1104 R.string.global_action_assist) { 1105 @Override 1106 public void onPress() { 1107 Intent intent = new Intent(Intent.ACTION_ASSIST); 1108 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); 1109 mContext.startActivity(intent); 1110 } 1111 1112 @Override 1113 public boolean showDuringKeyguard() { 1114 return true; 1115 } 1116 1117 @Override 1118 public boolean showBeforeProvisioning() { 1119 return true; 1120 } 1121 }; 1122 } 1123 1124 private Action getVoiceAssistAction() { 1125 return new SinglePressAction(R.drawable.ic_voice_search, 1126 R.string.global_action_voice_assist) { 1127 @Override 1128 public void onPress() { 1129 Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST); 1130 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); 1131 mContext.startActivity(intent); 1132 } 1133 1134 @Override 1135 public boolean showDuringKeyguard() { 1136 return true; 1137 } 1138 1139 @Override 1140 public boolean showBeforeProvisioning() { 1141 return true; 1142 } 1143 }; 1144 } 1145 1146 @VisibleForTesting 1147 class LockDownAction extends SinglePressAction { 1148 LockDownAction() { 1149 super(R.drawable.ic_lock_lockdown, R.string.global_action_lockdown); 1150 } 1151 1152 @Override 1153 public void onPress() { 1154 mLockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, 1155 UserHandle.USER_ALL); 1156 mUiEventLogger.log(GlobalActionsEvent.GA_LOCKDOWN_PRESS); 1157 try { 1158 mIWindowManager.lockNow(null); 1159 // Lock profiles (if any) on the background thread. 1160 mBackgroundExecutor.execute(() -> lockProfiles()); 1161 } catch (RemoteException e) { 1162 Log.e(TAG, "Error while trying to lock device.", e); 1163 } 1164 } 1165 1166 @Override 1167 public boolean showDuringKeyguard() { 1168 return true; 1169 } 1170 1171 @Override 1172 public boolean showBeforeProvisioning() { 1173 return false; 1174 } 1175 } 1176 1177 private void lockProfiles() { 1178 final int currentUserId = getCurrentUser().id; 1179 final int[] profileIds = mUserManager.getEnabledProfileIds(currentUserId); 1180 for (final int id : profileIds) { 1181 if (id != currentUserId) { 1182 mTrustManager.setDeviceLockedForUser(id, true); 1183 } 1184 } 1185 } 1186 1187 protected UserInfo getCurrentUser() { 1188 try { 1189 return mIActivityManager.getCurrentUser(); 1190 } catch (RemoteException re) { 1191 return null; 1192 } 1193 } 1194 1195 /** 1196 * Non-thread-safe current user provider that caches the result - helpful when a method needs 1197 * to fetch it an indeterminate number of times. 1198 */ 1199 private class CurrentUserProvider { 1200 private UserInfo mUserInfo = null; 1201 private boolean mFetched = false; 1202 1203 @Nullable 1204 UserInfo get() { 1205 if (!mFetched) { 1206 mFetched = true; 1207 mUserInfo = getCurrentUser(); 1208 } 1209 return mUserInfo; 1210 } 1211 } 1212 1213 private void addUserActions(List<Action> actions, UserInfo currentUser) { 1214 if (mUserManager.isUserSwitcherEnabled()) { 1215 List<UserInfo> users = mUserManager.getUsers(); 1216 for (final UserInfo user : users) { 1217 if (user.supportsSwitchToByUser()) { 1218 boolean isCurrentUser = currentUser == null 1219 ? user.id == 0 : (currentUser.id == user.id); 1220 Drawable icon = user.iconPath != null ? Drawable.createFromPath(user.iconPath) 1221 : null; 1222 SinglePressAction switchToUser = new SinglePressAction( 1223 R.drawable.ic_menu_cc, icon, 1224 (user.name != null ? user.name : "Primary") 1225 + (isCurrentUser ? " \u2714" : "")) { 1226 public void onPress() { 1227 try { 1228 mIActivityManager.switchUser(user.id); 1229 } catch (RemoteException re) { 1230 Log.e(TAG, "Couldn't switch user " + re); 1231 } 1232 } 1233 1234 public boolean showDuringKeyguard() { 1235 return true; 1236 } 1237 1238 public boolean showBeforeProvisioning() { 1239 return false; 1240 } 1241 }; 1242 addIfShouldShowAction(actions, switchToUser); 1243 } 1244 } 1245 } 1246 } 1247 1248 protected void prepareDialog() { 1249 refreshSilentMode(); 1250 mAirplaneModeOn.updateState(mAirplaneState); 1251 mAdapter.notifyDataSetChanged(); 1252 mLifecycle.setCurrentState(Lifecycle.State.RESUMED); 1253 } 1254 1255 private void refreshSilentMode() { 1256 if (!mHasVibrator) { 1257 Integer value = mRingerModeTracker.getRingerMode().getValue(); 1258 final boolean silentModeOn = value != null && value != AudioManager.RINGER_MODE_NORMAL; 1259 ((ToggleAction) mSilentModeAction).updateState( 1260 silentModeOn ? ToggleState.On : ToggleState.Off); 1261 } 1262 } 1263 1264 /** 1265 * {@inheritDoc} 1266 */ 1267 @Override 1268 public void onDismiss(DialogInterface dialog) { 1269 if (mDialog == dialog) { 1270 mDialog = null; 1271 } 1272 mUiEventLogger.log(GlobalActionsEvent.GA_POWER_MENU_CLOSE); 1273 mWindowManagerFuncs.onGlobalActionsHidden(); 1274 mLifecycle.setCurrentState(Lifecycle.State.CREATED); 1275 } 1276 1277 /** 1278 * {@inheritDoc} 1279 */ 1280 @Override 1281 public void onShow(DialogInterface dialog) { 1282 mMetricsLogger.visible(MetricsEvent.POWER_MENU); 1283 mUiEventLogger.log(GlobalActionsEvent.GA_POWER_MENU_OPEN); 1284 } 1285 1286 /** 1287 * The adapter used for power menu items shown in the global actions dialog. 1288 */ 1289 public class MyAdapter extends MultiListAdapter { 1290 private int countItems(boolean separated) { 1291 int count = 0; 1292 for (int i = 0; i < mItems.size(); i++) { 1293 final Action action = mItems.get(i); 1294 1295 if (action.shouldBeSeparated() == separated) { 1296 count++; 1297 } 1298 } 1299 return count; 1300 } 1301 1302 @Override 1303 public int countSeparatedItems() { 1304 return countItems(true); 1305 } 1306 1307 @Override 1308 public int countListItems() { 1309 return countItems(false); 1310 } 1311 1312 @Override 1313 public int getCount() { 1314 return countSeparatedItems() + countListItems(); 1315 } 1316 1317 @Override 1318 public boolean isEnabled(int position) { 1319 return getItem(position).isEnabled(); 1320 } 1321 1322 @Override 1323 public boolean areAllItemsEnabled() { 1324 return false; 1325 } 1326 1327 @Override 1328 public Action getItem(int position) { 1329 int filteredPos = 0; 1330 for (int i = 0; i < mItems.size(); i++) { 1331 final Action action = mItems.get(i); 1332 if (!shouldShowAction(action)) { 1333 continue; 1334 } 1335 if (filteredPos == position) { 1336 return action; 1337 } 1338 filteredPos++; 1339 } 1340 1341 throw new IllegalArgumentException("position " + position 1342 + " out of range of showable actions" 1343 + ", filtered count=" + getCount() 1344 + ", keyguardshowing=" + mKeyguardShowing 1345 + ", provisioned=" + mDeviceProvisioned); 1346 } 1347 1348 /** 1349 * Get the row ID for an item 1350 * @param position The position of the item within the adapter's data set 1351 * @return 1352 */ 1353 public long getItemId(int position) { 1354 return position; 1355 } 1356 1357 @Override 1358 public View getView(int position, View convertView, ViewGroup parent) { 1359 Action action = getItem(position); 1360 View view = action.create(mContext, convertView, parent, LayoutInflater.from(mContext)); 1361 view.setOnClickListener(v -> onClickItem(position)); 1362 if (action instanceof LongPressAction) { 1363 view.setOnLongClickListener(v -> onLongClickItem(position)); 1364 } 1365 return view; 1366 } 1367 1368 @Override 1369 public boolean onLongClickItem(int position) { 1370 final Action action = mAdapter.getItem(position); 1371 if (action instanceof LongPressAction) { 1372 if (mDialog != null) { 1373 // Usually clicking an item shuts down the phone, locks, or starts an activity. 1374 // We don't want to animate back into the power button when that happens, so we 1375 // disable the dialog animation before dismissing. 1376 mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); 1377 mDialog.dismiss(); 1378 } else { 1379 Log.w(TAG, "Action long-clicked while mDialog is null."); 1380 } 1381 return ((LongPressAction) action).onLongPress(); 1382 } 1383 return false; 1384 } 1385 1386 @Override 1387 public void onClickItem(int position) { 1388 Action item = mAdapter.getItem(position); 1389 if (!(item instanceof SilentModeTriStateAction)) { 1390 if (mDialog != null) { 1391 // don't dismiss the dialog if we're opening the power options menu 1392 if (!(item instanceof PowerOptionsAction)) { 1393 // Usually clicking an item shuts down the phone, locks, or starts an 1394 // activity. We don't want to animate back into the power button when that 1395 // happens, so we disable the dialog animation before dismissing. 1396 mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); 1397 mDialog.dismiss(); 1398 } 1399 } else { 1400 Log.w(TAG, "Action clicked while mDialog is null."); 1401 } 1402 item.onPress(); 1403 } 1404 } 1405 1406 @Override 1407 public boolean shouldBeSeparated(int position) { 1408 return getItem(position).shouldBeSeparated(); 1409 } 1410 } 1411 1412 /** 1413 * The adapter used for items in the overflow menu. 1414 */ 1415 public class MyPowerOptionsAdapter extends BaseAdapter { 1416 @Override 1417 public int getCount() { 1418 return mPowerItems.size(); 1419 } 1420 1421 @Override 1422 public Action getItem(int position) { 1423 return mPowerItems.get(position); 1424 } 1425 1426 @Override 1427 public long getItemId(int position) { 1428 return position; 1429 } 1430 1431 @Override 1432 public View getView(int position, View convertView, ViewGroup parent) { 1433 Action action = getItem(position); 1434 if (action == null) { 1435 Log.w(TAG, "No power options action found at position: " + position); 1436 return null; 1437 } 1438 int viewLayoutResource = com.android.systemui.R.layout.global_actions_power_item; 1439 View view = convertView != null ? convertView 1440 : LayoutInflater.from(mContext).inflate(viewLayoutResource, parent, false); 1441 view.setOnClickListener(v -> onClickItem(position)); 1442 if (action instanceof LongPressAction) { 1443 view.setOnLongClickListener(v -> onLongClickItem(position)); 1444 } 1445 ImageView icon = view.findViewById(R.id.icon); 1446 TextView messageView = view.findViewById(R.id.message); 1447 messageView.setSelected(true); // necessary for marquee to work 1448 1449 icon.setImageDrawable(action.getIcon(mContext)); 1450 icon.setScaleType(ScaleType.CENTER_CROP); 1451 1452 if (action.getMessage() != null) { 1453 messageView.setText(action.getMessage()); 1454 } else { 1455 messageView.setText(action.getMessageResId()); 1456 } 1457 return view; 1458 } 1459 1460 private boolean onLongClickItem(int position) { 1461 final Action action = getItem(position); 1462 if (action instanceof LongPressAction) { 1463 if (mDialog != null) { 1464 // Usually clicking an item shuts down the phone, locks, or starts an activity. 1465 // We don't want to animate back into the power button when that happens, so we 1466 // disable the dialog animation before dismissing. 1467 mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); 1468 mDialog.dismiss(); 1469 } else { 1470 Log.w(TAG, "Action long-clicked while mDialog is null."); 1471 } 1472 return ((LongPressAction) action).onLongPress(); 1473 } 1474 return false; 1475 } 1476 1477 private void onClickItem(int position) { 1478 Action item = getItem(position); 1479 if (!(item instanceof SilentModeTriStateAction)) { 1480 if (mDialog != null) { 1481 // Usually clicking an item shuts down the phone, locks, or starts an activity. 1482 // We don't want to animate back into the power button when that happens, so we 1483 // disable the dialog animation before dismissing. 1484 mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); 1485 mDialog.dismiss(); 1486 } else { 1487 Log.w(TAG, "Action clicked while mDialog is null."); 1488 } 1489 item.onPress(); 1490 } 1491 } 1492 } 1493 1494 /** 1495 * The adapter used for items in the power options menu, triggered by the PowerOptionsAction. 1496 */ 1497 public class MyOverflowAdapter extends BaseAdapter { 1498 @Override 1499 public int getCount() { 1500 return mOverflowItems.size(); 1501 } 1502 1503 @Override 1504 public Action getItem(int position) { 1505 return mOverflowItems.get(position); 1506 } 1507 1508 @Override 1509 public long getItemId(int position) { 1510 return position; 1511 } 1512 1513 @Override 1514 public View getView(int position, View convertView, ViewGroup parent) { 1515 Action action = getItem(position); 1516 if (action == null) { 1517 Log.w(TAG, "No overflow action found at position: " + position); 1518 return null; 1519 } 1520 int viewLayoutResource = com.android.systemui.R.layout.controls_more_item; 1521 View view = convertView != null ? convertView 1522 : LayoutInflater.from(mContext).inflate(viewLayoutResource, parent, false); 1523 TextView textView = (TextView) view; 1524 if (action.getMessageResId() != 0) { 1525 textView.setText(action.getMessageResId()); 1526 } else { 1527 textView.setText(action.getMessage()); 1528 } 1529 return textView; 1530 } 1531 1532 protected boolean onLongClickItem(int position) { 1533 final Action action = getItem(position); 1534 if (action instanceof LongPressAction) { 1535 if (mDialog != null) { 1536 // Usually clicking an item shuts down the phone, locks, or starts an activity. 1537 // We don't want to animate back into the power button when that happens, so we 1538 // disable the dialog animation before dismissing. 1539 mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); 1540 mDialog.dismiss(); 1541 } else { 1542 Log.w(TAG, "Action long-clicked while mDialog is null."); 1543 } 1544 return ((LongPressAction) action).onLongPress(); 1545 } 1546 return false; 1547 } 1548 1549 protected void onClickItem(int position) { 1550 Action item = getItem(position); 1551 if (!(item instanceof SilentModeTriStateAction)) { 1552 if (mDialog != null) { 1553 // Usually clicking an item shuts down the phone, locks, or starts an activity. 1554 // We don't want to animate back into the power button when that happens, so we 1555 // disable the dialog animation before dismissing. 1556 mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); 1557 mDialog.dismiss(); 1558 } else { 1559 Log.w(TAG, "Action clicked while mDialog is null."); 1560 } 1561 item.onPress(); 1562 } 1563 } 1564 } 1565 1566 // note: the scheme below made more sense when we were planning on having 1567 // 8 different things in the global actions dialog. seems overkill with 1568 // only 3 items now, but may as well keep this flexible approach so it will 1569 // be easy should someone decide at the last minute to include something 1570 // else, such as 'enable wifi', or 'enable bluetooth' 1571 1572 /** 1573 * What each item in the global actions dialog must be able to support. 1574 */ 1575 public interface Action { 1576 /** 1577 * @return Text that will be announced when dialog is created. null for none. 1578 */ 1579 CharSequence getLabelForAccessibility(Context context); 1580 1581 /** 1582 * Create the item's view 1583 * @param context 1584 * @param convertView 1585 * @param parent 1586 * @param inflater 1587 * @return 1588 */ 1589 View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater); 1590 1591 /** 1592 * Handle a regular press 1593 */ 1594 void onPress(); 1595 1596 /** 1597 * @return whether this action should appear in the dialog when the keygaurd is showing. 1598 */ 1599 boolean showDuringKeyguard(); 1600 1601 /** 1602 * @return whether this action should appear in the dialog before the 1603 * device is provisioned.f 1604 */ 1605 boolean showBeforeProvisioning(); 1606 1607 /** 1608 * @return whether this action is enabled 1609 */ 1610 boolean isEnabled(); 1611 1612 /** 1613 * @return whether this action should be in a separate section 1614 */ 1615 default boolean shouldBeSeparated() { 1616 return false; 1617 } 1618 1619 /** 1620 * Return the id of the message associated with this action, or 0 if it doesn't have one. 1621 * @return 1622 */ 1623 int getMessageResId(); 1624 1625 /** 1626 * Return the icon drawable for this action. 1627 */ 1628 Drawable getIcon(Context context); 1629 1630 /** 1631 * Return the message associated with this action, or null if it doesn't have one. 1632 * @return 1633 */ 1634 CharSequence getMessage(); 1635 1636 /** 1637 * @return whether the action should be visible 1638 */ 1639 default boolean shouldShow() { 1640 return true; 1641 } 1642 } 1643 1644 /** 1645 * An action that also supports long press. 1646 */ 1647 private interface LongPressAction extends Action { 1648 boolean onLongPress(); 1649 } 1650 1651 /** 1652 * A single press action maintains no state, just responds to a press and takes an action. 1653 */ 1654 1655 private abstract class SinglePressAction implements Action { 1656 private final int mIconResId; 1657 private final Drawable mIcon; 1658 private final int mMessageResId; 1659 private final CharSequence mMessage; 1660 1661 protected SinglePressAction(int iconResId, int messageResId) { 1662 mIconResId = iconResId; 1663 mMessageResId = messageResId; 1664 mMessage = null; 1665 mIcon = null; 1666 } 1667 1668 protected SinglePressAction(int iconResId, Drawable icon, CharSequence message) { 1669 mIconResId = iconResId; 1670 mMessageResId = 0; 1671 mMessage = message; 1672 mIcon = icon; 1673 } 1674 1675 public boolean isEnabled() { 1676 return true; 1677 } 1678 1679 public String getStatus() { 1680 return null; 1681 } 1682 1683 public abstract void onPress(); 1684 1685 public CharSequence getLabelForAccessibility(Context context) { 1686 if (mMessage != null) { 1687 return mMessage; 1688 } else { 1689 return context.getString(mMessageResId); 1690 } 1691 } 1692 1693 public int getMessageResId() { 1694 return mMessageResId; 1695 } 1696 1697 public CharSequence getMessage() { 1698 return mMessage; 1699 } 1700 1701 @Override 1702 public Drawable getIcon(Context context) { 1703 if (mIcon != null) { 1704 return mIcon; 1705 } else { 1706 return context.getDrawable(mIconResId); 1707 } 1708 } 1709 1710 public View create( 1711 Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { 1712 View v = inflater.inflate(getGridItemLayoutResource(), parent, false /* attach */); 1713 // ConstraintLayout flow needs an ID to reference 1714 v.setId(View.generateViewId()); 1715 1716 ImageView icon = v.findViewById(R.id.icon); 1717 TextView messageView = v.findViewById(R.id.message); 1718 messageView.setSelected(true); // necessary for marquee to work 1719 1720 icon.setImageDrawable(getIcon(context)); 1721 icon.setScaleType(ScaleType.CENTER_CROP); 1722 1723 if (mMessage != null) { 1724 messageView.setText(mMessage); 1725 } else { 1726 messageView.setText(mMessageResId); 1727 } 1728 1729 return v; 1730 } 1731 } 1732 1733 protected int getGridItemLayoutResource() { 1734 return com.android.systemui.R.layout.global_actions_grid_item_lite; 1735 } 1736 1737 private enum ToggleState { 1738 Off(false), 1739 TurningOn(true), 1740 TurningOff(true), 1741 On(false); 1742 1743 private final boolean mInTransition; 1744 1745 ToggleState(boolean intermediate) { 1746 mInTransition = intermediate; 1747 } 1748 1749 public boolean inTransition() { 1750 return mInTransition; 1751 } 1752 } 1753 1754 /** 1755 * A toggle action knows whether it is on or off, and displays an icon and status message 1756 * accordingly. 1757 */ 1758 private abstract class ToggleAction implements Action { 1759 1760 protected ToggleState mState = ToggleState.Off; 1761 1762 // prefs 1763 protected int mEnabledIconResId; 1764 protected int mDisabledIconResid; 1765 protected int mMessageResId; 1766 protected int mEnabledStatusMessageResId; 1767 protected int mDisabledStatusMessageResId; 1768 1769 /** 1770 * @param enabledIconResId The icon for when this action is on. 1771 * @param disabledIconResid The icon for when this action is off. 1772 * @param message The general information message, e.g 'Silent Mode' 1773 * @param enabledStatusMessageResId The on status message, e.g 'sound disabled' 1774 * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled' 1775 */ 1776 ToggleAction(int enabledIconResId, 1777 int disabledIconResid, 1778 int message, 1779 int enabledStatusMessageResId, 1780 int disabledStatusMessageResId) { 1781 mEnabledIconResId = enabledIconResId; 1782 mDisabledIconResid = disabledIconResid; 1783 mMessageResId = message; 1784 mEnabledStatusMessageResId = enabledStatusMessageResId; 1785 mDisabledStatusMessageResId = disabledStatusMessageResId; 1786 } 1787 1788 /** 1789 * Override to make changes to resource IDs just before creating the View. 1790 */ 1791 void willCreate() { 1792 1793 } 1794 1795 @Override 1796 public CharSequence getLabelForAccessibility(Context context) { 1797 return context.getString(mMessageResId); 1798 } 1799 1800 private boolean isOn() { 1801 return mState == ToggleState.On || mState == ToggleState.TurningOn; 1802 } 1803 1804 @Override 1805 public CharSequence getMessage() { 1806 return null; 1807 } 1808 @Override 1809 public int getMessageResId() { 1810 return isOn() ? mEnabledStatusMessageResId : mDisabledStatusMessageResId; 1811 } 1812 1813 private int getIconResId() { 1814 return isOn() ? mEnabledIconResId : mDisabledIconResid; 1815 } 1816 1817 @Override 1818 public Drawable getIcon(Context context) { 1819 return context.getDrawable(getIconResId()); 1820 } 1821 1822 public View create(Context context, View convertView, ViewGroup parent, 1823 LayoutInflater inflater) { 1824 willCreate(); 1825 1826 View v = inflater.inflate(com.android.systemui.R.layout.global_actions_grid_item_v2, 1827 parent, false /* attach */); 1828 ViewGroup.LayoutParams p = v.getLayoutParams(); 1829 p.width = WRAP_CONTENT; 1830 v.setLayoutParams(p); 1831 1832 ImageView icon = (ImageView) v.findViewById(R.id.icon); 1833 TextView messageView = (TextView) v.findViewById(R.id.message); 1834 final boolean enabled = isEnabled(); 1835 1836 if (messageView != null) { 1837 messageView.setText(getMessageResId()); 1838 messageView.setEnabled(enabled); 1839 messageView.setSelected(true); // necessary for marquee to work 1840 } 1841 1842 if (icon != null) { 1843 icon.setImageDrawable(context.getDrawable(getIconResId())); 1844 icon.setEnabled(enabled); 1845 } 1846 1847 v.setEnabled(enabled); 1848 1849 return v; 1850 } 1851 1852 public final void onPress() { 1853 if (mState.inTransition()) { 1854 Log.w(TAG, "shouldn't be able to toggle when in transition"); 1855 return; 1856 } 1857 1858 final boolean nowOn = !(mState == ToggleState.On); 1859 onToggle(nowOn); 1860 changeStateFromPress(nowOn); 1861 } 1862 1863 public boolean isEnabled() { 1864 return !mState.inTransition(); 1865 } 1866 1867 /** 1868 * Implementations may override this if their state can be in on of the intermediate states 1869 * until some notification is received (e.g airplane mode is 'turning off' until we know the 1870 * wireless connections are back online 1871 * 1872 * @param buttonOn Whether the button was turned on or off 1873 */ 1874 protected void changeStateFromPress(boolean buttonOn) { 1875 mState = buttonOn ? ToggleState.On : ToggleState.Off; 1876 } 1877 1878 abstract void onToggle(boolean on); 1879 1880 public void updateState(ToggleState state) { 1881 mState = state; 1882 } 1883 } 1884 1885 private class AirplaneModeAction extends ToggleAction { 1886 AirplaneModeAction() { 1887 super( 1888 R.drawable.ic_lock_airplane_mode, 1889 R.drawable.ic_lock_airplane_mode_off, 1890 R.string.global_actions_toggle_airplane_mode, 1891 R.string.global_actions_airplane_mode_on_status, 1892 R.string.global_actions_airplane_mode_off_status); 1893 } 1894 1895 void onToggle(boolean on) { 1896 if (mHasTelephony && TelephonyProperties.in_ecm_mode().orElse(false)) { 1897 mIsWaitingForEcmExit = true; 1898 // Launch ECM exit dialog 1899 Intent ecmDialogIntent = 1900 new Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null); 1901 ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 1902 mContext.startActivity(ecmDialogIntent); 1903 } else { 1904 changeAirplaneModeSystemSetting(on); 1905 } 1906 } 1907 1908 @Override 1909 protected void changeStateFromPress(boolean buttonOn) { 1910 if (!mHasTelephony) return; 1911 1912 // In ECM mode airplane state cannot be changed 1913 if (!TelephonyProperties.in_ecm_mode().orElse(false)) { 1914 mState = buttonOn ? ToggleState.TurningOn : ToggleState.TurningOff; 1915 mAirplaneState = mState; 1916 } 1917 } 1918 1919 public boolean showDuringKeyguard() { 1920 return true; 1921 } 1922 1923 public boolean showBeforeProvisioning() { 1924 return false; 1925 } 1926 } 1927 1928 private class SilentModeToggleAction extends ToggleAction { 1929 SilentModeToggleAction() { 1930 super(R.drawable.ic_audio_vol_mute, 1931 R.drawable.ic_audio_vol, 1932 R.string.global_action_toggle_silent_mode, 1933 R.string.global_action_silent_mode_on_status, 1934 R.string.global_action_silent_mode_off_status); 1935 } 1936 1937 void onToggle(boolean on) { 1938 if (on) { 1939 mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT); 1940 } else { 1941 mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL); 1942 } 1943 } 1944 1945 public boolean showDuringKeyguard() { 1946 return true; 1947 } 1948 1949 public boolean showBeforeProvisioning() { 1950 return false; 1951 } 1952 } 1953 1954 private static class SilentModeTriStateAction implements Action, View.OnClickListener { 1955 1956 private static final int[] ITEM_IDS = {R.id.option1, R.id.option2, R.id.option3}; 1957 1958 private final AudioManager mAudioManager; 1959 private final Handler mHandler; 1960 1961 SilentModeTriStateAction(AudioManager audioManager, Handler handler) { 1962 mAudioManager = audioManager; 1963 mHandler = handler; 1964 } 1965 1966 private int ringerModeToIndex(int ringerMode) { 1967 // They just happen to coincide 1968 return ringerMode; 1969 } 1970 1971 private int indexToRingerMode(int index) { 1972 // They just happen to coincide 1973 return index; 1974 } 1975 1976 @Override 1977 public CharSequence getLabelForAccessibility(Context context) { 1978 return null; 1979 } 1980 1981 @Override 1982 public int getMessageResId() { 1983 return 0; 1984 } 1985 1986 @Override 1987 public CharSequence getMessage() { 1988 return null; 1989 } 1990 1991 @Override 1992 public Drawable getIcon(Context context) { 1993 return null; 1994 } 1995 1996 1997 public View create(Context context, View convertView, ViewGroup parent, 1998 LayoutInflater inflater) { 1999 View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false); 2000 2001 int selectedIndex = ringerModeToIndex(mAudioManager.getRingerMode()); 2002 for (int i = 0; i < 3; i++) { 2003 View itemView = v.findViewById(ITEM_IDS[i]); 2004 itemView.setSelected(selectedIndex == i); 2005 // Set up click handler 2006 itemView.setTag(i); 2007 itemView.setOnClickListener(this); 2008 } 2009 return v; 2010 } 2011 2012 public void onPress() { 2013 } 2014 2015 public boolean showDuringKeyguard() { 2016 return true; 2017 } 2018 2019 public boolean showBeforeProvisioning() { 2020 return false; 2021 } 2022 2023 public boolean isEnabled() { 2024 return true; 2025 } 2026 2027 void willCreate() { 2028 } 2029 2030 public void onClick(View v) { 2031 if (!(v.getTag() instanceof Integer)) return; 2032 2033 int index = (Integer) v.getTag(); 2034 mAudioManager.setRingerMode(indexToRingerMode(index)); 2035 mHandler.sendEmptyMessageDelayed(MESSAGE_DISMISS, DIALOG_DISMISS_DELAY); 2036 } 2037 } 2038 2039 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 2040 public void onReceive(Context context, Intent intent) { 2041 String action = intent.getAction(); 2042 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) 2043 || Intent.ACTION_SCREEN_OFF.equals(action)) { 2044 String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY); 2045 if (!SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) { 2046 mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_DISMISS, reason)); 2047 } 2048 } else if (TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) { 2049 // Airplane mode can be changed after ECM exits if airplane toggle button 2050 // is pressed during ECM mode 2051 if (!(intent.getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false)) 2052 && mIsWaitingForEcmExit) { 2053 mIsWaitingForEcmExit = false; 2054 changeAirplaneModeSystemSetting(true); 2055 } 2056 } 2057 } 2058 }; 2059 2060 private final TelephonyCallback.ServiceStateListener mPhoneStateListener = 2061 new TelephonyCallback.ServiceStateListener() { 2062 @Override 2063 public void onServiceStateChanged(ServiceState serviceState) { 2064 if (!mHasTelephony) return; 2065 if (mAirplaneModeOn == null) { 2066 Log.d(TAG, "Service changed before actions created"); 2067 return; 2068 } 2069 final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF; 2070 mAirplaneState = inAirplaneMode ? ToggleState.On : ToggleState.Off; 2071 mAirplaneModeOn.updateState(mAirplaneState); 2072 mAdapter.notifyDataSetChanged(); 2073 mOverflowAdapter.notifyDataSetChanged(); 2074 mPowerAdapter.notifyDataSetChanged(); 2075 } 2076 }; 2077 2078 private final ContentObserver mAirplaneModeObserver = new ContentObserver(mMainHandler) { 2079 @Override 2080 public void onChange(boolean selfChange) { 2081 onAirplaneModeChanged(); 2082 } 2083 }; 2084 2085 private static final int MESSAGE_DISMISS = 0; 2086 private static final int MESSAGE_REFRESH = 1; 2087 private static final int DIALOG_DISMISS_DELAY = 300; // ms 2088 private static final int DIALOG_PRESS_DELAY = 850; // ms 2089 2090 @VisibleForTesting void setZeroDialogPressDelayForTesting() { 2091 mDialogPressDelay = 0; // ms 2092 } 2093 2094 private Handler mHandler = new Handler() { 2095 public void handleMessage(Message msg) { 2096 switch (msg.what) { 2097 case MESSAGE_DISMISS: 2098 if (mDialog != null) { 2099 if (SYSTEM_DIALOG_REASON_DREAM.equals(msg.obj)) { 2100 // Hide instantly. 2101 mDialog.hide(); 2102 mDialog.dismiss(); 2103 } else { 2104 mDialog.dismiss(); 2105 } 2106 mDialog = null; 2107 } 2108 break; 2109 case MESSAGE_REFRESH: 2110 refreshSilentMode(); 2111 mAdapter.notifyDataSetChanged(); 2112 break; 2113 } 2114 } 2115 }; 2116 2117 private void onAirplaneModeChanged() { 2118 // Let the service state callbacks handle the state. 2119 if (mHasTelephony || mAirplaneModeOn == null) return; 2120 2121 boolean airplaneModeOn = mGlobalSettings.getInt( 2122 Settings.Global.AIRPLANE_MODE_ON, 2123 0) == 1; 2124 mAirplaneState = airplaneModeOn ? ToggleState.On : ToggleState.Off; 2125 mAirplaneModeOn.updateState(mAirplaneState); 2126 } 2127 2128 /** 2129 * Change the airplane mode system setting 2130 */ 2131 private void changeAirplaneModeSystemSetting(boolean on) { 2132 mGlobalSettings.putInt(Settings.Global.AIRPLANE_MODE_ON, on ? 1 : 0); 2133 Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); 2134 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); 2135 intent.putExtra("state", on); 2136 mContext.sendBroadcastAsUser(intent, UserHandle.ALL); 2137 if (!mHasTelephony) { 2138 mAirplaneState = on ? ToggleState.On : ToggleState.Off; 2139 } 2140 } 2141 2142 @NonNull 2143 @Override 2144 public Lifecycle getLifecycle() { 2145 return mLifecycle; 2146 } 2147 2148 @VisibleForTesting 2149 static class ActionsDialogLite extends SystemUIDialog implements DialogInterface, 2150 ColorExtractor.OnColorsChangedListener { 2151 2152 protected final Context mContext; 2153 protected MultiListLayout mGlobalActionsLayout; 2154 protected final MyAdapter mAdapter; 2155 protected final MyOverflowAdapter mOverflowAdapter; 2156 protected final MyPowerOptionsAdapter mPowerOptionsAdapter; 2157 protected final IStatusBarService mStatusBarService; 2158 protected final IBinder mToken = new Binder(); 2159 protected Drawable mBackgroundDrawable; 2160 protected final SysuiColorExtractor mColorExtractor; 2161 private boolean mKeyguardShowing; 2162 protected float mScrimAlpha; 2163 protected final NotificationShadeWindowController mNotificationShadeWindowController; 2164 protected final SysUiState mSysUiState; 2165 private ListPopupWindow mOverflowPopup; 2166 private Dialog mPowerOptionsDialog; 2167 protected final Runnable mOnRefreshCallback; 2168 private UiEventLogger mUiEventLogger; 2169 private GestureDetector mGestureDetector; 2170 private Optional<StatusBar> mStatusBarOptional; 2171 private KeyguardUpdateMonitor mKeyguardUpdateMonitor; 2172 private LockPatternUtils mLockPatternUtils; 2173 2174 protected ViewGroup mContainer; 2175 2176 @VisibleForTesting 2177 protected GestureDetector.SimpleOnGestureListener mGestureListener = 2178 new GestureDetector.SimpleOnGestureListener() { 2179 @Override 2180 public boolean onDown(MotionEvent e) { 2181 // All gestures begin with this message, so continue listening 2182 return true; 2183 } 2184 2185 @Override 2186 public boolean onSingleTapUp(MotionEvent e) { 2187 // Close without opening shade 2188 mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE); 2189 cancel(); 2190 return false; 2191 } 2192 2193 @Override 2194 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, 2195 float distanceY) { 2196 if (distanceY < 0 && distanceY > distanceX 2197 && e1.getY() <= mStatusBarOptional.map( 2198 StatusBar::getStatusBarHeight).orElse(0)) { 2199 // Downwards scroll from top 2200 openShadeAndDismiss(); 2201 return true; 2202 } 2203 return false; 2204 } 2205 2206 @Override 2207 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, 2208 float velocityY) { 2209 if (velocityY > 0 && Math.abs(velocityY) > Math.abs(velocityX) 2210 && e1.getY() <= mStatusBarOptional.map( 2211 StatusBar::getStatusBarHeight).orElse(0)) { 2212 // Downwards fling from top 2213 openShadeAndDismiss(); 2214 return true; 2215 } 2216 return false; 2217 } 2218 }; 2219 2220 ActionsDialogLite(Context context, int themeRes, MyAdapter adapter, 2221 MyOverflowAdapter overflowAdapter, 2222 SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService, 2223 NotificationShadeWindowController notificationShadeWindowController, 2224 SysUiState sysuiState, Runnable onRefreshCallback, boolean keyguardShowing, 2225 MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger, 2226 Optional<StatusBar> statusBarOptional, 2227 KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils, 2228 SystemUIDialogManager systemUiDialogManager) { 2229 // We set dismissOnDeviceLock to false because we have a custom broadcast receiver to 2230 // dismiss this dialog when the device is locked. 2231 super(context, themeRes, false /* dismissOnDeviceLock */, 2232 systemUiDialogManager); 2233 mContext = context; 2234 mAdapter = adapter; 2235 mOverflowAdapter = overflowAdapter; 2236 mPowerOptionsAdapter = powerAdapter; 2237 mColorExtractor = sysuiColorExtractor; 2238 mStatusBarService = statusBarService; 2239 mNotificationShadeWindowController = notificationShadeWindowController; 2240 mSysUiState = sysuiState; 2241 mOnRefreshCallback = onRefreshCallback; 2242 mKeyguardShowing = keyguardShowing; 2243 mUiEventLogger = uiEventLogger; 2244 mStatusBarOptional = statusBarOptional; 2245 mKeyguardUpdateMonitor = keyguardUpdateMonitor; 2246 mLockPatternUtils = lockPatternUtils; 2247 mGestureDetector = new GestureDetector(mContext, mGestureListener); 2248 } 2249 2250 @Override 2251 protected void onCreate(Bundle savedInstanceState) { 2252 super.onCreate(savedInstanceState); 2253 initializeLayout(); 2254 } 2255 2256 @Override 2257 protected int getWidth() { 2258 return MATCH_PARENT; 2259 } 2260 2261 @Override 2262 protected int getHeight() { 2263 return MATCH_PARENT; 2264 } 2265 2266 @Override 2267 public boolean onTouchEvent(MotionEvent event) { 2268 return mGestureDetector.onTouchEvent(event) || super.onTouchEvent(event); 2269 } 2270 2271 private void openShadeAndDismiss() { 2272 mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE); 2273 if (mStatusBarOptional.map(StatusBar::isKeyguardShowing).orElse(false)) { 2274 // match existing lockscreen behavior to open QS when swiping from status bar 2275 mStatusBarOptional.ifPresent( 2276 statusBar -> statusBar.animateExpandSettingsPanel(null)); 2277 } else { 2278 // otherwise, swiping down should expand notification shade 2279 mStatusBarOptional.ifPresent( 2280 statusBar -> statusBar.animateExpandNotificationsPanel()); 2281 } 2282 dismiss(); 2283 } 2284 2285 private ListPopupWindow createPowerOverflowPopup() { 2286 GlobalActionsPopupMenu popup = new GlobalActionsPopupMenu( 2287 new ContextThemeWrapper( 2288 mContext, 2289 com.android.systemui.R.style.Control_ListPopupWindow 2290 ), false /* isDropDownMode */); 2291 popup.setOnItemClickListener( 2292 (parent, view, position, id) -> mOverflowAdapter.onClickItem(position)); 2293 popup.setOnItemLongClickListener( 2294 (parent, view, position, id) -> mOverflowAdapter.onLongClickItem(position)); 2295 View overflowButton = 2296 findViewById(com.android.systemui.R.id.global_actions_overflow_button); 2297 popup.setAnchorView(overflowButton); 2298 popup.setAdapter(mOverflowAdapter); 2299 return popup; 2300 } 2301 2302 public void showPowerOptionsMenu() { 2303 mPowerOptionsDialog = GlobalActionsPowerDialog.create(mContext, mPowerOptionsAdapter); 2304 mPowerOptionsDialog.show(); 2305 } 2306 2307 protected void showPowerOverflowMenu() { 2308 mOverflowPopup = createPowerOverflowPopup(); 2309 mOverflowPopup.show(); 2310 } 2311 2312 protected int getLayoutResource() { 2313 return com.android.systemui.R.layout.global_actions_grid_lite; 2314 } 2315 2316 protected void initializeLayout() { 2317 setContentView(getLayoutResource()); 2318 fixNavBarClipping(); 2319 2320 mGlobalActionsLayout = findViewById(com.android.systemui.R.id.global_actions_view); 2321 mGlobalActionsLayout.setListViewAccessibilityDelegate(new View.AccessibilityDelegate() { 2322 @Override 2323 public boolean dispatchPopulateAccessibilityEvent( 2324 View host, AccessibilityEvent event) { 2325 // Populate the title here, just as Activity does 2326 event.getText().add(mContext.getString(R.string.global_actions)); 2327 return true; 2328 } 2329 }); 2330 mGlobalActionsLayout.setRotationListener(this::onRotate); 2331 mGlobalActionsLayout.setAdapter(mAdapter); 2332 mContainer = findViewById(com.android.systemui.R.id.global_actions_container); 2333 mContainer.setOnTouchListener((v, event) -> { 2334 mGestureDetector.onTouchEvent(event); 2335 return v.onTouchEvent(event); 2336 }); 2337 2338 View overflowButton = findViewById( 2339 com.android.systemui.R.id.global_actions_overflow_button); 2340 if (overflowButton != null) { 2341 if (mOverflowAdapter.getCount() > 0) { 2342 overflowButton.setOnClickListener((view) -> showPowerOverflowMenu()); 2343 LinearLayout.LayoutParams params = 2344 (LinearLayout.LayoutParams) mGlobalActionsLayout.getLayoutParams(); 2345 params.setMarginEnd(0); 2346 mGlobalActionsLayout.setLayoutParams(params); 2347 } else { 2348 overflowButton.setVisibility(View.GONE); 2349 LinearLayout.LayoutParams params = 2350 (LinearLayout.LayoutParams) mGlobalActionsLayout.getLayoutParams(); 2351 params.setMarginEnd(mContext.getResources().getDimensionPixelSize( 2352 com.android.systemui.R.dimen.global_actions_side_margin)); 2353 mGlobalActionsLayout.setLayoutParams(params); 2354 } 2355 } 2356 2357 if (mBackgroundDrawable == null) { 2358 mBackgroundDrawable = new ScrimDrawable(); 2359 mScrimAlpha = 1.0f; 2360 } 2361 2362 // If user entered from the lock screen and smart lock was enabled, disable it 2363 int user = KeyguardUpdateMonitor.getCurrentUser(); 2364 boolean userHasTrust = mKeyguardUpdateMonitor.getUserHasTrust(user); 2365 if (mKeyguardShowing && userHasTrust) { 2366 mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser()); 2367 showSmartLockDisabledMessage(); 2368 } 2369 } 2370 2371 protected void fixNavBarClipping() { 2372 ViewGroup content = findViewById(android.R.id.content); 2373 content.setClipChildren(false); 2374 content.setClipToPadding(false); 2375 ViewGroup contentParent = (ViewGroup) content.getParent(); 2376 contentParent.setClipChildren(false); 2377 contentParent.setClipToPadding(false); 2378 } 2379 2380 private void showSmartLockDisabledMessage() { 2381 // Since power menu is the top window, make a Toast-like view that will show up 2382 View message = LayoutInflater.from(mContext) 2383 .inflate(com.android.systemui.R.layout.global_actions_toast, mContainer, false); 2384 2385 // Set up animation 2386 AccessibilityManager mAccessibilityManager = 2387 (AccessibilityManager) getContext().getSystemService( 2388 Context.ACCESSIBILITY_SERVICE); 2389 final int visibleTime = mAccessibilityManager.getRecommendedTimeoutMillis( 2390 TOAST_VISIBLE_TIME, AccessibilityManager.FLAG_CONTENT_TEXT); 2391 message.setVisibility(View.VISIBLE); 2392 message.setAlpha(0f); 2393 mContainer.addView(message); 2394 2395 // Fade in 2396 message.animate() 2397 .alpha(1f) 2398 .setDuration(TOAST_FADE_TIME) 2399 .setListener(new AnimatorListenerAdapter() { 2400 @Override 2401 public void onAnimationEnd(Animator animation) { 2402 // Then fade out 2403 message.animate() 2404 .alpha(0f) 2405 .setDuration(TOAST_FADE_TIME) 2406 .setStartDelay(visibleTime) 2407 .setListener(null); 2408 } 2409 }); 2410 } 2411 2412 @Override 2413 protected void onStart() { 2414 super.onStart(); 2415 mGlobalActionsLayout.updateList(); 2416 2417 if (mBackgroundDrawable instanceof ScrimDrawable) { 2418 mColorExtractor.addOnColorsChangedListener(this); 2419 GradientColors colors = mColorExtractor.getNeutralColors(); 2420 updateColors(colors, false /* animate */); 2421 } 2422 } 2423 2424 /** 2425 * Updates background and system bars according to current GradientColors. 2426 * 2427 * @param colors Colors and hints to use. 2428 * @param animate Interpolates gradient if true, just sets otherwise. 2429 */ 2430 private void updateColors(GradientColors colors, boolean animate) { 2431 if (!(mBackgroundDrawable instanceof ScrimDrawable)) { 2432 return; 2433 } 2434 ((ScrimDrawable) mBackgroundDrawable).setColor(Color.BLACK, animate); 2435 View decorView = getWindow().getDecorView(); 2436 if (colors.supportsDarkText()) { 2437 decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR 2438 | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); 2439 } else { 2440 decorView.setSystemUiVisibility(0); 2441 } 2442 } 2443 2444 @Override 2445 protected void onStop() { 2446 super.onStop(); 2447 mColorExtractor.removeOnColorsChangedListener(this); 2448 } 2449 2450 @Override 2451 public void onBackPressed() { 2452 super.onBackPressed(); 2453 mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_BACK); 2454 } 2455 2456 @Override 2457 public void show() { 2458 super.show(); 2459 mNotificationShadeWindowController.setRequestTopUi(true, TAG); 2460 mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, true) 2461 .commitUpdate(mContext.getDisplayId()); 2462 } 2463 2464 @Override 2465 public void dismiss() { 2466 dismissOverflow(); 2467 dismissPowerOptions(); 2468 2469 mNotificationShadeWindowController.setRequestTopUi(false, TAG); 2470 mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, false) 2471 .commitUpdate(mContext.getDisplayId()); 2472 2473 super.dismiss(); 2474 } 2475 2476 protected final void dismissOverflow() { 2477 if (mOverflowPopup != null) { 2478 mOverflowPopup.dismiss(); 2479 } 2480 } 2481 2482 protected final void dismissPowerOptions() { 2483 if (mPowerOptionsDialog != null) { 2484 mPowerOptionsDialog.dismiss(); 2485 } 2486 } 2487 2488 protected final void setRotationSuggestionsEnabled(boolean enabled) { 2489 try { 2490 final int userId = Binder.getCallingUserHandle().getIdentifier(); 2491 final int what = enabled 2492 ? StatusBarManager.DISABLE2_NONE 2493 : StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS; 2494 mStatusBarService.disable2ForUser(what, mToken, mContext.getPackageName(), userId); 2495 } catch (RemoteException ex) { 2496 throw ex.rethrowFromSystemServer(); 2497 } 2498 } 2499 2500 @Override 2501 public void onColorsChanged(ColorExtractor extractor, int which) { 2502 if (mKeyguardShowing) { 2503 if ((WallpaperManager.FLAG_LOCK & which) != 0) { 2504 updateColors(extractor.getColors(WallpaperManager.FLAG_LOCK), 2505 true /* animate */); 2506 } 2507 } else { 2508 if ((WallpaperManager.FLAG_SYSTEM & which) != 0) { 2509 updateColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM), 2510 true /* animate */); 2511 } 2512 } 2513 } 2514 2515 public void setKeyguardShowing(boolean keyguardShowing) { 2516 mKeyguardShowing = keyguardShowing; 2517 } 2518 2519 public void refreshDialog() { 2520 mOnRefreshCallback.run(); 2521 2522 // Dismiss the dropdown menus. 2523 dismissOverflow(); 2524 dismissPowerOptions(); 2525 2526 // Update the list as the max number of items per row has probably changed. 2527 mGlobalActionsLayout.updateList(); 2528 } 2529 2530 public void onRotate(int from, int to) { 2531 refreshDialog(); 2532 } 2533 } 2534 } 2535