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