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