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