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