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.server.policy;
19 import com.android.internal.app.AlertController;
20 import com.android.internal.app.AlertController.AlertParams;
21 import com.android.internal.logging.MetricsLogger;
22 import com.android.internal.logging.MetricsProto.MetricsEvent;
23 import com.android.internal.telephony.TelephonyIntents;
24 import com.android.internal.telephony.TelephonyProperties;
25 import com.android.internal.R;
26 import com.android.internal.widget.LockPatternUtils;
28 import android.app.ActivityManager;
29 import android.app.ActivityManagerNative;
30 import android.app.AlertDialog;
31 import android.app.Dialog;
32 import android.content.BroadcastReceiver;
33 import android.content.Context;
34 import android.content.DialogInterface;
35 import android.content.Intent;
36 import android.content.IntentFilter;
37 import android.content.pm.UserInfo;
38 import android.database.ContentObserver;
39 import android.graphics.drawable.Drawable;
40 import android.media.AudioManager;
41 import android.net.ConnectivityManager;
42 import android.os.Build;
43 import android.os.Bundle;
44 import android.os.Handler;
45 import android.os.Message;
46 import android.os.RemoteException;
47 import android.os.ServiceManager;
48 import android.os.SystemClock;
49 import android.os.SystemProperties;
50 import android.os.UserHandle;
51 import android.os.UserManager;
52 import android.os.Vibrator;
53 import android.provider.Settings;
54 import android.service.dreams.DreamService;
55 import android.service.dreams.IDreamManager;
56 import android.telephony.PhoneStateListener;
57 import android.telephony.ServiceState;
58 import android.telephony.TelephonyManager;
59 import android.text.TextUtils;
60 import android.util.ArraySet;
61 import android.util.Log;
62 import android.util.TypedValue;
63 import android.view.InputDevice;
64 import android.view.KeyEvent;
65 import android.view.LayoutInflater;
66 import android.view.MotionEvent;
67 import android.view.View;
68 import android.view.ViewConfiguration;
69 import android.view.ViewGroup;
70 import android.view.WindowManager;
71 import android.view.WindowManagerGlobal;
72 import android.view.WindowManagerPolicy.WindowManagerFuncs;
73 import android.view.accessibility.AccessibilityEvent;
74 import android.widget.AdapterView;
75 import android.widget.BaseAdapter;
76 import android.widget.ImageView;
77 import android.widget.ImageView.ScaleType;
78 import android.widget.ListView;
79 import android.widget.TextView;
81 import java.util.ArrayList;
82 import java.util.List;
85 * Helper to show the global actions dialog. Each item is an {@link Action} that
86 * may show depending on whether the keyguard is showing, and whether the device
89 class GlobalActions implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
91 private static final String TAG = "GlobalActions";
93 private static final boolean SHOW_SILENT_TOGGLE = true;
95 /* Valid settings for global actions keys.
96 * see config.xml config_globalActionList */
97 private static final String GLOBAL_ACTION_KEY_POWER = "power";
98 private static final String GLOBAL_ACTION_KEY_AIRPLANE = "airplane";
99 private static final String GLOBAL_ACTION_KEY_BUGREPORT = "bugreport";
100 private static final String GLOBAL_ACTION_KEY_SILENT = "silent";
101 private static final String GLOBAL_ACTION_KEY_USERS = "users";
102 private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings";
103 private static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown";
104 private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist";
105 private static final String GLOBAL_ACTION_KEY_ASSIST = "assist";
106 private static final String GLOBAL_ACTION_KEY_RESTART = "restart";
108 private final Context mContext;
109 private final WindowManagerFuncs mWindowManagerFuncs;
110 private final AudioManager mAudioManager;
111 private final IDreamManager mDreamManager;
113 private ArrayList<Action> mItems;
114 private GlobalActionsDialog mDialog;
116 private Action mSilentModeAction;
117 private ToggleAction mAirplaneModeOn;
119 private MyAdapter mAdapter;
121 private boolean mKeyguardShowing = false;
122 private boolean mDeviceProvisioned = false;
123 private ToggleAction.State mAirplaneState = ToggleAction.State.Off;
124 private boolean mIsWaitingForEcmExit = false;
125 private boolean mHasTelephony;
126 private boolean mHasVibrator;
127 private final boolean mShowSilentToggle;
130 * @param context everything needs a context :(
132 public GlobalActions(Context context, WindowManagerFuncs windowManagerFuncs) {
134 mWindowManagerFuncs = windowManagerFuncs;
135 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
136 mDreamManager = IDreamManager.Stub.asInterface(
137 ServiceManager.getService(DreamService.DREAM_SERVICE));
139 // receive broadcasts
140 IntentFilter filter = new IntentFilter();
141 filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
142 filter.addAction(Intent.ACTION_SCREEN_OFF);
143 filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
144 context.registerReceiver(mBroadcastReceiver, filter);
146 ConnectivityManager cm = (ConnectivityManager)
147 context.getSystemService(Context.CONNECTIVITY_SERVICE);
148 mHasTelephony = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
150 // get notified of phone state changes
151 TelephonyManager telephonyManager =
152 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
153 telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
154 mContext.getContentResolver().registerContentObserver(
155 Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
156 mAirplaneModeObserver);
157 Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
158 mHasVibrator = vibrator != null && vibrator.hasVibrator();
160 mShowSilentToggle = SHOW_SILENT_TOGGLE && !mContext.getResources().getBoolean(
161 com.android.internal.R.bool.config_useFixedVolume);
165 * Show the global actions dialog (creating if necessary)
166 * @param keyguardShowing True if keyguard is showing
168 public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
169 mKeyguardShowing = keyguardShowing;
170 mDeviceProvisioned = isDeviceProvisioned;
171 if (mDialog != null) {
174 // Show delayed, so that the dismiss of the previous dialog completes
175 mHandler.sendEmptyMessage(MESSAGE_SHOW);
181 private void awakenIfNecessary() {
182 if (mDreamManager != null) {
184 if (mDreamManager.isDreaming()) {
185 mDreamManager.awaken();
187 } catch (RemoteException e) {
193 private void handleShow() {
195 mDialog = createDialog();
198 // If we only have 1 item and it's a simple press action, just do this action.
199 if (mAdapter.getCount() == 1
200 && mAdapter.getItem(0) instanceof SinglePressAction
201 && !(mAdapter.getItem(0) instanceof LongPressAction)) {
202 ((SinglePressAction) mAdapter.getItem(0)).onPress();
204 WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
205 attrs.setTitle("GlobalActions");
206 mDialog.getWindow().setAttributes(attrs);
208 mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND);
213 * Create the global actions dialog.
214 * @return A new dialog.
216 private GlobalActionsDialog createDialog() {
217 // Simple toggle style if there's no vibrator, otherwise use a tri-state
219 mSilentModeAction = new SilentModeToggleAction();
221 mSilentModeAction = new SilentModeTriStateAction(mContext, mAudioManager, mHandler);
223 mAirplaneModeOn = new ToggleAction(
224 R.drawable.ic_lock_airplane_mode,
225 R.drawable.ic_lock_airplane_mode_off,
226 R.string.global_actions_toggle_airplane_mode,
227 R.string.global_actions_airplane_mode_on_status,
228 R.string.global_actions_airplane_mode_off_status) {
230 void onToggle(boolean on) {
231 if (mHasTelephony && Boolean.parseBoolean(
232 SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) {
233 mIsWaitingForEcmExit = true;
234 // Launch ECM exit dialog
235 Intent ecmDialogIntent =
236 new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);
237 ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
238 mContext.startActivity(ecmDialogIntent);
240 changeAirplaneModeSystemSetting(on);
245 protected void changeStateFromPress(boolean buttonOn) {
246 if (!mHasTelephony) return;
248 // In ECM mode airplane state cannot be changed
249 if (!(Boolean.parseBoolean(
250 SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)))) {
251 mState = buttonOn ? State.TurningOn : State.TurningOff;
252 mAirplaneState = mState;
256 public boolean showDuringKeyguard() {
260 public boolean showBeforeProvisioning() {
264 onAirplaneModeChanged();
266 mItems = new ArrayList<Action>();
267 String[] defaultActions = mContext.getResources().getStringArray(
268 com.android.internal.R.array.config_globalActionsList);
270 ArraySet<String> addedKeys = new ArraySet<String>();
271 for (int i = 0; i < defaultActions.length; i++) {
272 String actionKey = defaultActions[i];
273 if (addedKeys.contains(actionKey)) {
274 // If we already have added this, don't add it again.
277 if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) {
278 mItems.add(new PowerAction());
279 } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
280 mItems.add(mAirplaneModeOn);
281 } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
282 if (Settings.Global.getInt(mContext.getContentResolver(),
283 Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) {
284 mItems.add(new BugReportAction());
286 } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) {
287 if (mShowSilentToggle) {
288 mItems.add(mSilentModeAction);
290 } else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) {
291 if (SystemProperties.getBoolean("fw.power_user_switcher", false)) {
292 addUsersToMenu(mItems);
294 } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
295 mItems.add(getSettingsAction());
296 } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
297 mItems.add(getLockdownAction());
298 } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) {
299 mItems.add(getVoiceAssistAction());
300 } else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) {
301 mItems.add(getAssistAction());
302 } else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) {
303 mItems.add(new RestartAction());
305 Log.e(TAG, "Invalid global action key " + actionKey);
307 // Add here so we don't add more than one.
308 addedKeys.add(actionKey);
311 mAdapter = new MyAdapter();
313 AlertParams params = new AlertParams(mContext);
314 params.mAdapter = mAdapter;
315 params.mOnClickListener = this;
316 params.mForceInverseBackground = true;
318 GlobalActionsDialog dialog = new GlobalActionsDialog(mContext, params);
319 dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
321 dialog.getListView().setItemsCanFocus(true);
322 dialog.getListView().setLongClickable(true);
323 dialog.getListView().setOnItemLongClickListener(
324 new AdapterView.OnItemLongClickListener() {
326 public boolean onItemLongClick(AdapterView<?> parent, View view, int position,
328 final Action action = mAdapter.getItem(position);
329 if (action instanceof LongPressAction) {
330 return ((LongPressAction) action).onLongPress();
335 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
337 dialog.setOnDismissListener(this);
342 private final class PowerAction extends SinglePressAction implements LongPressAction {
343 private PowerAction() {
344 super(com.android.internal.R.drawable.ic_lock_power_off,
345 R.string.global_action_power_off);
349 public boolean onLongPress() {
350 UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
351 if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
352 mWindowManagerFuncs.rebootSafeMode(true);
359 public boolean showDuringKeyguard() {
364 public boolean showBeforeProvisioning() {
369 public void onPress() {
370 // shutdown by making sure radio and power are handled accordingly.
371 mWindowManagerFuncs.shutdown(false /* confirm */);
375 private final class RestartAction extends SinglePressAction implements LongPressAction {
376 private RestartAction() {
377 super(R.drawable.ic_restart, R.string.global_action_restart);
381 public boolean onLongPress() {
382 UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
383 if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
384 mWindowManagerFuncs.rebootSafeMode(true);
391 public boolean showDuringKeyguard() {
396 public boolean showBeforeProvisioning() {
401 public void onPress() {
402 mWindowManagerFuncs.reboot(false /* confirm */);
407 private class BugReportAction extends SinglePressAction implements LongPressAction {
409 public BugReportAction() {
410 super(com.android.internal.R.drawable.ic_lock_bugreport, R.string.bugreport_title);
414 public void onPress() {
415 // don't actually trigger the bugreport if we are running stability
417 if (ActivityManager.isUserAMonkey()) {
420 // Add a little delay before executing, to give the
421 // dialog a chance to go away before it takes a
423 mHandler.postDelayed(new Runnable() {
427 // Take an "interactive" bugreport.
428 MetricsLogger.action(mContext,
429 MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE);
430 ActivityManagerNative.getDefault().requestBugReport(
431 ActivityManager.BUGREPORT_OPTION_INTERACTIVE);
432 } catch (RemoteException e) {
439 public boolean onLongPress() {
440 // don't actually trigger the bugreport if we are running stability
442 if (ActivityManager.isUserAMonkey()) {
446 // Take a "full" bugreport.
447 MetricsLogger.action(mContext, MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
448 ActivityManagerNative.getDefault().requestBugReport(
449 ActivityManager.BUGREPORT_OPTION_FULL);
450 } catch (RemoteException e) {
455 public boolean showDuringKeyguard() {
460 public boolean showBeforeProvisioning() {
465 public String getStatus() {
466 return mContext.getString(
467 com.android.internal.R.string.bugreport_status,
468 Build.VERSION.RELEASE,
473 private Action getSettingsAction() {
474 return new SinglePressAction(com.android.internal.R.drawable.ic_settings,
475 R.string.global_action_settings) {
478 public void onPress() {
479 Intent intent = new Intent(Settings.ACTION_SETTINGS);
480 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
481 mContext.startActivity(intent);
485 public boolean showDuringKeyguard() {
490 public boolean showBeforeProvisioning() {
496 private Action getAssistAction() {
497 return new SinglePressAction(com.android.internal.R.drawable.ic_action_assist_focused,
498 R.string.global_action_assist) {
500 public void onPress() {
501 Intent intent = new Intent(Intent.ACTION_ASSIST);
502 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
503 mContext.startActivity(intent);
507 public boolean showDuringKeyguard() {
512 public boolean showBeforeProvisioning() {
518 private Action getVoiceAssistAction() {
519 return new SinglePressAction(com.android.internal.R.drawable.ic_voice_search,
520 R.string.global_action_voice_assist) {
522 public void onPress() {
523 Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST);
524 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
525 mContext.startActivity(intent);
529 public boolean showDuringKeyguard() {
534 public boolean showBeforeProvisioning() {
540 private Action getLockdownAction() {
541 return new SinglePressAction(com.android.internal.R.drawable.ic_lock_lock,
542 R.string.global_action_lockdown) {
545 public void onPress() {
546 new LockPatternUtils(mContext).requireCredentialEntry(UserHandle.USER_ALL);
548 WindowManagerGlobal.getWindowManagerService().lockNow(null);
549 } catch (RemoteException e) {
550 Log.e(TAG, "Error while trying to lock device.", e);
555 public boolean showDuringKeyguard() {
560 public boolean showBeforeProvisioning() {
566 private UserInfo getCurrentUser() {
568 return ActivityManagerNative.getDefault().getCurrentUser();
569 } catch (RemoteException re) {
574 private boolean isCurrentUserOwner() {
575 UserInfo currentUser = getCurrentUser();
576 return currentUser == null || currentUser.isPrimary();
579 private void addUsersToMenu(ArrayList<Action> items) {
580 UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
581 if (um.isUserSwitcherEnabled()) {
582 List<UserInfo> users = um.getUsers();
583 UserInfo currentUser = getCurrentUser();
584 for (final UserInfo user : users) {
585 if (user.supportsSwitchToByUser()) {
586 boolean isCurrentUser = currentUser == null
587 ? user.id == 0 : (currentUser.id == user.id);
588 Drawable icon = user.iconPath != null ? Drawable.createFromPath(user.iconPath)
590 SinglePressAction switchToUser = new SinglePressAction(
591 com.android.internal.R.drawable.ic_menu_cc, icon,
592 (user.name != null ? user.name : "Primary")
593 + (isCurrentUser ? " \u2714" : "")) {
594 public void onPress() {
596 ActivityManagerNative.getDefault().switchUser(user.id);
597 } catch (RemoteException re) {
598 Log.e(TAG, "Couldn't switch user " + re);
602 public boolean showDuringKeyguard() {
606 public boolean showBeforeProvisioning() {
610 items.add(switchToUser);
616 private void prepareDialog() {
618 mAirplaneModeOn.updateState(mAirplaneState);
619 mAdapter.notifyDataSetChanged();
620 mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
621 if (mShowSilentToggle) {
622 IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
623 mContext.registerReceiver(mRingerModeReceiver, filter);
627 private void refreshSilentMode() {
629 final boolean silentModeOn =
630 mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
631 ((ToggleAction)mSilentModeAction).updateState(
632 silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off);
637 public void onDismiss(DialogInterface dialog) {
638 if (mShowSilentToggle) {
640 mContext.unregisterReceiver(mRingerModeReceiver);
641 } catch (IllegalArgumentException ie) {
649 public void onClick(DialogInterface dialog, int which) {
650 if (!(mAdapter.getItem(which) instanceof SilentModeTriStateAction)) {
653 mAdapter.getItem(which).onPress();
657 * The adapter used for the list within the global actions dialog, taking
658 * into account whether the keyguard is showing via
659 * {@link GlobalActions#mKeyguardShowing} and whether the device is provisioned
660 * via {@link GlobalActions#mDeviceProvisioned}.
662 private class MyAdapter extends BaseAdapter {
664 public int getCount() {
667 for (int i = 0; i < mItems.size(); i++) {
668 final Action action = mItems.get(i);
670 if (mKeyguardShowing && !action.showDuringKeyguard()) {
673 if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
682 public boolean isEnabled(int position) {
683 return getItem(position).isEnabled();
687 public boolean areAllItemsEnabled() {
691 public Action getItem(int position) {
694 for (int i = 0; i < mItems.size(); i++) {
695 final Action action = mItems.get(i);
696 if (mKeyguardShowing && !action.showDuringKeyguard()) {
699 if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
702 if (filteredPos == position) {
708 throw new IllegalArgumentException("position " + position
709 + " out of range of showable actions"
710 + ", filtered count=" + getCount()
711 + ", keyguardshowing=" + mKeyguardShowing
712 + ", provisioned=" + mDeviceProvisioned);
716 public long getItemId(int position) {
720 public View getView(int position, View convertView, ViewGroup parent) {
721 Action action = getItem(position);
722 return action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
726 // note: the scheme below made more sense when we were planning on having
727 // 8 different things in the global actions dialog. seems overkill with
728 // only 3 items now, but may as well keep this flexible approach so it will
729 // be easy should someone decide at the last minute to include something
730 // else, such as 'enable wifi', or 'enable bluetooth'
733 * What each item in the global actions dialog must be able to support.
735 private interface Action {
737 * @return Text that will be announced when dialog is created. null
740 CharSequence getLabelForAccessibility(Context context);
742 View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater);
747 * @return whether this action should appear in the dialog when the keygaurd
750 boolean showDuringKeyguard();
753 * @return whether this action should appear in the dialog before the
754 * device is provisioned.
756 boolean showBeforeProvisioning();
762 * An action that also supports long press.
764 private interface LongPressAction extends Action {
765 boolean onLongPress();
769 * A single press action maintains no state, just responds to a press
770 * and takes an action.
772 private static abstract class SinglePressAction implements Action {
773 private final int mIconResId;
774 private final Drawable mIcon;
775 private final int mMessageResId;
776 private final CharSequence mMessage;
778 protected SinglePressAction(int iconResId, int messageResId) {
779 mIconResId = iconResId;
780 mMessageResId = messageResId;
785 protected SinglePressAction(int iconResId, Drawable icon, CharSequence message) {
786 mIconResId = iconResId;
792 public boolean isEnabled() {
796 public String getStatus() {
800 abstract public void onPress();
802 public CharSequence getLabelForAccessibility(Context context) {
803 if (mMessage != null) {
806 return context.getString(mMessageResId);
811 Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
812 View v = inflater.inflate(R.layout.global_actions_item, parent, false);
814 ImageView icon = (ImageView) v.findViewById(R.id.icon);
815 TextView messageView = (TextView) v.findViewById(R.id.message);
817 TextView statusView = (TextView) v.findViewById(R.id.status);
818 final String status = getStatus();
819 if (!TextUtils.isEmpty(status)) {
820 statusView.setText(status);
822 statusView.setVisibility(View.GONE);
825 icon.setImageDrawable(mIcon);
826 icon.setScaleType(ScaleType.CENTER_CROP);
827 } else if (mIconResId != 0) {
828 icon.setImageDrawable(context.getDrawable(mIconResId));
830 if (mMessage != null) {
831 messageView.setText(mMessage);
833 messageView.setText(mMessageResId);
841 * A toggle action knows whether it is on or off, and displays an icon
842 * and status message accordingly.
844 private static abstract class ToggleAction implements Action {
852 private final boolean inTransition;
854 State(boolean intermediate) {
855 inTransition = intermediate;
858 public boolean inTransition() {
863 protected State mState = State.Off;
866 protected int mEnabledIconResId;
867 protected int mDisabledIconResid;
868 protected int mMessageResId;
869 protected int mEnabledStatusMessageResId;
870 protected int mDisabledStatusMessageResId;
873 * @param enabledIconResId The icon for when this action is on.
874 * @param disabledIconResid The icon for when this action is off.
875 * @param essage The general information message, e.g 'Silent Mode'
876 * @param enabledStatusMessageResId The on status message, e.g 'sound disabled'
877 * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled'
879 public ToggleAction(int enabledIconResId,
880 int disabledIconResid,
882 int enabledStatusMessageResId,
883 int disabledStatusMessageResId) {
884 mEnabledIconResId = enabledIconResId;
885 mDisabledIconResid = disabledIconResid;
886 mMessageResId = message;
887 mEnabledStatusMessageResId = enabledStatusMessageResId;
888 mDisabledStatusMessageResId = disabledStatusMessageResId;
892 * Override to make changes to resource IDs just before creating the
900 public CharSequence getLabelForAccessibility(Context context) {
901 return context.getString(mMessageResId);
904 public View create(Context context, View convertView, ViewGroup parent,
905 LayoutInflater inflater) {
908 View v = inflater.inflate(R
909 .layout.global_actions_item, parent, false);
911 ImageView icon = (ImageView) v.findViewById(R.id.icon);
912 TextView messageView = (TextView) v.findViewById(R.id.message);
913 TextView statusView = (TextView) v.findViewById(R.id.status);
914 final boolean enabled = isEnabled();
916 if (messageView != null) {
917 messageView.setText(mMessageResId);
918 messageView.setEnabled(enabled);
921 boolean on = ((mState == State.On) || (mState == State.TurningOn));
923 icon.setImageDrawable(context.getDrawable(
924 (on ? mEnabledIconResId : mDisabledIconResid)));
925 icon.setEnabled(enabled);
928 if (statusView != null) {
929 statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
930 statusView.setVisibility(View.VISIBLE);
931 statusView.setEnabled(enabled);
933 v.setEnabled(enabled);
938 public final void onPress() {
939 if (mState.inTransition()) {
940 Log.w(TAG, "shouldn't be able to toggle when in transition");
944 final boolean nowOn = !(mState == State.On);
946 changeStateFromPress(nowOn);
949 public boolean isEnabled() {
950 return !mState.inTransition();
954 * Implementations may override this if their state can be in on of the intermediate
955 * states until some notification is received (e.g airplane mode is 'turning off' until
956 * we know the wireless connections are back online
957 * @param buttonOn Whether the button was turned on or off
959 protected void changeStateFromPress(boolean buttonOn) {
960 mState = buttonOn ? State.On : State.Off;
963 abstract void onToggle(boolean on);
965 public void updateState(State state) {
970 private class SilentModeToggleAction extends ToggleAction {
971 public SilentModeToggleAction() {
972 super(R.drawable.ic_audio_vol_mute,
973 R.drawable.ic_audio_vol,
974 R.string.global_action_toggle_silent_mode,
975 R.string.global_action_silent_mode_on_status,
976 R.string.global_action_silent_mode_off_status);
979 void onToggle(boolean on) {
981 mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
983 mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
987 public boolean showDuringKeyguard() {
991 public boolean showBeforeProvisioning() {
996 private static class SilentModeTriStateAction implements Action, View.OnClickListener {
998 private final int[] ITEM_IDS = { R.id.option1, R.id.option2, R.id.option3 };
1000 private final AudioManager mAudioManager;
1001 private final Handler mHandler;
1002 private final Context mContext;
1004 SilentModeTriStateAction(Context context, AudioManager audioManager, Handler handler) {
1005 mAudioManager = audioManager;
1010 private int ringerModeToIndex(int ringerMode) {
1011 // They just happen to coincide
1015 private int indexToRingerMode(int index) {
1016 // They just happen to coincide
1021 public CharSequence getLabelForAccessibility(Context context) {
1025 public View create(Context context, View convertView, ViewGroup parent,
1026 LayoutInflater inflater) {
1027 View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false);
1029 int selectedIndex = ringerModeToIndex(mAudioManager.getRingerMode());
1030 for (int i = 0; i < 3; i++) {
1031 View itemView = v.findViewById(ITEM_IDS[i]);
1032 itemView.setSelected(selectedIndex == i);
1033 // Set up click handler
1035 itemView.setOnClickListener(this);
1040 public void onPress() {
1043 public boolean showDuringKeyguard() {
1047 public boolean showBeforeProvisioning() {
1051 public boolean isEnabled() {
1058 public void onClick(View v) {
1059 if (!(v.getTag() instanceof Integer)) return;
1061 int index = (Integer) v.getTag();
1062 mAudioManager.setRingerMode(indexToRingerMode(index));
1063 mHandler.sendEmptyMessageDelayed(MESSAGE_DISMISS, DIALOG_DISMISS_DELAY);
1067 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
1068 public void onReceive(Context context, Intent intent) {
1069 String action = intent.getAction();
1070 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
1071 || Intent.ACTION_SCREEN_OFF.equals(action)) {
1072 String reason = intent.getStringExtra(PhoneWindowManager.SYSTEM_DIALOG_REASON_KEY);
1073 if (!PhoneWindowManager.SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) {
1074 mHandler.sendEmptyMessage(MESSAGE_DISMISS);
1076 } else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) {
1077 // Airplane mode can be changed after ECM exits if airplane toggle button
1078 // is pressed during ECM mode
1079 if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) &&
1080 mIsWaitingForEcmExit) {
1081 mIsWaitingForEcmExit = false;
1082 changeAirplaneModeSystemSetting(true);
1088 PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
1090 public void onServiceStateChanged(ServiceState serviceState) {
1091 if (!mHasTelephony) return;
1092 final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF;
1093 mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off;
1094 mAirplaneModeOn.updateState(mAirplaneState);
1095 mAdapter.notifyDataSetChanged();
1099 private BroadcastReceiver mRingerModeReceiver = new BroadcastReceiver() {
1101 public void onReceive(Context context, Intent intent) {
1102 if (intent.getAction().equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
1103 mHandler.sendEmptyMessage(MESSAGE_REFRESH);
1108 private ContentObserver mAirplaneModeObserver = new ContentObserver(new Handler()) {
1110 public void onChange(boolean selfChange) {
1111 onAirplaneModeChanged();
1115 private static final int MESSAGE_DISMISS = 0;
1116 private static final int MESSAGE_REFRESH = 1;
1117 private static final int MESSAGE_SHOW = 2;
1118 private static final int DIALOG_DISMISS_DELAY = 300; // ms
1120 private Handler mHandler = new Handler() {
1121 public void handleMessage(Message msg) {
1123 case MESSAGE_DISMISS:
1124 if (mDialog != null) {
1129 case MESSAGE_REFRESH:
1130 refreshSilentMode();
1131 mAdapter.notifyDataSetChanged();
1140 private void onAirplaneModeChanged() {
1141 // Let the service state callbacks handle the state.
1142 if (mHasTelephony) return;
1144 boolean airplaneModeOn = Settings.Global.getInt(
1145 mContext.getContentResolver(),
1146 Settings.Global.AIRPLANE_MODE_ON,
1148 mAirplaneState = airplaneModeOn ? ToggleAction.State.On : ToggleAction.State.Off;
1149 mAirplaneModeOn.updateState(mAirplaneState);
1153 * Change the airplane mode system setting
1155 private void changeAirplaneModeSystemSetting(boolean on) {
1156 Settings.Global.putInt(
1157 mContext.getContentResolver(),
1158 Settings.Global.AIRPLANE_MODE_ON,
1160 Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
1161 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
1162 intent.putExtra("state", on);
1163 mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
1164 if (!mHasTelephony) {
1165 mAirplaneState = on ? ToggleAction.State.On : ToggleAction.State.Off;
1169 private static final class GlobalActionsDialog extends Dialog implements DialogInterface {
1170 private final Context mContext;
1171 private final int mWindowTouchSlop;
1172 private final AlertController mAlert;
1173 private final MyAdapter mAdapter;
1175 private EnableAccessibilityController mEnableAccessibilityController;
1177 private boolean mIntercepted;
1178 private boolean mCancelOnUp;
1180 public GlobalActionsDialog(Context context, AlertParams params) {
1181 super(context, getDialogTheme(context));
1182 mContext = getContext();
1183 mAlert = AlertController.create(mContext, this, getWindow());
1184 mAdapter = (MyAdapter) params.mAdapter;
1185 mWindowTouchSlop = ViewConfiguration.get(context).getScaledWindowTouchSlop();
1186 params.apply(mAlert);
1189 private static int getDialogTheme(Context context) {
1190 TypedValue outValue = new TypedValue();
1191 context.getTheme().resolveAttribute(com.android.internal.R.attr.alertDialogTheme,
1193 return outValue.resourceId;
1197 protected void onStart() {
1198 // If global accessibility gesture can be performed, we will take care
1199 // of dismissing the dialog on touch outside. This is because the dialog
1200 // is dismissed on the first down while the global gesture is a long press
1201 // with two fingers anywhere on the screen.
1202 if (EnableAccessibilityController.canEnableAccessibilityViaGesture(mContext)) {
1203 mEnableAccessibilityController = new EnableAccessibilityController(mContext,
1210 super.setCanceledOnTouchOutside(false);
1212 mEnableAccessibilityController = null;
1213 super.setCanceledOnTouchOutside(true);
1220 protected void onStop() {
1221 if (mEnableAccessibilityController != null) {
1222 mEnableAccessibilityController.onDestroy();
1228 public boolean dispatchTouchEvent(MotionEvent event) {
1229 if (mEnableAccessibilityController != null) {
1230 final int action = event.getActionMasked();
1231 if (action == MotionEvent.ACTION_DOWN) {
1232 View decor = getWindow().getDecorView();
1233 final int eventX = (int) event.getX();
1234 final int eventY = (int) event.getY();
1235 if (eventX < -mWindowTouchSlop
1236 || eventY < -mWindowTouchSlop
1237 || eventX >= decor.getWidth() + mWindowTouchSlop
1238 || eventY >= decor.getHeight() + mWindowTouchSlop) {
1243 if (!mIntercepted) {
1244 mIntercepted = mEnableAccessibilityController.onInterceptTouchEvent(event);
1246 final long now = SystemClock.uptimeMillis();
1247 event = MotionEvent.obtain(now, now,
1248 MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
1249 event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
1253 return mEnableAccessibilityController.onTouchEvent(event);
1256 if (action == MotionEvent.ACTION_UP) {
1260 mCancelOnUp = false;
1261 mIntercepted = false;
1265 return super.dispatchTouchEvent(event);
1268 public ListView getListView() {
1269 return mAlert.getListView();
1273 protected void onCreate(Bundle savedInstanceState) {
1274 super.onCreate(savedInstanceState);
1275 mAlert.installContent();
1279 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
1280 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
1281 for (int i = 0; i < mAdapter.getCount(); ++i) {
1282 CharSequence label =
1283 mAdapter.getItem(i).getLabelForAccessibility(getContext());
1284 if (label != null) {
1285 event.getText().add(label);
1289 return super.dispatchPopulateAccessibilityEvent(event);
1293 public boolean onKeyDown(int keyCode, KeyEvent event) {
1294 if (mAlert.onKeyDown(keyCode, event)) {
1297 return super.onKeyDown(keyCode, event);
1301 public boolean onKeyUp(int keyCode, KeyEvent event) {
1302 if (mAlert.onKeyUp(keyCode, event)) {
1305 return super.onKeyUp(keyCode, event);