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 17 package com.android.internal.policy.impl; 18 19 import android.app.Activity; 20 import android.app.AlertDialog; 21 import android.app.StatusBarManager; 22 import android.content.BroadcastReceiver; 23 import android.content.Context; 24 import android.content.DialogInterface; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.media.AudioManager; 28 import android.os.Handler; 29 import android.os.Message; 30 import android.os.SystemProperties; 31 import android.provider.Settings; 32 import android.telephony.PhoneStateListener; 33 import android.telephony.ServiceState; 34 import android.telephony.TelephonyManager; 35 import android.util.Log; 36 import android.view.LayoutInflater; 37 import android.view.View; 38 import android.view.ViewGroup; 39 import android.view.WindowManager; 40 import android.widget.BaseAdapter; 41 import android.widget.ImageView; 42 import android.widget.TextView; 43 import com.android.internal.R; 44 import com.android.internal.app.ShutdownThread; 45 import com.android.internal.telephony.TelephonyIntents; 46 import com.android.internal.telephony.TelephonyProperties; 47 import com.google.android.collect.Lists; 48 49 import 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 */ 56 class 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 + " out of " 320 + "range of showable actions, filtered count = " 321 + "= " + getCount() + ", keyguardshowing=" + mKeyguardShowing 322 + ", provisioned=" + mDeviceProvisioned); 323 } 324 325 326 public long getItemId(int position) { 327 return position; 328 } 329 330 public View getView(int position, View convertView, ViewGroup parent) { 331 Action action = getItem(position); 332 return action.create(mContext, convertView, parent, LayoutInflater.from(mContext)); 333 } 334 } 335 336 // note: the scheme below made more sense when we were planning on having 337 // 8 different things in the global actions dialog. seems overkill with 338 // only 3 items now, but may as well keep this flexible approach so it will 339 // be easy should someone decide at the last minute to include something 340 // else, such as 'enable wifi', or 'enable bluetooth' 341 342 /** 343 * What each item in the global actions dialog must be able to support. 344 */ 345 private interface Action { 346 View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater); 347 348 void onPress(); 349 350 /** 351 * @return whether this action should appear in the dialog when the keygaurd 352 * is showing. 353 */ 354 boolean showDuringKeyguard(); 355 356 /** 357 * @return whether this action should appear in the dialog before the 358 * device is provisioned. 359 */ 360 boolean showBeforeProvisioning(); 361 362 boolean isEnabled(); 363 } 364 365 /** 366 * A single press action maintains no state, just responds to a press 367 * and takes an action. 368 */ 369 private static abstract class SinglePressAction implements Action { 370 private final int mIconResId; 371 private final int mMessageResId; 372 373 protected SinglePressAction(int iconResId, int messageResId) { 374 mIconResId = iconResId; 375 mMessageResId = messageResId; 376 } 377 378 public boolean isEnabled() { 379 return true; 380 } 381 382 abstract public void onPress(); 383 384 public View create( 385 Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { 386 View v = (convertView != null) ? 387 convertView : 388 inflater.inflate(R.layout.global_actions_item, parent, false); 389 390 ImageView icon = (ImageView) v.findViewById(R.id.icon); 391 TextView messageView = (TextView) v.findViewById(R.id.message); 392 393 v.findViewById(R.id.status).setVisibility(View.GONE); 394 395 icon.setImageDrawable(context.getResources().getDrawable(mIconResId)); 396 messageView.setText(mMessageResId); 397 398 return v; 399 } 400 } 401 402 /** 403 * A toggle action knows whether it is on or off, and displays an icon 404 * and status message accordingly. 405 */ 406 private static abstract class ToggleAction implements Action { 407 408 enum State { 409 Off(false), 410 TurningOn(true), 411 TurningOff(true), 412 On(false); 413 414 private final boolean inTransition; 415 416 State(boolean intermediate) { 417 inTransition = intermediate; 418 } 419 420 public boolean inTransition() { 421 return inTransition; 422 } 423 } 424 425 protected State mState = State.Off; 426 427 // prefs 428 protected int mEnabledIconResId; 429 protected int mDisabledIconResid; 430 protected int mMessageResId; 431 protected int mEnabledStatusMessageResId; 432 protected int mDisabledStatusMessageResId; 433 434 /** 435 * @param enabledIconResId The icon for when this action is on. 436 * @param disabledIconResid The icon for when this action is off. 437 * @param essage The general information message, e.g 'Silent Mode' 438 * @param enabledStatusMessageResId The on status message, e.g 'sound disabled' 439 * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled' 440 */ 441 public ToggleAction(int enabledIconResId, 442 int disabledIconResid, 443 int essage, 444 int enabledStatusMessageResId, 445 int disabledStatusMessageResId) { 446 mEnabledIconResId = enabledIconResId; 447 mDisabledIconResid = disabledIconResid; 448 mMessageResId = essage; 449 mEnabledStatusMessageResId = enabledStatusMessageResId; 450 mDisabledStatusMessageResId = disabledStatusMessageResId; 451 } 452 453 /** 454 * Override to make changes to resource IDs just before creating the 455 * View. 456 */ 457 void willCreate() { 458 459 } 460 461 public View create(Context context, View convertView, ViewGroup parent, 462 LayoutInflater inflater) { 463 willCreate(); 464 465 View v = (convertView != null) ? 466 convertView : 467 inflater.inflate(R 468 .layout.global_actions_item, parent, false); 469 470 ImageView icon = (ImageView) v.findViewById(R.id.icon); 471 TextView messageView = (TextView) v.findViewById(R.id.message); 472 TextView statusView = (TextView) v.findViewById(R.id.status); 473 474 messageView.setText(mMessageResId); 475 476 boolean on = ((mState == State.On) || (mState == State.TurningOn)); 477 icon.setImageDrawable(context.getResources().getDrawable( 478 (on ? mEnabledIconResId : mDisabledIconResid))); 479 statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId); 480 statusView.setVisibility(View.VISIBLE); 481 482 final boolean enabled = isEnabled(); 483 messageView.setEnabled(enabled); 484 statusView.setEnabled(enabled); 485 icon.setEnabled(enabled); 486 v.setEnabled(enabled); 487 488 return v; 489 } 490 491 public final void onPress() { 492 if (mState.inTransition()) { 493 Log.w(TAG, "shouldn't be able to toggle when in transition"); 494 return; 495 } 496 497 final boolean nowOn = !(mState == State.On); 498 onToggle(nowOn); 499 changeStateFromPress(nowOn); 500 } 501 502 public boolean isEnabled() { 503 return !mState.inTransition(); 504 } 505 506 /** 507 * Implementations may override this if their state can be in on of the intermediate 508 * states until some notification is received (e.g airplane mode is 'turning off' until 509 * we know the wireless connections are back online 510 * @param buttonOn Whether the button was turned on or off 511 */ 512 protected void changeStateFromPress(boolean buttonOn) { 513 mState = buttonOn ? State.On : State.Off; 514 } 515 516 abstract void onToggle(boolean on); 517 518 public void updateState(State state) { 519 mState = state; 520 } 521 } 522 523 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 524 public void onReceive(Context context, Intent intent) { 525 String action = intent.getAction(); 526 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) 527 || Intent.ACTION_SCREEN_OFF.equals(action)) { 528 String reason = intent.getStringExtra(PhoneWindowManager.SYSTEM_DIALOG_REASON_KEY); 529 if (!PhoneWindowManager.SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) { 530 mHandler.sendEmptyMessage(MESSAGE_DISMISS); 531 } 532 } else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) { 533 // Airplane mode can be changed after ECM exits if airplane toggle button 534 // is pressed during ECM mode 535 if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) && 536 mIsWaitingForEcmExit) { 537 mIsWaitingForEcmExit = false; 538 changeAirplaneModeSystemSetting(true); 539 } 540 } 541 } 542 }; 543 544 PhoneStateListener mPhoneStateListener = new PhoneStateListener() { 545 @Override 546 public void onServiceStateChanged(ServiceState serviceState) { 547 final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF; 548 mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off; 549 mAirplaneModeOn.updateState(mAirplaneState); 550 mAdapter.notifyDataSetChanged(); 551 } 552 }; 553 554 private static final int MESSAGE_DISMISS = 0; 555 private Handler mHandler = new Handler() { 556 public void handleMessage(Message msg) { 557 if (msg.what == MESSAGE_DISMISS) { 558 if (mDialog != null) { 559 mDialog.dismiss(); 560 } 561 } 562 } 563 }; 564 565 /** 566 * Change the airplane mode system setting 567 */ 568 private void changeAirplaneModeSystemSetting(boolean on) { 569 Settings.System.putInt( 570 mContext.getContentResolver(), 571 Settings.System.AIRPLANE_MODE_ON, 572 on ? 1 : 0); 573 Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); 574 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); 575 intent.putExtra("state", on); 576 mContext.sendBroadcast(intent); 577 } 578 } 579