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