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