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