579 lines
20 KiB
Java
579 lines
20 KiB
Java
/*
|
|
* 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_vibrate
|
|
: 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);
|
|
}
|
|
}
|