OSDN Git Service

DO NOT MERGE. Grant MMS Uri permissions as the calling UID.
[android-x86/frameworks-base.git] / services / core / java / com / android / server / policy / GlobalActions.java
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.android.server.policy;
18
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;
27
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;
80
81 import java.util.ArrayList;
82 import java.util.List;
83
84 /**
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
87  * is provisioned.
88  */
89 class GlobalActions implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener  {
90
91     private static final String TAG = "GlobalActions";
92
93     private static final boolean SHOW_SILENT_TOGGLE = true;
94
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";
107
108     private final Context mContext;
109     private final WindowManagerFuncs mWindowManagerFuncs;
110     private final AudioManager mAudioManager;
111     private final IDreamManager mDreamManager;
112
113     private ArrayList<Action> mItems;
114     private GlobalActionsDialog mDialog;
115
116     private Action mSilentModeAction;
117     private ToggleAction mAirplaneModeOn;
118
119     private MyAdapter mAdapter;
120
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;
128
129     /**
130      * @param context everything needs a context :(
131      */
132     public GlobalActions(Context context, WindowManagerFuncs windowManagerFuncs) {
133         mContext = context;
134         mWindowManagerFuncs = windowManagerFuncs;
135         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
136         mDreamManager = IDreamManager.Stub.asInterface(
137                 ServiceManager.getService(DreamService.DREAM_SERVICE));
138
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);
145
146         ConnectivityManager cm = (ConnectivityManager)
147                 context.getSystemService(Context.CONNECTIVITY_SERVICE);
148         mHasTelephony = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
149
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();
159
160         mShowSilentToggle = SHOW_SILENT_TOGGLE && !mContext.getResources().getBoolean(
161                 com.android.internal.R.bool.config_useFixedVolume);
162     }
163
164     /**
165      * Show the global actions dialog (creating if necessary)
166      * @param keyguardShowing True if keyguard is showing
167      */
168     public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
169         mKeyguardShowing = keyguardShowing;
170         mDeviceProvisioned = isDeviceProvisioned;
171         if (mDialog != null) {
172             mDialog.dismiss();
173             mDialog = null;
174             // Show delayed, so that the dismiss of the previous dialog completes
175             mHandler.sendEmptyMessage(MESSAGE_SHOW);
176         } else {
177             handleShow();
178         }
179     }
180
181     private void awakenIfNecessary() {
182         if (mDreamManager != null) {
183             try {
184                 if (mDreamManager.isDreaming()) {
185                     mDreamManager.awaken();
186                 }
187             } catch (RemoteException e) {
188                 // we tried
189             }
190         }
191     }
192
193     private void handleShow() {
194         awakenIfNecessary();
195         mDialog = createDialog();
196         prepareDialog();
197
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();
203         } else {
204             WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
205             attrs.setTitle("GlobalActions");
206             mDialog.getWindow().setAttributes(attrs);
207             mDialog.show();
208             mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND);
209         }
210     }
211
212     /**
213      * Create the global actions dialog.
214      * @return A new dialog.
215      */
216     private GlobalActionsDialog createDialog() {
217         // Simple toggle style if there's no vibrator, otherwise use a tri-state
218         if (!mHasVibrator) {
219             mSilentModeAction = new SilentModeToggleAction();
220         } else {
221             mSilentModeAction = new SilentModeTriStateAction(mContext, mAudioManager, mHandler);
222         }
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) {
229
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);
239                 } else {
240                     changeAirplaneModeSystemSetting(on);
241                 }
242             }
243
244             @Override
245             protected void changeStateFromPress(boolean buttonOn) {
246                 if (!mHasTelephony) return;
247
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;
253                 }
254             }
255
256             public boolean showDuringKeyguard() {
257                 return true;
258             }
259
260             public boolean showBeforeProvisioning() {
261                 return false;
262             }
263         };
264         onAirplaneModeChanged();
265
266         mItems = new ArrayList<Action>();
267         String[] defaultActions = mContext.getResources().getStringArray(
268                 com.android.internal.R.array.config_globalActionsList);
269
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.
275                 continue;
276             }
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());
285                 }
286             } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) {
287                 if (mShowSilentToggle) {
288                     mItems.add(mSilentModeAction);
289                 }
290             } else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) {
291                 if (SystemProperties.getBoolean("fw.power_user_switcher", false)) {
292                     addUsersToMenu(mItems);
293                 }
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());
304             } else {
305                 Log.e(TAG, "Invalid global action key " + actionKey);
306             }
307             // Add here so we don't add more than one.
308             addedKeys.add(actionKey);
309         }
310
311         mAdapter = new MyAdapter();
312
313         AlertParams params = new AlertParams(mContext);
314         params.mAdapter = mAdapter;
315         params.mOnClickListener = this;
316         params.mForceInverseBackground = true;
317
318         GlobalActionsDialog dialog = new GlobalActionsDialog(mContext, params);
319         dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
320
321         dialog.getListView().setItemsCanFocus(true);
322         dialog.getListView().setLongClickable(true);
323         dialog.getListView().setOnItemLongClickListener(
324                 new AdapterView.OnItemLongClickListener() {
325                     @Override
326                     public boolean onItemLongClick(AdapterView<?> parent, View view, int position,
327                             long id) {
328                         final Action action = mAdapter.getItem(position);
329                         if (action instanceof LongPressAction) {
330                             return ((LongPressAction) action).onLongPress();
331                         }
332                         return false;
333                     }
334         });
335         dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
336
337         dialog.setOnDismissListener(this);
338
339         return dialog;
340     }
341
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);
346         }
347
348         @Override
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);
353                 return true;
354             }
355             return false;
356         }
357
358         @Override
359         public boolean showDuringKeyguard() {
360             return true;
361         }
362
363         @Override
364         public boolean showBeforeProvisioning() {
365             return true;
366         }
367
368         @Override
369         public void onPress() {
370             // shutdown by making sure radio and power are handled accordingly.
371             mWindowManagerFuncs.shutdown(false /* confirm */);
372         }
373     }
374
375     private final class RestartAction extends SinglePressAction implements LongPressAction {
376         private RestartAction() {
377             super(R.drawable.ic_restart, R.string.global_action_restart);
378         }
379
380         @Override
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);
385                 return true;
386             }
387             return false;
388         }
389
390         @Override
391         public boolean showDuringKeyguard() {
392             return true;
393         }
394
395         @Override
396         public boolean showBeforeProvisioning() {
397             return true;
398         }
399
400         @Override
401         public void onPress() {
402             mWindowManagerFuncs.reboot(false /* confirm */);
403         }
404     }
405
406
407     private class BugReportAction extends SinglePressAction implements LongPressAction {
408
409         public BugReportAction() {
410             super(com.android.internal.R.drawable.ic_lock_bugreport, R.string.bugreport_title);
411         }
412
413         @Override
414         public void onPress() {
415             // don't actually trigger the bugreport if we are running stability
416             // tests via monkey
417             if (ActivityManager.isUserAMonkey()) {
418                 return;
419             }
420             // Add a little delay before executing, to give the
421             // dialog a chance to go away before it takes a
422             // screenshot.
423             mHandler.postDelayed(new Runnable() {
424                 @Override
425                 public void run() {
426                     try {
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) {
433                     }
434                 }
435             }, 500);
436         }
437
438         @Override
439         public boolean onLongPress() {
440             // don't actually trigger the bugreport if we are running stability
441             // tests via monkey
442             if (ActivityManager.isUserAMonkey()) {
443                 return false;
444             }
445             try {
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) {
451             }
452             return false;
453         }
454
455         public boolean showDuringKeyguard() {
456             return true;
457         }
458
459         @Override
460         public boolean showBeforeProvisioning() {
461             return false;
462         }
463
464         @Override
465         public String getStatus() {
466             return mContext.getString(
467                     com.android.internal.R.string.bugreport_status,
468                     Build.VERSION.RELEASE,
469                     Build.ID);
470         }
471     }
472
473     private Action getSettingsAction() {
474         return new SinglePressAction(com.android.internal.R.drawable.ic_settings,
475                 R.string.global_action_settings) {
476
477             @Override
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);
482             }
483
484             @Override
485             public boolean showDuringKeyguard() {
486                 return true;
487             }
488
489             @Override
490             public boolean showBeforeProvisioning() {
491                 return true;
492             }
493         };
494     }
495
496     private Action getAssistAction() {
497         return new SinglePressAction(com.android.internal.R.drawable.ic_action_assist_focused,
498                 R.string.global_action_assist) {
499             @Override
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);
504             }
505
506             @Override
507             public boolean showDuringKeyguard() {
508                 return true;
509             }
510
511             @Override
512             public boolean showBeforeProvisioning() {
513                 return true;
514             }
515         };
516     }
517
518     private Action getVoiceAssistAction() {
519         return new SinglePressAction(com.android.internal.R.drawable.ic_voice_search,
520                 R.string.global_action_voice_assist) {
521             @Override
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);
526             }
527
528             @Override
529             public boolean showDuringKeyguard() {
530                 return true;
531             }
532
533             @Override
534             public boolean showBeforeProvisioning() {
535                 return true;
536             }
537         };
538     }
539
540     private Action getLockdownAction() {
541         return new SinglePressAction(com.android.internal.R.drawable.ic_lock_lock,
542                 R.string.global_action_lockdown) {
543
544             @Override
545             public void onPress() {
546                 new LockPatternUtils(mContext).requireCredentialEntry(UserHandle.USER_ALL);
547                 try {
548                     WindowManagerGlobal.getWindowManagerService().lockNow(null);
549                 } catch (RemoteException e) {
550                     Log.e(TAG, "Error while trying to lock device.", e);
551                 }
552             }
553
554             @Override
555             public boolean showDuringKeyguard() {
556                 return true;
557             }
558
559             @Override
560             public boolean showBeforeProvisioning() {
561                 return false;
562             }
563         };
564     }
565
566     private UserInfo getCurrentUser() {
567         try {
568             return ActivityManagerNative.getDefault().getCurrentUser();
569         } catch (RemoteException re) {
570             return null;
571         }
572     }
573
574     private boolean isCurrentUserOwner() {
575         UserInfo currentUser = getCurrentUser();
576         return currentUser == null || currentUser.isPrimary();
577     }
578
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)
589                             : null;
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() {
595                             try {
596                                 ActivityManagerNative.getDefault().switchUser(user.id);
597                             } catch (RemoteException re) {
598                                 Log.e(TAG, "Couldn't switch user " + re);
599                             }
600                         }
601
602                         public boolean showDuringKeyguard() {
603                             return true;
604                         }
605
606                         public boolean showBeforeProvisioning() {
607                             return false;
608                         }
609                     };
610                     items.add(switchToUser);
611                 }
612             }
613         }
614     }
615
616     private void prepareDialog() {
617         refreshSilentMode();
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);
624         }
625     }
626
627     private void refreshSilentMode() {
628         if (!mHasVibrator) {
629             final boolean silentModeOn =
630                     mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
631             ((ToggleAction)mSilentModeAction).updateState(
632                     silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off);
633         }
634     }
635
636     /** {@inheritDoc} */
637     public void onDismiss(DialogInterface dialog) {
638         if (mShowSilentToggle) {
639             try {
640                 mContext.unregisterReceiver(mRingerModeReceiver);
641             } catch (IllegalArgumentException ie) {
642                 // ignore this
643                 Log.w(TAG, ie);
644             }
645         }
646     }
647
648     /** {@inheritDoc} */
649     public void onClick(DialogInterface dialog, int which) {
650         if (!(mAdapter.getItem(which) instanceof SilentModeTriStateAction)) {
651             dialog.dismiss();
652         }
653         mAdapter.getItem(which).onPress();
654     }
655
656     /**
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}.
661      */
662     private class MyAdapter extends BaseAdapter {
663
664         public int getCount() {
665             int count = 0;
666
667             for (int i = 0; i < mItems.size(); i++) {
668                 final Action action = mItems.get(i);
669
670                 if (mKeyguardShowing && !action.showDuringKeyguard()) {
671                     continue;
672                 }
673                 if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
674                     continue;
675                 }
676                 count++;
677             }
678             return count;
679         }
680
681         @Override
682         public boolean isEnabled(int position) {
683             return getItem(position).isEnabled();
684         }
685
686         @Override
687         public boolean areAllItemsEnabled() {
688             return false;
689         }
690
691         public Action getItem(int position) {
692
693             int filteredPos = 0;
694             for (int i = 0; i < mItems.size(); i++) {
695                 final Action action = mItems.get(i);
696                 if (mKeyguardShowing && !action.showDuringKeyguard()) {
697                     continue;
698                 }
699                 if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
700                     continue;
701                 }
702                 if (filteredPos == position) {
703                     return action;
704                 }
705                 filteredPos++;
706             }
707
708             throw new IllegalArgumentException("position " + position
709                     + " out of range of showable actions"
710                     + ", filtered count=" + getCount()
711                     + ", keyguardshowing=" + mKeyguardShowing
712                     + ", provisioned=" + mDeviceProvisioned);
713         }
714
715
716         public long getItemId(int position) {
717             return position;
718         }
719
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));
723         }
724     }
725
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'
731
732     /**
733      * What each item in the global actions dialog must be able to support.
734      */
735     private interface Action {
736         /**
737          * @return Text that will be announced when dialog is created.  null
738          *     for none.
739          */
740         CharSequence getLabelForAccessibility(Context context);
741
742         View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater);
743
744         void onPress();
745
746         /**
747          * @return whether this action should appear in the dialog when the keygaurd
748          *    is showing.
749          */
750         boolean showDuringKeyguard();
751
752         /**
753          * @return whether this action should appear in the dialog before the
754          *   device is provisioned.
755          */
756         boolean showBeforeProvisioning();
757
758         boolean isEnabled();
759     }
760
761     /**
762      * An action that also supports long press.
763      */
764     private interface LongPressAction extends Action {
765         boolean onLongPress();
766     }
767
768     /**
769      * A single press action maintains no state, just responds to a press
770      * and takes an action.
771      */
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;
777
778         protected SinglePressAction(int iconResId, int messageResId) {
779             mIconResId = iconResId;
780             mMessageResId = messageResId;
781             mMessage = null;
782             mIcon = null;
783         }
784
785         protected SinglePressAction(int iconResId, Drawable icon, CharSequence message) {
786             mIconResId = iconResId;
787             mMessageResId = 0;
788             mMessage = message;
789             mIcon = icon;
790         }
791
792         public boolean isEnabled() {
793             return true;
794         }
795
796         public String getStatus() {
797             return null;
798         }
799
800         abstract public void onPress();
801
802         public CharSequence getLabelForAccessibility(Context context) {
803             if (mMessage != null) {
804                 return mMessage;
805             } else {
806                 return context.getString(mMessageResId);
807             }
808         }
809
810         public View create(
811                 Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
812             View v = inflater.inflate(R.layout.global_actions_item, parent, false);
813
814             ImageView icon = (ImageView) v.findViewById(R.id.icon);
815             TextView messageView = (TextView) v.findViewById(R.id.message);
816
817             TextView statusView = (TextView) v.findViewById(R.id.status);
818             final String status = getStatus();
819             if (!TextUtils.isEmpty(status)) {
820                 statusView.setText(status);
821             } else {
822                 statusView.setVisibility(View.GONE);
823             }
824             if (mIcon != null) {
825                 icon.setImageDrawable(mIcon);
826                 icon.setScaleType(ScaleType.CENTER_CROP);
827             } else if (mIconResId != 0) {
828                 icon.setImageDrawable(context.getDrawable(mIconResId));
829             }
830             if (mMessage != null) {
831                 messageView.setText(mMessage);
832             } else {
833                 messageView.setText(mMessageResId);
834             }
835
836             return v;
837         }
838     }
839
840     /**
841      * A toggle action knows whether it is on or off, and displays an icon
842      * and status message accordingly.
843      */
844     private static abstract class ToggleAction implements Action {
845
846         enum State {
847             Off(false),
848             TurningOn(true),
849             TurningOff(true),
850             On(false);
851
852             private final boolean inTransition;
853
854             State(boolean intermediate) {
855                 inTransition = intermediate;
856             }
857
858             public boolean inTransition() {
859                 return inTransition;
860             }
861         }
862
863         protected State mState = State.Off;
864
865         // prefs
866         protected int mEnabledIconResId;
867         protected int mDisabledIconResid;
868         protected int mMessageResId;
869         protected int mEnabledStatusMessageResId;
870         protected int mDisabledStatusMessageResId;
871
872         /**
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'
878          */
879         public ToggleAction(int enabledIconResId,
880                 int disabledIconResid,
881                 int message,
882                 int enabledStatusMessageResId,
883                 int disabledStatusMessageResId) {
884             mEnabledIconResId = enabledIconResId;
885             mDisabledIconResid = disabledIconResid;
886             mMessageResId = message;
887             mEnabledStatusMessageResId = enabledStatusMessageResId;
888             mDisabledStatusMessageResId = disabledStatusMessageResId;
889         }
890
891         /**
892          * Override to make changes to resource IDs just before creating the
893          * View.
894          */
895         void willCreate() {
896
897         }
898
899         @Override
900         public CharSequence getLabelForAccessibility(Context context) {
901             return context.getString(mMessageResId);
902         }
903
904         public View create(Context context, View convertView, ViewGroup parent,
905                 LayoutInflater inflater) {
906             willCreate();
907
908             View v = inflater.inflate(R
909                             .layout.global_actions_item, parent, false);
910
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();
915
916             if (messageView != null) {
917                 messageView.setText(mMessageResId);
918                 messageView.setEnabled(enabled);
919             }
920
921             boolean on = ((mState == State.On) || (mState == State.TurningOn));
922             if (icon != null) {
923                 icon.setImageDrawable(context.getDrawable(
924                         (on ? mEnabledIconResId : mDisabledIconResid)));
925                 icon.setEnabled(enabled);
926             }
927
928             if (statusView != null) {
929                 statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
930                 statusView.setVisibility(View.VISIBLE);
931                 statusView.setEnabled(enabled);
932             }
933             v.setEnabled(enabled);
934
935             return v;
936         }
937
938         public final void onPress() {
939             if (mState.inTransition()) {
940                 Log.w(TAG, "shouldn't be able to toggle when in transition");
941                 return;
942             }
943
944             final boolean nowOn = !(mState == State.On);
945             onToggle(nowOn);
946             changeStateFromPress(nowOn);
947         }
948
949         public boolean isEnabled() {
950             return !mState.inTransition();
951         }
952
953         /**
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
958          */
959         protected void changeStateFromPress(boolean buttonOn) {
960             mState = buttonOn ? State.On : State.Off;
961         }
962
963         abstract void onToggle(boolean on);
964
965         public void updateState(State state) {
966             mState = state;
967         }
968     }
969
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);
977         }
978
979         void onToggle(boolean on) {
980             if (on) {
981                 mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
982             } else {
983                 mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
984             }
985         }
986
987         public boolean showDuringKeyguard() {
988             return true;
989         }
990
991         public boolean showBeforeProvisioning() {
992             return false;
993         }
994     }
995
996     private static class SilentModeTriStateAction implements Action, View.OnClickListener {
997
998         private final int[] ITEM_IDS = { R.id.option1, R.id.option2, R.id.option3 };
999
1000         private final AudioManager mAudioManager;
1001         private final Handler mHandler;
1002         private final Context mContext;
1003
1004         SilentModeTriStateAction(Context context, AudioManager audioManager, Handler handler) {
1005             mAudioManager = audioManager;
1006             mHandler = handler;
1007             mContext = context;
1008         }
1009
1010         private int ringerModeToIndex(int ringerMode) {
1011             // They just happen to coincide
1012             return ringerMode;
1013         }
1014
1015         private int indexToRingerMode(int index) {
1016             // They just happen to coincide
1017             return index;
1018         }
1019
1020         @Override
1021         public CharSequence getLabelForAccessibility(Context context) {
1022             return null;
1023         }
1024
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);
1028
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
1034                 itemView.setTag(i);
1035                 itemView.setOnClickListener(this);
1036             }
1037             return v;
1038         }
1039
1040         public void onPress() {
1041         }
1042
1043         public boolean showDuringKeyguard() {
1044             return true;
1045         }
1046
1047         public boolean showBeforeProvisioning() {
1048             return false;
1049         }
1050
1051         public boolean isEnabled() {
1052             return true;
1053         }
1054
1055         void willCreate() {
1056         }
1057
1058         public void onClick(View v) {
1059             if (!(v.getTag() instanceof Integer)) return;
1060
1061             int index = (Integer) v.getTag();
1062             mAudioManager.setRingerMode(indexToRingerMode(index));
1063             mHandler.sendEmptyMessageDelayed(MESSAGE_DISMISS, DIALOG_DISMISS_DELAY);
1064         }
1065     }
1066
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);
1075                 }
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);
1083                 }
1084             }
1085         }
1086     };
1087
1088     PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
1089         @Override
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();
1096         }
1097     };
1098
1099     private BroadcastReceiver mRingerModeReceiver = new BroadcastReceiver() {
1100         @Override
1101         public void onReceive(Context context, Intent intent) {
1102             if (intent.getAction().equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
1103                 mHandler.sendEmptyMessage(MESSAGE_REFRESH);
1104             }
1105         }
1106     };
1107
1108     private ContentObserver mAirplaneModeObserver = new ContentObserver(new Handler()) {
1109         @Override
1110         public void onChange(boolean selfChange) {
1111             onAirplaneModeChanged();
1112         }
1113     };
1114
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
1119
1120     private Handler mHandler = new Handler() {
1121         public void handleMessage(Message msg) {
1122             switch (msg.what) {
1123             case MESSAGE_DISMISS:
1124                 if (mDialog != null) {
1125                     mDialog.dismiss();
1126                     mDialog = null;
1127                 }
1128                 break;
1129             case MESSAGE_REFRESH:
1130                 refreshSilentMode();
1131                 mAdapter.notifyDataSetChanged();
1132                 break;
1133             case MESSAGE_SHOW:
1134                 handleShow();
1135                 break;
1136             }
1137         }
1138     };
1139
1140     private void onAirplaneModeChanged() {
1141         // Let the service state callbacks handle the state.
1142         if (mHasTelephony) return;
1143
1144         boolean airplaneModeOn = Settings.Global.getInt(
1145                 mContext.getContentResolver(),
1146                 Settings.Global.AIRPLANE_MODE_ON,
1147                 0) == 1;
1148         mAirplaneState = airplaneModeOn ? ToggleAction.State.On : ToggleAction.State.Off;
1149         mAirplaneModeOn.updateState(mAirplaneState);
1150     }
1151
1152     /**
1153      * Change the airplane mode system setting
1154      */
1155     private void changeAirplaneModeSystemSetting(boolean on) {
1156         Settings.Global.putInt(
1157                 mContext.getContentResolver(),
1158                 Settings.Global.AIRPLANE_MODE_ON,
1159                 on ? 1 : 0);
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;
1166         }
1167     }
1168
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;
1174
1175         private EnableAccessibilityController mEnableAccessibilityController;
1176
1177         private boolean mIntercepted;
1178         private boolean mCancelOnUp;
1179
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);
1187         }
1188
1189         private static int getDialogTheme(Context context) {
1190             TypedValue outValue = new TypedValue();
1191             context.getTheme().resolveAttribute(com.android.internal.R.attr.alertDialogTheme,
1192                     outValue, true);
1193             return outValue.resourceId;
1194         }
1195
1196         @Override
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,
1204                         new Runnable() {
1205                     @Override
1206                     public void run() {
1207                         dismiss();
1208                     }
1209                 });
1210                 super.setCanceledOnTouchOutside(false);
1211             } else {
1212                 mEnableAccessibilityController = null;
1213                 super.setCanceledOnTouchOutside(true);
1214             }
1215
1216             super.onStart();
1217         }
1218
1219         @Override
1220         protected void onStop() {
1221             if (mEnableAccessibilityController != null) {
1222                 mEnableAccessibilityController.onDestroy();
1223             }
1224             super.onStop();
1225         }
1226
1227         @Override
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) {
1239                         mCancelOnUp = true;
1240                     }
1241                 }
1242                 try {
1243                     if (!mIntercepted) {
1244                         mIntercepted = mEnableAccessibilityController.onInterceptTouchEvent(event);
1245                         if (mIntercepted) {
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);
1250                             mCancelOnUp = true;
1251                         }
1252                     } else {
1253                         return mEnableAccessibilityController.onTouchEvent(event);
1254                     }
1255                 } finally {
1256                     if (action == MotionEvent.ACTION_UP) {
1257                         if (mCancelOnUp) {
1258                             cancel();
1259                         }
1260                         mCancelOnUp = false;
1261                         mIntercepted = false;
1262                     }
1263                 }
1264             }
1265             return super.dispatchTouchEvent(event);
1266         }
1267
1268         public ListView getListView() {
1269             return mAlert.getListView();
1270         }
1271
1272         @Override
1273         protected void onCreate(Bundle savedInstanceState) {
1274             super.onCreate(savedInstanceState);
1275             mAlert.installContent();
1276         }
1277
1278         @Override
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);
1286                     }
1287                 }
1288             }
1289             return super.dispatchPopulateAccessibilityEvent(event);
1290         }
1291
1292         @Override
1293         public boolean onKeyDown(int keyCode, KeyEvent event) {
1294             if (mAlert.onKeyDown(keyCode, event)) {
1295                 return true;
1296             }
1297             return super.onKeyDown(keyCode, event);
1298         }
1299
1300         @Override
1301         public boolean onKeyUp(int keyCode, KeyEvent event) {
1302             if (mAlert.onKeyUp(keyCode, event)) {
1303                 return true;
1304             }
1305             return super.onKeyUp(keyCode, event);
1306         }
1307     }
1308 }