2 * Copyright (C) 2008 The Android Open Source Project
3 * Copyright (C) 2010-2015 CyanogenMod Project
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
18 package com.android.server.policy;
20 import com.android.internal.app.AlertController;
21 import com.android.internal.app.AlertController.AlertParams;
22 import com.android.internal.logging.MetricsLogger;
23 import com.android.internal.logging.MetricsProto.MetricsEvent;
24 import com.android.internal.telephony.TelephonyIntents;
25 import com.android.internal.telephony.TelephonyProperties;
26 import com.android.internal.R;
27 import com.android.internal.widget.LockPatternUtils;
29 import android.app.ActivityManager;
30 import android.app.ActivityManagerNative;
31 import android.app.AlertDialog;
32 import android.app.Dialog;
33 import android.content.BroadcastReceiver;
34 import android.content.ComponentName;
35 import android.content.Context;
36 import android.content.ContentResolver;
37 import android.content.DialogInterface;
38 import android.content.Intent;
39 import android.content.IntentFilter;
40 import android.content.pm.UserInfo;
41 import android.content.ServiceConnection;
42 import android.database.ContentObserver;
43 import android.graphics.drawable.Drawable;
44 import android.Manifest;
45 import android.media.AudioManager;
46 import android.net.ConnectivityManager;
47 import android.os.Build;
48 import android.os.Bundle;
49 import android.os.Handler;
50 import android.os.IBinder;
51 import android.os.IPowerManager;
52 import android.os.Message;
53 import android.os.Messenger;
54 import android.os.RemoteException;
55 import android.os.ServiceManager;
56 import android.os.SystemClock;
57 import android.os.SystemProperties;
58 import android.os.UserHandle;
59 import android.os.UserManager;
60 import android.os.Vibrator;
61 import android.provider.Settings;
62 import android.service.dreams.DreamService;
63 import android.service.dreams.IDreamManager;
64 import android.telephony.PhoneStateListener;
65 import android.telephony.ServiceState;
66 import android.telephony.TelephonyManager;
67 import android.text.TextUtils;
68 import android.util.ArraySet;
69 import android.util.Log;
70 import android.util.TypedValue;
71 import android.view.InputDevice;
72 import android.view.KeyEvent;
73 import android.view.LayoutInflater;
74 import android.view.MotionEvent;
75 import android.view.View;
76 import android.view.ViewConfiguration;
77 import android.view.ViewGroup;
78 import android.view.WindowManager;
79 import android.view.WindowManagerGlobal;
80 import android.view.WindowManagerPolicy.WindowManagerFuncs;
81 import android.view.accessibility.AccessibilityEvent;
82 import android.widget.AdapterView;
83 import android.widget.BaseAdapter;
84 import android.widget.ImageView;
85 import android.widget.ImageView.ScaleType;
86 import android.widget.ListView;
87 import android.widget.TextView;
88 import cyanogenmod.providers.CMSettings;
90 import java.util.ArrayList;
91 import java.util.List;
92 import java.util.UUID;
94 import static com.android.internal.util.cm.PowerMenuConstants.*;
97 * Helper to show the global actions dialog. Each item is an {@link Action} that
98 * may show depending on whether the keyguard is showing, and whether the device
101 class GlobalActions implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
103 private static final String TAG = "GlobalActions";
105 private static final boolean SHOW_SILENT_TOGGLE = true;
107 private final Context mContext;
108 private final WindowManagerFuncs mWindowManagerFuncs;
109 private final AudioManager mAudioManager;
110 private final IDreamManager mDreamManager;
112 private ArrayList<Action> mItems;
113 private GlobalActionsDialog mDialog;
115 private Action mSilentModeAction;
116 private ToggleAction mAirplaneModeOn;
118 private MyAdapter mAdapter;
120 private boolean mKeyguardShowing = false;
121 private boolean mDeviceProvisioned = false;
122 private ToggleAction.State mAirplaneState = ToggleAction.State.Off;
123 private boolean mIsWaitingForEcmExit = false;
124 private boolean mHasTelephony;
125 private boolean mHasVibrator;
126 private final boolean mShowSilentToggle;
128 // Power menu customizations
132 * @param context everything needs a context :(
134 public GlobalActions(Context context, WindowManagerFuncs windowManagerFuncs) {
136 mWindowManagerFuncs = windowManagerFuncs;
137 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
138 mDreamManager = IDreamManager.Stub.asInterface(
139 ServiceManager.getService(DreamService.DREAM_SERVICE));
141 // receive broadcasts
142 IntentFilter filter = new IntentFilter();
143 filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
144 filter.addAction(Intent.ACTION_SCREEN_OFF);
145 filter.addAction(Intent.UPDATE_POWER_MENU);
146 filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
147 context.registerReceiver(mBroadcastReceiver, filter);
149 ConnectivityManager cm = (ConnectivityManager)
150 context.getSystemService(Context.CONNECTIVITY_SERVICE);
151 mHasTelephony = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
153 // get notified of phone state changes
154 TelephonyManager telephonyManager =
155 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
156 telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
157 mContext.getContentResolver().registerContentObserver(
158 Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
159 mAirplaneModeObserver);
160 Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
161 mHasVibrator = vibrator != null && vibrator.hasVibrator();
163 mShowSilentToggle = SHOW_SILENT_TOGGLE && !mContext.getResources().getBoolean(
164 com.android.internal.R.bool.config_useFixedVolume);
166 // Set the initial status of airplane mode toggle
167 mAirplaneState = getUpdatedAirplaneToggleState();
169 updatePowerMenuActions();
173 * Show the global actions dialog (creating if necessary)
174 * @param keyguardShowing True if keyguard is showing
176 public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
177 mKeyguardShowing = keyguardShowing;
178 mDeviceProvisioned = isDeviceProvisioned;
179 if (mDialog != null) {
182 mDialog = createDialog();
183 // Show delayed, so that the dismiss of the previous dialog completes
184 mHandler.sendEmptyMessage(MESSAGE_SHOW);
186 mDialog = createDialog();
191 private void awakenIfNecessary() {
192 if (mDreamManager != null) {
194 if (mDreamManager.isDreaming()) {
195 mDreamManager.awaken();
197 } catch (RemoteException e) {
203 private void handleShow() {
207 // If we only have 1 item and it's a simple press action, just do this action.
208 if (mAdapter.getCount() == 1
209 && mAdapter.getItem(0) instanceof SinglePressAction
210 && !(mAdapter.getItem(0) instanceof LongPressAction)) {
211 ((SinglePressAction) mAdapter.getItem(0)).onPress();
213 WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
214 attrs.setTitle("GlobalActions");
215 mDialog.getWindow().setAttributes(attrs);
217 mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND);
222 * Create the global actions dialog.
223 * @return A new dialog.
225 private GlobalActionsDialog createDialog() {
226 // Simple toggle style if there's no vibrator, otherwise use a tri-state
228 mSilentModeAction = new SilentModeToggleAction();
230 mSilentModeAction = new SilentModeTriStateAction(mContext, mAudioManager, mHandler);
232 mAirplaneModeOn = new ToggleAction(
233 R.drawable.ic_lock_airplane_mode,
234 R.drawable.ic_lock_airplane_mode_off,
235 R.string.global_actions_toggle_airplane_mode,
236 R.string.global_actions_airplane_mode_on_status,
237 R.string.global_actions_airplane_mode_off_status) {
239 void onToggle(boolean on) {
240 if (mHasTelephony && Boolean.parseBoolean(
241 SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) {
242 mIsWaitingForEcmExit = true;
243 // Launch ECM exit dialog
244 Intent ecmDialogIntent =
245 new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);
246 ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
247 mContext.startActivity(ecmDialogIntent);
249 changeAirplaneModeSystemSetting(on);
254 protected void changeStateFromPress(boolean buttonOn) {
255 if (!mHasTelephony) return;
257 // In ECM mode airplane state cannot be changed
258 if (!(Boolean.parseBoolean(
259 SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)))) {
260 mState = buttonOn ? State.TurningOn : State.TurningOff;
261 mAirplaneState = mState;
265 public boolean showDuringKeyguard() {
269 public boolean showBeforeProvisioning() {
273 onAirplaneModeChanged();
275 mItems = new ArrayList<Action>();
277 String[] actionsArray;
278 if (mActions == null) {
279 actionsArray = mContext.getResources().getStringArray(
280 com.android.internal.R.array.config_globalActionsList);
282 actionsArray = mActions.split("\\|");
285 // Always add the power off option
286 mItems.add(new PowerAction());
288 ArraySet<String> addedKeys = new ArraySet<String>();
289 for (int i = 0; i < actionsArray.length; i++) {
290 String actionKey = actionsArray[i];
291 if (addedKeys.contains(actionKey)) {
292 // If we already have added this, don't add it again.
295 if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) {
297 } else if (GLOBAL_ACTION_KEY_REBOOT.equals(actionKey)) {
298 mItems.add(new RebootAction());
299 } else if (GLOBAL_ACTION_KEY_SCREENSHOT.equals(actionKey)) {
300 mItems.add(getScreenshotAction());
301 } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
302 mItems.add(mAirplaneModeOn);
303 } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
304 if (Settings.Global.getInt(mContext.getContentResolver(),
305 Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) {
306 mItems.add(new BugReportAction());
308 } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) {
309 if (mShowSilentToggle) {
310 mItems.add(mSilentModeAction);
312 } else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) {
313 List<UserInfo> users = ((UserManager) mContext.getSystemService(
314 Context.USER_SERVICE)).getUsers();
315 if (users.size() > 1) {
316 addUsersToMenu(mItems);
318 } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
319 mItems.add(getSettingsAction());
320 } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
321 mItems.add(getLockdownAction());
322 } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) {
323 mItems.add(getVoiceAssistAction());
324 } else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) {
325 mItems.add(getAssistAction());
327 Log.e(TAG, "Invalid global action key " + actionKey);
329 // Add here so we don't add more than one.
330 addedKeys.add(actionKey);
333 mAdapter = new MyAdapter();
335 AlertParams params = new AlertParams(mContext);
336 params.mAdapter = mAdapter;
337 params.mOnClickListener = this;
338 params.mForceInverseBackground = true;
340 GlobalActionsDialog dialog = new GlobalActionsDialog(mContext, params);
341 dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
343 dialog.getListView().setItemsCanFocus(true);
344 dialog.getListView().setLongClickable(true);
345 dialog.getListView().setOnItemLongClickListener(
346 new AdapterView.OnItemLongClickListener() {
348 public boolean onItemLongClick(AdapterView<?> parent, View view, int position,
350 final Action action = mAdapter.getItem(position);
351 if (action instanceof LongPressAction) {
352 return ((LongPressAction) action).onLongPress();
357 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
359 dialog.setOnDismissListener(this);
364 private final class PowerAction extends SinglePressAction implements LongPressAction {
365 private PowerAction() {
366 super(com.android.internal.R.drawable.ic_lock_power_off,
367 R.string.global_action_power_off);
371 public boolean onLongPress() {
372 UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
373 if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
374 mWindowManagerFuncs.rebootSafeMode(true);
381 public boolean showDuringKeyguard() {
386 public boolean showBeforeProvisioning() {
391 public void onPress() {
392 // shutdown by making sure radio and power are handled accordingly.
393 mWindowManagerFuncs.shutdown(false /* confirm */);
397 private final class RebootAction extends SinglePressAction {
398 private RebootAction() {
399 super(com.android.internal.R.drawable.ic_lock_power_reboot,
400 R.string.global_action_reboot);
404 public boolean showDuringKeyguard() {
409 public boolean showBeforeProvisioning() {
414 public void onPress() {
416 IPowerManager pm = IPowerManager.Stub.asInterface(ServiceManager
417 .getService(Context.POWER_SERVICE));
418 pm.reboot(true, null, false);
419 } catch (RemoteException e) {
420 Log.e(TAG, "PowerManager service died!", e);
426 private Action getScreenshotAction() {
427 return new SinglePressAction(com.android.internal.R.drawable.ic_lock_screenshot,
428 R.string.global_action_screenshot) {
430 public void onPress() {
434 public boolean showDuringKeyguard() {
438 public boolean showBeforeProvisioning() {
444 private class BugReportAction extends SinglePressAction implements LongPressAction {
445 public BugReportAction() {
446 super(com.android.internal.R.drawable.ic_lock_bugreport, R.string.bugreport_title);
450 public void onPress() {
451 // don't actually trigger the bugreport if we are running stability
453 if (ActivityManager.isUserAMonkey()) {
456 // Add a little delay before executing, to give the
457 // dialog a chance to go away before it takes a
459 mHandler.postDelayed(new Runnable() {
463 // Take an "interactive" bugreport.
464 MetricsLogger.action(mContext,
465 MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE);
466 ActivityManagerNative.getDefault().requestBugReport(
467 ActivityManager.BUGREPORT_OPTION_INTERACTIVE);
468 } catch (RemoteException e) {
475 public boolean onLongPress() {
476 // don't actually trigger the bugreport if we are running stability
478 if (ActivityManager.isUserAMonkey()) {
482 // Take a "full" bugreport.
483 MetricsLogger.action(mContext, MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
484 ActivityManagerNative.getDefault().requestBugReport(
485 ActivityManager.BUGREPORT_OPTION_FULL);
486 } catch (RemoteException e) {
491 public boolean showDuringKeyguard() {
496 public boolean showBeforeProvisioning() {
501 public String getStatus() {
502 return mContext.getString(
503 com.android.internal.R.string.bugreport_status,
504 Build.VERSION.RELEASE,
509 private Action getSettingsAction() {
510 return new SinglePressAction(com.android.internal.R.drawable.ic_lock_settings,
511 R.string.global_action_settings) {
514 public void onPress() {
515 Intent intent = new Intent(Settings.ACTION_SETTINGS);
516 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
517 mContext.startActivity(intent);
521 public boolean showDuringKeyguard() {
526 public boolean showBeforeProvisioning() {
532 private Action getAssistAction() {
533 return new SinglePressAction(com.android.internal.R.drawable.ic_action_assist_focused,
534 R.string.global_action_assist) {
536 public void onPress() {
537 Intent intent = new Intent(Intent.ACTION_ASSIST);
538 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
539 mContext.startActivity(intent);
543 public boolean showDuringKeyguard() {
548 public boolean showBeforeProvisioning() {
554 private Action getVoiceAssistAction() {
555 return new SinglePressAction(com.android.internal.R.drawable.ic_voice_search,
556 R.string.global_action_voice_assist) {
558 public void onPress() {
559 Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST);
560 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
561 mContext.startActivity(intent);
565 public boolean showDuringKeyguard() {
570 public boolean showBeforeProvisioning() {
576 private Action getLockdownAction() {
577 return new SinglePressAction(com.android.internal.R.drawable.ic_lock_lock,
578 R.string.global_action_lockdown) {
581 public void onPress() {
582 new LockPatternUtils(mContext).requireCredentialEntry(UserHandle.USER_ALL);
584 WindowManagerGlobal.getWindowManagerService().lockNow(null);
585 } catch (RemoteException e) {
586 Log.e(TAG, "Error while trying to lock device.", e);
591 public boolean showDuringKeyguard() {
596 public boolean showBeforeProvisioning() {
602 private UserInfo getCurrentUser() {
604 return ActivityManagerNative.getDefault().getCurrentUser();
605 } catch (RemoteException re) {
610 private boolean isCurrentUserOwner() {
611 UserInfo currentUser = getCurrentUser();
612 return currentUser == null || currentUser.isPrimary();
615 private void addUsersToMenu(ArrayList<Action> items) {
616 UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
617 if (um.isUserSwitcherEnabled()) {
618 List<UserInfo> users = um.getUsers();
619 UserInfo currentUser = getCurrentUser();
620 for (final UserInfo user : users) {
621 if (user.supportsSwitchToByUser()) {
622 boolean isCurrentUser = currentUser == null
623 ? user.id == 0 : (currentUser.id == user.id);
624 Drawable icon = user.iconPath != null ? Drawable.createFromPath(user.iconPath)
626 SinglePressAction switchToUser = new SinglePressAction(
627 com.android.internal.R.drawable.ic_lock_user, icon,
628 (user.name != null ? user.name : "Primary")
629 + (isCurrentUser ? " \u2714" : "")) {
630 public void onPress() {
632 ActivityManagerNative.getDefault().switchUser(user.id);
633 } catch (RemoteException re) {
634 Log.e(TAG, "Couldn't switch user " + re);
638 public boolean showDuringKeyguard() {
642 public boolean showBeforeProvisioning() {
646 items.add(switchToUser);
653 * functions needed for taking screenhots.
654 * This leverages the built in ICS screenshot functionality
656 final Object mScreenshotLock = new Object();
657 ServiceConnection mScreenshotConnection = null;
659 final Runnable mScreenshotTimeout = new Runnable() {
660 @Override public void run() {
661 synchronized (mScreenshotLock) {
662 if (mScreenshotConnection != null) {
663 mContext.unbindService(mScreenshotConnection);
664 mScreenshotConnection = null;
670 private void takeScreenshot() {
671 synchronized (mScreenshotLock) {
672 if (mScreenshotConnection != null) {
675 ComponentName cn = new ComponentName("com.android.systemui",
676 "com.android.systemui.screenshot.TakeScreenshotService");
677 Intent intent = new Intent();
678 intent.setComponent(cn);
679 ServiceConnection conn = new ServiceConnection() {
681 public void onServiceConnected(ComponentName name, IBinder service) {
682 synchronized (mScreenshotLock) {
683 if (mScreenshotConnection != this) {
686 Messenger messenger = new Messenger(service);
687 Message msg = Message.obtain(null, 1);
688 final ServiceConnection myConn = this;
689 Handler h = new Handler(mHandler.getLooper()) {
691 public void handleMessage(Message msg) {
692 synchronized (mScreenshotLock) {
693 if (mScreenshotConnection == myConn) {
694 mContext.unbindService(mScreenshotConnection);
695 mScreenshotConnection = null;
696 mHandler.removeCallbacks(mScreenshotTimeout);
701 msg.replyTo = new Messenger(h);
702 msg.arg1 = msg.arg2 = 0;
704 /* remove for the time being
705 if (mStatusBar != null && mStatusBar.isVisibleLw())
707 if (mNavigationBar != null && mNavigationBar.isVisibleLw())
711 /* wait for the dialog box to close */
714 } catch (InterruptedException ie) {
718 /* take the screenshot */
721 } catch (RemoteException e) {
727 public void onServiceDisconnected(ComponentName name) {}
729 if (mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE)) {
730 mScreenshotConnection = conn;
731 mHandler.postDelayed(mScreenshotTimeout, 10000);
736 private void prepareDialog() {
738 mAirplaneModeOn.updateState(mAirplaneState);
739 mAdapter.notifyDataSetChanged();
740 mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
741 if (mShowSilentToggle) {
742 IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
743 mContext.registerReceiver(mRingerModeReceiver, filter);
747 private void refreshSilentMode() {
749 final boolean silentModeOn =
750 mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
751 ((ToggleAction)mSilentModeAction).updateState(
752 silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off);
757 public void onDismiss(DialogInterface dialog) {
758 if (mShowSilentToggle) {
760 mContext.unregisterReceiver(mRingerModeReceiver);
761 } catch (IllegalArgumentException ie) {
769 public void onClick(DialogInterface dialog, int which) {
770 if (!(mAdapter.getItem(which) instanceof SilentModeTriStateAction)) {
773 mAdapter.getItem(which).onPress();
777 * The adapter used for the list within the global actions dialog, taking
778 * into account whether the keyguard is showing via
779 * {@link GlobalActions#mKeyguardShowing} and whether the device is provisioned
780 * via {@link GlobalActions#mDeviceProvisioned}.
782 private class MyAdapter extends BaseAdapter {
784 public int getCount() {
787 for (int i = 0; i < mItems.size(); i++) {
788 final Action action = mItems.get(i);
790 if (mKeyguardShowing && !action.showDuringKeyguard()) {
793 if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
802 public boolean isEnabled(int position) {
803 return getItem(position).isEnabled();
807 public boolean areAllItemsEnabled() {
811 public Action getItem(int position) {
814 for (int i = 0; i < mItems.size(); i++) {
815 final Action action = mItems.get(i);
816 if (mKeyguardShowing && !action.showDuringKeyguard()) {
819 if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
822 if (filteredPos == position) {
828 throw new IllegalArgumentException("position " + position
829 + " out of range of showable actions"
830 + ", filtered count=" + getCount()
831 + ", keyguardshowing=" + mKeyguardShowing
832 + ", provisioned=" + mDeviceProvisioned);
836 public long getItemId(int position) {
840 public View getView(int position, View convertView, ViewGroup parent) {
841 Action action = getItem(position);
842 return action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
846 // note: the scheme below made more sense when we were planning on having
847 // 8 different things in the global actions dialog. seems overkill with
848 // only 3 items now, but may as well keep this flexible approach so it will
849 // be easy should someone decide at the last minute to include something
850 // else, such as 'enable wifi', or 'enable bluetooth'
853 * What each item in the global actions dialog must be able to support.
855 private interface Action {
857 * @return Text that will be announced when dialog is created. null
860 CharSequence getLabelForAccessibility(Context context);
862 View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater);
867 * @return whether this action should appear in the dialog when the keygaurd
870 boolean showDuringKeyguard();
873 * @return whether this action should appear in the dialog before the
874 * device is provisioned.
876 boolean showBeforeProvisioning();
882 * An action that also supports long press.
884 private interface LongPressAction extends Action {
885 boolean onLongPress();
889 * A single press action maintains no state, just responds to a press
890 * and takes an action.
892 private static abstract class SinglePressAction implements Action {
893 private final int mIconResId;
894 private final Drawable mIcon;
895 private final int mMessageResId;
896 private final CharSequence mMessage;
898 protected SinglePressAction(int iconResId, int messageResId) {
899 mIconResId = iconResId;
900 mMessageResId = messageResId;
905 protected SinglePressAction(int iconResId, Drawable icon, CharSequence message) {
906 mIconResId = iconResId;
912 public boolean isEnabled() {
916 public String getStatus() {
920 abstract public void onPress();
922 public CharSequence getLabelForAccessibility(Context context) {
923 if (mMessage != null) {
926 return context.getString(mMessageResId);
931 Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
932 View v = inflater.inflate(R.layout.global_actions_item, parent, false);
934 ImageView icon = (ImageView) v.findViewById(R.id.icon);
935 TextView messageView = (TextView) v.findViewById(R.id.message);
937 TextView statusView = (TextView) v.findViewById(R.id.status);
938 final String status = getStatus();
939 if (!TextUtils.isEmpty(status)) {
940 statusView.setText(status);
942 statusView.setVisibility(View.GONE);
945 icon.setImageDrawable(mIcon);
946 icon.setScaleType(ScaleType.CENTER_CROP);
947 } else if (mIconResId != 0) {
948 icon.setImageDrawable(context.getDrawable(mIconResId));
950 if (mMessage != null) {
951 messageView.setText(mMessage);
953 messageView.setText(mMessageResId);
961 * A toggle action knows whether it is on or off, and displays an icon
962 * and status message accordingly.
964 private static abstract class ToggleAction implements Action {
972 private final boolean inTransition;
974 State(boolean intermediate) {
975 inTransition = intermediate;
978 public boolean inTransition() {
983 protected State mState = State.Off;
986 protected int mEnabledIconResId;
987 protected int mDisabledIconResid;
988 protected int mMessageResId;
989 protected int mEnabledStatusMessageResId;
990 protected int mDisabledStatusMessageResId;
993 * @param enabledIconResId The icon for when this action is on.
994 * @param disabledIconResid The icon for when this action is off.
995 * @param essage The general information message, e.g 'Silent Mode'
996 * @param enabledStatusMessageResId The on status message, e.g 'sound disabled'
997 * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled'
999 public ToggleAction(int enabledIconResId,
1000 int disabledIconResid,
1002 int enabledStatusMessageResId,
1003 int disabledStatusMessageResId) {
1004 mEnabledIconResId = enabledIconResId;
1005 mDisabledIconResid = disabledIconResid;
1006 mMessageResId = message;
1007 mEnabledStatusMessageResId = enabledStatusMessageResId;
1008 mDisabledStatusMessageResId = disabledStatusMessageResId;
1012 * Override to make changes to resource IDs just before creating the
1020 public CharSequence getLabelForAccessibility(Context context) {
1021 return context.getString(mMessageResId);
1024 public View create(Context context, View convertView, ViewGroup parent,
1025 LayoutInflater inflater) {
1028 View v = inflater.inflate(R
1029 .layout.global_actions_item, parent, false);
1031 ImageView icon = (ImageView) v.findViewById(R.id.icon);
1032 TextView messageView = (TextView) v.findViewById(R.id.message);
1033 TextView statusView = (TextView) v.findViewById(R.id.status);
1034 final boolean enabled = isEnabled();
1036 if (messageView != null) {
1037 messageView.setText(mMessageResId);
1038 messageView.setEnabled(enabled);
1041 boolean on = ((mState == State.On) || (mState == State.TurningOn));
1043 icon.setImageDrawable(context.getDrawable(
1044 (on ? mEnabledIconResId : mDisabledIconResid)));
1045 icon.setEnabled(enabled);
1048 if (statusView != null) {
1049 statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
1050 statusView.setVisibility(View.VISIBLE);
1051 statusView.setEnabled(enabled);
1053 v.setEnabled(enabled);
1058 public final void onPress() {
1059 if (mState.inTransition()) {
1060 Log.w(TAG, "shouldn't be able to toggle when in transition");
1064 final boolean nowOn = !(mState == State.On);
1066 changeStateFromPress(nowOn);
1069 public boolean isEnabled() {
1070 return !mState.inTransition();
1074 * Implementations may override this if their state can be in on of the intermediate
1075 * states until some notification is received (e.g airplane mode is 'turning off' until
1076 * we know the wireless connections are back online
1077 * @param buttonOn Whether the button was turned on or off
1079 protected void changeStateFromPress(boolean buttonOn) {
1080 mState = buttonOn ? State.On : State.Off;
1083 abstract void onToggle(boolean on);
1085 public void updateState(State state) {
1090 private class SilentModeToggleAction extends ToggleAction {
1091 public SilentModeToggleAction() {
1092 super(R.drawable.ic_audio_vol_mute,
1093 R.drawable.ic_audio_vol,
1094 R.string.global_action_toggle_silent_mode,
1095 R.string.global_action_silent_mode_on_status,
1096 R.string.global_action_silent_mode_off_status);
1099 void onToggle(boolean on) {
1101 mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
1103 mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
1107 public boolean showDuringKeyguard() {
1111 public boolean showBeforeProvisioning() {
1116 private static class SilentModeTriStateAction implements Action, View.OnClickListener {
1118 private final int[] ITEM_IDS = { R.id.option1, R.id.option2, R.id.option3 };
1120 private final AudioManager mAudioManager;
1121 private final Handler mHandler;
1122 private final Context mContext;
1124 SilentModeTriStateAction(Context context, AudioManager audioManager, Handler handler) {
1125 mAudioManager = audioManager;
1130 private int ringerModeToIndex(int ringerMode) {
1131 // They just happen to coincide
1135 private int indexToRingerMode(int index) {
1136 // They just happen to coincide
1141 public CharSequence getLabelForAccessibility(Context context) {
1145 public View create(Context context, View convertView, ViewGroup parent,
1146 LayoutInflater inflater) {
1147 View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false);
1149 int selectedIndex = ringerModeToIndex(mAudioManager.getRingerMode());
1150 for (int i = 0; i < 3; i++) {
1151 View itemView = v.findViewById(ITEM_IDS[i]);
1152 itemView.setSelected(selectedIndex == i);
1153 // Set up click handler
1155 itemView.setOnClickListener(this);
1160 public void onPress() {
1163 public boolean showDuringKeyguard() {
1167 public boolean showBeforeProvisioning() {
1171 public boolean isEnabled() {
1178 public void onClick(View v) {
1179 if (!(v.getTag() instanceof Integer)) return;
1181 int index = (Integer) v.getTag();
1182 mAudioManager.setRingerMode(indexToRingerMode(index));
1183 mHandler.sendEmptyMessageDelayed(MESSAGE_DISMISS, DIALOG_DISMISS_DELAY);
1187 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
1188 public void onReceive(Context context, Intent intent) {
1189 String action = intent.getAction();
1190 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
1191 || Intent.ACTION_SCREEN_OFF.equals(action)) {
1192 String reason = intent.getStringExtra(PhoneWindowManager.SYSTEM_DIALOG_REASON_KEY);
1193 if (!PhoneWindowManager.SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) {
1194 mHandler.sendEmptyMessage(MESSAGE_DISMISS);
1196 } else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) {
1197 // Airplane mode can be changed after ECM exits if airplane toggle button
1198 // is pressed during ECM mode
1199 if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) &&
1200 mIsWaitingForEcmExit) {
1201 mIsWaitingForEcmExit = false;
1202 changeAirplaneModeSystemSetting(true);
1204 } else if (Intent.UPDATE_POWER_MENU.equals(action)) {
1205 updatePowerMenuActions();
1210 protected void updatePowerMenuActions() {
1211 ContentResolver resolver = mContext.getContentResolver();
1212 mActions = CMSettings.Secure.getStringForUser(resolver,
1213 CMSettings.Secure.POWER_MENU_ACTIONS, UserHandle.USER_CURRENT);
1216 PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
1218 public void onServiceStateChanged(ServiceState serviceState) {
1219 if (!mHasTelephony) return;
1220 final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF;
1221 mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off;
1222 mAirplaneModeOn.updateState(mAirplaneState);
1223 mAdapter.notifyDataSetChanged();
1227 private BroadcastReceiver mRingerModeReceiver = new BroadcastReceiver() {
1229 public void onReceive(Context context, Intent intent) {
1230 if (intent.getAction().equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
1231 mHandler.sendEmptyMessage(MESSAGE_REFRESH);
1236 private ContentObserver mAirplaneModeObserver = new ContentObserver(new Handler()) {
1238 public void onChange(boolean selfChange) {
1239 onAirplaneModeChanged();
1243 private static final int MESSAGE_DISMISS = 0;
1244 private static final int MESSAGE_REFRESH = 1;
1245 private static final int MESSAGE_SHOW = 2;
1246 private static final int DIALOG_DISMISS_DELAY = 300; // ms
1248 private Handler mHandler = new Handler() {
1249 public void handleMessage(Message msg) {
1251 case MESSAGE_DISMISS:
1252 if (mDialog != null) {
1257 case MESSAGE_REFRESH:
1258 refreshSilentMode();
1259 mAdapter.notifyDataSetChanged();
1268 private ToggleAction.State getUpdatedAirplaneToggleState() {
1269 return (Settings.Global.getInt(mContext.getContentResolver(),
1270 Settings.Global.AIRPLANE_MODE_ON, 0) == 1) ?
1271 ToggleAction.State.On : ToggleAction.State.Off;
1274 private void onAirplaneModeChanged() {
1275 // Let the service state callbacks handle the state.
1276 if (mHasTelephony) return;
1278 mAirplaneState = getUpdatedAirplaneToggleState();
1279 mAirplaneModeOn.updateState(mAirplaneState);
1283 * Change the airplane mode system setting
1285 private void changeAirplaneModeSystemSetting(boolean on) {
1286 Settings.Global.putInt(
1287 mContext.getContentResolver(),
1288 Settings.Global.AIRPLANE_MODE_ON,
1290 Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
1291 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
1292 intent.putExtra("state", on);
1293 mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
1294 if (!mHasTelephony) {
1295 mAirplaneState = on ? ToggleAction.State.On : ToggleAction.State.Off;
1299 private static final class GlobalActionsDialog extends Dialog implements DialogInterface {
1300 private final Context mContext;
1301 private final int mWindowTouchSlop;
1302 private final AlertController mAlert;
1303 private final MyAdapter mAdapter;
1305 private EnableAccessibilityController mEnableAccessibilityController;
1307 private boolean mIntercepted;
1308 private boolean mCancelOnUp;
1310 public GlobalActionsDialog(Context context, AlertParams params) {
1311 super(context, getDialogTheme(context));
1312 mContext = getContext();
1313 mAlert = new AlertController(mContext, this, getWindow());
1314 mAdapter = (MyAdapter) params.mAdapter;
1315 mWindowTouchSlop = ViewConfiguration.get(context).getScaledWindowTouchSlop();
1316 params.apply(mAlert);
1319 private static int getDialogTheme(Context context) {
1320 TypedValue outValue = new TypedValue();
1321 context.getTheme().resolveAttribute(com.android.internal.R.attr.alertDialogTheme,
1323 return outValue.resourceId;
1327 protected void onStart() {
1328 // If global accessibility gesture can be performed, we will take care
1329 // of dismissing the dialog on touch outside. This is because the dialog
1330 // is dismissed on the first down while the global gesture is a long press
1331 // with two fingers anywhere on the screen.
1332 if (EnableAccessibilityController.canEnableAccessibilityViaGesture(mContext)) {
1333 mEnableAccessibilityController = new EnableAccessibilityController(mContext,
1340 super.setCanceledOnTouchOutside(false);
1342 mEnableAccessibilityController = null;
1343 super.setCanceledOnTouchOutside(true);
1350 protected void onStop() {
1351 if (mEnableAccessibilityController != null) {
1352 mEnableAccessibilityController.onDestroy();
1358 public boolean dispatchTouchEvent(MotionEvent event) {
1359 if (mEnableAccessibilityController != null) {
1360 final int action = event.getActionMasked();
1361 if (action == MotionEvent.ACTION_DOWN) {
1362 View decor = getWindow().getDecorView();
1363 final int eventX = (int) event.getX();
1364 final int eventY = (int) event.getY();
1365 if (eventX < -mWindowTouchSlop
1366 || eventY < -mWindowTouchSlop
1367 || eventX >= decor.getWidth() + mWindowTouchSlop
1368 || eventY >= decor.getHeight() + mWindowTouchSlop) {
1373 if (!mIntercepted) {
1374 mIntercepted = mEnableAccessibilityController.onInterceptTouchEvent(event);
1376 final long now = SystemClock.uptimeMillis();
1377 event = MotionEvent.obtain(now, now,
1378 MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
1379 event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
1383 return mEnableAccessibilityController.onTouchEvent(event);
1386 if (action == MotionEvent.ACTION_UP) {
1390 mCancelOnUp = false;
1391 mIntercepted = false;
1395 return super.dispatchTouchEvent(event);
1398 public ListView getListView() {
1399 return mAlert.getListView();
1403 protected void onCreate(Bundle savedInstanceState) {
1404 super.onCreate(savedInstanceState);
1405 mAlert.installContent();
1409 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
1410 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
1411 for (int i = 0; i < mAdapter.getCount(); ++i) {
1412 CharSequence label =
1413 mAdapter.getItem(i).getLabelForAccessibility(getContext());
1414 if (label != null) {
1415 event.getText().add(label);
1419 return super.dispatchPopulateAccessibilityEvent(event);
1423 public boolean onKeyDown(int keyCode, KeyEvent event) {
1424 if (mAlert.onKeyDown(keyCode, event)) {
1427 return super.onKeyDown(keyCode, event);
1431 public boolean onKeyUp(int keyCode, KeyEvent event) {
1432 if (mAlert.onKeyUp(keyCode, event)) {
1435 return super.onKeyUp(keyCode, event);