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