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 17 package com.android.internal.policy.impl; 18 19 import com.android.internal.app.AlertController; 20 import com.android.internal.app.AlertController.AlertParams; 21 import com.android.internal.telephony.TelephonyIntents; 22 import com.android.internal.telephony.TelephonyProperties; 23 import com.android.internal.R; 24 25 import android.app.ActivityManagerNative; 26 import android.app.AlertDialog; 27 import android.app.Dialog; 28 import android.content.BroadcastReceiver; 29 import android.content.Context; 30 import android.content.DialogInterface; 31 import android.content.Intent; 32 import android.content.IntentFilter; 33 import android.content.pm.UserInfo; 34 import android.database.ContentObserver; 35 import android.graphics.drawable.Drawable; 36 import android.media.AudioManager; 37 import android.net.ConnectivityManager; 38 import android.os.Bundle; 39 import android.os.Handler; 40 import android.os.Message; 41 import android.os.RemoteException; 42 import android.os.ServiceManager; 43 import android.os.SystemClock; 44 import android.os.SystemProperties; 45 import android.os.UserHandle; 46 import android.os.UserManager; 47 import android.os.Vibrator; 48 import android.provider.Settings; 49 import android.service.dreams.DreamService; 50 import android.service.dreams.IDreamManager; 51 import android.telephony.PhoneStateListener; 52 import android.telephony.ServiceState; 53 import android.telephony.TelephonyManager; 54 import android.util.Log; 55 import android.util.TypedValue; 56 import android.view.InputDevice; 57 import android.view.KeyEvent; 58 import android.view.LayoutInflater; 59 import android.view.MotionEvent; 60 import android.view.View; 61 import android.view.ViewConfiguration; 62 import android.view.ViewGroup; 63 import android.view.WindowManager; 64 import android.view.WindowManagerPolicy.WindowManagerFuncs; 65 import android.widget.AdapterView; 66 import android.widget.BaseAdapter; 67 import android.widget.ImageView; 68 import android.widget.ImageView.ScaleType; 69 import android.widget.ListView; 70 import android.widget.TextView; 71 72 import java.util.ArrayList; 73 import 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 */ 80 class 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 List<UserInfo> users = ((UserManager) mContext.getSystemService(Context.USER_SERVICE)) 367 .getUsers(); 368 if (users.size() > 1) { 369 UserInfo currentUser = getCurrentUser(); 370 for (final UserInfo user : users) { 371 boolean isCurrentUser = currentUser == null 372 ? user.id == 0 : (currentUser.id == user.id); 373 Drawable icon = user.iconPath != null ? Drawable.createFromPath(user.iconPath) 374 : null; 375 SinglePressAction switchToUser = new SinglePressAction( 376 com.android.internal.R.drawable.ic_menu_cc, icon, 377 (user.name != null ? user.name : "Primary") 378 + (isCurrentUser ? " \u2714" : "")) { 379 public void onPress() { 380 try { 381 ActivityManagerNative.getDefault().switchUser(user.id); 382 } catch (RemoteException re) { 383 Log.e(TAG, "Couldn't switch user " + re); 384 } 385 } 386 387 public boolean showDuringKeyguard() { 388 return true; 389 } 390 391 public boolean showBeforeProvisioning() { 392 return false; 393 } 394 }; 395 items.add(switchToUser); 396 } 397 } 398 } 399 400 private void prepareDialog() { 401 refreshSilentMode(); 402 mAirplaneModeOn.updateState(mAirplaneState); 403 mAdapter.notifyDataSetChanged(); 404 mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 405 if (mShowSilentToggle) { 406 IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION); 407 mContext.registerReceiver(mRingerModeReceiver, filter); 408 } 409 } 410 411 private void refreshSilentMode() { 412 if (!mHasVibrator) { 413 final boolean silentModeOn = 414 mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL; 415 ((ToggleAction)mSilentModeAction).updateState( 416 silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off); 417 } 418 } 419 420 /** {@inheritDoc} */ 421 public void onDismiss(DialogInterface dialog) { 422 if (mShowSilentToggle) { 423 try { 424 mContext.unregisterReceiver(mRingerModeReceiver); 425 } catch (IllegalArgumentException ie) { 426 // ignore this 427 Log.w(TAG, ie); 428 } 429 } 430 } 431 432 /** {@inheritDoc} */ 433 public void onClick(DialogInterface dialog, int which) { 434 if (!(mAdapter.getItem(which) instanceof SilentModeTriStateAction)) { 435 dialog.dismiss(); 436 } 437 mAdapter.getItem(which).onPress(); 438 } 439 440 /** 441 * The adapter used for the list within the global actions dialog, taking 442 * into account whether the keyguard is showing via 443 * {@link GlobalActions#mKeyguardShowing} and whether the device is provisioned 444 * via {@link GlobalActions#mDeviceProvisioned}. 445 */ 446 private class MyAdapter extends BaseAdapter { 447 448 public int getCount() { 449 int count = 0; 450 451 for (int i = 0; i < mItems.size(); i++) { 452 final Action action = mItems.get(i); 453 454 if (mKeyguardShowing && !action.showDuringKeyguard()) { 455 continue; 456 } 457 if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { 458 continue; 459 } 460 count++; 461 } 462 return count; 463 } 464 465 @Override 466 public boolean isEnabled(int position) { 467 return getItem(position).isEnabled(); 468 } 469 470 @Override 471 public boolean areAllItemsEnabled() { 472 return false; 473 } 474 475 public Action getItem(int position) { 476 477 int filteredPos = 0; 478 for (int i = 0; i < mItems.size(); i++) { 479 final Action action = mItems.get(i); 480 if (mKeyguardShowing && !action.showDuringKeyguard()) { 481 continue; 482 } 483 if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { 484 continue; 485 } 486 if (filteredPos == position) { 487 return action; 488 } 489 filteredPos++; 490 } 491 492 throw new IllegalArgumentException("position " + position 493 + " out of range of showable actions" 494 + ", filtered count=" + getCount() 495 + ", keyguardshowing=" + mKeyguardShowing 496 + ", provisioned=" + mDeviceProvisioned); 497 } 498 499 500 public long getItemId(int position) { 501 return position; 502 } 503 504 public View getView(int position, View convertView, ViewGroup parent) { 505 Action action = getItem(position); 506 return action.create(mContext, convertView, parent, LayoutInflater.from(mContext)); 507 } 508 } 509 510 // note: the scheme below made more sense when we were planning on having 511 // 8 different things in the global actions dialog. seems overkill with 512 // only 3 items now, but may as well keep this flexible approach so it will 513 // be easy should someone decide at the last minute to include something 514 // else, such as 'enable wifi', or 'enable bluetooth' 515 516 /** 517 * What each item in the global actions dialog must be able to support. 518 */ 519 private interface Action { 520 View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater); 521 522 void onPress(); 523 524 public boolean onLongPress(); 525 526 /** 527 * @return whether this action should appear in the dialog when the keygaurd 528 * is showing. 529 */ 530 boolean showDuringKeyguard(); 531 532 /** 533 * @return whether this action should appear in the dialog before the 534 * device is provisioned. 535 */ 536 boolean showBeforeProvisioning(); 537 538 boolean isEnabled(); 539 } 540 541 /** 542 * A single press action maintains no state, just responds to a press 543 * and takes an action. 544 */ 545 private static abstract class SinglePressAction implements Action { 546 private final int mIconResId; 547 private final Drawable mIcon; 548 private final int mMessageResId; 549 private final CharSequence mMessage; 550 551 protected SinglePressAction(int iconResId, int messageResId) { 552 mIconResId = iconResId; 553 mMessageResId = messageResId; 554 mMessage = null; 555 mIcon = null; 556 } 557 558 protected SinglePressAction(int iconResId, Drawable icon, CharSequence message) { 559 mIconResId = iconResId; 560 mMessageResId = 0; 561 mMessage = message; 562 mIcon = icon; 563 } 564 565 protected SinglePressAction(int iconResId, CharSequence message) { 566 mIconResId = iconResId; 567 mMessageResId = 0; 568 mMessage = message; 569 mIcon = null; 570 } 571 572 public boolean isEnabled() { 573 return true; 574 } 575 576 abstract public void onPress(); 577 578 public boolean onLongPress() { 579 return false; 580 } 581 582 public View create( 583 Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { 584 View v = inflater.inflate(R.layout.global_actions_item, parent, false); 585 586 ImageView icon = (ImageView) v.findViewById(R.id.icon); 587 TextView messageView = (TextView) v.findViewById(R.id.message); 588 589 v.findViewById(R.id.status).setVisibility(View.GONE); 590 if (mIcon != null) { 591 icon.setImageDrawable(mIcon); 592 icon.setScaleType(ScaleType.CENTER_CROP); 593 } else if (mIconResId != 0) { 594 icon.setImageDrawable(context.getResources().getDrawable(mIconResId)); 595 } 596 if (mMessage != null) { 597 messageView.setText(mMessage); 598 } else { 599 messageView.setText(mMessageResId); 600 } 601 602 return v; 603 } 604 } 605 606 /** 607 * A toggle action knows whether it is on or off, and displays an icon 608 * and status message accordingly. 609 */ 610 private static abstract class ToggleAction implements Action { 611 612 enum State { 613 Off(false), 614 TurningOn(true), 615 TurningOff(true), 616 On(false); 617 618 private final boolean inTransition; 619 620 State(boolean intermediate) { 621 inTransition = intermediate; 622 } 623 624 public boolean inTransition() { 625 return inTransition; 626 } 627 } 628 629 protected State mState = State.Off; 630 631 // prefs 632 protected int mEnabledIconResId; 633 protected int mDisabledIconResid; 634 protected int mMessageResId; 635 protected int mEnabledStatusMessageResId; 636 protected int mDisabledStatusMessageResId; 637 638 /** 639 * @param enabledIconResId The icon for when this action is on. 640 * @param disabledIconResid The icon for when this action is off. 641 * @param essage The general information message, e.g 'Silent Mode' 642 * @param enabledStatusMessageResId The on status message, e.g 'sound disabled' 643 * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled' 644 */ 645 public ToggleAction(int enabledIconResId, 646 int disabledIconResid, 647 int message, 648 int enabledStatusMessageResId, 649 int disabledStatusMessageResId) { 650 mEnabledIconResId = enabledIconResId; 651 mDisabledIconResid = disabledIconResid; 652 mMessageResId = message; 653 mEnabledStatusMessageResId = enabledStatusMessageResId; 654 mDisabledStatusMessageResId = disabledStatusMessageResId; 655 } 656 657 /** 658 * Override to make changes to resource IDs just before creating the 659 * View. 660 */ 661 void willCreate() { 662 663 } 664 665 public View create(Context context, View convertView, ViewGroup parent, 666 LayoutInflater inflater) { 667 willCreate(); 668 669 View v = inflater.inflate(R 670 .layout.global_actions_item, parent, false); 671 672 ImageView icon = (ImageView) v.findViewById(R.id.icon); 673 TextView messageView = (TextView) v.findViewById(R.id.message); 674 TextView statusView = (TextView) v.findViewById(R.id.status); 675 final boolean enabled = isEnabled(); 676 677 if (messageView != null) { 678 messageView.setText(mMessageResId); 679 messageView.setEnabled(enabled); 680 } 681 682 boolean on = ((mState == State.On) || (mState == State.TurningOn)); 683 if (icon != null) { 684 icon.setImageDrawable(context.getResources().getDrawable( 685 (on ? mEnabledIconResId : mDisabledIconResid))); 686 icon.setEnabled(enabled); 687 } 688 689 if (statusView != null) { 690 statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId); 691 statusView.setVisibility(View.VISIBLE); 692 statusView.setEnabled(enabled); 693 } 694 v.setEnabled(enabled); 695 696 return v; 697 } 698 699 public final void onPress() { 700 if (mState.inTransition()) { 701 Log.w(TAG, "shouldn't be able to toggle when in transition"); 702 return; 703 } 704 705 final boolean nowOn = !(mState == State.On); 706 onToggle(nowOn); 707 changeStateFromPress(nowOn); 708 } 709 710 public boolean onLongPress() { 711 return false; 712 } 713 714 public boolean isEnabled() { 715 return !mState.inTransition(); 716 } 717 718 /** 719 * Implementations may override this if their state can be in on of the intermediate 720 * states until some notification is received (e.g airplane mode is 'turning off' until 721 * we know the wireless connections are back online 722 * @param buttonOn Whether the button was turned on or off 723 */ 724 protected void changeStateFromPress(boolean buttonOn) { 725 mState = buttonOn ? State.On : State.Off; 726 } 727 728 abstract void onToggle(boolean on); 729 730 public void updateState(State state) { 731 mState = state; 732 } 733 } 734 735 private class SilentModeToggleAction extends ToggleAction { 736 public SilentModeToggleAction() { 737 super(R.drawable.ic_audio_vol_mute, 738 R.drawable.ic_audio_vol, 739 R.string.global_action_toggle_silent_mode, 740 R.string.global_action_silent_mode_on_status, 741 R.string.global_action_silent_mode_off_status); 742 } 743 744 void onToggle(boolean on) { 745 if (on) { 746 mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT); 747 } else { 748 mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL); 749 } 750 } 751 752 public boolean showDuringKeyguard() { 753 return true; 754 } 755 756 public boolean showBeforeProvisioning() { 757 return false; 758 } 759 } 760 761 private static class SilentModeTriStateAction implements Action, View.OnClickListener { 762 763 private final int[] ITEM_IDS = { R.id.option1, R.id.option2, R.id.option3 }; 764 765 private final AudioManager mAudioManager; 766 private final Handler mHandler; 767 private final Context mContext; 768 769 SilentModeTriStateAction(Context context, AudioManager audioManager, Handler handler) { 770 mAudioManager = audioManager; 771 mHandler = handler; 772 mContext = context; 773 } 774 775 private int ringerModeToIndex(int ringerMode) { 776 // They just happen to coincide 777 return ringerMode; 778 } 779 780 private int indexToRingerMode(int index) { 781 // They just happen to coincide 782 return index; 783 } 784 785 public View create(Context context, View convertView, ViewGroup parent, 786 LayoutInflater inflater) { 787 View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false); 788 789 int selectedIndex = ringerModeToIndex(mAudioManager.getRingerMode()); 790 for (int i = 0; i < 3; i++) { 791 View itemView = v.findViewById(ITEM_IDS[i]); 792 itemView.setSelected(selectedIndex == i); 793 // Set up click handler 794 itemView.setTag(i); 795 itemView.setOnClickListener(this); 796 } 797 return v; 798 } 799 800 public void onPress() { 801 } 802 803 public boolean onLongPress() { 804 return false; 805 } 806 807 public boolean showDuringKeyguard() { 808 return true; 809 } 810 811 public boolean showBeforeProvisioning() { 812 return false; 813 } 814 815 public boolean isEnabled() { 816 return true; 817 } 818 819 void willCreate() { 820 } 821 822 public void onClick(View v) { 823 if (!(v.getTag() instanceof Integer)) return; 824 825 int index = (Integer) v.getTag(); 826 mAudioManager.setRingerMode(indexToRingerMode(index)); 827 mHandler.sendEmptyMessageDelayed(MESSAGE_DISMISS, DIALOG_DISMISS_DELAY); 828 } 829 } 830 831 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 832 public void onReceive(Context context, Intent intent) { 833 String action = intent.getAction(); 834 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) 835 || Intent.ACTION_SCREEN_OFF.equals(action)) { 836 String reason = intent.getStringExtra(PhoneWindowManager.SYSTEM_DIALOG_REASON_KEY); 837 if (!PhoneWindowManager.SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) { 838 mHandler.sendEmptyMessage(MESSAGE_DISMISS); 839 } 840 } else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) { 841 // Airplane mode can be changed after ECM exits if airplane toggle button 842 // is pressed during ECM mode 843 if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) && 844 mIsWaitingForEcmExit) { 845 mIsWaitingForEcmExit = false; 846 changeAirplaneModeSystemSetting(true); 847 } 848 } 849 } 850 }; 851 852 PhoneStateListener mPhoneStateListener = new PhoneStateListener() { 853 @Override 854 public void onServiceStateChanged(ServiceState serviceState) { 855 if (!mHasTelephony) return; 856 final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF; 857 mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off; 858 mAirplaneModeOn.updateState(mAirplaneState); 859 mAdapter.notifyDataSetChanged(); 860 } 861 }; 862 863 private BroadcastReceiver mRingerModeReceiver = new BroadcastReceiver() { 864 @Override 865 public void onReceive(Context context, Intent intent) { 866 if (intent.getAction().equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { 867 mHandler.sendEmptyMessage(MESSAGE_REFRESH); 868 } 869 } 870 }; 871 872 private ContentObserver mAirplaneModeObserver = new ContentObserver(new Handler()) { 873 @Override 874 public void onChange(boolean selfChange) { 875 onAirplaneModeChanged(); 876 } 877 }; 878 879 private static final int MESSAGE_DISMISS = 0; 880 private static final int MESSAGE_REFRESH = 1; 881 private static final int MESSAGE_SHOW = 2; 882 private static final int DIALOG_DISMISS_DELAY = 300; // ms 883 884 private Handler mHandler = new Handler() { 885 public void handleMessage(Message msg) { 886 switch (msg.what) { 887 case MESSAGE_DISMISS: 888 if (mDialog != null) { 889 mDialog.dismiss(); 890 } 891 break; 892 case MESSAGE_REFRESH: 893 refreshSilentMode(); 894 mAdapter.notifyDataSetChanged(); 895 break; 896 case MESSAGE_SHOW: 897 handleShow(); 898 break; 899 } 900 } 901 }; 902 903 private void onAirplaneModeChanged() { 904 // Let the service state callbacks handle the state. 905 if (mHasTelephony) return; 906 907 boolean airplaneModeOn = Settings.Global.getInt( 908 mContext.getContentResolver(), 909 Settings.Global.AIRPLANE_MODE_ON, 910 0) == 1; 911 mAirplaneState = airplaneModeOn ? ToggleAction.State.On : ToggleAction.State.Off; 912 mAirplaneModeOn.updateState(mAirplaneState); 913 } 914 915 /** 916 * Change the airplane mode system setting 917 */ 918 private void changeAirplaneModeSystemSetting(boolean on) { 919 Settings.Global.putInt( 920 mContext.getContentResolver(), 921 Settings.Global.AIRPLANE_MODE_ON, 922 on ? 1 : 0); 923 Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); 924 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); 925 intent.putExtra("state", on); 926 mContext.sendBroadcastAsUser(intent, UserHandle.ALL); 927 if (!mHasTelephony) { 928 mAirplaneState = on ? ToggleAction.State.On : ToggleAction.State.Off; 929 } 930 } 931 932 private static final class GlobalActionsDialog extends Dialog implements DialogInterface { 933 private final Context mContext; 934 private final int mWindowTouchSlop; 935 private final AlertController mAlert; 936 937 private EnableAccessibilityController mEnableAccessibilityController; 938 939 private boolean mIntercepted; 940 private boolean mCancelOnUp; 941 942 public GlobalActionsDialog(Context context, AlertParams params) { 943 super(context, getDialogTheme(context)); 944 mContext = context; 945 mAlert = new AlertController(mContext, this, getWindow()); 946 mWindowTouchSlop = ViewConfiguration.get(context).getScaledWindowTouchSlop(); 947 params.apply(mAlert); 948 } 949 950 private static int getDialogTheme(Context context) { 951 TypedValue outValue = new TypedValue(); 952 context.getTheme().resolveAttribute(com.android.internal.R.attr.alertDialogTheme, 953 outValue, true); 954 return outValue.resourceId; 955 } 956 957 @Override 958 protected void onStart() { 959 // If global accessibility gesture can be performed, we will take care 960 // of dismissing the dialog on touch outside. This is because the dialog 961 // is dismissed on the first down while the global gesture is a long press 962 // with two fingers anywhere on the screen. 963 if (EnableAccessibilityController.canEnableAccessibilityViaGesture(mContext)) { 964 mEnableAccessibilityController = new EnableAccessibilityController(mContext); 965 super.setCanceledOnTouchOutside(false); 966 } else { 967 mEnableAccessibilityController = null; 968 super.setCanceledOnTouchOutside(true); 969 } 970 super.onStart(); 971 } 972 973 @Override 974 protected void onStop() { 975 if (mEnableAccessibilityController != null) { 976 mEnableAccessibilityController.onDestroy(); 977 } 978 super.onStop(); 979 } 980 981 @Override 982 public boolean dispatchTouchEvent(MotionEvent event) { 983 if (mEnableAccessibilityController != null) { 984 final int action = event.getActionMasked(); 985 if (action == MotionEvent.ACTION_DOWN) { 986 View decor = getWindow().getDecorView(); 987 final int eventX = (int) event.getX(); 988 final int eventY = (int) event.getY(); 989 if (eventX < -mWindowTouchSlop 990 || eventY < -mWindowTouchSlop 991 || eventX >= decor.getWidth() + mWindowTouchSlop 992 || eventY >= decor.getHeight() + mWindowTouchSlop) { 993 mCancelOnUp = true; 994 } 995 } 996 try { 997 if (!mIntercepted) { 998 mIntercepted = mEnableAccessibilityController.onInterceptTouchEvent(event); 999 if (mIntercepted) { 1000 final long now = SystemClock.uptimeMillis(); 1001 event = MotionEvent.obtain(now, now, 1002 MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); 1003 event.setSource(InputDevice.SOURCE_TOUCHSCREEN); 1004 mCancelOnUp = true; 1005 } 1006 } else { 1007 return mEnableAccessibilityController.onTouchEvent(event); 1008 } 1009 } finally { 1010 if (action == MotionEvent.ACTION_UP) { 1011 if (mCancelOnUp) { 1012 cancel(); 1013 } 1014 mCancelOnUp = false; 1015 mIntercepted = false; 1016 } 1017 } 1018 } 1019 return super.dispatchTouchEvent(event); 1020 } 1021 1022 public ListView getListView() { 1023 return mAlert.getListView(); 1024 } 1025 1026 @Override 1027 protected void onCreate(Bundle savedInstanceState) { 1028 super.onCreate(savedInstanceState); 1029 mAlert.installContent(); 1030 } 1031 1032 @Override 1033 public boolean onKeyDown(int keyCode, KeyEvent event) { 1034 if (mAlert.onKeyDown(keyCode, event)) { 1035 return true; 1036 } 1037 return super.onKeyDown(keyCode, event); 1038 } 1039 1040 @Override 1041 public boolean onKeyUp(int keyCode, KeyEvent event) { 1042 if (mAlert.onKeyUp(keyCode, event)) { 1043 return true; 1044 } 1045 return super.onKeyUp(keyCode, event); 1046 } 1047 } 1048 } 1049