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