GlobalActions.java revision 742a67127366c376fdf188ff99ba30b27d3bf90c
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.internal.policy.impl; 18 19import com.android.internal.app.ShutdownThread; 20import com.android.internal.telephony.TelephonyIntents; 21import com.android.internal.telephony.TelephonyProperties; 22import com.android.internal.R; 23 24import android.app.ActivityManagerNative; 25import android.app.AlertDialog; 26import android.content.BroadcastReceiver; 27import android.content.Context; 28import android.content.DialogInterface; 29import android.content.Intent; 30import android.content.IntentFilter; 31import android.content.pm.UserInfo; 32import android.media.AudioManager; 33import android.os.Handler; 34import android.os.Message; 35import android.os.RemoteException; 36import android.os.SystemProperties; 37import android.provider.Settings; 38import android.telephony.PhoneStateListener; 39import android.telephony.ServiceState; 40import android.telephony.TelephonyManager; 41import android.util.Log; 42import android.view.LayoutInflater; 43import android.view.View; 44import android.view.ViewGroup; 45import android.view.WindowManager; 46import android.widget.BaseAdapter; 47import android.widget.ImageView; 48import android.widget.TextView; 49 50import java.util.ArrayList; 51import java.util.List; 52 53/** 54 * Helper to show the global actions dialog. Each item is an {@link Action} that 55 * may show depending on whether the keyguard is showing, and whether the device 56 * is provisioned. 57 */ 58class GlobalActions implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener { 59 60 private static final String TAG = "GlobalActions"; 61 62 private static final boolean SHOW_SILENT_TOGGLE = true; 63 64 private final Context mContext; 65 private final AudioManager mAudioManager; 66 67 private ArrayList<Action> mItems; 68 private AlertDialog mDialog; 69 70 private SilentModeAction mSilentModeAction; 71 private ToggleAction mAirplaneModeOn; 72 73 private MyAdapter mAdapter; 74 75 private boolean mKeyguardShowing = false; 76 private boolean mDeviceProvisioned = false; 77 private ToggleAction.State mAirplaneState = ToggleAction.State.Off; 78 private boolean mIsWaitingForEcmExit = false; 79 80 /** 81 * @param context everything needs a context :( 82 */ 83 public GlobalActions(Context context) { 84 mContext = context; 85 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 86 87 // receive broadcasts 88 IntentFilter filter = new IntentFilter(); 89 filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 90 filter.addAction(Intent.ACTION_SCREEN_OFF); 91 filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); 92 context.registerReceiver(mBroadcastReceiver, filter); 93 94 // get notified of phone state changes 95 TelephonyManager telephonyManager = 96 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 97 telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE); 98 } 99 100 /** 101 * Show the global actions dialog (creating if necessary) 102 * @param keyguardShowing True if keyguard is showing 103 */ 104 public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) { 105 mKeyguardShowing = keyguardShowing; 106 mDeviceProvisioned = isDeviceProvisioned; 107 if (mDialog != null) { 108 mDialog.dismiss(); 109 } 110 mDialog = createDialog(); 111 prepareDialog(); 112 113 mDialog.show(); 114 mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND); 115 } 116 117 /** 118 * Create the global actions dialog. 119 * @return A new dialog. 120 */ 121 private AlertDialog createDialog() { 122 mSilentModeAction = new SilentModeAction(mAudioManager, mHandler); 123 124 mAirplaneModeOn = new ToggleAction( 125 R.drawable.ic_lock_airplane_mode, 126 R.drawable.ic_lock_airplane_mode_off, 127 R.string.global_actions_toggle_airplane_mode, 128 R.string.global_actions_airplane_mode_on_status, 129 R.string.global_actions_airplane_mode_off_status) { 130 131 void onToggle(boolean on) { 132 if (Boolean.parseBoolean( 133 SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) { 134 mIsWaitingForEcmExit = true; 135 // Launch ECM exit dialog 136 Intent ecmDialogIntent = 137 new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null); 138 ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 139 mContext.startActivity(ecmDialogIntent); 140 } else { 141 changeAirplaneModeSystemSetting(on); 142 } 143 } 144 145 @Override 146 protected void changeStateFromPress(boolean buttonOn) { 147 // In ECM mode airplane state cannot be changed 148 if (!(Boolean.parseBoolean( 149 SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)))) { 150 mState = buttonOn ? State.TurningOn : State.TurningOff; 151 mAirplaneState = mState; 152 } 153 } 154 155 public boolean showDuringKeyguard() { 156 return true; 157 } 158 159 public boolean showBeforeProvisioning() { 160 return false; 161 } 162 }; 163 164 mItems = new ArrayList<Action>(); 165 166 // first: power off 167 mItems.add( 168 new SinglePressAction( 169 com.android.internal.R.drawable.ic_lock_power_off, 170 R.string.global_action_power_off) { 171 172 public void onPress() { 173 // shutdown by making sure radio and power are handled accordingly. 174 ShutdownThread.shutdown(mContext, true); 175 } 176 177 public boolean showDuringKeyguard() { 178 return true; 179 } 180 181 public boolean showBeforeProvisioning() { 182 return true; 183 } 184 }); 185 186 // next: airplane mode 187 mItems.add(mAirplaneModeOn); 188 189 // last: silent mode 190 if (SHOW_SILENT_TOGGLE) { 191 mItems.add(mSilentModeAction); 192 } 193 194 List<UserInfo> users = mContext.getPackageManager().getUsers(); 195 if (users.size() > 1) { 196 for (final UserInfo user : users) { 197 SinglePressAction switchToUser = new SinglePressAction( 198 com.android.internal.R.drawable.ic_menu_cc, 199 user.name != null ? user.name : "Primary") { 200 public void onPress() { 201 try { 202 ActivityManagerNative.getDefault().switchUser(user.id); 203 } catch (RemoteException re) { 204 Log.e(TAG, "Couldn't switch user " + re); 205 } 206 } 207 208 public boolean showDuringKeyguard() { 209 return true; 210 } 211 212 public boolean showBeforeProvisioning() { 213 return false; 214 } 215 }; 216 mItems.add(switchToUser); 217 } 218 } 219 mAdapter = new MyAdapter(); 220 221 final AlertDialog.Builder ab = new AlertDialog.Builder(mContext); 222 223 ab.setAdapter(mAdapter, this) 224 .setInverseBackgroundForced(true); 225 226 final AlertDialog dialog = ab.create(); 227 dialog.getListView().setItemsCanFocus(true); 228 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); 229 230 dialog.setOnDismissListener(this); 231 232 return dialog; 233 } 234 235 private void prepareDialog() { 236 final boolean silentModeOn = 237 mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL; 238 mAirplaneModeOn.updateState(mAirplaneState); 239 mAdapter.notifyDataSetChanged(); 240 if (mKeyguardShowing) { 241 mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 242 } else { 243 mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); 244 } 245 if (SHOW_SILENT_TOGGLE) { 246 IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION); 247 mContext.registerReceiver(mRingerModeReceiver, filter); 248 } 249 } 250 251 252 /** {@inheritDoc} */ 253 public void onDismiss(DialogInterface dialog) { 254 if (SHOW_SILENT_TOGGLE) { 255 mContext.unregisterReceiver(mRingerModeReceiver); 256 } 257 } 258 259 /** {@inheritDoc} */ 260 public void onClick(DialogInterface dialog, int which) { 261 if (!(mAdapter.getItem(which) instanceof SilentModeAction)) { 262 dialog.dismiss(); 263 } 264 mAdapter.getItem(which).onPress(); 265 } 266 267 /** 268 * The adapter used for the list within the global actions dialog, taking 269 * into account whether the keyguard is showing via 270 * {@link GlobalActions#mKeyguardShowing} and whether the device is provisioned 271 * via {@link GlobalActions#mDeviceProvisioned}. 272 */ 273 private class MyAdapter extends BaseAdapter { 274 275 public int getCount() { 276 int count = 0; 277 278 for (int i = 0; i < mItems.size(); i++) { 279 final Action action = mItems.get(i); 280 281 if (mKeyguardShowing && !action.showDuringKeyguard()) { 282 continue; 283 } 284 if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { 285 continue; 286 } 287 count++; 288 } 289 return count; 290 } 291 292 @Override 293 public boolean isEnabled(int position) { 294 return getItem(position).isEnabled(); 295 } 296 297 @Override 298 public boolean areAllItemsEnabled() { 299 return false; 300 } 301 302 public Action getItem(int position) { 303 304 int filteredPos = 0; 305 for (int i = 0; i < mItems.size(); i++) { 306 final Action action = mItems.get(i); 307 if (mKeyguardShowing && !action.showDuringKeyguard()) { 308 continue; 309 } 310 if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { 311 continue; 312 } 313 if (filteredPos == position) { 314 return action; 315 } 316 filteredPos++; 317 } 318 319 throw new IllegalArgumentException("position " + position 320 + " out of range of showable actions" 321 + ", filtered count=" + getCount() 322 + ", keyguardshowing=" + mKeyguardShowing 323 + ", provisioned=" + mDeviceProvisioned); 324 } 325 326 327 public long getItemId(int position) { 328 return position; 329 } 330 331 public View getView(int position, View convertView, ViewGroup parent) { 332 Action action = getItem(position); 333 return action.create(mContext, convertView, parent, LayoutInflater.from(mContext)); 334 } 335 } 336 337 // note: the scheme below made more sense when we were planning on having 338 // 8 different things in the global actions dialog. seems overkill with 339 // only 3 items now, but may as well keep this flexible approach so it will 340 // be easy should someone decide at the last minute to include something 341 // else, such as 'enable wifi', or 'enable bluetooth' 342 343 /** 344 * What each item in the global actions dialog must be able to support. 345 */ 346 private interface Action { 347 View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater); 348 349 void onPress(); 350 351 /** 352 * @return whether this action should appear in the dialog when the keygaurd 353 * is showing. 354 */ 355 boolean showDuringKeyguard(); 356 357 /** 358 * @return whether this action should appear in the dialog before the 359 * device is provisioned. 360 */ 361 boolean showBeforeProvisioning(); 362 363 boolean isEnabled(); 364 } 365 366 /** 367 * A single press action maintains no state, just responds to a press 368 * and takes an action. 369 */ 370 private static abstract class SinglePressAction implements Action { 371 private final int mIconResId; 372 private final int mMessageResId; 373 private final CharSequence mMessage; 374 375 protected SinglePressAction(int iconResId, int messageResId) { 376 mIconResId = iconResId; 377 mMessageResId = messageResId; 378 mMessage = null; 379 } 380 381 protected SinglePressAction(int iconResId, CharSequence message) { 382 mIconResId = iconResId; 383 mMessageResId = 0; 384 mMessage = message; 385 } 386 public boolean isEnabled() { 387 return true; 388 } 389 390 abstract public void onPress(); 391 392 public View create( 393 Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { 394 View v = inflater.inflate(R.layout.global_actions_item, parent, false); 395 396 ImageView icon = (ImageView) v.findViewById(R.id.icon); 397 TextView messageView = (TextView) v.findViewById(R.id.message); 398 399 v.findViewById(R.id.status).setVisibility(View.GONE); 400 401 icon.setImageDrawable(context.getResources().getDrawable(mIconResId)); 402 if (mMessage != null) { 403 messageView.setText(mMessage); 404 } else { 405 messageView.setText(mMessageResId); 406 } 407 408 return v; 409 } 410 } 411 412 /** 413 * A toggle action knows whether it is on or off, and displays an icon 414 * and status message accordingly. 415 */ 416 private static abstract class ToggleAction implements Action { 417 418 enum State { 419 Off(false), 420 TurningOn(true), 421 TurningOff(true), 422 On(false); 423 424 private final boolean inTransition; 425 426 State(boolean intermediate) { 427 inTransition = intermediate; 428 } 429 430 public boolean inTransition() { 431 return inTransition; 432 } 433 } 434 435 protected State mState = State.Off; 436 437 // prefs 438 protected int mEnabledIconResId; 439 protected int mDisabledIconResid; 440 protected int mMessageResId; 441 protected int mEnabledStatusMessageResId; 442 protected int mDisabledStatusMessageResId; 443 444 /** 445 * @param enabledIconResId The icon for when this action is on. 446 * @param disabledIconResid The icon for when this action is off. 447 * @param essage The general information message, e.g 'Silent Mode' 448 * @param enabledStatusMessageResId The on status message, e.g 'sound disabled' 449 * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled' 450 */ 451 public ToggleAction(int enabledIconResId, 452 int disabledIconResid, 453 int essage, 454 int enabledStatusMessageResId, 455 int disabledStatusMessageResId) { 456 mEnabledIconResId = enabledIconResId; 457 mDisabledIconResid = disabledIconResid; 458 mMessageResId = essage; 459 mEnabledStatusMessageResId = enabledStatusMessageResId; 460 mDisabledStatusMessageResId = disabledStatusMessageResId; 461 } 462 463 /** 464 * Override to make changes to resource IDs just before creating the 465 * View. 466 */ 467 void willCreate() { 468 469 } 470 471 public View create(Context context, View convertView, ViewGroup parent, 472 LayoutInflater inflater) { 473 willCreate(); 474 475 View v = inflater.inflate(R 476 .layout.global_actions_item, parent, false); 477 478 ImageView icon = (ImageView) v.findViewById(R.id.icon); 479 TextView messageView = (TextView) v.findViewById(R.id.message); 480 TextView statusView = (TextView) v.findViewById(R.id.status); 481 final boolean enabled = isEnabled(); 482 483 if (messageView != null) { 484 messageView.setText(mMessageResId); 485 messageView.setEnabled(enabled); 486 } 487 488 boolean on = ((mState == State.On) || (mState == State.TurningOn)); 489 if (icon != null) { 490 icon.setImageDrawable(context.getResources().getDrawable( 491 (on ? mEnabledIconResId : mDisabledIconResid))); 492 icon.setEnabled(enabled); 493 } 494 495 if (statusView != null) { 496 statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId); 497 statusView.setVisibility(View.VISIBLE); 498 statusView.setEnabled(enabled); 499 } 500 v.setEnabled(enabled); 501 502 return v; 503 } 504 505 public final void onPress() { 506 if (mState.inTransition()) { 507 Log.w(TAG, "shouldn't be able to toggle when in transition"); 508 return; 509 } 510 511 final boolean nowOn = !(mState == State.On); 512 onToggle(nowOn); 513 changeStateFromPress(nowOn); 514 } 515 516 public boolean isEnabled() { 517 return !mState.inTransition(); 518 } 519 520 /** 521 * Implementations may override this if their state can be in on of the intermediate 522 * states until some notification is received (e.g airplane mode is 'turning off' until 523 * we know the wireless connections are back online 524 * @param buttonOn Whether the button was turned on or off 525 */ 526 protected void changeStateFromPress(boolean buttonOn) { 527 mState = buttonOn ? State.On : State.Off; 528 } 529 530 abstract void onToggle(boolean on); 531 532 public void updateState(State state) { 533 mState = state; 534 } 535 } 536 537 private static class SilentModeAction implements Action, View.OnClickListener { 538 539 private final int[] ITEM_IDS = { R.id.option1, R.id.option2, R.id.option3 }; 540 541 private final AudioManager mAudioManager; 542 private final Handler mHandler; 543 544 SilentModeAction(AudioManager audioManager, Handler handler) { 545 mAudioManager = audioManager; 546 mHandler = handler; 547 } 548 549 private int ringerModeToIndex(int ringerMode) { 550 // They just happen to coincide 551 return ringerMode; 552 } 553 554 private int indexToRingerMode(int index) { 555 // They just happen to coincide 556 return index; 557 } 558 559 public View create(Context context, View convertView, ViewGroup parent, 560 LayoutInflater inflater) { 561 View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false); 562 563 int selectedIndex = ringerModeToIndex(mAudioManager.getRingerMode()); 564 for (int i = 0; i < 3; i++) { 565 View itemView = v.findViewById(ITEM_IDS[i]); 566 itemView.setSelected(selectedIndex == i); 567 // Set up click handler 568 itemView.setTag(i); 569 itemView.setOnClickListener(this); 570 } 571 return v; 572 } 573 574 public void onPress() { 575 } 576 577 public boolean showDuringKeyguard() { 578 return true; 579 } 580 581 public boolean showBeforeProvisioning() { 582 return false; 583 } 584 585 public boolean isEnabled() { 586 return true; 587 } 588 589 void willCreate() { 590 } 591 592 public void onClick(View v) { 593 if (!(v.getTag() instanceof Integer)) return; 594 595 int index = (Integer) v.getTag(); 596 mAudioManager.setRingerMode(indexToRingerMode(index)); 597 mHandler.sendEmptyMessageDelayed(MESSAGE_DISMISS, DIALOG_DISMISS_DELAY); 598 } 599 } 600 601 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 602 public void onReceive(Context context, Intent intent) { 603 String action = intent.getAction(); 604 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) 605 || Intent.ACTION_SCREEN_OFF.equals(action)) { 606 String reason = intent.getStringExtra(PhoneWindowManager.SYSTEM_DIALOG_REASON_KEY); 607 if (!PhoneWindowManager.SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) { 608 mHandler.sendEmptyMessage(MESSAGE_DISMISS); 609 } 610 } else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) { 611 // Airplane mode can be changed after ECM exits if airplane toggle button 612 // is pressed during ECM mode 613 if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) && 614 mIsWaitingForEcmExit) { 615 mIsWaitingForEcmExit = false; 616 changeAirplaneModeSystemSetting(true); 617 } 618 } 619 } 620 }; 621 622 PhoneStateListener mPhoneStateListener = new PhoneStateListener() { 623 @Override 624 public void onServiceStateChanged(ServiceState serviceState) { 625 final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF; 626 mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off; 627 mAirplaneModeOn.updateState(mAirplaneState); 628 mAdapter.notifyDataSetChanged(); 629 } 630 }; 631 632 private BroadcastReceiver mRingerModeReceiver = new BroadcastReceiver() { 633 @Override 634 public void onReceive(Context context, Intent intent) { 635 if (intent.getAction().equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { 636 mHandler.sendEmptyMessage(MESSAGE_REFRESH); 637 } 638 } 639 }; 640 641 private static final int MESSAGE_DISMISS = 0; 642 private static final int MESSAGE_REFRESH = 1; 643 private static final int DIALOG_DISMISS_DELAY = 300; // ms 644 645 private Handler mHandler = new Handler() { 646 public void handleMessage(Message msg) { 647 if (msg.what == MESSAGE_DISMISS) { 648 if (mDialog != null) { 649 mDialog.dismiss(); 650 } 651 } else if (msg.what == MESSAGE_REFRESH) { 652 mAdapter.notifyDataSetChanged(); 653 } 654 } 655 }; 656 657 /** 658 * Change the airplane mode system setting 659 */ 660 private void changeAirplaneModeSystemSetting(boolean on) { 661 Settings.System.putInt( 662 mContext.getContentResolver(), 663 Settings.System.AIRPLANE_MODE_ON, 664 on ? 1 : 0); 665 Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); 666 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); 667 intent.putExtra("state", on); 668 mContext.sendBroadcast(intent); 669 } 670} 671