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