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