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