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 15package com.android.systemui.globalactions; 16 17import com.android.internal.R; 18import com.android.internal.app.AlertController; 19import com.android.internal.app.AlertController.AlertParams; 20import com.android.internal.logging.MetricsLogger; 21import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 22import com.android.internal.util.EmergencyAffordanceManager; 23import com.android.internal.telephony.TelephonyIntents; 24import com.android.internal.telephony.TelephonyProperties; 25import com.android.internal.widget.LockPatternUtils; 26import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; 27 28import android.app.ActivityManager; 29import android.app.Dialog; 30import android.content.BroadcastReceiver; 31import android.content.Context; 32import android.content.DialogInterface; 33import android.content.Intent; 34import android.content.IntentFilter; 35import android.content.pm.UserInfo; 36import android.database.ContentObserver; 37import android.graphics.drawable.Drawable; 38import android.media.AudioManager; 39import android.net.ConnectivityManager; 40import android.os.Build; 41import android.os.Bundle; 42import android.os.Handler; 43import android.os.Message; 44import android.os.RemoteException; 45import android.os.ServiceManager; 46import android.os.SystemProperties; 47import android.os.UserHandle; 48import android.os.UserManager; 49import android.os.Vibrator; 50import android.provider.Settings; 51import android.service.dreams.DreamService; 52import android.service.dreams.IDreamManager; 53import android.telephony.PhoneStateListener; 54import android.telephony.ServiceState; 55import android.telephony.TelephonyManager; 56import android.text.TextUtils; 57import android.util.ArraySet; 58import android.util.Log; 59import android.util.TypedValue; 60import android.view.KeyEvent; 61import android.view.LayoutInflater; 62import android.view.View; 63import android.view.ViewGroup; 64import android.view.WindowManager; 65import android.view.WindowManagerGlobal; 66import android.view.accessibility.AccessibilityEvent; 67import android.widget.AdapterView; 68import android.widget.BaseAdapter; 69import android.widget.ImageView; 70import android.widget.ImageView.ScaleType; 71import android.widget.ListView; 72import android.widget.TextView; 73 74import java.util.ArrayList; 75import java.util.List; 76 77/** 78 * Helper to show the global actions dialog. Each item is an {@link Action} that 79 * may show depending on whether the keyguard is showing, and whether the device 80 * is provisioned. 81 */ 82class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener { 83 84 static public final String SYSTEM_DIALOG_REASON_KEY = "reason"; 85 static public final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions"; 86 87 private static final String TAG = "GlobalActionsDialog"; 88 89 private static final boolean SHOW_SILENT_TOGGLE = true; 90 91 /* Valid settings for global actions keys. 92 * see config.xml config_globalActionList */ 93 private static final String GLOBAL_ACTION_KEY_POWER = "power"; 94 private static final String GLOBAL_ACTION_KEY_AIRPLANE = "airplane"; 95 private static final String GLOBAL_ACTION_KEY_BUGREPORT = "bugreport"; 96 private static final String GLOBAL_ACTION_KEY_SILENT = "silent"; 97 private static final String GLOBAL_ACTION_KEY_USERS = "users"; 98 private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings"; 99 private static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown"; 100 private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist"; 101 private static final String GLOBAL_ACTION_KEY_ASSIST = "assist"; 102 private static final String GLOBAL_ACTION_KEY_RESTART = "restart"; 103 104 private final Context mContext; 105 private final GlobalActionsManager mWindowManagerFuncs; 106 private final AudioManager mAudioManager; 107 private final IDreamManager mDreamManager; 108 109 private ArrayList<Action> mItems; 110 private ActionsDialog mDialog; 111 112 private Action mSilentModeAction; 113 private ToggleAction mAirplaneModeOn; 114 115 private MyAdapter mAdapter; 116 117 private boolean mKeyguardShowing = false; 118 private boolean mDeviceProvisioned = false; 119 private ToggleAction.State mAirplaneState = ToggleAction.State.Off; 120 private boolean mIsWaitingForEcmExit = false; 121 private boolean mHasTelephony; 122 private boolean mHasVibrator; 123 private final boolean mShowSilentToggle; 124 private final EmergencyAffordanceManager mEmergencyAffordanceManager; 125 126 /** 127 * @param context everything needs a context :( 128 */ 129 public GlobalActionsDialog(Context context, GlobalActionsManager windowManagerFuncs) { 130 mContext = context; 131 mWindowManagerFuncs = windowManagerFuncs; 132 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 133 mDreamManager = IDreamManager.Stub.asInterface( 134 ServiceManager.getService(DreamService.DREAM_SERVICE)); 135 136 // receive broadcasts 137 IntentFilter filter = new IntentFilter(); 138 filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 139 filter.addAction(Intent.ACTION_SCREEN_OFF); 140 filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); 141 context.registerReceiver(mBroadcastReceiver, filter); 142 143 ConnectivityManager cm = (ConnectivityManager) 144 context.getSystemService(Context.CONNECTIVITY_SERVICE); 145 mHasTelephony = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE); 146 147 // get notified of phone state changes 148 TelephonyManager telephonyManager = 149 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 150 telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE); 151 mContext.getContentResolver().registerContentObserver( 152 Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true, 153 mAirplaneModeObserver); 154 Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); 155 mHasVibrator = vibrator != null && vibrator.hasVibrator(); 156 157 mShowSilentToggle = SHOW_SILENT_TOGGLE && !mContext.getResources().getBoolean( 158 R.bool.config_useFixedVolume); 159 160 mEmergencyAffordanceManager = new EmergencyAffordanceManager(context); 161 } 162 163 /** 164 * Show the global actions dialog (creating if necessary) 165 * @param keyguardShowing True if keyguard is showing 166 */ 167 public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) { 168 mKeyguardShowing = keyguardShowing; 169 mDeviceProvisioned = isDeviceProvisioned; 170 if (mDialog != null) { 171 mDialog.dismiss(); 172 mDialog = null; 173 // Show delayed, so that the dismiss of the previous dialog completes 174 mHandler.sendEmptyMessage(MESSAGE_SHOW); 175 } else { 176 handleShow(); 177 } 178 } 179 180 private void awakenIfNecessary() { 181 if (mDreamManager != null) { 182 try { 183 if (mDreamManager.isDreaming()) { 184 mDreamManager.awaken(); 185 } 186 } catch (RemoteException e) { 187 // we tried 188 } 189 } 190 } 191 192 private void handleShow() { 193 awakenIfNecessary(); 194 mDialog = createDialog(); 195 prepareDialog(); 196 197 // If we only have 1 item and it's a simple press action, just do this action. 198 if (mAdapter.getCount() == 1 199 && mAdapter.getItem(0) instanceof SinglePressAction 200 && !(mAdapter.getItem(0) instanceof LongPressAction)) { 201 ((SinglePressAction) mAdapter.getItem(0)).onPress(); 202 } else { 203 WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes(); 204 attrs.setTitle("ActionsDialog"); 205 mDialog.getWindow().setAttributes(attrs); 206 mDialog.show(); 207 mWindowManagerFuncs.onGlobalActionsShown(); 208 mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND); 209 } 210 } 211 212 /** 213 * Create the global actions dialog. 214 * @return A new dialog. 215 */ 216 private ActionsDialog createDialog() { 217 // Simple toggle style if there's no vibrator, otherwise use a tri-state 218 if (!mHasVibrator) { 219 mSilentModeAction = new SilentModeToggleAction(); 220 } else { 221 mSilentModeAction = new SilentModeTriStateAction(mContext, mAudioManager, mHandler); 222 } 223 mAirplaneModeOn = new ToggleAction( 224 R.drawable.ic_lock_airplane_mode, 225 R.drawable.ic_lock_airplane_mode_off, 226 R.string.global_actions_toggle_airplane_mode, 227 R.string.global_actions_airplane_mode_on_status, 228 R.string.global_actions_airplane_mode_off_status) { 229 230 void onToggle(boolean on) { 231 if (mHasTelephony && Boolean.parseBoolean( 232 SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) { 233 mIsWaitingForEcmExit = true; 234 // Launch ECM exit dialog 235 Intent ecmDialogIntent = 236 new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null); 237 ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 238 mContext.startActivity(ecmDialogIntent); 239 } else { 240 changeAirplaneModeSystemSetting(on); 241 } 242 } 243 244 @Override 245 protected void changeStateFromPress(boolean buttonOn) { 246 if (!mHasTelephony) return; 247 248 // In ECM mode airplane state cannot be changed 249 if (!(Boolean.parseBoolean( 250 SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)))) { 251 mState = buttonOn ? State.TurningOn : State.TurningOff; 252 mAirplaneState = mState; 253 } 254 } 255 256 public boolean showDuringKeyguard() { 257 return true; 258 } 259 260 public boolean showBeforeProvisioning() { 261 return false; 262 } 263 }; 264 onAirplaneModeChanged(); 265 266 mItems = new ArrayList<Action>(); 267 String[] defaultActions = mContext.getResources().getStringArray( 268 R.array.config_globalActionsList); 269 270 ArraySet<String> addedKeys = new ArraySet<String>(); 271 for (int i = 0; i < defaultActions.length; i++) { 272 String actionKey = defaultActions[i]; 273 if (addedKeys.contains(actionKey)) { 274 // If we already have added this, don't add it again. 275 continue; 276 } 277 if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) { 278 mItems.add(new PowerAction()); 279 } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) { 280 mItems.add(mAirplaneModeOn); 281 } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) { 282 if (Settings.Global.getInt(mContext.getContentResolver(), 283 Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) { 284 mItems.add(new BugReportAction()); 285 } 286 } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) { 287 if (mShowSilentToggle) { 288 mItems.add(mSilentModeAction); 289 } 290 } else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) { 291 if (SystemProperties.getBoolean("fw.power_user_switcher", false)) { 292 addUsersToMenu(mItems); 293 } 294 } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) { 295 mItems.add(getSettingsAction()); 296 } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) { 297 mItems.add(getLockdownAction()); 298 } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) { 299 mItems.add(getVoiceAssistAction()); 300 } else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) { 301 mItems.add(getAssistAction()); 302 } else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) { 303 mItems.add(new RestartAction()); 304 } else { 305 Log.e(TAG, "Invalid global action key " + actionKey); 306 } 307 // Add here so we don't add more than one. 308 addedKeys.add(actionKey); 309 } 310 311 if (mEmergencyAffordanceManager.needsEmergencyAffordance()) { 312 mItems.add(getEmergencyAction()); 313 } 314 315 mAdapter = new MyAdapter(); 316 317 AlertParams params = new AlertParams(mContext); 318 params.mAdapter = mAdapter; 319 params.mOnClickListener = this; 320 params.mForceInverseBackground = true; 321 322 ActionsDialog dialog = new ActionsDialog(mContext, params); 323 dialog.setCanceledOnTouchOutside(false); // Handled by the custom class. 324 325 dialog.getListView().setItemsCanFocus(true); 326 dialog.getListView().setLongClickable(true); 327 dialog.getListView().setOnItemLongClickListener( 328 new AdapterView.OnItemLongClickListener() { 329 @Override 330 public boolean onItemLongClick(AdapterView<?> parent, View view, int position, 331 long id) { 332 final Action action = mAdapter.getItem(position); 333 if (action instanceof LongPressAction) { 334 return ((LongPressAction) action).onLongPress(); 335 } 336 return false; 337 } 338 }); 339 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 340 341 dialog.setOnDismissListener(this); 342 343 return dialog; 344 } 345 346 private final class PowerAction extends SinglePressAction implements LongPressAction { 347 private PowerAction() { 348 super(R.drawable.ic_lock_power_off, 349 R.string.global_action_power_off); 350 } 351 352 @Override 353 public boolean onLongPress() { 354 UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); 355 if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { 356 mWindowManagerFuncs.reboot(true); 357 return true; 358 } 359 return false; 360 } 361 362 @Override 363 public boolean showDuringKeyguard() { 364 return true; 365 } 366 367 @Override 368 public boolean showBeforeProvisioning() { 369 return true; 370 } 371 372 @Override 373 public void onPress() { 374 // shutdown by making sure radio and power are handled accordingly. 375 mWindowManagerFuncs.shutdown(); 376 } 377 } 378 379 private final class RestartAction extends SinglePressAction implements LongPressAction { 380 private RestartAction() { 381 super(R.drawable.ic_restart, R.string.global_action_restart); 382 } 383 384 @Override 385 public boolean onLongPress() { 386 UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); 387 if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { 388 mWindowManagerFuncs.reboot(true); 389 return true; 390 } 391 return false; 392 } 393 394 @Override 395 public boolean showDuringKeyguard() { 396 return true; 397 } 398 399 @Override 400 public boolean showBeforeProvisioning() { 401 return true; 402 } 403 404 @Override 405 public void onPress() { 406 mWindowManagerFuncs.reboot(false); 407 } 408 } 409 410 411 private class BugReportAction extends SinglePressAction implements LongPressAction { 412 413 public BugReportAction() { 414 super(R.drawable.ic_lock_bugreport, R.string.bugreport_title); 415 } 416 417 @Override 418 public void onPress() { 419 // don't actually trigger the bugreport if we are running stability 420 // tests via monkey 421 if (ActivityManager.isUserAMonkey()) { 422 return; 423 } 424 // Add a little delay before executing, to give the 425 // dialog a chance to go away before it takes a 426 // screenshot. 427 mHandler.postDelayed(new Runnable() { 428 @Override 429 public void run() { 430 try { 431 // Take an "interactive" bugreport. 432 MetricsLogger.action(mContext, 433 MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE); 434 ActivityManager.getService().requestBugReport( 435 ActivityManager.BUGREPORT_OPTION_INTERACTIVE); 436 } catch (RemoteException e) { 437 } 438 } 439 }, 500); 440 } 441 442 @Override 443 public boolean onLongPress() { 444 // don't actually trigger the bugreport if we are running stability 445 // tests via monkey 446 if (ActivityManager.isUserAMonkey()) { 447 return false; 448 } 449 try { 450 // Take a "full" bugreport. 451 MetricsLogger.action(mContext, MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL); 452 ActivityManager.getService().requestBugReport( 453 ActivityManager.BUGREPORT_OPTION_FULL); 454 } catch (RemoteException e) { 455 } 456 return false; 457 } 458 459 public boolean showDuringKeyguard() { 460 return true; 461 } 462 463 @Override 464 public boolean showBeforeProvisioning() { 465 return false; 466 } 467 468 @Override 469 public String getStatus() { 470 return mContext.getString( 471 R.string.bugreport_status, 472 Build.VERSION.RELEASE, 473 Build.ID); 474 } 475 } 476 477 private Action getSettingsAction() { 478 return new SinglePressAction(R.drawable.ic_settings, 479 R.string.global_action_settings) { 480 481 @Override 482 public void onPress() { 483 Intent intent = new Intent(Settings.ACTION_SETTINGS); 484 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); 485 mContext.startActivity(intent); 486 } 487 488 @Override 489 public boolean showDuringKeyguard() { 490 return true; 491 } 492 493 @Override 494 public boolean showBeforeProvisioning() { 495 return true; 496 } 497 }; 498 } 499 500 private Action getEmergencyAction() { 501 return new SinglePressAction(R.drawable.emergency_icon, 502 R.string.global_action_emergency) { 503 @Override 504 public void onPress() { 505 mEmergencyAffordanceManager.performEmergencyCall(); 506 } 507 508 @Override 509 public boolean showDuringKeyguard() { 510 return true; 511 } 512 513 @Override 514 public boolean showBeforeProvisioning() { 515 return true; 516 } 517 }; 518 } 519 520 private Action getAssistAction() { 521 return new SinglePressAction(R.drawable.ic_action_assist_focused, 522 R.string.global_action_assist) { 523 @Override 524 public void onPress() { 525 Intent intent = new Intent(Intent.ACTION_ASSIST); 526 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); 527 mContext.startActivity(intent); 528 } 529 530 @Override 531 public boolean showDuringKeyguard() { 532 return true; 533 } 534 535 @Override 536 public boolean showBeforeProvisioning() { 537 return true; 538 } 539 }; 540 } 541 542 private Action getVoiceAssistAction() { 543 return new SinglePressAction(R.drawable.ic_voice_search, 544 R.string.global_action_voice_assist) { 545 @Override 546 public void onPress() { 547 Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST); 548 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); 549 mContext.startActivity(intent); 550 } 551 552 @Override 553 public boolean showDuringKeyguard() { 554 return true; 555 } 556 557 @Override 558 public boolean showBeforeProvisioning() { 559 return true; 560 } 561 }; 562 } 563 564 private Action getLockdownAction() { 565 return new SinglePressAction(R.drawable.ic_lock_lock, 566 R.string.global_action_lockdown) { 567 568 @Override 569 public void onPress() { 570 new LockPatternUtils(mContext).requireCredentialEntry(UserHandle.USER_ALL); 571 try { 572 WindowManagerGlobal.getWindowManagerService().lockNow(null); 573 } catch (RemoteException e) { 574 Log.e(TAG, "Error while trying to lock device.", e); 575 } 576 } 577 578 @Override 579 public boolean showDuringKeyguard() { 580 return true; 581 } 582 583 @Override 584 public boolean showBeforeProvisioning() { 585 return false; 586 } 587 }; 588 } 589 590 private UserInfo getCurrentUser() { 591 try { 592 return ActivityManager.getService().getCurrentUser(); 593 } catch (RemoteException re) { 594 return null; 595 } 596 } 597 598 private boolean isCurrentUserOwner() { 599 UserInfo currentUser = getCurrentUser(); 600 return currentUser == null || currentUser.isPrimary(); 601 } 602 603 private void addUsersToMenu(ArrayList<Action> items) { 604 UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); 605 if (um.isUserSwitcherEnabled()) { 606 List<UserInfo> users = um.getUsers(); 607 UserInfo currentUser = getCurrentUser(); 608 for (final UserInfo user : users) { 609 if (user.supportsSwitchToByUser()) { 610 boolean isCurrentUser = currentUser == null 611 ? user.id == 0 : (currentUser.id == user.id); 612 Drawable icon = user.iconPath != null ? Drawable.createFromPath(user.iconPath) 613 : null; 614 SinglePressAction switchToUser = new SinglePressAction( 615 R.drawable.ic_menu_cc, icon, 616 (user.name != null ? user.name : "Primary") 617 + (isCurrentUser ? " \u2714" : "")) { 618 public void onPress() { 619 try { 620 ActivityManager.getService().switchUser(user.id); 621 } catch (RemoteException re) { 622 Log.e(TAG, "Couldn't switch user " + re); 623 } 624 } 625 626 public boolean showDuringKeyguard() { 627 return true; 628 } 629 630 public boolean showBeforeProvisioning() { 631 return false; 632 } 633 }; 634 items.add(switchToUser); 635 } 636 } 637 } 638 } 639 640 private void prepareDialog() { 641 refreshSilentMode(); 642 mAirplaneModeOn.updateState(mAirplaneState); 643 mAdapter.notifyDataSetChanged(); 644 mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 645 if (mShowSilentToggle) { 646 IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION); 647 mContext.registerReceiver(mRingerModeReceiver, filter); 648 } 649 } 650 651 private void refreshSilentMode() { 652 if (!mHasVibrator) { 653 final boolean silentModeOn = 654 mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL; 655 ((ToggleAction)mSilentModeAction).updateState( 656 silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off); 657 } 658 } 659 660 /** {@inheritDoc} */ 661 public void onDismiss(DialogInterface dialog) { 662 mWindowManagerFuncs.onGlobalActionsHidden(); 663 if (mShowSilentToggle) { 664 try { 665 mContext.unregisterReceiver(mRingerModeReceiver); 666 } catch (IllegalArgumentException ie) { 667 // ignore this 668 Log.w(TAG, ie); 669 } 670 } 671 } 672 673 /** {@inheritDoc} */ 674 public void onClick(DialogInterface dialog, int which) { 675 if (!(mAdapter.getItem(which) instanceof SilentModeTriStateAction)) { 676 dialog.dismiss(); 677 } 678 mAdapter.getItem(which).onPress(); 679 } 680 681 /** 682 * The adapter used for the list within the global actions dialog, taking 683 * into account whether the keyguard is showing via 684 * {@link com.android.systemui.globalactions.GlobalActionsDialog#mKeyguardShowing} and whether the device is provisioned 685 * via {@link com.android.systemui.globalactions.GlobalActionsDialog#mDeviceProvisioned}. 686 */ 687 private class MyAdapter extends BaseAdapter { 688 689 public int getCount() { 690 int count = 0; 691 692 for (int i = 0; i < mItems.size(); i++) { 693 final Action action = mItems.get(i); 694 695 if (mKeyguardShowing && !action.showDuringKeyguard()) { 696 continue; 697 } 698 if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { 699 continue; 700 } 701 count++; 702 } 703 return count; 704 } 705 706 @Override 707 public boolean isEnabled(int position) { 708 return getItem(position).isEnabled(); 709 } 710 711 @Override 712 public boolean areAllItemsEnabled() { 713 return false; 714 } 715 716 public Action getItem(int position) { 717 718 int filteredPos = 0; 719 for (int i = 0; i < mItems.size(); i++) { 720 final Action action = mItems.get(i); 721 if (mKeyguardShowing && !action.showDuringKeyguard()) { 722 continue; 723 } 724 if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { 725 continue; 726 } 727 if (filteredPos == position) { 728 return action; 729 } 730 filteredPos++; 731 } 732 733 throw new IllegalArgumentException("position " + position 734 + " out of range of showable actions" 735 + ", filtered count=" + getCount() 736 + ", keyguardshowing=" + mKeyguardShowing 737 + ", provisioned=" + mDeviceProvisioned); 738 } 739 740 741 public long getItemId(int position) { 742 return position; 743 } 744 745 public View getView(int position, View convertView, ViewGroup parent) { 746 Action action = getItem(position); 747 return action.create(mContext, convertView, parent, LayoutInflater.from(mContext)); 748 } 749 } 750 751 // note: the scheme below made more sense when we were planning on having 752 // 8 different things in the global actions dialog. seems overkill with 753 // only 3 items now, but may as well keep this flexible approach so it will 754 // be easy should someone decide at the last minute to include something 755 // else, such as 'enable wifi', or 'enable bluetooth' 756 757 /** 758 * What each item in the global actions dialog must be able to support. 759 */ 760 private interface Action { 761 /** 762 * @return Text that will be announced when dialog is created. null 763 * for none. 764 */ 765 CharSequence getLabelForAccessibility(Context context); 766 767 View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater); 768 769 void onPress(); 770 771 /** 772 * @return whether this action should appear in the dialog when the keygaurd 773 * is showing. 774 */ 775 boolean showDuringKeyguard(); 776 777 /** 778 * @return whether this action should appear in the dialog before the 779 * device is provisioned. 780 */ 781 boolean showBeforeProvisioning(); 782 783 boolean isEnabled(); 784 } 785 786 /** 787 * An action that also supports long press. 788 */ 789 private interface LongPressAction extends Action { 790 boolean onLongPress(); 791 } 792 793 /** 794 * A single press action maintains no state, just responds to a press 795 * and takes an action. 796 */ 797 private static abstract class SinglePressAction implements Action { 798 private final int mIconResId; 799 private final Drawable mIcon; 800 private final int mMessageResId; 801 private final CharSequence mMessage; 802 803 protected SinglePressAction(int iconResId, int messageResId) { 804 mIconResId = iconResId; 805 mMessageResId = messageResId; 806 mMessage = null; 807 mIcon = null; 808 } 809 810 protected SinglePressAction(int iconResId, Drawable icon, CharSequence message) { 811 mIconResId = iconResId; 812 mMessageResId = 0; 813 mMessage = message; 814 mIcon = icon; 815 } 816 817 public boolean isEnabled() { 818 return true; 819 } 820 821 public String getStatus() { 822 return null; 823 } 824 825 abstract public void onPress(); 826 827 public CharSequence getLabelForAccessibility(Context context) { 828 if (mMessage != null) { 829 return mMessage; 830 } else { 831 return context.getString(mMessageResId); 832 } 833 } 834 835 public View create( 836 Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { 837 View v = inflater.inflate(R.layout.global_actions_item, parent, false); 838 839 ImageView icon = (ImageView) v.findViewById(R.id.icon); 840 TextView messageView = (TextView) v.findViewById(R.id.message); 841 842 TextView statusView = (TextView) v.findViewById(R.id.status); 843 final String status = getStatus(); 844 if (!TextUtils.isEmpty(status)) { 845 statusView.setText(status); 846 } else { 847 statusView.setVisibility(View.GONE); 848 } 849 if (mIcon != null) { 850 icon.setImageDrawable(mIcon); 851 icon.setScaleType(ScaleType.CENTER_CROP); 852 } else if (mIconResId != 0) { 853 icon.setImageDrawable(context.getDrawable(mIconResId)); 854 } 855 if (mMessage != null) { 856 messageView.setText(mMessage); 857 } else { 858 messageView.setText(mMessageResId); 859 } 860 861 return v; 862 } 863 } 864 865 /** 866 * A toggle action knows whether it is on or off, and displays an icon 867 * and status message accordingly. 868 */ 869 private static abstract class ToggleAction implements Action { 870 871 enum State { 872 Off(false), 873 TurningOn(true), 874 TurningOff(true), 875 On(false); 876 877 private final boolean inTransition; 878 879 State(boolean intermediate) { 880 inTransition = intermediate; 881 } 882 883 public boolean inTransition() { 884 return inTransition; 885 } 886 } 887 888 protected State mState = State.Off; 889 890 // prefs 891 protected int mEnabledIconResId; 892 protected int mDisabledIconResid; 893 protected int mMessageResId; 894 protected int mEnabledStatusMessageResId; 895 protected int mDisabledStatusMessageResId; 896 897 /** 898 * @param enabledIconResId The icon for when this action is on. 899 * @param disabledIconResid The icon for when this action is off. 900 * @param message The general information message, e.g 'Silent Mode' 901 * @param enabledStatusMessageResId The on status message, e.g 'sound disabled' 902 * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled' 903 */ 904 public ToggleAction(int enabledIconResId, 905 int disabledIconResid, 906 int message, 907 int enabledStatusMessageResId, 908 int disabledStatusMessageResId) { 909 mEnabledIconResId = enabledIconResId; 910 mDisabledIconResid = disabledIconResid; 911 mMessageResId = message; 912 mEnabledStatusMessageResId = enabledStatusMessageResId; 913 mDisabledStatusMessageResId = disabledStatusMessageResId; 914 } 915 916 /** 917 * Override to make changes to resource IDs just before creating the 918 * View. 919 */ 920 void willCreate() { 921 922 } 923 924 @Override 925 public CharSequence getLabelForAccessibility(Context context) { 926 return context.getString(mMessageResId); 927 } 928 929 public View create(Context context, View convertView, ViewGroup parent, 930 LayoutInflater inflater) { 931 willCreate(); 932 933 View v = inflater.inflate(R 934 .layout.global_actions_item, parent, false); 935 936 ImageView icon = (ImageView) v.findViewById(R.id.icon); 937 TextView messageView = (TextView) v.findViewById(R.id.message); 938 TextView statusView = (TextView) v.findViewById(R.id.status); 939 final boolean enabled = isEnabled(); 940 941 if (messageView != null) { 942 messageView.setText(mMessageResId); 943 messageView.setEnabled(enabled); 944 } 945 946 boolean on = ((mState == State.On) || (mState == State.TurningOn)); 947 if (icon != null) { 948 icon.setImageDrawable(context.getDrawable( 949 (on ? mEnabledIconResId : mDisabledIconResid))); 950 icon.setEnabled(enabled); 951 } 952 953 if (statusView != null) { 954 statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId); 955 statusView.setVisibility(View.VISIBLE); 956 statusView.setEnabled(enabled); 957 } 958 v.setEnabled(enabled); 959 960 return v; 961 } 962 963 public final void onPress() { 964 if (mState.inTransition()) { 965 Log.w(TAG, "shouldn't be able to toggle when in transition"); 966 return; 967 } 968 969 final boolean nowOn = !(mState == State.On); 970 onToggle(nowOn); 971 changeStateFromPress(nowOn); 972 } 973 974 public boolean isEnabled() { 975 return !mState.inTransition(); 976 } 977 978 /** 979 * Implementations may override this if their state can be in on of the intermediate 980 * states until some notification is received (e.g airplane mode is 'turning off' until 981 * we know the wireless connections are back online 982 * @param buttonOn Whether the button was turned on or off 983 */ 984 protected void changeStateFromPress(boolean buttonOn) { 985 mState = buttonOn ? State.On : State.Off; 986 } 987 988 abstract void onToggle(boolean on); 989 990 public void updateState(State state) { 991 mState = state; 992 } 993 } 994 995 private class SilentModeToggleAction extends ToggleAction { 996 public SilentModeToggleAction() { 997 super(R.drawable.ic_audio_vol_mute, 998 R.drawable.ic_audio_vol, 999 R.string.global_action_toggle_silent_mode, 1000 R.string.global_action_silent_mode_on_status, 1001 R.string.global_action_silent_mode_off_status); 1002 } 1003 1004 void onToggle(boolean on) { 1005 if (on) { 1006 mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT); 1007 } else { 1008 mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL); 1009 } 1010 } 1011 1012 public boolean showDuringKeyguard() { 1013 return true; 1014 } 1015 1016 public boolean showBeforeProvisioning() { 1017 return false; 1018 } 1019 } 1020 1021 private static class SilentModeTriStateAction implements Action, View.OnClickListener { 1022 1023 private final int[] ITEM_IDS = { R.id.option1, R.id.option2, R.id.option3 }; 1024 1025 private final AudioManager mAudioManager; 1026 private final Handler mHandler; 1027 private final Context mContext; 1028 1029 SilentModeTriStateAction(Context context, AudioManager audioManager, Handler handler) { 1030 mAudioManager = audioManager; 1031 mHandler = handler; 1032 mContext = context; 1033 } 1034 1035 private int ringerModeToIndex(int ringerMode) { 1036 // They just happen to coincide 1037 return ringerMode; 1038 } 1039 1040 private int indexToRingerMode(int index) { 1041 // They just happen to coincide 1042 return index; 1043 } 1044 1045 @Override 1046 public CharSequence getLabelForAccessibility(Context context) { 1047 return null; 1048 } 1049 1050 public View create(Context context, View convertView, ViewGroup parent, 1051 LayoutInflater inflater) { 1052 View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false); 1053 1054 int selectedIndex = ringerModeToIndex(mAudioManager.getRingerMode()); 1055 for (int i = 0; i < 3; i++) { 1056 View itemView = v.findViewById(ITEM_IDS[i]); 1057 itemView.setSelected(selectedIndex == i); 1058 // Set up click handler 1059 itemView.setTag(i); 1060 itemView.setOnClickListener(this); 1061 } 1062 return v; 1063 } 1064 1065 public void onPress() { 1066 } 1067 1068 public boolean showDuringKeyguard() { 1069 return true; 1070 } 1071 1072 public boolean showBeforeProvisioning() { 1073 return false; 1074 } 1075 1076 public boolean isEnabled() { 1077 return true; 1078 } 1079 1080 void willCreate() { 1081 } 1082 1083 public void onClick(View v) { 1084 if (!(v.getTag() instanceof Integer)) return; 1085 1086 int index = (Integer) v.getTag(); 1087 mAudioManager.setRingerMode(indexToRingerMode(index)); 1088 mHandler.sendEmptyMessageDelayed(MESSAGE_DISMISS, DIALOG_DISMISS_DELAY); 1089 } 1090 } 1091 1092 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 1093 public void onReceive(Context context, Intent intent) { 1094 String action = intent.getAction(); 1095 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) 1096 || Intent.ACTION_SCREEN_OFF.equals(action)) { 1097 String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY); 1098 if (!SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) { 1099 mHandler.sendEmptyMessage(MESSAGE_DISMISS); 1100 } 1101 } else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) { 1102 // Airplane mode can be changed after ECM exits if airplane toggle button 1103 // is pressed during ECM mode 1104 if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) && 1105 mIsWaitingForEcmExit) { 1106 mIsWaitingForEcmExit = false; 1107 changeAirplaneModeSystemSetting(true); 1108 } 1109 } 1110 } 1111 }; 1112 1113 PhoneStateListener mPhoneStateListener = new PhoneStateListener() { 1114 @Override 1115 public void onServiceStateChanged(ServiceState serviceState) { 1116 if (!mHasTelephony) return; 1117 final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF; 1118 mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off; 1119 mAirplaneModeOn.updateState(mAirplaneState); 1120 mAdapter.notifyDataSetChanged(); 1121 } 1122 }; 1123 1124 private BroadcastReceiver mRingerModeReceiver = new BroadcastReceiver() { 1125 @Override 1126 public void onReceive(Context context, Intent intent) { 1127 if (intent.getAction().equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { 1128 mHandler.sendEmptyMessage(MESSAGE_REFRESH); 1129 } 1130 } 1131 }; 1132 1133 private ContentObserver mAirplaneModeObserver = new ContentObserver(new Handler()) { 1134 @Override 1135 public void onChange(boolean selfChange) { 1136 onAirplaneModeChanged(); 1137 } 1138 }; 1139 1140 private static final int MESSAGE_DISMISS = 0; 1141 private static final int MESSAGE_REFRESH = 1; 1142 private static final int MESSAGE_SHOW = 2; 1143 private static final int DIALOG_DISMISS_DELAY = 300; // ms 1144 1145 private Handler mHandler = new Handler() { 1146 public void handleMessage(Message msg) { 1147 switch (msg.what) { 1148 case MESSAGE_DISMISS: 1149 if (mDialog != null) { 1150 mDialog.dismiss(); 1151 mDialog = null; 1152 } 1153 break; 1154 case MESSAGE_REFRESH: 1155 refreshSilentMode(); 1156 mAdapter.notifyDataSetChanged(); 1157 break; 1158 case MESSAGE_SHOW: 1159 handleShow(); 1160 break; 1161 } 1162 } 1163 }; 1164 1165 private void onAirplaneModeChanged() { 1166 // Let the service state callbacks handle the state. 1167 if (mHasTelephony) return; 1168 1169 boolean airplaneModeOn = Settings.Global.getInt( 1170 mContext.getContentResolver(), 1171 Settings.Global.AIRPLANE_MODE_ON, 1172 0) == 1; 1173 mAirplaneState = airplaneModeOn ? ToggleAction.State.On : ToggleAction.State.Off; 1174 mAirplaneModeOn.updateState(mAirplaneState); 1175 } 1176 1177 /** 1178 * Change the airplane mode system setting 1179 */ 1180 private void changeAirplaneModeSystemSetting(boolean on) { 1181 Settings.Global.putInt( 1182 mContext.getContentResolver(), 1183 Settings.Global.AIRPLANE_MODE_ON, 1184 on ? 1 : 0); 1185 Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); 1186 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); 1187 intent.putExtra("state", on); 1188 mContext.sendBroadcastAsUser(intent, UserHandle.ALL); 1189 if (!mHasTelephony) { 1190 mAirplaneState = on ? ToggleAction.State.On : ToggleAction.State.Off; 1191 } 1192 } 1193 1194 private static final class ActionsDialog extends Dialog implements DialogInterface { 1195 private final Context mContext; 1196 private final AlertController mAlert; 1197 private final MyAdapter mAdapter; 1198 1199 public ActionsDialog(Context context, AlertParams params) { 1200 super(context, getDialogTheme(context)); 1201 mContext = getContext(); 1202 mAlert = AlertController.create(mContext, this, getWindow()); 1203 mAdapter = (MyAdapter) params.mAdapter; 1204 params.apply(mAlert); 1205 } 1206 1207 private static int getDialogTheme(Context context) { 1208 TypedValue outValue = new TypedValue(); 1209 context.getTheme().resolveAttribute(R.attr.alertDialogTheme, 1210 outValue, true); 1211 return outValue.resourceId; 1212 } 1213 1214 @Override 1215 protected void onStart() { 1216 super.setCanceledOnTouchOutside(true); 1217 super.onStart(); 1218 } 1219 1220 public ListView getListView() { 1221 return mAlert.getListView(); 1222 } 1223 1224 @Override 1225 protected void onCreate(Bundle savedInstanceState) { 1226 super.onCreate(savedInstanceState); 1227 mAlert.installContent(); 1228 } 1229 1230 @Override 1231 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 1232 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { 1233 for (int i = 0; i < mAdapter.getCount(); ++i) { 1234 CharSequence label = 1235 mAdapter.getItem(i).getLabelForAccessibility(getContext()); 1236 if (label != null) { 1237 event.getText().add(label); 1238 } 1239 } 1240 } 1241 return super.dispatchPopulateAccessibilityEvent(event); 1242 } 1243 1244 @Override 1245 public boolean onKeyDown(int keyCode, KeyEvent event) { 1246 if (mAlert.onKeyDown(keyCode, event)) { 1247 return true; 1248 } 1249 return super.onKeyDown(keyCode, event); 1250 } 1251 1252 @Override 1253 public boolean onKeyUp(int keyCode, KeyEvent event) { 1254 if (mAlert.onKeyUp(keyCode, event)) { 1255 return true; 1256 } 1257 return super.onKeyUp(keyCode, event); 1258 } 1259 } 1260} 1261