1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package com.android.systemui.globalactions; 16 17 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 18 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 19 20 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST; 21 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; 22 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; 23 24 import android.app.ActivityManager; 25 import android.app.Dialog; 26 import android.app.KeyguardManager; 27 import android.app.PendingIntent; 28 import android.app.StatusBarManager; 29 import android.app.WallpaperManager; 30 import android.app.admin.DevicePolicyManager; 31 import android.app.trust.TrustManager; 32 import android.content.BroadcastReceiver; 33 import android.content.Context; 34 import android.content.DialogInterface; 35 import android.content.Intent; 36 import android.content.IntentFilter; 37 import android.content.pm.UserInfo; 38 import android.database.ContentObserver; 39 import android.graphics.drawable.Drawable; 40 import android.media.AudioManager; 41 import android.net.ConnectivityManager; 42 import android.os.Binder; 43 import android.os.Handler; 44 import android.os.IBinder; 45 import android.os.Message; 46 import android.os.RemoteException; 47 import android.os.ServiceManager; 48 import android.os.SystemProperties; 49 import android.os.UserHandle; 50 import android.os.UserManager; 51 import android.os.Vibrator; 52 import android.provider.Settings; 53 import android.service.dreams.DreamService; 54 import android.service.dreams.IDreamManager; 55 import android.telephony.PhoneStateListener; 56 import android.telephony.ServiceState; 57 import android.telephony.TelephonyManager; 58 import android.text.TextUtils; 59 import android.util.ArraySet; 60 import android.util.FeatureFlagUtils; 61 import android.util.Log; 62 import android.view.ContextThemeWrapper; 63 import android.view.LayoutInflater; 64 import android.view.View; 65 import android.view.ViewGroup; 66 import android.view.Window; 67 import android.view.WindowManager; 68 import android.view.WindowManagerGlobal; 69 import android.view.accessibility.AccessibilityEvent; 70 import android.widget.FrameLayout; 71 import android.widget.ImageView; 72 import android.widget.ImageView.ScaleType; 73 import android.widget.TextView; 74 75 import com.android.internal.R; 76 import com.android.internal.colorextraction.ColorExtractor; 77 import com.android.internal.colorextraction.ColorExtractor.GradientColors; 78 import com.android.internal.colorextraction.drawable.ScrimDrawable; 79 import com.android.internal.logging.MetricsLogger; 80 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 81 import com.android.internal.statusbar.IStatusBarService; 82 import com.android.internal.telephony.TelephonyIntents; 83 import com.android.internal.telephony.TelephonyProperties; 84 import com.android.internal.util.EmergencyAffordanceManager; 85 import com.android.internal.util.ScreenRecordHelper; 86 import com.android.internal.util.ScreenshotHelper; 87 import com.android.internal.view.RotationPolicy; 88 import com.android.internal.widget.LockPatternUtils; 89 import com.android.systemui.Dependency; 90 import com.android.systemui.Interpolators; 91 import com.android.systemui.MultiListLayout; 92 import com.android.systemui.MultiListLayout.MultiListAdapter; 93 import com.android.systemui.colorextraction.SysuiColorExtractor; 94 import com.android.systemui.plugins.ActivityStarter; 95 import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; 96 import com.android.systemui.plugins.GlobalActionsPanelPlugin; 97 import com.android.systemui.statusbar.phone.ScrimController; 98 import com.android.systemui.statusbar.phone.UnlockMethodCache; 99 import com.android.systemui.statusbar.policy.ConfigurationController; 100 import com.android.systemui.util.EmergencyDialerConstants; 101 import com.android.systemui.util.leak.RotationUtils; 102 import com.android.systemui.volume.SystemUIInterpolators.LogAccelerateInterpolator; 103 104 import java.util.ArrayList; 105 import java.util.List; 106 107 /** 108 * Helper to show the global actions dialog. Each item is an {@link Action} that 109 * may show depending on whether the keyguard is showing, and whether the device 110 * is provisioned. 111 */ 112 public class GlobalActionsDialog implements DialogInterface.OnDismissListener, 113 DialogInterface.OnShowListener, ConfigurationController.ConfigurationListener { 114 115 static public final String SYSTEM_DIALOG_REASON_KEY = "reason"; 116 static public final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions"; 117 static public final String SYSTEM_DIALOG_REASON_DREAM = "dream"; 118 119 private static final String TAG = "GlobalActionsDialog"; 120 121 private static final boolean SHOW_SILENT_TOGGLE = true; 122 123 /* Valid settings for global actions keys. 124 * see config.xml config_globalActionList */ 125 private static final String GLOBAL_ACTION_KEY_POWER = "power"; 126 private static final String GLOBAL_ACTION_KEY_AIRPLANE = "airplane"; 127 private static final String GLOBAL_ACTION_KEY_BUGREPORT = "bugreport"; 128 private static final String GLOBAL_ACTION_KEY_SILENT = "silent"; 129 private static final String GLOBAL_ACTION_KEY_USERS = "users"; 130 private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings"; 131 private static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown"; 132 private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist"; 133 private static final String GLOBAL_ACTION_KEY_ASSIST = "assist"; 134 private static final String GLOBAL_ACTION_KEY_RESTART = "restart"; 135 private static final String GLOBAL_ACTION_KEY_LOGOUT = "logout"; 136 private static final String GLOBAL_ACTION_KEY_EMERGENCY = "emergency"; 137 private static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot"; 138 139 private final Context mContext; 140 private final GlobalActionsManager mWindowManagerFuncs; 141 private final AudioManager mAudioManager; 142 private final IDreamManager mDreamManager; 143 private final DevicePolicyManager mDevicePolicyManager; 144 private final LockPatternUtils mLockPatternUtils; 145 private final KeyguardManager mKeyguardManager; 146 147 private ArrayList<Action> mItems; 148 private ActionsDialog mDialog; 149 150 private Action mSilentModeAction; 151 private ToggleAction mAirplaneModeOn; 152 153 private MyAdapter mAdapter; 154 155 private boolean mKeyguardShowing = false; 156 private boolean mDeviceProvisioned = false; 157 private ToggleAction.State mAirplaneState = ToggleAction.State.Off; 158 private boolean mIsWaitingForEcmExit = false; 159 private boolean mHasTelephony; 160 private boolean mHasVibrator; 161 private boolean mHasLogoutButton; 162 private boolean mHasLockdownButton; 163 private final boolean mShowSilentToggle; 164 private final EmergencyAffordanceManager mEmergencyAffordanceManager; 165 private final ScreenshotHelper mScreenshotHelper; 166 private final ScreenRecordHelper mScreenRecordHelper; 167 private final ActivityStarter mActivityStarter; 168 private GlobalActionsPanelPlugin mPanelPlugin; 169 170 /** 171 * @param context everything needs a context :( 172 */ 173 public GlobalActionsDialog(Context context, GlobalActionsManager windowManagerFuncs) { 174 mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme); 175 mWindowManagerFuncs = windowManagerFuncs; 176 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 177 mDreamManager = IDreamManager.Stub.asInterface( 178 ServiceManager.getService(DreamService.DREAM_SERVICE)); 179 mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService( 180 Context.DEVICE_POLICY_SERVICE); 181 mLockPatternUtils = new LockPatternUtils(mContext); 182 mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 183 184 // receive broadcasts 185 IntentFilter filter = new IntentFilter(); 186 filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 187 filter.addAction(Intent.ACTION_SCREEN_OFF); 188 filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); 189 context.registerReceiver(mBroadcastReceiver, filter); 190 191 ConnectivityManager cm = (ConnectivityManager) 192 context.getSystemService(Context.CONNECTIVITY_SERVICE); 193 mHasTelephony = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE); 194 195 // get notified of phone state changes 196 TelephonyManager telephonyManager = 197 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 198 telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE); 199 mContext.getContentResolver().registerContentObserver( 200 Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true, 201 mAirplaneModeObserver); 202 Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); 203 mHasVibrator = vibrator != null && vibrator.hasVibrator(); 204 205 mShowSilentToggle = SHOW_SILENT_TOGGLE && !mContext.getResources().getBoolean( 206 R.bool.config_useFixedVolume); 207 208 mEmergencyAffordanceManager = new EmergencyAffordanceManager(context); 209 mScreenshotHelper = new ScreenshotHelper(context); 210 mScreenRecordHelper = new ScreenRecordHelper(context); 211 212 Dependency.get(ConfigurationController.class).addCallback(this); 213 214 mActivityStarter = Dependency.get(ActivityStarter.class); 215 UnlockMethodCache unlockMethodCache = UnlockMethodCache.getInstance(context); 216 unlockMethodCache.addListener( 217 () -> { 218 if (mDialog != null && mDialog.mPanelController != null) { 219 boolean locked = !unlockMethodCache.canSkipBouncer(); 220 mDialog.mPanelController.onDeviceLockStateChanged(locked); 221 } 222 }); 223 } 224 225 /** 226 * Show the global actions dialog (creating if necessary) 227 * 228 * @param keyguardShowing True if keyguard is showing 229 */ 230 public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned, 231 GlobalActionsPanelPlugin panelPlugin) { 232 mKeyguardShowing = keyguardShowing; 233 mDeviceProvisioned = isDeviceProvisioned; 234 mPanelPlugin = panelPlugin; 235 if (mDialog != null) { 236 mDialog.dismiss(); 237 mDialog = null; 238 // Show delayed, so that the dismiss of the previous dialog completes 239 mHandler.sendEmptyMessage(MESSAGE_SHOW); 240 } else { 241 handleShow(); 242 } 243 } 244 245 /** 246 * Dismiss the global actions dialog, if it's currently shown 247 */ 248 public void dismissDialog() { 249 mHandler.removeMessages(MESSAGE_DISMISS); 250 mHandler.sendEmptyMessage(MESSAGE_DISMISS); 251 } 252 253 private void awakenIfNecessary() { 254 if (mDreamManager != null) { 255 try { 256 if (mDreamManager.isDreaming()) { 257 mDreamManager.awaken(); 258 } 259 } catch (RemoteException e) { 260 // we tried 261 } 262 } 263 } 264 265 private void handleShow() { 266 awakenIfNecessary(); 267 mDialog = createDialog(); 268 prepareDialog(); 269 270 // If we only have 1 item and it's a simple press action, just do this action. 271 if (mAdapter.getCount() == 1 272 && mAdapter.getItem(0) instanceof SinglePressAction 273 && !(mAdapter.getItem(0) instanceof LongPressAction)) { 274 ((SinglePressAction) mAdapter.getItem(0)).onPress(); 275 } else { 276 WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes(); 277 attrs.setTitle("ActionsDialog"); 278 attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 279 mDialog.getWindow().setAttributes(attrs); 280 mDialog.show(); 281 mWindowManagerFuncs.onGlobalActionsShown(); 282 } 283 } 284 285 /** 286 * Create the global actions dialog. 287 * 288 * @return A new dialog. 289 */ 290 private ActionsDialog createDialog() { 291 // Simple toggle style if there's no vibrator, otherwise use a tri-state 292 if (!mHasVibrator) { 293 mSilentModeAction = new SilentModeToggleAction(); 294 } else { 295 mSilentModeAction = new SilentModeTriStateAction(mAudioManager, mHandler); 296 } 297 mAirplaneModeOn = new ToggleAction( 298 R.drawable.ic_lock_airplane_mode, 299 R.drawable.ic_lock_airplane_mode_off, 300 R.string.global_actions_toggle_airplane_mode, 301 R.string.global_actions_airplane_mode_on_status, 302 R.string.global_actions_airplane_mode_off_status) { 303 304 void onToggle(boolean on) { 305 if (mHasTelephony && Boolean.parseBoolean( 306 SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) { 307 mIsWaitingForEcmExit = true; 308 // Launch ECM exit dialog 309 Intent ecmDialogIntent = 310 new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null); 311 ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 312 mContext.startActivity(ecmDialogIntent); 313 } else { 314 changeAirplaneModeSystemSetting(on); 315 } 316 } 317 318 @Override 319 protected void changeStateFromPress(boolean buttonOn) { 320 if (!mHasTelephony) return; 321 322 // In ECM mode airplane state cannot be changed 323 if (!(Boolean.parseBoolean( 324 SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)))) { 325 mState = buttonOn ? State.TurningOn : State.TurningOff; 326 mAirplaneState = mState; 327 } 328 } 329 330 public boolean showDuringKeyguard() { 331 return true; 332 } 333 334 public boolean showBeforeProvisioning() { 335 return false; 336 } 337 }; 338 onAirplaneModeChanged(); 339 340 mItems = new ArrayList<Action>(); 341 String[] defaultActions = mContext.getResources().getStringArray( 342 R.array.config_globalActionsList); 343 344 ArraySet<String> addedKeys = new ArraySet<String>(); 345 mHasLogoutButton = false; 346 mHasLockdownButton = false; 347 for (int i = 0; i < defaultActions.length; i++) { 348 String actionKey = defaultActions[i]; 349 if (addedKeys.contains(actionKey)) { 350 // If we already have added this, don't add it again. 351 continue; 352 } 353 if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) { 354 mItems.add(new PowerAction()); 355 } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) { 356 mItems.add(mAirplaneModeOn); 357 } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) { 358 if (Settings.Global.getInt(mContext.getContentResolver(), 359 Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) { 360 mItems.add(new BugReportAction()); 361 } 362 } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) { 363 if (mShowSilentToggle) { 364 mItems.add(mSilentModeAction); 365 } 366 } else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) { 367 if (SystemProperties.getBoolean("fw.power_user_switcher", false)) { 368 addUsersToMenu(mItems); 369 } 370 } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) { 371 mItems.add(getSettingsAction()); 372 } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) { 373 if (Settings.Secure.getIntForUser(mContext.getContentResolver(), 374 Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0, getCurrentUser().id) != 0 375 && shouldDisplayLockdown()) { 376 mItems.add(getLockdownAction()); 377 mHasLockdownButton = true; 378 } 379 } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) { 380 mItems.add(getVoiceAssistAction()); 381 } else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) { 382 mItems.add(getAssistAction()); 383 } else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) { 384 mItems.add(new RestartAction()); 385 } else if (GLOBAL_ACTION_KEY_SCREENSHOT.equals(actionKey)) { 386 mItems.add(new ScreenshotAction()); 387 } else if (GLOBAL_ACTION_KEY_LOGOUT.equals(actionKey)) { 388 if (mDevicePolicyManager.isLogoutEnabled() 389 && getCurrentUser().id != UserHandle.USER_SYSTEM) { 390 mItems.add(new LogoutAction()); 391 mHasLogoutButton = true; 392 } 393 } else if (GLOBAL_ACTION_KEY_EMERGENCY.equals(actionKey)) { 394 if (!mEmergencyAffordanceManager.needsEmergencyAffordance()) { 395 mItems.add(new EmergencyDialerAction()); 396 } 397 } else { 398 Log.e(TAG, "Invalid global action key " + actionKey); 399 } 400 // Add here so we don't add more than one. 401 addedKeys.add(actionKey); 402 } 403 404 if (mEmergencyAffordanceManager.needsEmergencyAffordance()) { 405 mItems.add(new EmergencyAffordanceAction()); 406 } 407 408 mAdapter = new MyAdapter(); 409 410 GlobalActionsPanelPlugin.PanelViewController panelViewController = 411 mPanelPlugin != null 412 ? mPanelPlugin.onPanelShown( 413 new GlobalActionsPanelPlugin.Callbacks() { 414 @Override 415 public void dismissGlobalActionsMenu() { 416 if (mDialog != null) { 417 mDialog.dismiss(); 418 } 419 } 420 421 @Override 422 public void startPendingIntentDismissingKeyguard( 423 PendingIntent intent) { 424 mActivityStarter 425 .startPendingIntentDismissingKeyguard(intent); 426 } 427 }, 428 mKeyguardManager.isDeviceLocked()) 429 : null; 430 431 ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, panelViewController); 432 dialog.setCanceledOnTouchOutside(false); // Handled by the custom class. 433 dialog.setKeyguardShowing(mKeyguardShowing); 434 435 dialog.setOnDismissListener(this); 436 dialog.setOnShowListener(this); 437 438 return dialog; 439 } 440 441 private boolean shouldDisplayLockdown() { 442 int userId = getCurrentUser().id; 443 // Lockdown is meaningless without a place to go. 444 if (!mKeyguardManager.isDeviceSecure(userId)) { 445 return false; 446 } 447 448 // Only show the lockdown button if the device isn't locked down (for whatever reason). 449 int state = mLockPatternUtils.getStrongAuthForUser(userId); 450 return (state == STRONG_AUTH_NOT_REQUIRED 451 || state == SOME_AUTH_REQUIRED_AFTER_USER_REQUEST); 452 } 453 454 @Override 455 public void onUiModeChanged() { 456 mContext.getTheme().applyStyle(mContext.getThemeResId(), true); 457 if (mDialog != null && mDialog.isShowing()) { 458 mDialog.refreshDialog(); 459 } 460 } 461 462 public void destroy() { 463 Dependency.get(ConfigurationController.class).removeCallback(this); 464 } 465 466 private final class PowerAction extends SinglePressAction implements LongPressAction { 467 private PowerAction() { 468 super(R.drawable.ic_lock_power_off, 469 R.string.global_action_power_off); 470 } 471 472 @Override 473 public boolean onLongPress() { 474 UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); 475 if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { 476 mWindowManagerFuncs.reboot(true); 477 return true; 478 } 479 return false; 480 } 481 482 @Override 483 public boolean showDuringKeyguard() { 484 return true; 485 } 486 487 @Override 488 public boolean showBeforeProvisioning() { 489 return true; 490 } 491 492 @Override 493 public void onPress() { 494 // shutdown by making sure radio and power are handled accordingly. 495 mWindowManagerFuncs.shutdown(); 496 } 497 } 498 499 private abstract class EmergencyAction extends SinglePressAction { 500 EmergencyAction(int iconResId, int messageResId) { 501 super(iconResId, messageResId); 502 } 503 504 @Override 505 public boolean shouldBeSeparated() { 506 return shouldUseSeparatedView(); 507 } 508 509 @Override 510 public View create( 511 Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { 512 View v = super.create(context, convertView, parent, inflater); 513 int textColor; 514 if (shouldBeSeparated()) { 515 textColor = v.getResources().getColor( 516 com.android.systemui.R.color.global_actions_alert_text); 517 } else { 518 textColor = v.getResources().getColor( 519 com.android.systemui.R.color.global_actions_text); 520 } 521 TextView messageView = v.findViewById(R.id.message); 522 messageView.setTextColor(textColor); 523 messageView.setSelected(true); // necessary for marquee to work 524 ImageView icon = (ImageView) v.findViewById(R.id.icon); 525 icon.getDrawable().setTint(textColor); 526 return v; 527 } 528 529 @Override 530 public boolean showDuringKeyguard() { 531 return true; 532 } 533 534 @Override 535 public boolean showBeforeProvisioning() { 536 return true; 537 } 538 } 539 540 private class EmergencyAffordanceAction extends EmergencyAction { 541 EmergencyAffordanceAction() { 542 super(R.drawable.emergency_icon, 543 R.string.global_action_emergency); 544 } 545 546 @Override 547 public void onPress() { 548 mEmergencyAffordanceManager.performEmergencyCall(); 549 } 550 } 551 552 private class EmergencyDialerAction extends EmergencyAction { 553 private EmergencyDialerAction() { 554 super(com.android.systemui.R.drawable.ic_emergency_star, 555 R.string.global_action_emergency); 556 } 557 558 @Override 559 public void onPress() { 560 MetricsLogger.action(mContext, MetricsEvent.ACTION_EMERGENCY_DIALER_FROM_POWER_MENU); 561 Intent intent = new Intent(EmergencyDialerConstants.ACTION_DIAL); 562 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK 563 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 564 | Intent.FLAG_ACTIVITY_CLEAR_TOP); 565 intent.putExtra(EmergencyDialerConstants.EXTRA_ENTRY_TYPE, 566 EmergencyDialerConstants.ENTRY_TYPE_POWER_MENU); 567 mContext.startActivityAsUser(intent, UserHandle.CURRENT); 568 } 569 } 570 571 private final class RestartAction extends SinglePressAction implements LongPressAction { 572 private RestartAction() { 573 super(R.drawable.ic_restart, R.string.global_action_restart); 574 } 575 576 @Override 577 public boolean onLongPress() { 578 UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); 579 if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { 580 mWindowManagerFuncs.reboot(true); 581 return true; 582 } 583 return false; 584 } 585 586 @Override 587 public boolean showDuringKeyguard() { 588 return true; 589 } 590 591 @Override 592 public boolean showBeforeProvisioning() { 593 return true; 594 } 595 596 @Override 597 public void onPress() { 598 mWindowManagerFuncs.reboot(false); 599 } 600 } 601 602 private class ScreenshotAction extends SinglePressAction implements LongPressAction { 603 public ScreenshotAction() { 604 super(R.drawable.ic_screenshot, R.string.global_action_screenshot); 605 } 606 607 @Override 608 public void onPress() { 609 // Add a little delay before executing, to give the 610 // dialog a chance to go away before it takes a 611 // screenshot. 612 // TODO: instead, omit global action dialog layer 613 mHandler.postDelayed(new Runnable() { 614 @Override 615 public void run() { 616 mScreenshotHelper.takeScreenshot(1, true, true, mHandler); 617 MetricsLogger.action(mContext, 618 MetricsEvent.ACTION_SCREENSHOT_POWER_MENU); 619 } 620 }, 500); 621 } 622 623 @Override 624 public boolean showDuringKeyguard() { 625 return true; 626 } 627 628 @Override 629 public boolean showBeforeProvisioning() { 630 return false; 631 } 632 633 @Override 634 public boolean onLongPress() { 635 if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SCREENRECORD_LONG_PRESS)) { 636 mScreenRecordHelper.launchRecordPrompt(); 637 } else { 638 onPress(); 639 } 640 return true; 641 } 642 } 643 644 private class BugReportAction extends SinglePressAction implements LongPressAction { 645 646 public BugReportAction() { 647 super(R.drawable.ic_lock_bugreport, R.string.bugreport_title); 648 } 649 650 @Override 651 public void onPress() { 652 // don't actually trigger the bugreport if we are running stability 653 // tests via monkey 654 if (ActivityManager.isUserAMonkey()) { 655 return; 656 } 657 // Add a little delay before executing, to give the 658 // dialog a chance to go away before it takes a 659 // screenshot. 660 mHandler.postDelayed(new Runnable() { 661 @Override 662 public void run() { 663 try { 664 // Take an "interactive" bugreport. 665 MetricsLogger.action(mContext, 666 MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE); 667 ActivityManager.getService().requestBugReport( 668 ActivityManager.BUGREPORT_OPTION_INTERACTIVE); 669 } catch (RemoteException e) { 670 } 671 } 672 }, 500); 673 } 674 675 @Override 676 public boolean onLongPress() { 677 // don't actually trigger the bugreport if we are running stability 678 // tests via monkey 679 if (ActivityManager.isUserAMonkey()) { 680 return false; 681 } 682 try { 683 // Take a "full" bugreport. 684 MetricsLogger.action(mContext, MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL); 685 ActivityManager.getService().requestBugReport( 686 ActivityManager.BUGREPORT_OPTION_FULL); 687 } catch (RemoteException e) { 688 } 689 return false; 690 } 691 692 public boolean showDuringKeyguard() { 693 return true; 694 } 695 696 @Override 697 public boolean showBeforeProvisioning() { 698 return false; 699 } 700 } 701 702 private final class LogoutAction extends SinglePressAction { 703 private LogoutAction() { 704 super(R.drawable.ic_logout, R.string.global_action_logout); 705 } 706 707 @Override 708 public boolean showDuringKeyguard() { 709 return true; 710 } 711 712 @Override 713 public boolean showBeforeProvisioning() { 714 return false; 715 } 716 717 @Override 718 public void onPress() { 719 // Add a little delay before executing, to give the dialog a chance to go away before 720 // switching user 721 mHandler.postDelayed(() -> { 722 try { 723 int currentUserId = getCurrentUser().id; 724 ActivityManager.getService().switchUser(UserHandle.USER_SYSTEM); 725 ActivityManager.getService().stopUser(currentUserId, true /*force*/, null); 726 } catch (RemoteException re) { 727 Log.e(TAG, "Couldn't logout user " + re); 728 } 729 }, 500); 730 } 731 } 732 733 private Action getSettingsAction() { 734 return new SinglePressAction(R.drawable.ic_settings, 735 R.string.global_action_settings) { 736 737 @Override 738 public void onPress() { 739 Intent intent = new Intent(Settings.ACTION_SETTINGS); 740 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); 741 mContext.startActivity(intent); 742 } 743 744 @Override 745 public boolean showDuringKeyguard() { 746 return true; 747 } 748 749 @Override 750 public boolean showBeforeProvisioning() { 751 return true; 752 } 753 }; 754 } 755 756 private Action getAssistAction() { 757 return new SinglePressAction(R.drawable.ic_action_assist_focused, 758 R.string.global_action_assist) { 759 @Override 760 public void onPress() { 761 Intent intent = new Intent(Intent.ACTION_ASSIST); 762 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); 763 mContext.startActivity(intent); 764 } 765 766 @Override 767 public boolean showDuringKeyguard() { 768 return true; 769 } 770 771 @Override 772 public boolean showBeforeProvisioning() { 773 return true; 774 } 775 }; 776 } 777 778 private Action getVoiceAssistAction() { 779 return new SinglePressAction(R.drawable.ic_voice_search, 780 R.string.global_action_voice_assist) { 781 @Override 782 public void onPress() { 783 Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST); 784 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); 785 mContext.startActivity(intent); 786 } 787 788 @Override 789 public boolean showDuringKeyguard() { 790 return true; 791 } 792 793 @Override 794 public boolean showBeforeProvisioning() { 795 return true; 796 } 797 }; 798 } 799 800 private Action getLockdownAction() { 801 return new SinglePressAction(R.drawable.ic_lock_lockdown, 802 R.string.global_action_lockdown) { 803 804 @Override 805 public void onPress() { 806 new LockPatternUtils(mContext) 807 .requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, 808 UserHandle.USER_ALL); 809 try { 810 WindowManagerGlobal.getWindowManagerService().lockNow(null); 811 // Lock profiles (if any) on the background thread. 812 final Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER)); 813 bgHandler.post(() -> lockProfiles()); 814 } catch (RemoteException e) { 815 Log.e(TAG, "Error while trying to lock device.", e); 816 } 817 } 818 819 @Override 820 public boolean showDuringKeyguard() { 821 return true; 822 } 823 824 @Override 825 public boolean showBeforeProvisioning() { 826 return false; 827 } 828 }; 829 } 830 831 private void lockProfiles() { 832 final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); 833 final TrustManager tm = (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE); 834 final int currentUserId = getCurrentUser().id; 835 final int[] profileIds = um.getEnabledProfileIds(currentUserId); 836 for (final int id : profileIds) { 837 if (id != currentUserId) { 838 tm.setDeviceLockedForUser(id, true); 839 } 840 } 841 } 842 843 private UserInfo getCurrentUser() { 844 try { 845 return ActivityManager.getService().getCurrentUser(); 846 } catch (RemoteException re) { 847 return null; 848 } 849 } 850 851 private boolean isCurrentUserOwner() { 852 UserInfo currentUser = getCurrentUser(); 853 return currentUser == null || currentUser.isPrimary(); 854 } 855 856 private void addUsersToMenu(ArrayList<Action> items) { 857 UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); 858 if (um.isUserSwitcherEnabled()) { 859 List<UserInfo> users = um.getUsers(); 860 UserInfo currentUser = getCurrentUser(); 861 for (final UserInfo user : users) { 862 if (user.supportsSwitchToByUser()) { 863 boolean isCurrentUser = currentUser == null 864 ? user.id == 0 : (currentUser.id == user.id); 865 Drawable icon = user.iconPath != null ? Drawable.createFromPath(user.iconPath) 866 : null; 867 SinglePressAction switchToUser = new SinglePressAction( 868 R.drawable.ic_menu_cc, icon, 869 (user.name != null ? user.name : "Primary") 870 + (isCurrentUser ? " \u2714" : "")) { 871 public void onPress() { 872 try { 873 ActivityManager.getService().switchUser(user.id); 874 } catch (RemoteException re) { 875 Log.e(TAG, "Couldn't switch user " + re); 876 } 877 } 878 879 public boolean showDuringKeyguard() { 880 return true; 881 } 882 883 public boolean showBeforeProvisioning() { 884 return false; 885 } 886 }; 887 items.add(switchToUser); 888 } 889 } 890 } 891 } 892 893 private void prepareDialog() { 894 refreshSilentMode(); 895 mAirplaneModeOn.updateState(mAirplaneState); 896 mAdapter.notifyDataSetChanged(); 897 if (mShowSilentToggle) { 898 IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION); 899 mContext.registerReceiver(mRingerModeReceiver, filter); 900 } 901 } 902 903 private void refreshSilentMode() { 904 if (!mHasVibrator) { 905 final boolean silentModeOn = 906 mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL; 907 ((ToggleAction) mSilentModeAction).updateState( 908 silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off); 909 } 910 } 911 912 /** {@inheritDoc} */ 913 public void onDismiss(DialogInterface dialog) { 914 mWindowManagerFuncs.onGlobalActionsHidden(); 915 if (mShowSilentToggle) { 916 try { 917 mContext.unregisterReceiver(mRingerModeReceiver); 918 } catch (IllegalArgumentException ie) { 919 // ignore this 920 Log.w(TAG, ie); 921 } 922 } 923 } 924 925 /** {@inheritDoc} */ 926 public void onShow(DialogInterface dialog) { 927 MetricsLogger.visible(mContext, MetricsEvent.POWER_MENU); 928 } 929 930 /** 931 * The adapter used for the list within the global actions dialog, taking 932 * into account whether the keyguard is showing via 933 * {@link com.android.systemui.globalactions.GlobalActionsDialog#mKeyguardShowing} and whether 934 * the device is provisioned 935 * via {@link com.android.systemui.globalactions.GlobalActionsDialog#mDeviceProvisioned}. 936 */ 937 public class MyAdapter extends MultiListAdapter { 938 private int countItems(boolean separated) { 939 int count = 0; 940 for (int i = 0; i < mItems.size(); i++) { 941 final Action action = mItems.get(i); 942 943 if (shouldBeShown(action) && action.shouldBeSeparated() == separated) { 944 count++; 945 } 946 } 947 return count; 948 } 949 950 private boolean shouldBeShown(Action action) { 951 if (mKeyguardShowing && !action.showDuringKeyguard()) { 952 return false; 953 } 954 if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { 955 return false; 956 } 957 return true; 958 } 959 960 @Override 961 public int countSeparatedItems() { 962 return countItems(true); 963 } 964 965 @Override 966 public int countListItems() { 967 return countItems(false); 968 } 969 970 @Override 971 public int getCount() { 972 return countSeparatedItems() + countListItems(); 973 } 974 975 @Override 976 public boolean isEnabled(int position) { 977 return getItem(position).isEnabled(); 978 } 979 980 @Override 981 public boolean areAllItemsEnabled() { 982 return false; 983 } 984 985 @Override 986 public Action getItem(int position) { 987 int filteredPos = 0; 988 for (int i = 0; i < mItems.size(); i++) { 989 final Action action = mItems.get(i); 990 if (!shouldBeShown(action)) { 991 continue; 992 } 993 if (filteredPos == position) { 994 return action; 995 } 996 filteredPos++; 997 } 998 999 throw new IllegalArgumentException("position " + position 1000 + " out of range of showable actions" 1001 + ", filtered count=" + getCount() 1002 + ", keyguardshowing=" + mKeyguardShowing 1003 + ", provisioned=" + mDeviceProvisioned); 1004 } 1005 1006 1007 public long getItemId(int position) { 1008 return position; 1009 } 1010 1011 @Override 1012 public View getView(int position, View convertView, ViewGroup parent) { 1013 Action action = getItem(position); 1014 View view = action.create(mContext, convertView, parent, LayoutInflater.from(mContext)); 1015 view.setOnClickListener(v -> onClickItem(position)); 1016 view.setOnLongClickListener(v -> onLongClickItem(position)); 1017 return view; 1018 } 1019 1020 @Override 1021 public boolean onLongClickItem(int position) { 1022 final Action action = mAdapter.getItem(position); 1023 if (action instanceof LongPressAction) { 1024 mDialog.dismiss(); 1025 return ((LongPressAction) action).onLongPress(); 1026 } 1027 return false; 1028 } 1029 1030 @Override 1031 public void onClickItem(int position) { 1032 Action item = mAdapter.getItem(position); 1033 if (!(item instanceof SilentModeTriStateAction)) { 1034 mDialog.dismiss(); 1035 } 1036 item.onPress(); 1037 } 1038 1039 @Override 1040 public boolean shouldBeSeparated(int position) { 1041 return getItem(position).shouldBeSeparated(); 1042 } 1043 } 1044 1045 // note: the scheme below made more sense when we were planning on having 1046 // 8 different things in the global actions dialog. seems overkill with 1047 // only 3 items now, but may as well keep this flexible approach so it will 1048 // be easy should someone decide at the last minute to include something 1049 // else, such as 'enable wifi', or 'enable bluetooth' 1050 1051 /** 1052 * What each item in the global actions dialog must be able to support. 1053 */ 1054 public interface Action { 1055 /** 1056 * @return Text that will be announced when dialog is created. null 1057 * for none. 1058 */ 1059 CharSequence getLabelForAccessibility(Context context); 1060 1061 View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater); 1062 1063 void onPress(); 1064 1065 /** 1066 * @return whether this action should appear in the dialog when the keygaurd 1067 * is showing. 1068 */ 1069 boolean showDuringKeyguard(); 1070 1071 /** 1072 * @return whether this action should appear in the dialog before the 1073 * device is provisioned.onlongpress 1074 * 1075 */ 1076 boolean showBeforeProvisioning(); 1077 1078 boolean isEnabled(); 1079 1080 default boolean shouldBeSeparated() { 1081 return false; 1082 } 1083 } 1084 1085 /** 1086 * An action that also supports long press. 1087 */ 1088 private interface LongPressAction extends Action { 1089 boolean onLongPress(); 1090 } 1091 1092 /** 1093 * A single press action maintains no state, just responds to a press 1094 * and takes an action. 1095 */ 1096 private static abstract class SinglePressAction implements Action { 1097 private final int mIconResId; 1098 private final Drawable mIcon; 1099 private final int mMessageResId; 1100 private final CharSequence mMessage; 1101 1102 protected SinglePressAction(int iconResId, int messageResId) { 1103 mIconResId = iconResId; 1104 mMessageResId = messageResId; 1105 mMessage = null; 1106 mIcon = null; 1107 } 1108 1109 protected SinglePressAction(int iconResId, Drawable icon, CharSequence message) { 1110 mIconResId = iconResId; 1111 mMessageResId = 0; 1112 mMessage = message; 1113 mIcon = icon; 1114 } 1115 1116 public boolean isEnabled() { 1117 return true; 1118 } 1119 1120 public String getStatus() { 1121 return null; 1122 } 1123 1124 abstract public void onPress(); 1125 1126 public CharSequence getLabelForAccessibility(Context context) { 1127 if (mMessage != null) { 1128 return mMessage; 1129 } else { 1130 return context.getString(mMessageResId); 1131 } 1132 } 1133 1134 protected int getActionLayoutId(Context context) { 1135 return com.android.systemui.R.layout.global_actions_grid_item; 1136 } 1137 1138 public View create( 1139 Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { 1140 View v = inflater.inflate(getActionLayoutId(context), parent, 1141 false); 1142 1143 ImageView icon = (ImageView) v.findViewById(R.id.icon); 1144 TextView messageView = (TextView) v.findViewById(R.id.message); 1145 messageView.setSelected(true); // necessary for marquee to work 1146 1147 TextView statusView = (TextView) v.findViewById(R.id.status); 1148 final String status = getStatus(); 1149 if (!TextUtils.isEmpty(status)) { 1150 statusView.setText(status); 1151 } else { 1152 statusView.setVisibility(View.GONE); 1153 } 1154 if (mIcon != null) { 1155 icon.setImageDrawable(mIcon); 1156 icon.setScaleType(ScaleType.CENTER_CROP); 1157 } else if (mIconResId != 0) { 1158 icon.setImageDrawable(context.getDrawable(mIconResId)); 1159 } 1160 if (mMessage != null) { 1161 messageView.setText(mMessage); 1162 } else { 1163 messageView.setText(mMessageResId); 1164 } 1165 1166 return v; 1167 } 1168 } 1169 1170 /** 1171 * A toggle action knows whether it is on or off, and displays an icon 1172 * and status message accordingly. 1173 */ 1174 private static abstract class ToggleAction implements Action { 1175 1176 enum State { 1177 Off(false), 1178 TurningOn(true), 1179 TurningOff(true), 1180 On(false); 1181 1182 private final boolean inTransition; 1183 1184 State(boolean intermediate) { 1185 inTransition = intermediate; 1186 } 1187 1188 public boolean inTransition() { 1189 return inTransition; 1190 } 1191 } 1192 1193 protected State mState = State.Off; 1194 1195 // prefs 1196 protected int mEnabledIconResId; 1197 protected int mDisabledIconResid; 1198 protected int mMessageResId; 1199 protected int mEnabledStatusMessageResId; 1200 protected int mDisabledStatusMessageResId; 1201 1202 /** 1203 * @param enabledIconResId The icon for when this action is on. 1204 * @param disabledIconResid The icon for when this action is off. 1205 * @param message The general information message, e.g 'Silent Mode' 1206 * @param enabledStatusMessageResId The on status message, e.g 'sound disabled' 1207 * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled' 1208 */ 1209 public ToggleAction(int enabledIconResId, 1210 int disabledIconResid, 1211 int message, 1212 int enabledStatusMessageResId, 1213 int disabledStatusMessageResId) { 1214 mEnabledIconResId = enabledIconResId; 1215 mDisabledIconResid = disabledIconResid; 1216 mMessageResId = message; 1217 mEnabledStatusMessageResId = enabledStatusMessageResId; 1218 mDisabledStatusMessageResId = disabledStatusMessageResId; 1219 } 1220 1221 /** 1222 * Override to make changes to resource IDs just before creating the 1223 * View. 1224 */ 1225 void willCreate() { 1226 1227 } 1228 1229 @Override 1230 public CharSequence getLabelForAccessibility(Context context) { 1231 return context.getString(mMessageResId); 1232 } 1233 1234 public View create(Context context, View convertView, ViewGroup parent, 1235 LayoutInflater inflater) { 1236 willCreate(); 1237 1238 View v = inflater.inflate(R 1239 .layout.global_actions_item, parent, false); 1240 1241 ImageView icon = (ImageView) v.findViewById(R.id.icon); 1242 TextView messageView = (TextView) v.findViewById(R.id.message); 1243 TextView statusView = (TextView) v.findViewById(R.id.status); 1244 final boolean enabled = isEnabled(); 1245 1246 if (messageView != null) { 1247 messageView.setText(mMessageResId); 1248 messageView.setEnabled(enabled); 1249 messageView.setSelected(true); // necessary for marquee to work 1250 } 1251 1252 boolean on = ((mState == State.On) || (mState == State.TurningOn)); 1253 if (icon != null) { 1254 icon.setImageDrawable(context.getDrawable( 1255 (on ? mEnabledIconResId : mDisabledIconResid))); 1256 icon.setEnabled(enabled); 1257 } 1258 1259 if (statusView != null) { 1260 statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId); 1261 statusView.setVisibility(View.VISIBLE); 1262 statusView.setEnabled(enabled); 1263 } 1264 v.setEnabled(enabled); 1265 1266 return v; 1267 } 1268 1269 public final void onPress() { 1270 if (mState.inTransition()) { 1271 Log.w(TAG, "shouldn't be able to toggle when in transition"); 1272 return; 1273 } 1274 1275 final boolean nowOn = !(mState == State.On); 1276 onToggle(nowOn); 1277 changeStateFromPress(nowOn); 1278 } 1279 1280 public boolean isEnabled() { 1281 return !mState.inTransition(); 1282 } 1283 1284 /** 1285 * Implementations may override this if their state can be in on of the intermediate 1286 * states until some notification is received (e.g airplane mode is 'turning off' until 1287 * we know the wireless connections are back online 1288 * 1289 * @param buttonOn Whether the button was turned on or off 1290 */ 1291 protected void changeStateFromPress(boolean buttonOn) { 1292 mState = buttonOn ? State.On : State.Off; 1293 } 1294 1295 abstract void onToggle(boolean on); 1296 1297 public void updateState(State state) { 1298 mState = state; 1299 } 1300 } 1301 1302 private class SilentModeToggleAction extends ToggleAction { 1303 public SilentModeToggleAction() { 1304 super(R.drawable.ic_audio_vol_mute, 1305 R.drawable.ic_audio_vol, 1306 R.string.global_action_toggle_silent_mode, 1307 R.string.global_action_silent_mode_on_status, 1308 R.string.global_action_silent_mode_off_status); 1309 } 1310 1311 void onToggle(boolean on) { 1312 if (on) { 1313 mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT); 1314 } else { 1315 mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL); 1316 } 1317 } 1318 1319 public boolean showDuringKeyguard() { 1320 return true; 1321 } 1322 1323 public boolean showBeforeProvisioning() { 1324 return false; 1325 } 1326 } 1327 1328 private static class SilentModeTriStateAction implements Action, View.OnClickListener { 1329 1330 private final int[] ITEM_IDS = {R.id.option1, R.id.option2, R.id.option3}; 1331 1332 private final AudioManager mAudioManager; 1333 private final Handler mHandler; 1334 1335 SilentModeTriStateAction(AudioManager audioManager, Handler handler) { 1336 mAudioManager = audioManager; 1337 mHandler = handler; 1338 } 1339 1340 private int ringerModeToIndex(int ringerMode) { 1341 // They just happen to coincide 1342 return ringerMode; 1343 } 1344 1345 private int indexToRingerMode(int index) { 1346 // They just happen to coincide 1347 return index; 1348 } 1349 1350 @Override 1351 public CharSequence getLabelForAccessibility(Context context) { 1352 return null; 1353 } 1354 1355 public View create(Context context, View convertView, ViewGroup parent, 1356 LayoutInflater inflater) { 1357 View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false); 1358 1359 int selectedIndex = ringerModeToIndex(mAudioManager.getRingerMode()); 1360 for (int i = 0; i < 3; i++) { 1361 View itemView = v.findViewById(ITEM_IDS[i]); 1362 itemView.setSelected(selectedIndex == i); 1363 // Set up click handler 1364 itemView.setTag(i); 1365 itemView.setOnClickListener(this); 1366 } 1367 return v; 1368 } 1369 1370 public void onPress() { 1371 } 1372 1373 public boolean showDuringKeyguard() { 1374 return true; 1375 } 1376 1377 public boolean showBeforeProvisioning() { 1378 return false; 1379 } 1380 1381 public boolean isEnabled() { 1382 return true; 1383 } 1384 1385 void willCreate() { 1386 } 1387 1388 public void onClick(View v) { 1389 if (!(v.getTag() instanceof Integer)) return; 1390 1391 int index = (Integer) v.getTag(); 1392 mAudioManager.setRingerMode(indexToRingerMode(index)); 1393 mHandler.sendEmptyMessageDelayed(MESSAGE_DISMISS, DIALOG_DISMISS_DELAY); 1394 } 1395 } 1396 1397 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 1398 public void onReceive(Context context, Intent intent) { 1399 String action = intent.getAction(); 1400 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) 1401 || Intent.ACTION_SCREEN_OFF.equals(action)) { 1402 String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY); 1403 if (!SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) { 1404 mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_DISMISS, reason)); 1405 } 1406 } else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) { 1407 // Airplane mode can be changed after ECM exits if airplane toggle button 1408 // is pressed during ECM mode 1409 if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) && 1410 mIsWaitingForEcmExit) { 1411 mIsWaitingForEcmExit = false; 1412 changeAirplaneModeSystemSetting(true); 1413 } 1414 } 1415 } 1416 }; 1417 1418 PhoneStateListener mPhoneStateListener = new PhoneStateListener() { 1419 @Override 1420 public void onServiceStateChanged(ServiceState serviceState) { 1421 if (!mHasTelephony) return; 1422 final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF; 1423 mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off; 1424 mAirplaneModeOn.updateState(mAirplaneState); 1425 mAdapter.notifyDataSetChanged(); 1426 } 1427 }; 1428 1429 private BroadcastReceiver mRingerModeReceiver = new BroadcastReceiver() { 1430 @Override 1431 public void onReceive(Context context, Intent intent) { 1432 if (intent.getAction().equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { 1433 mHandler.sendEmptyMessage(MESSAGE_REFRESH); 1434 } 1435 } 1436 }; 1437 1438 private ContentObserver mAirplaneModeObserver = new ContentObserver(new Handler()) { 1439 @Override 1440 public void onChange(boolean selfChange) { 1441 onAirplaneModeChanged(); 1442 } 1443 }; 1444 1445 private static final int MESSAGE_DISMISS = 0; 1446 private static final int MESSAGE_REFRESH = 1; 1447 private static final int MESSAGE_SHOW = 2; 1448 private static final int DIALOG_DISMISS_DELAY = 300; // ms 1449 1450 private Handler mHandler = new Handler() { 1451 public void handleMessage(Message msg) { 1452 switch (msg.what) { 1453 case MESSAGE_DISMISS: 1454 if (mDialog != null) { 1455 if (SYSTEM_DIALOG_REASON_DREAM.equals(msg.obj)) { 1456 mDialog.dismissImmediately(); 1457 } else { 1458 mDialog.dismiss(); 1459 } 1460 mDialog = null; 1461 } 1462 break; 1463 case MESSAGE_REFRESH: 1464 refreshSilentMode(); 1465 mAdapter.notifyDataSetChanged(); 1466 break; 1467 case MESSAGE_SHOW: 1468 handleShow(); 1469 break; 1470 } 1471 } 1472 }; 1473 1474 private void onAirplaneModeChanged() { 1475 // Let the service state callbacks handle the state. 1476 if (mHasTelephony) return; 1477 1478 boolean airplaneModeOn = Settings.Global.getInt( 1479 mContext.getContentResolver(), 1480 Settings.Global.AIRPLANE_MODE_ON, 1481 0) == 1; 1482 mAirplaneState = airplaneModeOn ? ToggleAction.State.On : ToggleAction.State.Off; 1483 mAirplaneModeOn.updateState(mAirplaneState); 1484 } 1485 1486 /** 1487 * Change the airplane mode system setting 1488 */ 1489 private void changeAirplaneModeSystemSetting(boolean on) { 1490 Settings.Global.putInt( 1491 mContext.getContentResolver(), 1492 Settings.Global.AIRPLANE_MODE_ON, 1493 on ? 1 : 0); 1494 Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); 1495 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); 1496 intent.putExtra("state", on); 1497 mContext.sendBroadcastAsUser(intent, UserHandle.ALL); 1498 if (!mHasTelephony) { 1499 mAirplaneState = on ? ToggleAction.State.On : ToggleAction.State.Off; 1500 } 1501 } 1502 1503 private static final class ActionsDialog extends Dialog implements DialogInterface, 1504 ColorExtractor.OnColorsChangedListener { 1505 1506 private final Context mContext; 1507 private final MyAdapter mAdapter; 1508 private final IStatusBarService mStatusBarService; 1509 private final IBinder mToken = new Binder(); 1510 private MultiListLayout mGlobalActionsLayout; 1511 private Drawable mBackgroundDrawable; 1512 private final SysuiColorExtractor mColorExtractor; 1513 private final GlobalActionsPanelPlugin.PanelViewController mPanelController; 1514 private boolean mKeyguardShowing; 1515 private boolean mShowing; 1516 private float mScrimAlpha; 1517 private ResetOrientationData mResetOrientationData; 1518 1519 ActionsDialog(Context context, MyAdapter adapter, 1520 GlobalActionsPanelPlugin.PanelViewController plugin) { 1521 super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions); 1522 mContext = context; 1523 mAdapter = adapter; 1524 mColorExtractor = Dependency.get(SysuiColorExtractor.class); 1525 mStatusBarService = Dependency.get(IStatusBarService.class); 1526 1527 // Window initialization 1528 Window window = getWindow(); 1529 window.requestFeature(Window.FEATURE_NO_TITLE); 1530 // Inflate the decor view, so the attributes below are not overwritten by the theme. 1531 window.getDecorView(); 1532 window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 1533 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE 1534 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; 1535 window.setLayout(MATCH_PARENT, MATCH_PARENT); 1536 window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); 1537 window.addFlags( 1538 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 1539 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 1540 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR 1541 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED 1542 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH 1543 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); 1544 window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY); 1545 setTitle(R.string.global_actions); 1546 1547 mPanelController = plugin; 1548 initializeLayout(); 1549 } 1550 1551 private boolean shouldUsePanel() { 1552 return mPanelController != null && mPanelController.getPanelContent() != null; 1553 } 1554 1555 private void initializePanel() { 1556 int rotation = RotationUtils.getRotation(mContext); 1557 boolean rotationLocked = RotationPolicy.isRotationLocked(mContext); 1558 if (rotation != RotationUtils.ROTATION_NONE) { 1559 if (rotationLocked) { 1560 if (mResetOrientationData == null) { 1561 mResetOrientationData = new ResetOrientationData(); 1562 mResetOrientationData.locked = true; 1563 mResetOrientationData.rotation = rotation; 1564 } 1565 1566 // Unlock rotation, so user can choose to rotate to portrait to see the panel. 1567 // This call is posted so that the rotation does not change until post-layout, 1568 // otherwise onConfigurationChanged() may not get invoked. 1569 mGlobalActionsLayout.post(() -> 1570 RotationPolicy.setRotationLockAtAngle( 1571 mContext, false, RotationUtils.ROTATION_NONE)); 1572 } 1573 } else { 1574 if (!rotationLocked) { 1575 if (mResetOrientationData == null) { 1576 mResetOrientationData = new ResetOrientationData(); 1577 mResetOrientationData.locked = false; 1578 } 1579 1580 // Lock to portrait, so the user doesn't accidentally hide the panel. 1581 // This call is posted so that the rotation does not change until post-layout, 1582 // otherwise onConfigurationChanged() may not get invoked. 1583 mGlobalActionsLayout.post(() -> 1584 RotationPolicy.setRotationLockAtAngle( 1585 mContext, true, RotationUtils.ROTATION_NONE)); 1586 } 1587 1588 // Disable rotation suggestions, if enabled 1589 setRotationSuggestionsEnabled(false); 1590 1591 FrameLayout panelContainer = 1592 findViewById(com.android.systemui.R.id.global_actions_panel_container); 1593 FrameLayout.LayoutParams panelParams = 1594 new FrameLayout.LayoutParams( 1595 FrameLayout.LayoutParams.MATCH_PARENT, 1596 FrameLayout.LayoutParams.MATCH_PARENT); 1597 panelContainer.addView(mPanelController.getPanelContent(), panelParams); 1598 mBackgroundDrawable = mPanelController.getBackgroundDrawable(); 1599 mScrimAlpha = 1f; 1600 } 1601 } 1602 1603 private void initializeLayout() { 1604 setContentView(getGlobalActionsLayoutId(mContext)); 1605 fixNavBarClipping(); 1606 mGlobalActionsLayout = findViewById(com.android.systemui.R.id.global_actions_view); 1607 mGlobalActionsLayout.setOutsideTouchListener(view -> dismiss()); 1608 ((View) mGlobalActionsLayout.getParent()).setOnClickListener(view -> dismiss()); 1609 mGlobalActionsLayout.setListViewAccessibilityDelegate(new View.AccessibilityDelegate() { 1610 @Override 1611 public boolean dispatchPopulateAccessibilityEvent( 1612 View host, AccessibilityEvent event) { 1613 // Populate the title here, just as Activity does 1614 event.getText().add(mContext.getString(R.string.global_actions)); 1615 return true; 1616 } 1617 }); 1618 mGlobalActionsLayout.setRotationListener(this::onRotate); 1619 mGlobalActionsLayout.setAdapter(mAdapter); 1620 1621 if (shouldUsePanel()) { 1622 initializePanel(); 1623 } 1624 if (mBackgroundDrawable == null) { 1625 mBackgroundDrawable = new ScrimDrawable(); 1626 mScrimAlpha = ScrimController.GRADIENT_SCRIM_ALPHA; 1627 } 1628 getWindow().setBackgroundDrawable(mBackgroundDrawable); 1629 } 1630 1631 private void fixNavBarClipping() { 1632 ViewGroup content = findViewById(android.R.id.content); 1633 content.setClipChildren(false); 1634 content.setClipToPadding(false); 1635 ViewGroup contentParent = (ViewGroup) content.getParent(); 1636 contentParent.setClipChildren(false); 1637 contentParent.setClipToPadding(false); 1638 } 1639 1640 private int getGlobalActionsLayoutId(Context context) { 1641 int rotation = RotationUtils.getRotation(context); 1642 boolean useGridLayout = isForceGridEnabled(context) 1643 || (shouldUsePanel() && rotation == RotationUtils.ROTATION_NONE); 1644 if (rotation == RotationUtils.ROTATION_SEASCAPE) { 1645 if (useGridLayout) { 1646 return com.android.systemui.R.layout.global_actions_grid_seascape; 1647 } else { 1648 return com.android.systemui.R.layout.global_actions_column_seascape; 1649 } 1650 } else { 1651 if (useGridLayout) { 1652 return com.android.systemui.R.layout.global_actions_grid; 1653 } else { 1654 return com.android.systemui.R.layout.global_actions_column; 1655 } 1656 } 1657 } 1658 1659 @Override 1660 protected void onStart() { 1661 super.setCanceledOnTouchOutside(true); 1662 super.onStart(); 1663 mGlobalActionsLayout.updateList(); 1664 1665 if (mBackgroundDrawable instanceof ScrimDrawable) { 1666 mColorExtractor.addOnColorsChangedListener(this); 1667 GradientColors colors = mColorExtractor.getNeutralColors(); 1668 updateColors(colors, false /* animate */); 1669 } 1670 } 1671 1672 /** 1673 * Updates background and system bars according to current GradientColors. 1674 * @param colors Colors and hints to use. 1675 * @param animate Interpolates gradient if true, just sets otherwise. 1676 */ 1677 private void updateColors(GradientColors colors, boolean animate) { 1678 if (!(mBackgroundDrawable instanceof ScrimDrawable)) { 1679 return; 1680 } 1681 ((ScrimDrawable) mBackgroundDrawable).setColor(colors.getMainColor(), animate); 1682 View decorView = getWindow().getDecorView(); 1683 if (colors.supportsDarkText()) { 1684 decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR | 1685 View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); 1686 } else { 1687 decorView.setSystemUiVisibility(0); 1688 } 1689 } 1690 1691 @Override 1692 protected void onStop() { 1693 super.onStop(); 1694 mColorExtractor.removeOnColorsChangedListener(this); 1695 } 1696 1697 @Override 1698 public void show() { 1699 super.show(); 1700 mShowing = true; 1701 mBackgroundDrawable.setAlpha(0); 1702 mGlobalActionsLayout.setTranslationX(mGlobalActionsLayout.getAnimationOffsetX()); 1703 mGlobalActionsLayout.setTranslationY(mGlobalActionsLayout.getAnimationOffsetY()); 1704 mGlobalActionsLayout.setAlpha(0); 1705 mGlobalActionsLayout.animate() 1706 .alpha(1) 1707 .translationX(0) 1708 .translationY(0) 1709 .setDuration(300) 1710 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) 1711 .setUpdateListener(animation -> { 1712 int alpha = (int) ((Float) animation.getAnimatedValue() 1713 * mScrimAlpha * 255); 1714 mBackgroundDrawable.setAlpha(alpha); 1715 }) 1716 .start(); 1717 } 1718 1719 @Override 1720 public void dismiss() { 1721 if (!mShowing) { 1722 return; 1723 } 1724 mShowing = false; 1725 mGlobalActionsLayout.setTranslationX(0); 1726 mGlobalActionsLayout.setTranslationY(0); 1727 mGlobalActionsLayout.setAlpha(1); 1728 mGlobalActionsLayout.animate() 1729 .alpha(0) 1730 .translationX(mGlobalActionsLayout.getAnimationOffsetX()) 1731 .translationY(mGlobalActionsLayout.getAnimationOffsetY()) 1732 .setDuration(300) 1733 .withEndAction(super::dismiss) 1734 .setInterpolator(new LogAccelerateInterpolator()) 1735 .setUpdateListener(animation -> { 1736 int alpha = (int) ((1f - (Float) animation.getAnimatedValue()) 1737 * mScrimAlpha * 255); 1738 mBackgroundDrawable.setAlpha(alpha); 1739 }) 1740 .start(); 1741 dismissPanel(); 1742 resetOrientation(); 1743 } 1744 1745 void dismissImmediately() { 1746 super.dismiss(); 1747 mShowing = false; 1748 dismissPanel(); 1749 resetOrientation(); 1750 } 1751 1752 private void dismissPanel() { 1753 if (mPanelController != null) { 1754 mPanelController.onDismissed(); 1755 } 1756 } 1757 1758 private void setRotationSuggestionsEnabled(boolean enabled) { 1759 try { 1760 final int userId = Binder.getCallingUserHandle().getIdentifier(); 1761 final int what = enabled 1762 ? StatusBarManager.DISABLE2_NONE 1763 : StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS; 1764 mStatusBarService.disable2ForUser(what, mToken, mContext.getPackageName(), userId); 1765 } catch (RemoteException ex) { 1766 throw ex.rethrowFromSystemServer(); 1767 } 1768 } 1769 1770 private void resetOrientation() { 1771 if (mResetOrientationData != null) { 1772 RotationPolicy.setRotationLockAtAngle(mContext, mResetOrientationData.locked, 1773 mResetOrientationData.rotation); 1774 } 1775 setRotationSuggestionsEnabled(true); 1776 } 1777 1778 @Override 1779 public void onColorsChanged(ColorExtractor extractor, int which) { 1780 if (mKeyguardShowing) { 1781 if ((WallpaperManager.FLAG_LOCK & which) != 0) { 1782 updateColors(extractor.getColors(WallpaperManager.FLAG_LOCK), 1783 true /* animate */); 1784 } 1785 } else { 1786 if ((WallpaperManager.FLAG_SYSTEM & which) != 0) { 1787 updateColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM), 1788 true /* animate */); 1789 } 1790 } 1791 } 1792 1793 public void setKeyguardShowing(boolean keyguardShowing) { 1794 mKeyguardShowing = keyguardShowing; 1795 } 1796 1797 public void refreshDialog() { 1798 initializeLayout(); 1799 mGlobalActionsLayout.updateList(); 1800 } 1801 1802 public void onRotate(int from, int to) { 1803 if (mShowing) { 1804 refreshDialog(); 1805 } 1806 } 1807 1808 private static class ResetOrientationData { 1809 public boolean locked; 1810 public int rotation; 1811 } 1812 } 1813 1814 /** 1815 * Determines whether or not debug mode has been activated for the Global Actions Panel. 1816 */ 1817 private static boolean isPanelDebugModeEnabled(Context context) { 1818 return Settings.Secure.getInt(context.getContentResolver(), 1819 Settings.Secure.GLOBAL_ACTIONS_PANEL_DEBUG_ENABLED, 0) == 1; 1820 } 1821 1822 /** 1823 * Determines whether or not the Global Actions menu should be forced to 1824 * use the newer grid-style layout. 1825 */ 1826 private static boolean isForceGridEnabled(Context context) { 1827 return isPanelDebugModeEnabled(context); 1828 } 1829 1830 /** 1831 * Determines whether the Global Actions menu should use a separated view for emergency actions. 1832 */ 1833 private static boolean shouldUseSeparatedView() { 1834 return true; 1835 } 1836 } 1837