• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.internal.policy.impl;
18 
19 import com.android.internal.app.AlertController;
20 import com.android.internal.app.AlertController.AlertParams;
21 import com.android.internal.telephony.TelephonyIntents;
22 import com.android.internal.telephony.TelephonyProperties;
23 import com.android.internal.R;
24 import com.android.internal.widget.LockPatternUtils;
25 
26 import android.app.ActivityManager;
27 import android.app.ActivityManagerNative;
28 import android.app.AlertDialog;
29 import android.app.Dialog;
30 import android.content.BroadcastReceiver;
31 import android.content.Context;
32 import android.content.DialogInterface;
33 import android.content.Intent;
34 import android.content.IntentFilter;
35 import android.content.pm.UserInfo;
36 import android.database.ContentObserver;
37 import android.graphics.drawable.Drawable;
38 import android.media.AudioManager;
39 import android.net.ConnectivityManager;
40 import android.os.Build;
41 import android.os.Bundle;
42 import android.os.Handler;
43 import android.os.Message;
44 import android.os.RemoteException;
45 import android.os.ServiceManager;
46 import android.os.SystemClock;
47 import android.os.SystemProperties;
48 import android.os.UserHandle;
49 import android.os.UserManager;
50 import android.os.Vibrator;
51 import android.provider.Settings;
52 import android.service.dreams.DreamService;
53 import android.service.dreams.IDreamManager;
54 import android.telephony.PhoneStateListener;
55 import android.telephony.ServiceState;
56 import android.telephony.TelephonyManager;
57 import android.text.TextUtils;
58 import android.util.ArraySet;
59 import android.util.Log;
60 import android.util.TypedValue;
61 import android.view.InputDevice;
62 import android.view.KeyEvent;
63 import android.view.LayoutInflater;
64 import android.view.MotionEvent;
65 import android.view.View;
66 import android.view.ViewConfiguration;
67 import android.view.ViewGroup;
68 import android.view.WindowManager;
69 import android.view.WindowManagerGlobal;
70 import android.view.WindowManagerInternal;
71 import android.view.WindowManagerPolicy.WindowManagerFuncs;
72 import android.view.accessibility.AccessibilityEvent;
73 import android.widget.AdapterView;
74 import android.widget.BaseAdapter;
75 import android.widget.ImageView;
76 import android.widget.ImageView.ScaleType;
77 import android.widget.ListView;
78 import android.widget.TextView;
79 
80 import java.util.ArrayList;
81 import java.util.List;
82 
83 /**
84  * Helper to show the global actions dialog.  Each item is an {@link Action} that
85  * may show depending on whether the keyguard is showing, and whether the device
86  * is provisioned.
87  */
88 class GlobalActions implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener  {
89 
90     private static final String TAG = "GlobalActions";
91 
92     private static final boolean SHOW_SILENT_TOGGLE = true;
93 
94     /* Valid settings for global actions keys.
95      * see config.xml config_globalActionList */
96     private static final String GLOBAL_ACTION_KEY_POWER = "power";
97     private static final String GLOBAL_ACTION_KEY_AIRPLANE = "airplane";
98     private static final String GLOBAL_ACTION_KEY_BUGREPORT = "bugreport";
99     private static final String GLOBAL_ACTION_KEY_SILENT = "silent";
100     private static final String GLOBAL_ACTION_KEY_USERS = "users";
101     private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings";
102     private static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown";
103 
104     private final Context mContext;
105     private final WindowManagerFuncs mWindowManagerFuncs;
106     private final AudioManager mAudioManager;
107     private final IDreamManager mDreamManager;
108 
109     private ArrayList<Action> mItems;
110     private GlobalActionsDialog mDialog;
111 
112     private Action mSilentModeAction;
113     private ToggleAction mAirplaneModeOn;
114 
115     private MyAdapter mAdapter;
116 
117     private boolean mKeyguardShowing = false;
118     private boolean mDeviceProvisioned = false;
119     private ToggleAction.State mAirplaneState = ToggleAction.State.Off;
120     private boolean mIsWaitingForEcmExit = false;
121     private boolean mHasTelephony;
122     private boolean mHasVibrator;
123     private final boolean mShowSilentToggle;
124 
125     /**
126      * @param context everything needs a context :(
127      */
GlobalActions(Context context, WindowManagerFuncs windowManagerFuncs)128     public GlobalActions(Context context, WindowManagerFuncs windowManagerFuncs) {
129         mContext = context;
130         mWindowManagerFuncs = windowManagerFuncs;
131         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
132         mDreamManager = IDreamManager.Stub.asInterface(
133                 ServiceManager.getService(DreamService.DREAM_SERVICE));
134 
135         // receive broadcasts
136         IntentFilter filter = new IntentFilter();
137         filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
138         filter.addAction(Intent.ACTION_SCREEN_OFF);
139         filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
140         context.registerReceiver(mBroadcastReceiver, filter);
141 
142         ConnectivityManager cm = (ConnectivityManager)
143                 context.getSystemService(Context.CONNECTIVITY_SERVICE);
144         mHasTelephony = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
145 
146         // get notified of phone state changes
147         TelephonyManager telephonyManager =
148                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
149         telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
150         mContext.getContentResolver().registerContentObserver(
151                 Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
152                 mAirplaneModeObserver);
153         Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
154         mHasVibrator = vibrator != null && vibrator.hasVibrator();
155 
156         mShowSilentToggle = SHOW_SILENT_TOGGLE && !mContext.getResources().getBoolean(
157                 com.android.internal.R.bool.config_useFixedVolume);
158     }
159 
160     /**
161      * Show the global actions dialog (creating if necessary)
162      * @param keyguardShowing True if keyguard is showing
163      */
showDialog(boolean keyguardShowing, boolean isDeviceProvisioned)164     public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
165         mKeyguardShowing = keyguardShowing;
166         mDeviceProvisioned = isDeviceProvisioned;
167         if (mDialog != null) {
168             mDialog.dismiss();
169             mDialog = null;
170             // Show delayed, so that the dismiss of the previous dialog completes
171             mHandler.sendEmptyMessage(MESSAGE_SHOW);
172         } else {
173             handleShow();
174         }
175     }
176 
awakenIfNecessary()177     private void awakenIfNecessary() {
178         if (mDreamManager != null) {
179             try {
180                 if (mDreamManager.isDreaming()) {
181                     mDreamManager.awaken();
182                 }
183             } catch (RemoteException e) {
184                 // we tried
185             }
186         }
187     }
188 
handleShow()189     private void handleShow() {
190         awakenIfNecessary();
191         mDialog = createDialog();
192         prepareDialog();
193 
194         // If we only have 1 item and it's a simple press action, just do this action.
195         if (mAdapter.getCount() == 1
196                 && mAdapter.getItem(0) instanceof SinglePressAction
197                 && !(mAdapter.getItem(0) instanceof LongPressAction)) {
198             ((SinglePressAction) mAdapter.getItem(0)).onPress();
199         } else {
200             WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
201             attrs.setTitle("GlobalActions");
202             mDialog.getWindow().setAttributes(attrs);
203             mDialog.show();
204             mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND);
205         }
206     }
207 
208     /**
209      * Create the global actions dialog.
210      * @return A new dialog.
211      */
createDialog()212     private GlobalActionsDialog createDialog() {
213         // Simple toggle style if there's no vibrator, otherwise use a tri-state
214         if (!mHasVibrator) {
215             mSilentModeAction = new SilentModeToggleAction();
216         } else {
217             mSilentModeAction = new SilentModeTriStateAction(mContext, mAudioManager, mHandler);
218         }
219         mAirplaneModeOn = new ToggleAction(
220                 R.drawable.ic_lock_airplane_mode,
221                 R.drawable.ic_lock_airplane_mode_off,
222                 R.string.global_actions_toggle_airplane_mode,
223                 R.string.global_actions_airplane_mode_on_status,
224                 R.string.global_actions_airplane_mode_off_status) {
225 
226             void onToggle(boolean on) {
227                 if (mHasTelephony && Boolean.parseBoolean(
228                         SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) {
229                     mIsWaitingForEcmExit = true;
230                     // Launch ECM exit dialog
231                     Intent ecmDialogIntent =
232                             new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);
233                     ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
234                     mContext.startActivity(ecmDialogIntent);
235                 } else {
236                     changeAirplaneModeSystemSetting(on);
237                 }
238             }
239 
240             @Override
241             protected void changeStateFromPress(boolean buttonOn) {
242                 if (!mHasTelephony) return;
243 
244                 // In ECM mode airplane state cannot be changed
245                 if (!(Boolean.parseBoolean(
246                         SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)))) {
247                     mState = buttonOn ? State.TurningOn : State.TurningOff;
248                     mAirplaneState = mState;
249                 }
250             }
251 
252             public boolean showDuringKeyguard() {
253                 return true;
254             }
255 
256             public boolean showBeforeProvisioning() {
257                 return false;
258             }
259         };
260         onAirplaneModeChanged();
261 
262         mItems = new ArrayList<Action>();
263         String[] defaultActions = mContext.getResources().getStringArray(
264                 com.android.internal.R.array.config_globalActionsList);
265 
266         ArraySet<String> addedKeys = new ArraySet<String>();
267         for (int i = 0; i < defaultActions.length; i++) {
268             String actionKey = defaultActions[i];
269             if (addedKeys.contains(actionKey)) {
270                 // If we already have added this, don't add it again.
271                 continue;
272             }
273             if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) {
274                 mItems.add(new PowerAction());
275             } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
276                 mItems.add(mAirplaneModeOn);
277             } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
278                 if (Settings.Global.getInt(mContext.getContentResolver(),
279                         Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) {
280                     mItems.add(getBugReportAction());
281                 }
282             } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) {
283                 if (mShowSilentToggle) {
284                     mItems.add(mSilentModeAction);
285                 }
286             } else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) {
287                 if (SystemProperties.getBoolean("fw.power_user_switcher", false)) {
288                     addUsersToMenu(mItems);
289                 }
290             } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
291                 mItems.add(getSettingsAction());
292             } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
293                 mItems.add(getLockdownAction());
294             } else {
295                 Log.e(TAG, "Invalid global action key " + actionKey);
296             }
297             // Add here so we don't add more than one.
298             addedKeys.add(actionKey);
299         }
300 
301         mAdapter = new MyAdapter();
302 
303         AlertParams params = new AlertParams(mContext);
304         params.mAdapter = mAdapter;
305         params.mOnClickListener = this;
306         params.mForceInverseBackground = true;
307 
308         GlobalActionsDialog dialog = new GlobalActionsDialog(mContext, params);
309         dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
310 
311         dialog.getListView().setItemsCanFocus(true);
312         dialog.getListView().setLongClickable(true);
313         dialog.getListView().setOnItemLongClickListener(
314                 new AdapterView.OnItemLongClickListener() {
315                     @Override
316                     public boolean onItemLongClick(AdapterView<?> parent, View view, int position,
317                             long id) {
318                         final Action action = mAdapter.getItem(position);
319                         if (action instanceof LongPressAction) {
320                             return ((LongPressAction) action).onLongPress();
321                         }
322                         return false;
323                     }
324         });
325         dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
326 
327         dialog.setOnDismissListener(this);
328 
329         return dialog;
330     }
331 
332     private final class PowerAction extends SinglePressAction implements LongPressAction {
PowerAction()333         private PowerAction() {
334             super(com.android.internal.R.drawable.ic_lock_power_off,
335                 R.string.global_action_power_off);
336         }
337 
338         @Override
onLongPress()339         public boolean onLongPress() {
340             mWindowManagerFuncs.rebootSafeMode(true);
341             return true;
342         }
343 
344         @Override
showDuringKeyguard()345         public boolean showDuringKeyguard() {
346             return true;
347         }
348 
349         @Override
showBeforeProvisioning()350         public boolean showBeforeProvisioning() {
351             return true;
352         }
353 
354         @Override
onPress()355         public void onPress() {
356             // shutdown by making sure radio and power are handled accordingly.
357             mWindowManagerFuncs.shutdown(false /* confirm */);
358         }
359     }
360 
getBugReportAction()361     private Action getBugReportAction() {
362         return new SinglePressAction(com.android.internal.R.drawable.ic_lock_bugreport,
363                 R.string.bugreport_title) {
364 
365             public void onPress() {
366                 AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
367                 builder.setTitle(com.android.internal.R.string.bugreport_title);
368                 builder.setMessage(com.android.internal.R.string.bugreport_message);
369                 builder.setNegativeButton(com.android.internal.R.string.cancel, null);
370                 builder.setPositiveButton(com.android.internal.R.string.report,
371                         new DialogInterface.OnClickListener() {
372                             @Override
373                             public void onClick(DialogInterface dialog, int which) {
374                                 // don't actually trigger the bugreport if we are running stability
375                                 // tests via monkey
376                                 if (ActivityManager.isUserAMonkey()) {
377                                     return;
378                                 }
379                                 // Add a little delay before executing, to give the
380                                 // dialog a chance to go away before it takes a
381                                 // screenshot.
382                                 mHandler.postDelayed(new Runnable() {
383                                     @Override public void run() {
384                                         try {
385                                             ActivityManagerNative.getDefault()
386                                                     .requestBugReport();
387                                         } catch (RemoteException e) {
388                                         }
389                                     }
390                                 }, 500);
391                             }
392                         });
393                 AlertDialog dialog = builder.create();
394                 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
395                 dialog.show();
396             }
397 
398             public boolean showDuringKeyguard() {
399                 return true;
400             }
401 
402             public boolean showBeforeProvisioning() {
403                 return false;
404             }
405 
406             @Override
407             public String getStatus() {
408                 return mContext.getString(
409                         com.android.internal.R.string.bugreport_status,
410                         Build.VERSION.RELEASE,
411                         Build.ID);
412             }
413         };
414     }
415 
416     private Action getSettingsAction() {
417         return new SinglePressAction(com.android.internal.R.drawable.ic_settings,
418                 R.string.global_action_settings) {
419 
420             @Override
421             public void onPress() {
422                 Intent intent = new Intent(Settings.ACTION_SETTINGS);
423                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
424                 mContext.startActivity(intent);
425             }
426 
427             @Override
428             public boolean showDuringKeyguard() {
429                 return true;
430             }
431 
432             @Override
433             public boolean showBeforeProvisioning() {
434                 return true;
435             }
436         };
437     }
438 
439     private Action getLockdownAction() {
440         return new SinglePressAction(com.android.internal.R.drawable.ic_lock_lock,
441                 R.string.global_action_lockdown) {
442 
443             @Override
444             public void onPress() {
445                 new LockPatternUtils(mContext).requireCredentialEntry(UserHandle.USER_ALL);
446                 try {
447                     WindowManagerGlobal.getWindowManagerService().lockNow(null);
448                 } catch (RemoteException e) {
449                     Log.e(TAG, "Error while trying to lock device.", e);
450                 }
451             }
452 
453             @Override
454             public boolean showDuringKeyguard() {
455                 return true;
456             }
457 
458             @Override
459             public boolean showBeforeProvisioning() {
460                 return false;
461             }
462         };
463     }
464 
465     private UserInfo getCurrentUser() {
466         try {
467             return ActivityManagerNative.getDefault().getCurrentUser();
468         } catch (RemoteException re) {
469             return null;
470         }
471     }
472 
473     private boolean isCurrentUserOwner() {
474         UserInfo currentUser = getCurrentUser();
475         return currentUser == null || currentUser.isPrimary();
476     }
477 
478     private void addUsersToMenu(ArrayList<Action> items) {
479         UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
480         if (um.isUserSwitcherEnabled()) {
481             List<UserInfo> users = um.getUsers();
482             UserInfo currentUser = getCurrentUser();
483             for (final UserInfo user : users) {
484                 if (user.supportsSwitchTo()) {
485                     boolean isCurrentUser = currentUser == null
486                             ? user.id == 0 : (currentUser.id == user.id);
487                     Drawable icon = user.iconPath != null ? Drawable.createFromPath(user.iconPath)
488                             : null;
489                     SinglePressAction switchToUser = new SinglePressAction(
490                             com.android.internal.R.drawable.ic_menu_cc, icon,
491                             (user.name != null ? user.name : "Primary")
492                             + (isCurrentUser ? " \u2714" : "")) {
493                         public void onPress() {
494                             try {
495                                 ActivityManagerNative.getDefault().switchUser(user.id);
496                             } catch (RemoteException re) {
497                                 Log.e(TAG, "Couldn't switch user " + re);
498                             }
499                         }
500 
501                         public boolean showDuringKeyguard() {
502                             return true;
503                         }
504 
505                         public boolean showBeforeProvisioning() {
506                             return false;
507                         }
508                     };
509                     items.add(switchToUser);
510                 }
511             }
512         }
513     }
514 
515     private void prepareDialog() {
516         refreshSilentMode();
517         mAirplaneModeOn.updateState(mAirplaneState);
518         mAdapter.notifyDataSetChanged();
519         mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
520         if (mShowSilentToggle) {
521             IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
522             mContext.registerReceiver(mRingerModeReceiver, filter);
523         }
524     }
525 
526     private void refreshSilentMode() {
527         if (!mHasVibrator) {
528             final boolean silentModeOn =
529                     mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
530             ((ToggleAction)mSilentModeAction).updateState(
531                     silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off);
532         }
533     }
534 
535     /** {@inheritDoc} */
536     public void onDismiss(DialogInterface dialog) {
537         if (mShowSilentToggle) {
538             try {
539                 mContext.unregisterReceiver(mRingerModeReceiver);
540             } catch (IllegalArgumentException ie) {
541                 // ignore this
542                 Log.w(TAG, ie);
543             }
544         }
545     }
546 
547     /** {@inheritDoc} */
548     public void onClick(DialogInterface dialog, int which) {
549         if (!(mAdapter.getItem(which) instanceof SilentModeTriStateAction)) {
550             dialog.dismiss();
551         }
552         mAdapter.getItem(which).onPress();
553     }
554 
555     /**
556      * The adapter used for the list within the global actions dialog, taking
557      * into account whether the keyguard is showing via
558      * {@link GlobalActions#mKeyguardShowing} and whether the device is provisioned
559      * via {@link GlobalActions#mDeviceProvisioned}.
560      */
561     private class MyAdapter extends BaseAdapter {
562 
563         public int getCount() {
564             int count = 0;
565 
566             for (int i = 0; i < mItems.size(); i++) {
567                 final Action action = mItems.get(i);
568 
569                 if (mKeyguardShowing && !action.showDuringKeyguard()) {
570                     continue;
571                 }
572                 if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
573                     continue;
574                 }
575                 count++;
576             }
577             return count;
578         }
579 
580         @Override
581         public boolean isEnabled(int position) {
582             return getItem(position).isEnabled();
583         }
584 
585         @Override
586         public boolean areAllItemsEnabled() {
587             return false;
588         }
589 
590         public Action getItem(int position) {
591 
592             int filteredPos = 0;
593             for (int i = 0; i < mItems.size(); i++) {
594                 final Action action = mItems.get(i);
595                 if (mKeyguardShowing && !action.showDuringKeyguard()) {
596                     continue;
597                 }
598                 if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
599                     continue;
600                 }
601                 if (filteredPos == position) {
602                     return action;
603                 }
604                 filteredPos++;
605             }
606 
607             throw new IllegalArgumentException("position " + position
608                     + " out of range of showable actions"
609                     + ", filtered count=" + getCount()
610                     + ", keyguardshowing=" + mKeyguardShowing
611                     + ", provisioned=" + mDeviceProvisioned);
612         }
613 
614 
615         public long getItemId(int position) {
616             return position;
617         }
618 
619         public View getView(int position, View convertView, ViewGroup parent) {
620             Action action = getItem(position);
621             return action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
622         }
623     }
624 
625     // note: the scheme below made more sense when we were planning on having
626     // 8 different things in the global actions dialog.  seems overkill with
627     // only 3 items now, but may as well keep this flexible approach so it will
628     // be easy should someone decide at the last minute to include something
629     // else, such as 'enable wifi', or 'enable bluetooth'
630 
631     /**
632      * What each item in the global actions dialog must be able to support.
633      */
634     private interface Action {
635         /**
636          * @return Text that will be announced when dialog is created.  null
637          *     for none.
638          */
639         CharSequence getLabelForAccessibility(Context context);
640 
641         View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater);
642 
643         void onPress();
644 
645         /**
646          * @return whether this action should appear in the dialog when the keygaurd
647          *    is showing.
648          */
649         boolean showDuringKeyguard();
650 
651         /**
652          * @return whether this action should appear in the dialog before the
653          *   device is provisioned.
654          */
655         boolean showBeforeProvisioning();
656 
657         boolean isEnabled();
658     }
659 
660     /**
661      * An action that also supports long press.
662      */
663     private interface LongPressAction extends Action {
664         boolean onLongPress();
665     }
666 
667     /**
668      * A single press action maintains no state, just responds to a press
669      * and takes an action.
670      */
671     private static abstract class SinglePressAction implements Action {
672         private final int mIconResId;
673         private final Drawable mIcon;
674         private final int mMessageResId;
675         private final CharSequence mMessage;
676 
677         protected SinglePressAction(int iconResId, int messageResId) {
678             mIconResId = iconResId;
679             mMessageResId = messageResId;
680             mMessage = null;
681             mIcon = null;
682         }
683 
684         protected SinglePressAction(int iconResId, Drawable icon, CharSequence message) {
685             mIconResId = iconResId;
686             mMessageResId = 0;
687             mMessage = message;
688             mIcon = icon;
689         }
690 
691         protected SinglePressAction(int iconResId, CharSequence message) {
692             mIconResId = iconResId;
693             mMessageResId = 0;
694             mMessage = message;
695             mIcon = null;
696         }
697 
698         public boolean isEnabled() {
699             return true;
700         }
701 
702         public String getStatus() {
703             return null;
704         }
705 
706         abstract public void onPress();
707 
708         public CharSequence getLabelForAccessibility(Context context) {
709             if (mMessage != null) {
710                 return mMessage;
711             } else {
712                 return context.getString(mMessageResId);
713             }
714         }
715 
716         public View create(
717                 Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
718             View v = inflater.inflate(R.layout.global_actions_item, parent, false);
719 
720             ImageView icon = (ImageView) v.findViewById(R.id.icon);
721             TextView messageView = (TextView) v.findViewById(R.id.message);
722 
723             TextView statusView = (TextView) v.findViewById(R.id.status);
724             final String status = getStatus();
725             if (!TextUtils.isEmpty(status)) {
726                 statusView.setText(status);
727             } else {
728                 statusView.setVisibility(View.GONE);
729             }
730             if (mIcon != null) {
731                 icon.setImageDrawable(mIcon);
732                 icon.setScaleType(ScaleType.CENTER_CROP);
733             } else if (mIconResId != 0) {
734                 icon.setImageDrawable(context.getDrawable(mIconResId));
735             }
736             if (mMessage != null) {
737                 messageView.setText(mMessage);
738             } else {
739                 messageView.setText(mMessageResId);
740             }
741 
742             return v;
743         }
744     }
745 
746     /**
747      * A toggle action knows whether it is on or off, and displays an icon
748      * and status message accordingly.
749      */
750     private static abstract class ToggleAction implements Action {
751 
752         enum State {
753             Off(false),
754             TurningOn(true),
755             TurningOff(true),
756             On(false);
757 
758             private final boolean inTransition;
759 
760             State(boolean intermediate) {
761                 inTransition = intermediate;
762             }
763 
764             public boolean inTransition() {
765                 return inTransition;
766             }
767         }
768 
769         protected State mState = State.Off;
770 
771         // prefs
772         protected int mEnabledIconResId;
773         protected int mDisabledIconResid;
774         protected int mMessageResId;
775         protected int mEnabledStatusMessageResId;
776         protected int mDisabledStatusMessageResId;
777 
778         /**
779          * @param enabledIconResId The icon for when this action is on.
780          * @param disabledIconResid The icon for when this action is off.
781          * @param essage The general information message, e.g 'Silent Mode'
782          * @param enabledStatusMessageResId The on status message, e.g 'sound disabled'
783          * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled'
784          */
785         public ToggleAction(int enabledIconResId,
786                 int disabledIconResid,
787                 int message,
788                 int enabledStatusMessageResId,
789                 int disabledStatusMessageResId) {
790             mEnabledIconResId = enabledIconResId;
791             mDisabledIconResid = disabledIconResid;
792             mMessageResId = message;
793             mEnabledStatusMessageResId = enabledStatusMessageResId;
794             mDisabledStatusMessageResId = disabledStatusMessageResId;
795         }
796 
797         /**
798          * Override to make changes to resource IDs just before creating the
799          * View.
800          */
801         void willCreate() {
802 
803         }
804 
805         @Override
806         public CharSequence getLabelForAccessibility(Context context) {
807             return context.getString(mMessageResId);
808         }
809 
810         public View create(Context context, View convertView, ViewGroup parent,
811                 LayoutInflater inflater) {
812             willCreate();
813 
814             View v = inflater.inflate(R
815                             .layout.global_actions_item, parent, false);
816 
817             ImageView icon = (ImageView) v.findViewById(R.id.icon);
818             TextView messageView = (TextView) v.findViewById(R.id.message);
819             TextView statusView = (TextView) v.findViewById(R.id.status);
820             final boolean enabled = isEnabled();
821 
822             if (messageView != null) {
823                 messageView.setText(mMessageResId);
824                 messageView.setEnabled(enabled);
825             }
826 
827             boolean on = ((mState == State.On) || (mState == State.TurningOn));
828             if (icon != null) {
829                 icon.setImageDrawable(context.getDrawable(
830                         (on ? mEnabledIconResId : mDisabledIconResid)));
831                 icon.setEnabled(enabled);
832             }
833 
834             if (statusView != null) {
835                 statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
836                 statusView.setVisibility(View.VISIBLE);
837                 statusView.setEnabled(enabled);
838             }
839             v.setEnabled(enabled);
840 
841             return v;
842         }
843 
844         public final void onPress() {
845             if (mState.inTransition()) {
846                 Log.w(TAG, "shouldn't be able to toggle when in transition");
847                 return;
848             }
849 
850             final boolean nowOn = !(mState == State.On);
851             onToggle(nowOn);
852             changeStateFromPress(nowOn);
853         }
854 
855         public boolean isEnabled() {
856             return !mState.inTransition();
857         }
858 
859         /**
860          * Implementations may override this if their state can be in on of the intermediate
861          * states until some notification is received (e.g airplane mode is 'turning off' until
862          * we know the wireless connections are back online
863          * @param buttonOn Whether the button was turned on or off
864          */
865         protected void changeStateFromPress(boolean buttonOn) {
866             mState = buttonOn ? State.On : State.Off;
867         }
868 
869         abstract void onToggle(boolean on);
870 
871         public void updateState(State state) {
872             mState = state;
873         }
874     }
875 
876     private class SilentModeToggleAction extends ToggleAction {
877         public SilentModeToggleAction() {
878             super(R.drawable.ic_audio_vol_mute,
879                     R.drawable.ic_audio_vol,
880                     R.string.global_action_toggle_silent_mode,
881                     R.string.global_action_silent_mode_on_status,
882                     R.string.global_action_silent_mode_off_status);
883         }
884 
885         void onToggle(boolean on) {
886             if (on) {
887                 mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
888             } else {
889                 mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
890             }
891         }
892 
893         public boolean showDuringKeyguard() {
894             return true;
895         }
896 
897         public boolean showBeforeProvisioning() {
898             return false;
899         }
900     }
901 
902     private static class SilentModeTriStateAction implements Action, View.OnClickListener {
903 
904         private final int[] ITEM_IDS = { R.id.option1, R.id.option2, R.id.option3 };
905 
906         private final AudioManager mAudioManager;
907         private final Handler mHandler;
908         private final Context mContext;
909 
910         SilentModeTriStateAction(Context context, AudioManager audioManager, Handler handler) {
911             mAudioManager = audioManager;
912             mHandler = handler;
913             mContext = context;
914         }
915 
916         private int ringerModeToIndex(int ringerMode) {
917             // They just happen to coincide
918             return ringerMode;
919         }
920 
921         private int indexToRingerMode(int index) {
922             // They just happen to coincide
923             return index;
924         }
925 
926         @Override
927         public CharSequence getLabelForAccessibility(Context context) {
928             return null;
929         }
930 
931         public View create(Context context, View convertView, ViewGroup parent,
932                 LayoutInflater inflater) {
933             View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false);
934 
935             int selectedIndex = ringerModeToIndex(mAudioManager.getRingerMode());
936             for (int i = 0; i < 3; i++) {
937                 View itemView = v.findViewById(ITEM_IDS[i]);
938                 itemView.setSelected(selectedIndex == i);
939                 // Set up click handler
940                 itemView.setTag(i);
941                 itemView.setOnClickListener(this);
942             }
943             return v;
944         }
945 
946         public void onPress() {
947         }
948 
949         public boolean showDuringKeyguard() {
950             return true;
951         }
952 
953         public boolean showBeforeProvisioning() {
954             return false;
955         }
956 
957         public boolean isEnabled() {
958             return true;
959         }
960 
961         void willCreate() {
962         }
963 
964         public void onClick(View v) {
965             if (!(v.getTag() instanceof Integer)) return;
966 
967             int index = (Integer) v.getTag();
968             mAudioManager.setRingerMode(indexToRingerMode(index));
969             mHandler.sendEmptyMessageDelayed(MESSAGE_DISMISS, DIALOG_DISMISS_DELAY);
970         }
971     }
972 
973     private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
974         public void onReceive(Context context, Intent intent) {
975             String action = intent.getAction();
976             if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
977                     || Intent.ACTION_SCREEN_OFF.equals(action)) {
978                 String reason = intent.getStringExtra(PhoneWindowManager.SYSTEM_DIALOG_REASON_KEY);
979                 if (!PhoneWindowManager.SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) {
980                     mHandler.sendEmptyMessage(MESSAGE_DISMISS);
981                 }
982             } else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) {
983                 // Airplane mode can be changed after ECM exits if airplane toggle button
984                 // is pressed during ECM mode
985                 if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) &&
986                         mIsWaitingForEcmExit) {
987                     mIsWaitingForEcmExit = false;
988                     changeAirplaneModeSystemSetting(true);
989                 }
990             }
991         }
992     };
993 
994     PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
995         @Override
996         public void onServiceStateChanged(ServiceState serviceState) {
997             if (!mHasTelephony) return;
998             final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF;
999             mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off;
1000             mAirplaneModeOn.updateState(mAirplaneState);
1001             mAdapter.notifyDataSetChanged();
1002         }
1003     };
1004 
1005     private BroadcastReceiver mRingerModeReceiver = new BroadcastReceiver() {
1006         @Override
1007         public void onReceive(Context context, Intent intent) {
1008             if (intent.getAction().equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
1009                 mHandler.sendEmptyMessage(MESSAGE_REFRESH);
1010             }
1011         }
1012     };
1013 
1014     private ContentObserver mAirplaneModeObserver = new ContentObserver(new Handler()) {
1015         @Override
1016         public void onChange(boolean selfChange) {
1017             onAirplaneModeChanged();
1018         }
1019     };
1020 
1021     private static final int MESSAGE_DISMISS = 0;
1022     private static final int MESSAGE_REFRESH = 1;
1023     private static final int MESSAGE_SHOW = 2;
1024     private static final int DIALOG_DISMISS_DELAY = 300; // ms
1025 
1026     private Handler mHandler = new Handler() {
1027         public void handleMessage(Message msg) {
1028             switch (msg.what) {
1029             case MESSAGE_DISMISS:
1030                 if (mDialog != null) {
1031                     mDialog.dismiss();
1032                     mDialog = null;
1033                 }
1034                 break;
1035             case MESSAGE_REFRESH:
1036                 refreshSilentMode();
1037                 mAdapter.notifyDataSetChanged();
1038                 break;
1039             case MESSAGE_SHOW:
1040                 handleShow();
1041                 break;
1042             }
1043         }
1044     };
1045 
1046     private void onAirplaneModeChanged() {
1047         // Let the service state callbacks handle the state.
1048         if (mHasTelephony) return;
1049 
1050         boolean airplaneModeOn = Settings.Global.getInt(
1051                 mContext.getContentResolver(),
1052                 Settings.Global.AIRPLANE_MODE_ON,
1053                 0) == 1;
1054         mAirplaneState = airplaneModeOn ? ToggleAction.State.On : ToggleAction.State.Off;
1055         mAirplaneModeOn.updateState(mAirplaneState);
1056     }
1057 
1058     /**
1059      * Change the airplane mode system setting
1060      */
1061     private void changeAirplaneModeSystemSetting(boolean on) {
1062         Settings.Global.putInt(
1063                 mContext.getContentResolver(),
1064                 Settings.Global.AIRPLANE_MODE_ON,
1065                 on ? 1 : 0);
1066         Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
1067         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
1068         intent.putExtra("state", on);
1069         mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
1070         if (!mHasTelephony) {
1071             mAirplaneState = on ? ToggleAction.State.On : ToggleAction.State.Off;
1072         }
1073     }
1074 
1075     private static final class GlobalActionsDialog extends Dialog implements DialogInterface {
1076         private final Context mContext;
1077         private final int mWindowTouchSlop;
1078         private final AlertController mAlert;
1079         private final MyAdapter mAdapter;
1080 
1081         private EnableAccessibilityController mEnableAccessibilityController;
1082 
1083         private boolean mIntercepted;
1084         private boolean mCancelOnUp;
1085 
1086         public GlobalActionsDialog(Context context, AlertParams params) {
1087             super(context, getDialogTheme(context));
1088             mContext = context;
1089             mAlert = new AlertController(mContext, this, getWindow());
1090             mAdapter = (MyAdapter) params.mAdapter;
1091             mWindowTouchSlop = ViewConfiguration.get(context).getScaledWindowTouchSlop();
1092             params.apply(mAlert);
1093         }
1094 
1095         private static int getDialogTheme(Context context) {
1096             TypedValue outValue = new TypedValue();
1097             context.getTheme().resolveAttribute(com.android.internal.R.attr.alertDialogTheme,
1098                     outValue, true);
1099             return outValue.resourceId;
1100         }
1101 
1102         @Override
1103         protected void onStart() {
1104             // If global accessibility gesture can be performed, we will take care
1105             // of dismissing the dialog on touch outside. This is because the dialog
1106             // is dismissed on the first down while the global gesture is a long press
1107             // with two fingers anywhere on the screen.
1108             if (EnableAccessibilityController.canEnableAccessibilityViaGesture(mContext)) {
1109                 mEnableAccessibilityController = new EnableAccessibilityController(mContext,
1110                         new Runnable() {
1111                     @Override
1112                     public void run() {
1113                         dismiss();
1114                     }
1115                 });
1116                 super.setCanceledOnTouchOutside(false);
1117             } else {
1118                 mEnableAccessibilityController = null;
1119                 super.setCanceledOnTouchOutside(true);
1120             }
1121 
1122             super.onStart();
1123         }
1124 
1125         @Override
1126         protected void onStop() {
1127             if (mEnableAccessibilityController != null) {
1128                 mEnableAccessibilityController.onDestroy();
1129             }
1130             super.onStop();
1131         }
1132 
1133         @Override
1134         public boolean dispatchTouchEvent(MotionEvent event) {
1135             if (mEnableAccessibilityController != null) {
1136                 final int action = event.getActionMasked();
1137                 if (action == MotionEvent.ACTION_DOWN) {
1138                     View decor = getWindow().getDecorView();
1139                     final int eventX = (int) event.getX();
1140                     final int eventY = (int) event.getY();
1141                     if (eventX < -mWindowTouchSlop
1142                             || eventY < -mWindowTouchSlop
1143                             || eventX >= decor.getWidth() + mWindowTouchSlop
1144                             || eventY >= decor.getHeight() + mWindowTouchSlop) {
1145                         mCancelOnUp = true;
1146                     }
1147                 }
1148                 try {
1149                     if (!mIntercepted) {
1150                         mIntercepted = mEnableAccessibilityController.onInterceptTouchEvent(event);
1151                         if (mIntercepted) {
1152                             final long now = SystemClock.uptimeMillis();
1153                             event = MotionEvent.obtain(now, now,
1154                                     MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
1155                             event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
1156                             mCancelOnUp = true;
1157                         }
1158                     } else {
1159                         return mEnableAccessibilityController.onTouchEvent(event);
1160                     }
1161                 } finally {
1162                     if (action == MotionEvent.ACTION_UP) {
1163                         if (mCancelOnUp) {
1164                             cancel();
1165                         }
1166                         mCancelOnUp = false;
1167                         mIntercepted = false;
1168                     }
1169                 }
1170             }
1171             return super.dispatchTouchEvent(event);
1172         }
1173 
1174         public ListView getListView() {
1175             return mAlert.getListView();
1176         }
1177 
1178         @Override
1179         protected void onCreate(Bundle savedInstanceState) {
1180             super.onCreate(savedInstanceState);
1181             mAlert.installContent();
1182         }
1183 
1184         @Override
1185         public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
1186             if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
1187                 for (int i = 0; i < mAdapter.getCount(); ++i) {
1188                     CharSequence label =
1189                             mAdapter.getItem(i).getLabelForAccessibility(getContext());
1190                     if (label != null) {
1191                         event.getText().add(label);
1192                     }
1193                 }
1194             }
1195             return super.dispatchPopulateAccessibilityEvent(event);
1196         }
1197 
1198         @Override
1199         public boolean onKeyDown(int keyCode, KeyEvent event) {
1200             if (mAlert.onKeyDown(keyCode, event)) {
1201                 return true;
1202             }
1203             return super.onKeyDown(keyCode, event);
1204         }
1205 
1206         @Override
1207         public boolean onKeyUp(int keyCode, KeyEvent event) {
1208             if (mAlert.onKeyUp(keyCode, event)) {
1209                 return true;
1210             }
1211             return super.onKeyUp(keyCode, event);
1212         }
1213     }
1214 }
1215