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