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