blob: b10bda212554ca5a6c1b467529a5539a675b6896 [file] [log] [blame]
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.policy.impl;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Message;
import android.os.SystemProperties;
import android.provider.Settings;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.internal.R;
import com.android.internal.app.ShutdownThread;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.TelephonyProperties;
import com.google.android.collect.Lists;
import java.util.ArrayList;
/**
* Helper to show the global actions dialog. Each item is an {@link Action} that
* may show depending on whether the keyguard is showing, and whether the device
* is provisioned.
*/
class GlobalActions implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
private static final String TAG = "GlobalActions";
private StatusBarManager mStatusBar;
private final Context mContext;
private final AudioManager mAudioManager;
private ArrayList<Action> mItems;
private AlertDialog mDialog;
private ToggleAction mSilentModeToggle;
private ToggleAction mAirplaneModeOn;
private MyAdapter mAdapter;
private boolean mKeyguardShowing = false;
private boolean mDeviceProvisioned = false;
private ToggleAction.State mAirplaneState = ToggleAction.State.Off;
private boolean mIsWaitingForEcmExit = false;
/**
* @param context everything needs a context :(
*/
public GlobalActions(Context context) {
mContext = context;
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
// receive broadcasts
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
context.registerReceiver(mBroadcastReceiver, filter);
// get notified of phone state changes
TelephonyManager telephonyManager =
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
}
/**
* Show the global actions dialog (creating if necessary)
* @param keyguardShowing True if keyguard is showing
*/
public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
mKeyguardShowing = keyguardShowing;
mDeviceProvisioned = isDeviceProvisioned;
if (mDialog == null) {
mStatusBar = (StatusBarManager)mContext.getSystemService(Context.STATUS_BAR_SERVICE);
mDialog = createDialog();
}
prepareDialog();
mStatusBar.disable(StatusBarManager.DISABLE_EXPAND);
mDialog.show();
}
/**
* Create the global actions dialog.
* @return A new dialog.
*/
private AlertDialog createDialog() {
mSilentModeToggle = new ToggleAction(
R.drawable.ic_lock_silent_mode,
R.drawable.ic_lock_silent_mode_off,
R.string.global_action_toggle_silent_mode,
R.string.global_action_silent_mode_on_status,
R.string.global_action_silent_mode_off_status) {
void willCreate() {
// XXX: FIXME: switch to ic_lock_vibrate_mode when available
mEnabledIconResId = (Settings.System.getInt(mContext.getContentResolver(),
Settings.System.VIBRATE_IN_SILENT, 1) == 1)
? R.drawable.ic_lock_silent_mode
: R.drawable.ic_lock_silent_mode;
}
void onToggle(boolean on) {
if (on) {
mAudioManager.setRingerMode((Settings.System.getInt(mContext.getContentResolver(),
Settings.System.VIBRATE_IN_SILENT, 1) == 1)
? AudioManager.RINGER_MODE_VIBRATE
: AudioManager.RINGER_MODE_SILENT);
} else {
mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
}
}
public boolean showDuringKeyguard() {
return true;
}
public boolean showBeforeProvisioning() {
return false;
}
};
mAirplaneModeOn = new ToggleAction(
R.drawable.ic_lock_airplane_mode,
R.drawable.ic_lock_airplane_mode_off,
R.string.global_actions_toggle_airplane_mode,
R.string.global_actions_airplane_mode_on_status,
R.string.global_actions_airplane_mode_off_status) {
void onToggle(boolean on) {
if (Boolean.parseBoolean(
SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) {
mIsWaitingForEcmExit = true;
// Launch ECM exit dialog
Intent ecmDialogIntent =
new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);
ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(ecmDialogIntent);
} else {
changeAirplaneModeSystemSetting(on);
}
}
@Override
protected void changeStateFromPress(boolean buttonOn) {
// In ECM mode airplane state cannot be changed
if (!(Boolean.parseBoolean(
SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)))) {
mState = buttonOn ? State.TurningOn : State.TurningOff;
mAirplaneState = mState;
}
}
public boolean showDuringKeyguard() {
return true;
}
public boolean showBeforeProvisioning() {
return false;
}
};
mItems = Lists.newArrayList(
// silent mode
mSilentModeToggle,
// next: airplane mode
mAirplaneModeOn,
// last: power off
new SinglePressAction(
com.android.internal.R.drawable.ic_lock_power_off,
R.string.global_action_power_off) {
public void onPress() {
// shutdown by making sure radio and power are handled accordingly.
ShutdownThread.shutdown(mContext, true);
}
public boolean showDuringKeyguard() {
return true;
}
public boolean showBeforeProvisioning() {
return true;
}
});
mAdapter = new MyAdapter();
final AlertDialog.Builder ab = new AlertDialog.Builder(mContext);
ab.setAdapter(mAdapter, this)
.setInverseBackgroundForced(true)
.setTitle(R.string.global_actions);
final AlertDialog dialog = ab.create();
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
if (!mContext.getResources().getBoolean(
com.android.internal.R.bool.config_sf_slowBlur)) {
dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND,
WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
}
dialog.setOnDismissListener(this);
return dialog;
}
private void prepareDialog() {
final boolean silentModeOn =
mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
mSilentModeToggle.updateState(
silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off);
mAirplaneModeOn.updateState(mAirplaneState);
mAdapter.notifyDataSetChanged();
if (mKeyguardShowing) {
mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
} else {
mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
}
}
/** {@inheritDoc} */
public void onDismiss(DialogInterface dialog) {
mStatusBar.disable(StatusBarManager.DISABLE_NONE);
}
/** {@inheritDoc} */
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
mAdapter.getItem(which).onPress();
}
/**
* The adapter used for the list within the global actions dialog, taking
* into account whether the keyguard is showing via
* {@link GlobalActions#mKeyguardShowing} and whether the device is provisioned
* via {@link GlobalActions#mDeviceProvisioned}.
*/
private class MyAdapter extends BaseAdapter {
public int getCount() {
int count = 0;
for (int i = 0; i < mItems.size(); i++) {
final Action action = mItems.get(i);
if (mKeyguardShowing && !action.showDuringKeyguard()) {
continue;
}
if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
continue;
}
count++;
}
return count;
}
@Override
public boolean isEnabled(int position) {
return getItem(position).isEnabled();
}
@Override
public boolean areAllItemsEnabled() {
return false;
}
public Action getItem(int position) {
int filteredPos = 0;
for (int i = 0; i < mItems.size(); i++) {
final Action action = mItems.get(i);
if (mKeyguardShowing && !action.showDuringKeyguard()) {
continue;
}
if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
continue;
}
if (filteredPos == position) {
return action;
}
filteredPos++;
}
throw new IllegalArgumentException("position " + position + " out of "
+ "range of showable actions, filtered count = "
+ "= " + getCount() + ", keyguardshowing=" + mKeyguardShowing
+ ", provisioned=" + mDeviceProvisioned);
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
Action action = getItem(position);
return action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
}
}
// note: the scheme below made more sense when we were planning on having
// 8 different things in the global actions dialog. seems overkill with
// only 3 items now, but may as well keep this flexible approach so it will
// be easy should someone decide at the last minute to include something
// else, such as 'enable wifi', or 'enable bluetooth'
/**
* What each item in the global actions dialog must be able to support.
*/
private interface Action {
View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater);
void onPress();
/**
* @return whether this action should appear in the dialog when the keygaurd
* is showing.
*/
boolean showDuringKeyguard();
/**
* @return whether this action should appear in the dialog before the
* device is provisioned.
*/
boolean showBeforeProvisioning();
boolean isEnabled();
}
/**
* A single press action maintains no state, just responds to a press
* and takes an action.
*/
private static abstract class SinglePressAction implements Action {
private final int mIconResId;
private final int mMessageResId;
protected SinglePressAction(int iconResId, int messageResId) {
mIconResId = iconResId;
mMessageResId = messageResId;
}
public boolean isEnabled() {
return true;
}
abstract public void onPress();
public View create(
Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
View v = (convertView != null) ?
convertView :
inflater.inflate(R.layout.global_actions_item, parent, false);
ImageView icon = (ImageView) v.findViewById(R.id.icon);
TextView messageView = (TextView) v.findViewById(R.id.message);
v.findViewById(R.id.status).setVisibility(View.GONE);
icon.setImageDrawable(context.getResources().getDrawable(mIconResId));
messageView.setText(mMessageResId);
return v;
}
}
/**
* A toggle action knows whether it is on or off, and displays an icon
* and status message accordingly.
*/
private static abstract class ToggleAction implements Action {
enum State {
Off(false),
TurningOn(true),
TurningOff(true),
On(false);
private final boolean inTransition;
State(boolean intermediate) {
inTransition = intermediate;
}
public boolean inTransition() {
return inTransition;
}
}
protected State mState = State.Off;
// prefs
protected int mEnabledIconResId;
protected int mDisabledIconResid;
protected int mMessageResId;
protected int mEnabledStatusMessageResId;
protected int mDisabledStatusMessageResId;
/**
* @param enabledIconResId The icon for when this action is on.
* @param disabledIconResid The icon for when this action is off.
* @param essage The general information message, e.g 'Silent Mode'
* @param enabledStatusMessageResId The on status message, e.g 'sound disabled'
* @param disabledStatusMessageResId The off status message, e.g. 'sound enabled'
*/
public ToggleAction(int enabledIconResId,
int disabledIconResid,
int essage,
int enabledStatusMessageResId,
int disabledStatusMessageResId) {
mEnabledIconResId = enabledIconResId;
mDisabledIconResid = disabledIconResid;
mMessageResId = essage;
mEnabledStatusMessageResId = enabledStatusMessageResId;
mDisabledStatusMessageResId = disabledStatusMessageResId;
}
/**
* Override to make changes to resource IDs just before creating the
* View.
*/
void willCreate() {
}
public View create(Context context, View convertView, ViewGroup parent,
LayoutInflater inflater) {
willCreate();
View v = (convertView != null) ?
convertView :
inflater.inflate(R
.layout.global_actions_item, parent, false);
ImageView icon = (ImageView) v.findViewById(R.id.icon);
TextView messageView = (TextView) v.findViewById(R.id.message);
TextView statusView = (TextView) v.findViewById(R.id.status);
messageView.setText(mMessageResId);
boolean on = ((mState == State.On) || (mState == State.TurningOn));
icon.setImageDrawable(context.getResources().getDrawable(
(on ? mEnabledIconResId : mDisabledIconResid)));
statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
statusView.setVisibility(View.VISIBLE);
final boolean enabled = isEnabled();
messageView.setEnabled(enabled);
statusView.setEnabled(enabled);
icon.setEnabled(enabled);
v.setEnabled(enabled);
return v;
}
public final void onPress() {
if (mState.inTransition()) {
Log.w(TAG, "shouldn't be able to toggle when in transition");
return;
}
final boolean nowOn = !(mState == State.On);
onToggle(nowOn);
changeStateFromPress(nowOn);
}
public boolean isEnabled() {
return !mState.inTransition();
}
/**
* Implementations may override this if their state can be in on of the intermediate
* states until some notification is received (e.g airplane mode is 'turning off' until
* we know the wireless connections are back online
* @param buttonOn Whether the button was turned on or off
*/
protected void changeStateFromPress(boolean buttonOn) {
mState = buttonOn ? State.On : State.Off;
}
abstract void onToggle(boolean on);
public void updateState(State state) {
mState = state;
}
}
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
|| Intent.ACTION_SCREEN_OFF.equals(action)) {
String reason = intent.getStringExtra(PhoneWindowManager.SYSTEM_DIALOG_REASON_KEY);
if (!PhoneWindowManager.SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) {
mHandler.sendEmptyMessage(MESSAGE_DISMISS);
}
} else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) {
// Airplane mode can be changed after ECM exits if airplane toggle button
// is pressed during ECM mode
if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) &&
mIsWaitingForEcmExit) {
mIsWaitingForEcmExit = false;
changeAirplaneModeSystemSetting(true);
}
}
}
};
PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
@Override
public void onServiceStateChanged(ServiceState serviceState) {
final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF;
mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off;
mAirplaneModeOn.updateState(mAirplaneState);
mAdapter.notifyDataSetChanged();
}
};
private static final int MESSAGE_DISMISS = 0;
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
if (msg.what == MESSAGE_DISMISS) {
if (mDialog != null) {
mDialog.dismiss();
}
}
}
};
/**
* Change the airplane mode system setting
*/
private void changeAirplaneModeSystemSetting(boolean on) {
Settings.System.putInt(
mContext.getContentResolver(),
Settings.System.AIRPLANE_MODE_ON,
on ? 1 : 0);
Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
intent.putExtra("state", on);
mContext.sendBroadcast(intent);
}
}