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