2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.internal.policy.impl;
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;
49 import java.util.ArrayList;
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
56 class GlobalActions implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
58 private static final String TAG = "GlobalActions";
60 private StatusBarManager mStatusBar;
62 private final Context mContext;
63 private final AudioManager mAudioManager;
65 private ArrayList<Action> mItems;
66 private AlertDialog mDialog;
68 private ToggleAction mSilentModeToggle;
69 private ToggleAction mAirplaneModeOn;
71 private MyAdapter mAdapter;
73 private boolean mKeyguardShowing = false;
74 private boolean mDeviceProvisioned = false;
75 private ToggleAction.State mAirplaneState = ToggleAction.State.Off;
76 private boolean mIsWaitingForEcmExit = false;
79 * @param context everything needs a context :(
81 public GlobalActions(Context context) {
83 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
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);
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);
99 * Show the global actions dialog (creating if necessary)
100 * @param keyguardShowing True if keyguard is showing
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();
111 mStatusBar.disable(StatusBarManager.DISABLE_EXPAND);
116 * Create the global actions dialog.
117 * @return A new dialog.
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) {
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;
135 void onToggle(boolean 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);
142 mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
146 public boolean showDuringKeyguard() {
150 public boolean showBeforeProvisioning() {
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) {
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);
172 changeAirplaneModeSystemSetting(on);
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;
186 public boolean showDuringKeyguard() {
190 public boolean showBeforeProvisioning() {
195 mItems = Lists.newArrayList(
198 // next: airplane mode
201 new SinglePressAction(
202 com.android.internal.R.drawable.ic_lock_power_off,
203 R.string.global_action_power_off) {
205 public void onPress() {
206 // shutdown by making sure radio and power are handled accordingly.
207 ShutdownThread.shutdown(mContext, true);
210 public boolean showDuringKeyguard() {
214 public boolean showBeforeProvisioning() {
219 mAdapter = new MyAdapter();
221 final AlertDialog.Builder ab = new AlertDialog.Builder(mContext);
223 ab.setAdapter(mAdapter, this)
224 .setInverseBackgroundForced(true);
226 final AlertDialog dialog = ab.create();
227 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
228 if (!mContext.getResources().getBoolean(
229 com.android.internal.R.bool.config_sf_slowBlur)) {
230 dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND,
231 WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
234 dialog.setOnDismissListener(this);
239 private void prepareDialog() {
240 final boolean silentModeOn =
241 mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
242 mSilentModeToggle.updateState(
243 silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off);
244 mAirplaneModeOn.updateState(mAirplaneState);
245 mAdapter.notifyDataSetChanged();
246 if (mKeyguardShowing) {
247 mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
249 mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
251 mDialog.setTitle(R.string.global_actions);
256 public void onDismiss(DialogInterface dialog) {
257 mStatusBar.disable(StatusBarManager.DISABLE_NONE);
261 public void onClick(DialogInterface dialog, int which) {
263 mAdapter.getItem(which).onPress();
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}.
273 private class MyAdapter extends BaseAdapter {
275 public int getCount() {
278 for (int i = 0; i < mItems.size(); i++) {
279 final Action action = mItems.get(i);
281 if (mKeyguardShowing && !action.showDuringKeyguard()) {
284 if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
293 public boolean isEnabled(int position) {
294 return getItem(position).isEnabled();
298 public boolean areAllItemsEnabled() {
302 public Action getItem(int position) {
305 for (int i = 0; i < mItems.size(); i++) {
306 final Action action = mItems.get(i);
307 if (mKeyguardShowing && !action.showDuringKeyguard()) {
310 if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
313 if (filteredPos == position) {
319 throw new IllegalArgumentException("position " + position + " out of "
320 + "range of showable actions, filtered count = "
321 + "= " + getCount() + ", keyguardshowing=" + mKeyguardShowing
322 + ", provisioned=" + mDeviceProvisioned);
326 public long getItemId(int position) {
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));
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'
343 * What each item in the global actions dialog must be able to support.
345 private interface Action {
346 View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater);
351 * @return whether this action should appear in the dialog when the keygaurd
354 boolean showDuringKeyguard();
357 * @return whether this action should appear in the dialog before the
358 * device is provisioned.
360 boolean showBeforeProvisioning();
366 * A single press action maintains no state, just responds to a press
367 * and takes an action.
369 private static abstract class SinglePressAction implements Action {
370 private final int mIconResId;
371 private final int mMessageResId;
373 protected SinglePressAction(int iconResId, int messageResId) {
374 mIconResId = iconResId;
375 mMessageResId = messageResId;
378 public boolean isEnabled() {
382 abstract public void onPress();
385 Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
386 View v = (convertView != null) ?
388 inflater.inflate(R.layout.global_actions_item, parent, false);
390 ImageView icon = (ImageView) v.findViewById(R.id.icon);
391 TextView messageView = (TextView) v.findViewById(R.id.message);
393 v.findViewById(R.id.status).setVisibility(View.GONE);
395 icon.setImageDrawable(context.getResources().getDrawable(mIconResId));
396 messageView.setText(mMessageResId);
403 * A toggle action knows whether it is on or off, and displays an icon
404 * and status message accordingly.
406 private static abstract class ToggleAction implements Action {
414 private final boolean inTransition;
416 State(boolean intermediate) {
417 inTransition = intermediate;
420 public boolean inTransition() {
425 protected State mState = State.Off;
428 protected int mEnabledIconResId;
429 protected int mDisabledIconResid;
430 protected int mMessageResId;
431 protected int mEnabledStatusMessageResId;
432 protected int mDisabledStatusMessageResId;
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'
441 public ToggleAction(int enabledIconResId,
442 int disabledIconResid,
444 int enabledStatusMessageResId,
445 int disabledStatusMessageResId) {
446 mEnabledIconResId = enabledIconResId;
447 mDisabledIconResid = disabledIconResid;
448 mMessageResId = essage;
449 mEnabledStatusMessageResId = enabledStatusMessageResId;
450 mDisabledStatusMessageResId = disabledStatusMessageResId;
454 * Override to make changes to resource IDs just before creating the
461 public View create(Context context, View convertView, ViewGroup parent,
462 LayoutInflater inflater) {
465 View v = (convertView != null) ?
468 .layout.global_actions_item, parent, false);
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);
474 messageView.setText(mMessageResId);
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);
482 final boolean enabled = isEnabled();
483 messageView.setEnabled(enabled);
484 statusView.setEnabled(enabled);
485 icon.setEnabled(enabled);
486 v.setEnabled(enabled);
491 public final void onPress() {
492 if (mState.inTransition()) {
493 Log.w(TAG, "shouldn't be able to toggle when in transition");
497 final boolean nowOn = !(mState == State.On);
499 changeStateFromPress(nowOn);
502 public boolean isEnabled() {
503 return !mState.inTransition();
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
512 protected void changeStateFromPress(boolean buttonOn) {
513 mState = buttonOn ? State.On : State.Off;
516 abstract void onToggle(boolean on);
518 public void updateState(State state) {
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);
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);
544 PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
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();
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) {
566 * Change the airplane mode system setting
568 private void changeAirplaneModeSystemSetting(boolean on) {
569 Settings.System.putInt(
570 mContext.getContentResolver(),
571 Settings.System.AIRPLANE_MODE_ON,
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);