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