GlobalActions.java revision 67403e096e6f59b7cd18a90d0d3f5c3d17c31407
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 android.app.Activity; 20import android.app.AlertDialog; 21import android.app.StatusBarManager; 22import android.content.BroadcastReceiver; 23import android.content.Context; 24import android.content.DialogInterface; 25import android.content.Intent; 26import android.content.IntentFilter; 27import android.media.AudioManager; 28import android.os.Handler; 29import android.os.Message; 30import android.os.SystemProperties; 31import android.provider.Settings; 32import android.telephony.PhoneStateListener; 33import android.telephony.ServiceState; 34import android.telephony.TelephonyManager; 35import android.util.Log; 36import android.view.LayoutInflater; 37import android.view.View; 38import android.view.ViewGroup; 39import android.view.WindowManager; 40import android.widget.BaseAdapter; 41import android.widget.ImageView; 42import android.widget.TextView; 43import com.android.internal.R; 44import com.android.internal.app.ShutdownThread; 45import com.android.internal.telephony.TelephonyIntents; 46import com.android.internal.telephony.TelephonyProperties; 47import com.google.android.collect.Lists; 48 49import java.util.ArrayList; 50 51/** 52 * Helper to show the global actions dialog. Each item is an {@link Action} that 53 * may show depending on whether the keyguard is showing, and whether the device 54 * is provisioned. 55 */ 56class GlobalActions implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener { 57 58 private static final String TAG = "GlobalActions"; 59 60 private StatusBarManager mStatusBar; 61 62 private final Context mContext; 63 private final AudioManager mAudioManager; 64 65 private ArrayList<Action> mItems; 66 private AlertDialog mDialog; 67 68 private ToggleAction mSilentModeToggle; 69 private ToggleAction mAirplaneModeOn; 70 71 private MyAdapter mAdapter; 72 73 private boolean mKeyguardShowing = false; 74 private boolean mDeviceProvisioned = false; 75 private ToggleAction.State mAirplaneState = ToggleAction.State.Off; 76 private boolean mIsWaitingForEcmExit = false; 77 78 /** 79 * @param context everything needs a context :( 80 */ 81 public GlobalActions(Context context) { 82 mContext = context; 83 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 84 85 // receive broadcasts 86 IntentFilter filter = new IntentFilter(); 87 filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 88 filter.addAction(Intent.ACTION_SCREEN_OFF); 89 filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); 90 context.registerReceiver(mBroadcastReceiver, filter); 91 92 // get notified of phone state changes 93 TelephonyManager telephonyManager = 94 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 95 telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE); 96 } 97 98 /** 99 * Show the global actions dialog (creating if necessary) 100 * @param keyguardShowing True if keyguard is showing 101 */ 102 public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) { 103 mKeyguardShowing = keyguardShowing; 104 mDeviceProvisioned = isDeviceProvisioned; 105 if (mDialog == null) { 106 mStatusBar = (StatusBarManager)mContext.getSystemService(Context.STATUS_BAR_SERVICE); 107 mDialog = createDialog(); 108 } 109 prepareDialog(); 110 111 mStatusBar.disable(StatusBarManager.DISABLE_EXPAND); 112 mDialog.show(); 113 } 114 115 /** 116 * Create the global actions dialog. 117 * @return A new dialog. 118 */ 119 private AlertDialog createDialog() { 120 mSilentModeToggle = new ToggleAction( 121 R.drawable.ic_lock_silent_mode, 122 R.drawable.ic_lock_silent_mode_off, 123 R.string.global_action_toggle_silent_mode, 124 R.string.global_action_silent_mode_on_status, 125 R.string.global_action_silent_mode_off_status) { 126 127 void willCreate() { 128 // XXX: FIXME: switch to ic_lock_vibrate_mode when available 129 mEnabledIconResId = (Settings.System.getInt(mContext.getContentResolver(), 130 Settings.System.VIBRATE_IN_SILENT, 1) == 1) 131 ? R.drawable.ic_lock_silent_mode_vibrate 132 : R.drawable.ic_lock_silent_mode; 133 } 134 135 void onToggle(boolean on) { 136 if (on) { 137 mAudioManager.setRingerMode((Settings.System.getInt(mContext.getContentResolver(), 138 Settings.System.VIBRATE_IN_SILENT, 1) == 1) 139 ? AudioManager.RINGER_MODE_VIBRATE 140 : AudioManager.RINGER_MODE_SILENT); 141 } else { 142 mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL); 143 } 144 } 145 146 public boolean showDuringKeyguard() { 147 return true; 148 } 149 150 public boolean showBeforeProvisioning() { 151 return false; 152 } 153 }; 154 155 mAirplaneModeOn = new ToggleAction( 156 R.drawable.ic_lock_airplane_mode, 157 R.drawable.ic_lock_airplane_mode_off, 158 R.string.global_actions_toggle_airplane_mode, 159 R.string.global_actions_airplane_mode_on_status, 160 R.string.global_actions_airplane_mode_off_status) { 161 162 void onToggle(boolean on) { 163 if (Boolean.parseBoolean( 164 SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) { 165 mIsWaitingForEcmExit = true; 166 // Launch ECM exit dialog 167 Intent ecmDialogIntent = 168 new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null); 169 ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 170 mContext.startActivity(ecmDialogIntent); 171 } else { 172 changeAirplaneModeSystemSetting(on); 173 } 174 } 175 176 @Override 177 protected void changeStateFromPress(boolean buttonOn) { 178 // In ECM mode airplane state cannot be changed 179 if (!(Boolean.parseBoolean( 180 SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)))) { 181 mState = buttonOn ? State.TurningOn : State.TurningOff; 182 mAirplaneState = mState; 183 } 184 } 185 186 public boolean showDuringKeyguard() { 187 return true; 188 } 189 190 public boolean showBeforeProvisioning() { 191 return false; 192 } 193 }; 194 195 mItems = Lists.newArrayList( 196 // silent mode 197 mSilentModeToggle, 198 // next: airplane mode 199 mAirplaneModeOn, 200 // last: power off 201 new SinglePressAction( 202 com.android.internal.R.drawable.ic_lock_power_off, 203 R.string.global_action_power_off) { 204 205 public void onPress() { 206 // shutdown by making sure radio and power are handled accordingly. 207 ShutdownThread.shutdown(mContext, true); 208 } 209 210 public boolean showDuringKeyguard() { 211 return true; 212 } 213 214 public boolean showBeforeProvisioning() { 215 return true; 216 } 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 .setTitle(R.string.global_actions); 226 227 final AlertDialog dialog = ab.create(); 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 mSilentModeToggle.updateState( 239 silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off); 240 mAirplaneModeOn.updateState(mAirplaneState); 241 mAdapter.notifyDataSetChanged(); 242 if (mKeyguardShowing) { 243 mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 244 } else { 245 mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); 246 } 247 } 248 249 250 /** {@inheritDoc} */ 251 public void onDismiss(DialogInterface dialog) { 252 mStatusBar.disable(StatusBarManager.DISABLE_NONE); 253 } 254 255 /** {@inheritDoc} */ 256 public void onClick(DialogInterface dialog, int which) { 257 dialog.dismiss(); 258 mAdapter.getItem(which).onPress(); 259 } 260 261 262 /** 263 * The adapter used for the list within the global actions dialog, taking 264 * into account whether the keyguard is showing via 265 * {@link GlobalActions#mKeyguardShowing} and whether the device is provisioned 266 * via {@link GlobalActions#mDeviceProvisioned}. 267 */ 268 private class MyAdapter extends BaseAdapter { 269 270 public int getCount() { 271 int count = 0; 272 273 for (int i = 0; i < mItems.size(); i++) { 274 final Action action = mItems.get(i); 275 276 if (mKeyguardShowing && !action.showDuringKeyguard()) { 277 continue; 278 } 279 if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { 280 continue; 281 } 282 count++; 283 } 284 return count; 285 } 286 287 @Override 288 public boolean isEnabled(int position) { 289 return getItem(position).isEnabled(); 290 } 291 292 @Override 293 public boolean areAllItemsEnabled() { 294 return false; 295 } 296 297 public Action getItem(int position) { 298 299 int filteredPos = 0; 300 for (int i = 0; i < mItems.size(); i++) { 301 final Action action = mItems.get(i); 302 if (mKeyguardShowing && !action.showDuringKeyguard()) { 303 continue; 304 } 305 if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { 306 continue; 307 } 308 if (filteredPos == position) { 309 return action; 310 } 311 filteredPos++; 312 } 313 314 throw new IllegalArgumentException("position " + position 315 + " out of range of showable actions" 316 + ", filtered count=" + getCount() 317 + ", keyguardshowing=" + mKeyguardShowing 318 + ", provisioned=" + mDeviceProvisioned); 319 } 320 321 322 public long getItemId(int position) { 323 return position; 324 } 325 326 public View getView(int position, View convertView, ViewGroup parent) { 327 Action action = getItem(position); 328 return action.create(mContext, convertView, parent, LayoutInflater.from(mContext)); 329 } 330 } 331 332 // note: the scheme below made more sense when we were planning on having 333 // 8 different things in the global actions dialog. seems overkill with 334 // only 3 items now, but may as well keep this flexible approach so it will 335 // be easy should someone decide at the last minute to include something 336 // else, such as 'enable wifi', or 'enable bluetooth' 337 338 /** 339 * What each item in the global actions dialog must be able to support. 340 */ 341 private interface Action { 342 View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater); 343 344 void onPress(); 345 346 /** 347 * @return whether this action should appear in the dialog when the keygaurd 348 * is showing. 349 */ 350 boolean showDuringKeyguard(); 351 352 /** 353 * @return whether this action should appear in the dialog before the 354 * device is provisioned. 355 */ 356 boolean showBeforeProvisioning(); 357 358 boolean isEnabled(); 359 } 360 361 /** 362 * A single press action maintains no state, just responds to a press 363 * and takes an action. 364 */ 365 private static abstract class SinglePressAction implements Action { 366 private final int mIconResId; 367 private final int mMessageResId; 368 369 protected SinglePressAction(int iconResId, int messageResId) { 370 mIconResId = iconResId; 371 mMessageResId = messageResId; 372 } 373 374 public boolean isEnabled() { 375 return true; 376 } 377 378 abstract public void onPress(); 379 380 public View create( 381 Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { 382 View v = (convertView != null) ? 383 convertView : 384 inflater.inflate(R.layout.global_actions_item, parent, false); 385 386 ImageView icon = (ImageView) v.findViewById(R.id.icon); 387 TextView messageView = (TextView) v.findViewById(R.id.message); 388 389 v.findViewById(R.id.status).setVisibility(View.GONE); 390 391 icon.setImageDrawable(context.getResources().getDrawable(mIconResId)); 392 messageView.setText(mMessageResId); 393 394 return v; 395 } 396 } 397 398 /** 399 * A toggle action knows whether it is on or off, and displays an icon 400 * and status message accordingly. 401 */ 402 private static abstract class ToggleAction implements Action { 403 404 enum State { 405 Off(false), 406 TurningOn(true), 407 TurningOff(true), 408 On(false); 409 410 private final boolean inTransition; 411 412 State(boolean intermediate) { 413 inTransition = intermediate; 414 } 415 416 public boolean inTransition() { 417 return inTransition; 418 } 419 } 420 421 protected State mState = State.Off; 422 423 // prefs 424 protected int mEnabledIconResId; 425 protected int mDisabledIconResid; 426 protected int mMessageResId; 427 protected int mEnabledStatusMessageResId; 428 protected int mDisabledStatusMessageResId; 429 430 /** 431 * @param enabledIconResId The icon for when this action is on. 432 * @param disabledIconResid The icon for when this action is off. 433 * @param essage The general information message, e.g 'Silent Mode' 434 * @param enabledStatusMessageResId The on status message, e.g 'sound disabled' 435 * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled' 436 */ 437 public ToggleAction(int enabledIconResId, 438 int disabledIconResid, 439 int essage, 440 int enabledStatusMessageResId, 441 int disabledStatusMessageResId) { 442 mEnabledIconResId = enabledIconResId; 443 mDisabledIconResid = disabledIconResid; 444 mMessageResId = essage; 445 mEnabledStatusMessageResId = enabledStatusMessageResId; 446 mDisabledStatusMessageResId = disabledStatusMessageResId; 447 } 448 449 /** 450 * Override to make changes to resource IDs just before creating the 451 * View. 452 */ 453 void willCreate() { 454 455 } 456 457 public View create(Context context, View convertView, ViewGroup parent, 458 LayoutInflater inflater) { 459 willCreate(); 460 461 View v = (convertView != null) ? 462 convertView : 463 inflater.inflate(R 464 .layout.global_actions_item, parent, false); 465 466 ImageView icon = (ImageView) v.findViewById(R.id.icon); 467 TextView messageView = (TextView) v.findViewById(R.id.message); 468 TextView statusView = (TextView) v.findViewById(R.id.status); 469 470 messageView.setText(mMessageResId); 471 472 boolean on = ((mState == State.On) || (mState == State.TurningOn)); 473 icon.setImageDrawable(context.getResources().getDrawable( 474 (on ? mEnabledIconResId : mDisabledIconResid))); 475 statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId); 476 statusView.setVisibility(View.VISIBLE); 477 478 final boolean enabled = isEnabled(); 479 messageView.setEnabled(enabled); 480 statusView.setEnabled(enabled); 481 icon.setEnabled(enabled); 482 v.setEnabled(enabled); 483 484 return v; 485 } 486 487 public final void onPress() { 488 if (mState.inTransition()) { 489 Log.w(TAG, "shouldn't be able to toggle when in transition"); 490 return; 491 } 492 493 final boolean nowOn = !(mState == State.On); 494 onToggle(nowOn); 495 changeStateFromPress(nowOn); 496 } 497 498 public boolean isEnabled() { 499 return !mState.inTransition(); 500 } 501 502 /** 503 * Implementations may override this if their state can be in on of the intermediate 504 * states until some notification is received (e.g airplane mode is 'turning off' until 505 * we know the wireless connections are back online 506 * @param buttonOn Whether the button was turned on or off 507 */ 508 protected void changeStateFromPress(boolean buttonOn) { 509 mState = buttonOn ? State.On : State.Off; 510 } 511 512 abstract void onToggle(boolean on); 513 514 public void updateState(State state) { 515 mState = state; 516 } 517 } 518 519 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 520 public void onReceive(Context context, Intent intent) { 521 String action = intent.getAction(); 522 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) 523 || Intent.ACTION_SCREEN_OFF.equals(action)) { 524 String reason = intent.getStringExtra(PhoneWindowManager.SYSTEM_DIALOG_REASON_KEY); 525 if (!PhoneWindowManager.SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) { 526 mHandler.sendEmptyMessage(MESSAGE_DISMISS); 527 } 528 } else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) { 529 // Airplane mode can be changed after ECM exits if airplane toggle button 530 // is pressed during ECM mode 531 if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) && 532 mIsWaitingForEcmExit) { 533 mIsWaitingForEcmExit = false; 534 changeAirplaneModeSystemSetting(true); 535 } 536 } 537 } 538 }; 539 540 PhoneStateListener mPhoneStateListener = new PhoneStateListener() { 541 @Override 542 public void onServiceStateChanged(ServiceState serviceState) { 543 final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF; 544 mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off; 545 mAirplaneModeOn.updateState(mAirplaneState); 546 mAdapter.notifyDataSetChanged(); 547 } 548 }; 549 550 private static final int MESSAGE_DISMISS = 0; 551 private Handler mHandler = new Handler() { 552 public void handleMessage(Message msg) { 553 if (msg.what == MESSAGE_DISMISS) { 554 if (mDialog != null) { 555 mDialog.dismiss(); 556 } 557 } 558 } 559 }; 560 561 /** 562 * Change the airplane mode system setting 563 */ 564 private void changeAirplaneModeSystemSetting(boolean on) { 565 Settings.System.putInt( 566 mContext.getContentResolver(), 567 Settings.System.AIRPLANE_MODE_ON, 568 on ? 1 : 0); 569 Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); 570 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); 571 intent.putExtra("state", on); 572 mContext.sendBroadcast(intent); 573 } 574} 575