GlobalActions.java revision ded7c652d754751e6fbde729d66825c69394d1cb
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 if (!mContext.getResources().getBoolean( 230 com.android.internal.R.bool.config_sf_slowBlur)) { 231 dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND, 232 WindowManager.LayoutParams.FLAG_BLUR_BEHIND); 233 } 234 235 dialog.setOnDismissListener(this); 236 237 return dialog; 238 } 239 240 private void prepareDialog() { 241 final boolean silentModeOn = 242 mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL; 243 mSilentModeToggle.updateState( 244 silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off); 245 mAirplaneModeOn.updateState(mAirplaneState); 246 mAdapter.notifyDataSetChanged(); 247 if (mKeyguardShowing) { 248 mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 249 } else { 250 mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); 251 } 252 } 253 254 255 /** {@inheritDoc} */ 256 public void onDismiss(DialogInterface dialog) { 257 mStatusBar.disable(StatusBarManager.DISABLE_NONE); 258 } 259 260 /** {@inheritDoc} */ 261 public void onClick(DialogInterface dialog, int which) { 262 dialog.dismiss(); 263 mAdapter.getItem(which).onPress(); 264 } 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 374 protected SinglePressAction(int iconResId, int messageResId) { 375 mIconResId = iconResId; 376 mMessageResId = messageResId; 377 } 378 379 public boolean isEnabled() { 380 return true; 381 } 382 383 abstract public void onPress(); 384 385 public View create( 386 Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { 387 View v = (convertView != null) ? 388 convertView : 389 inflater.inflate(R.layout.global_actions_item, parent, false); 390 391 ImageView icon = (ImageView) v.findViewById(R.id.icon); 392 TextView messageView = (TextView) v.findViewById(R.id.message); 393 394 v.findViewById(R.id.status).setVisibility(View.GONE); 395 396 icon.setImageDrawable(context.getResources().getDrawable(mIconResId)); 397 messageView.setText(mMessageResId); 398 399 return v; 400 } 401 } 402 403 /** 404 * A toggle action knows whether it is on or off, and displays an icon 405 * and status message accordingly. 406 */ 407 private static abstract class ToggleAction implements Action { 408 409 enum State { 410 Off(false), 411 TurningOn(true), 412 TurningOff(true), 413 On(false); 414 415 private final boolean inTransition; 416 417 State(boolean intermediate) { 418 inTransition = intermediate; 419 } 420 421 public boolean inTransition() { 422 return inTransition; 423 } 424 } 425 426 protected State mState = State.Off; 427 428 // prefs 429 protected int mEnabledIconResId; 430 protected int mDisabledIconResid; 431 protected int mMessageResId; 432 protected int mEnabledStatusMessageResId; 433 protected int mDisabledStatusMessageResId; 434 435 /** 436 * @param enabledIconResId The icon for when this action is on. 437 * @param disabledIconResid The icon for when this action is off. 438 * @param essage The general information message, e.g 'Silent Mode' 439 * @param enabledStatusMessageResId The on status message, e.g 'sound disabled' 440 * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled' 441 */ 442 public ToggleAction(int enabledIconResId, 443 int disabledIconResid, 444 int essage, 445 int enabledStatusMessageResId, 446 int disabledStatusMessageResId) { 447 mEnabledIconResId = enabledIconResId; 448 mDisabledIconResid = disabledIconResid; 449 mMessageResId = essage; 450 mEnabledStatusMessageResId = enabledStatusMessageResId; 451 mDisabledStatusMessageResId = disabledStatusMessageResId; 452 } 453 454 /** 455 * Override to make changes to resource IDs just before creating the 456 * View. 457 */ 458 void willCreate() { 459 460 } 461 462 public View create(Context context, View convertView, ViewGroup parent, 463 LayoutInflater inflater) { 464 willCreate(); 465 466 View v = (convertView != null) ? 467 convertView : 468 inflater.inflate(R 469 .layout.global_actions_item, parent, false); 470 471 ImageView icon = (ImageView) v.findViewById(R.id.icon); 472 TextView messageView = (TextView) v.findViewById(R.id.message); 473 TextView statusView = (TextView) v.findViewById(R.id.status); 474 475 messageView.setText(mMessageResId); 476 477 boolean on = ((mState == State.On) || (mState == State.TurningOn)); 478 icon.setImageDrawable(context.getResources().getDrawable( 479 (on ? mEnabledIconResId : mDisabledIconResid))); 480 statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId); 481 statusView.setVisibility(View.VISIBLE); 482 483 final boolean enabled = isEnabled(); 484 messageView.setEnabled(enabled); 485 statusView.setEnabled(enabled); 486 icon.setEnabled(enabled); 487 v.setEnabled(enabled); 488 489 return v; 490 } 491 492 public final void onPress() { 493 if (mState.inTransition()) { 494 Log.w(TAG, "shouldn't be able to toggle when in transition"); 495 return; 496 } 497 498 final boolean nowOn = !(mState == State.On); 499 onToggle(nowOn); 500 changeStateFromPress(nowOn); 501 } 502 503 public boolean isEnabled() { 504 return !mState.inTransition(); 505 } 506 507 /** 508 * Implementations may override this if their state can be in on of the intermediate 509 * states until some notification is received (e.g airplane mode is 'turning off' until 510 * we know the wireless connections are back online 511 * @param buttonOn Whether the button was turned on or off 512 */ 513 protected void changeStateFromPress(boolean buttonOn) { 514 mState = buttonOn ? State.On : State.Off; 515 } 516 517 abstract void onToggle(boolean on); 518 519 public void updateState(State state) { 520 mState = state; 521 } 522 } 523 524 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 525 public void onReceive(Context context, Intent intent) { 526 String action = intent.getAction(); 527 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) 528 || Intent.ACTION_SCREEN_OFF.equals(action)) { 529 String reason = intent.getStringExtra(PhoneWindowManager.SYSTEM_DIALOG_REASON_KEY); 530 if (!PhoneWindowManager.SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) { 531 mHandler.sendEmptyMessage(MESSAGE_DISMISS); 532 } 533 } else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) { 534 // Airplane mode can be changed after ECM exits if airplane toggle button 535 // is pressed during ECM mode 536 if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) && 537 mIsWaitingForEcmExit) { 538 mIsWaitingForEcmExit = false; 539 changeAirplaneModeSystemSetting(true); 540 } 541 } 542 } 543 }; 544 545 PhoneStateListener mPhoneStateListener = new PhoneStateListener() { 546 @Override 547 public void onServiceStateChanged(ServiceState serviceState) { 548 final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF; 549 mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off; 550 mAirplaneModeOn.updateState(mAirplaneState); 551 mAdapter.notifyDataSetChanged(); 552 } 553 }; 554 555 private static final int MESSAGE_DISMISS = 0; 556 private Handler mHandler = new Handler() { 557 public void handleMessage(Message msg) { 558 if (msg.what == MESSAGE_DISMISS) { 559 if (mDialog != null) { 560 mDialog.dismiss(); 561 } 562 } 563 } 564 }; 565 566 /** 567 * Change the airplane mode system setting 568 */ 569 private void changeAirplaneModeSystemSetting(boolean on) { 570 Settings.System.putInt( 571 mContext.getContentResolver(), 572 Settings.System.AIRPLANE_MODE_ON, 573 on ? 1 : 0); 574 Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); 575 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); 576 intent.putExtra("state", on); 577 mContext.sendBroadcast(intent); 578 } 579} 580