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