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