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