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