GlobalActions.java revision 43b013fe883933e7dcde7e2bb628ea1c6dfc444c
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(new BugReportAction());
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 class BugReportAction extends SinglePressAction implements LongPressAction {
371
372        public BugReportAction() {
373            super(com.android.internal.R.drawable.ic_lock_bugreport, R.string.bugreport_title);
374        }
375
376        @Override
377        public void onPress() {
378            // don't actually trigger the bugreport if we are running stability
379            // tests via monkey
380            if (ActivityManager.isUserAMonkey()) {
381                return;
382            }
383            // Add a little delay before executing, to give the
384            // dialog a chance to go away before it takes a
385            // screenshot.
386            mHandler.postDelayed(new Runnable() {
387                @Override
388                public void run() {
389                    try {
390                        // Take an "interactive" bugreport.
391                        ActivityManagerNative.getDefault().requestBugReport(true);
392                    } catch (RemoteException e) {
393                    }
394                }
395            }, 500);
396        }
397
398        @Override
399        public boolean onLongPress() {
400            // don't actually trigger the bugreport if we are running stability
401            // tests via monkey
402            if (ActivityManager.isUserAMonkey()) {
403                return false;
404            }
405            try {
406                // Take a "full" bugreport.
407                ActivityManagerNative.getDefault().requestBugReport(false);
408            } catch (RemoteException e) {
409            }
410            return false;
411        }
412
413        public boolean showDuringKeyguard() {
414            return true;
415        }
416
417        @Override
418        public boolean showBeforeProvisioning() {
419            return false;
420        }
421
422        @Override
423        public String getStatus() {
424            return mContext.getString(
425                    com.android.internal.R.string.bugreport_status,
426                    Build.VERSION.RELEASE,
427                    Build.ID);
428        }
429    }
430
431    private Action getSettingsAction() {
432        return new SinglePressAction(com.android.internal.R.drawable.ic_settings,
433                R.string.global_action_settings) {
434
435            @Override
436            public void onPress() {
437                Intent intent = new Intent(Settings.ACTION_SETTINGS);
438                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
439                mContext.startActivity(intent);
440            }
441
442            @Override
443            public boolean showDuringKeyguard() {
444                return true;
445            }
446
447            @Override
448            public boolean showBeforeProvisioning() {
449                return true;
450            }
451        };
452    }
453
454    private Action getAssistAction() {
455        return new SinglePressAction(com.android.internal.R.drawable.ic_action_assist_focused,
456                R.string.global_action_assist) {
457            @Override
458            public void onPress() {
459                Intent intent = new Intent(Intent.ACTION_ASSIST);
460                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
461                mContext.startActivity(intent);
462            }
463
464            @Override
465            public boolean showDuringKeyguard() {
466                return true;
467            }
468
469            @Override
470            public boolean showBeforeProvisioning() {
471                return true;
472            }
473        };
474    }
475
476    private Action getVoiceAssistAction() {
477        return new SinglePressAction(com.android.internal.R.drawable.ic_voice_search,
478                R.string.global_action_voice_assist) {
479            @Override
480            public void onPress() {
481                Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST);
482                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
483                mContext.startActivity(intent);
484            }
485
486            @Override
487            public boolean showDuringKeyguard() {
488                return true;
489            }
490
491            @Override
492            public boolean showBeforeProvisioning() {
493                return true;
494            }
495        };
496    }
497
498    private Action getLockdownAction() {
499        return new SinglePressAction(com.android.internal.R.drawable.ic_lock_lock,
500                R.string.global_action_lockdown) {
501
502            @Override
503            public void onPress() {
504                new LockPatternUtils(mContext).requireCredentialEntry(UserHandle.USER_ALL);
505                try {
506                    WindowManagerGlobal.getWindowManagerService().lockNow(null);
507                } catch (RemoteException e) {
508                    Log.e(TAG, "Error while trying to lock device.", e);
509                }
510            }
511
512            @Override
513            public boolean showDuringKeyguard() {
514                return true;
515            }
516
517            @Override
518            public boolean showBeforeProvisioning() {
519                return false;
520            }
521        };
522    }
523
524    private UserInfo getCurrentUser() {
525        try {
526            return ActivityManagerNative.getDefault().getCurrentUser();
527        } catch (RemoteException re) {
528            return null;
529        }
530    }
531
532    private boolean isCurrentUserOwner() {
533        UserInfo currentUser = getCurrentUser();
534        return currentUser == null || currentUser.isPrimary();
535    }
536
537    private void addUsersToMenu(ArrayList<Action> items) {
538        UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
539        if (um.isUserSwitcherEnabled()) {
540            List<UserInfo> users = um.getUsers();
541            UserInfo currentUser = getCurrentUser();
542            for (final UserInfo user : users) {
543                if (user.supportsSwitchToByUser()) {
544                    boolean isCurrentUser = currentUser == null
545                            ? user.id == 0 : (currentUser.id == user.id);
546                    Drawable icon = user.iconPath != null ? Drawable.createFromPath(user.iconPath)
547                            : null;
548                    SinglePressAction switchToUser = new SinglePressAction(
549                            com.android.internal.R.drawable.ic_menu_cc, icon,
550                            (user.name != null ? user.name : "Primary")
551                            + (isCurrentUser ? " \u2714" : "")) {
552                        public void onPress() {
553                            try {
554                                ActivityManagerNative.getDefault().switchUser(user.id);
555                            } catch (RemoteException re) {
556                                Log.e(TAG, "Couldn't switch user " + re);
557                            }
558                        }
559
560                        public boolean showDuringKeyguard() {
561                            return true;
562                        }
563
564                        public boolean showBeforeProvisioning() {
565                            return false;
566                        }
567                    };
568                    items.add(switchToUser);
569                }
570            }
571        }
572    }
573
574    private void prepareDialog() {
575        refreshSilentMode();
576        mAirplaneModeOn.updateState(mAirplaneState);
577        mAdapter.notifyDataSetChanged();
578        mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
579        if (mShowSilentToggle) {
580            IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
581            mContext.registerReceiver(mRingerModeReceiver, filter);
582        }
583    }
584
585    private void refreshSilentMode() {
586        if (!mHasVibrator) {
587            final boolean silentModeOn =
588                    mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
589            ((ToggleAction)mSilentModeAction).updateState(
590                    silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off);
591        }
592    }
593
594    /** {@inheritDoc} */
595    public void onDismiss(DialogInterface dialog) {
596        if (mShowSilentToggle) {
597            try {
598                mContext.unregisterReceiver(mRingerModeReceiver);
599            } catch (IllegalArgumentException ie) {
600                // ignore this
601                Log.w(TAG, ie);
602            }
603        }
604    }
605
606    /** {@inheritDoc} */
607    public void onClick(DialogInterface dialog, int which) {
608        if (!(mAdapter.getItem(which) instanceof SilentModeTriStateAction)) {
609            dialog.dismiss();
610        }
611        mAdapter.getItem(which).onPress();
612    }
613
614    /**
615     * The adapter used for the list within the global actions dialog, taking
616     * into account whether the keyguard is showing via
617     * {@link GlobalActions#mKeyguardShowing} and whether the device is provisioned
618     * via {@link GlobalActions#mDeviceProvisioned}.
619     */
620    private class MyAdapter extends BaseAdapter {
621
622        public int getCount() {
623            int count = 0;
624
625            for (int i = 0; i < mItems.size(); i++) {
626                final Action action = mItems.get(i);
627
628                if (mKeyguardShowing && !action.showDuringKeyguard()) {
629                    continue;
630                }
631                if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
632                    continue;
633                }
634                count++;
635            }
636            return count;
637        }
638
639        @Override
640        public boolean isEnabled(int position) {
641            return getItem(position).isEnabled();
642        }
643
644        @Override
645        public boolean areAllItemsEnabled() {
646            return false;
647        }
648
649        public Action getItem(int position) {
650
651            int filteredPos = 0;
652            for (int i = 0; i < mItems.size(); i++) {
653                final Action action = mItems.get(i);
654                if (mKeyguardShowing && !action.showDuringKeyguard()) {
655                    continue;
656                }
657                if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
658                    continue;
659                }
660                if (filteredPos == position) {
661                    return action;
662                }
663                filteredPos++;
664            }
665
666            throw new IllegalArgumentException("position " + position
667                    + " out of range of showable actions"
668                    + ", filtered count=" + getCount()
669                    + ", keyguardshowing=" + mKeyguardShowing
670                    + ", provisioned=" + mDeviceProvisioned);
671        }
672
673
674        public long getItemId(int position) {
675            return position;
676        }
677
678        public View getView(int position, View convertView, ViewGroup parent) {
679            Action action = getItem(position);
680            return action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
681        }
682    }
683
684    // note: the scheme below made more sense when we were planning on having
685    // 8 different things in the global actions dialog.  seems overkill with
686    // only 3 items now, but may as well keep this flexible approach so it will
687    // be easy should someone decide at the last minute to include something
688    // else, such as 'enable wifi', or 'enable bluetooth'
689
690    /**
691     * What each item in the global actions dialog must be able to support.
692     */
693    private interface Action {
694        /**
695         * @return Text that will be announced when dialog is created.  null
696         *     for none.
697         */
698        CharSequence getLabelForAccessibility(Context context);
699
700        View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater);
701
702        void onPress();
703
704        /**
705         * @return whether this action should appear in the dialog when the keygaurd
706         *    is showing.
707         */
708        boolean showDuringKeyguard();
709
710        /**
711         * @return whether this action should appear in the dialog before the
712         *   device is provisioned.
713         */
714        boolean showBeforeProvisioning();
715
716        boolean isEnabled();
717    }
718
719    /**
720     * An action that also supports long press.
721     */
722    private interface LongPressAction extends Action {
723        boolean onLongPress();
724    }
725
726    /**
727     * A single press action maintains no state, just responds to a press
728     * and takes an action.
729     */
730    private static abstract class SinglePressAction implements Action {
731        private final int mIconResId;
732        private final Drawable mIcon;
733        private final int mMessageResId;
734        private final CharSequence mMessage;
735
736        protected SinglePressAction(int iconResId, int messageResId) {
737            mIconResId = iconResId;
738            mMessageResId = messageResId;
739            mMessage = null;
740            mIcon = null;
741        }
742
743        protected SinglePressAction(int iconResId, Drawable icon, CharSequence message) {
744            mIconResId = iconResId;
745            mMessageResId = 0;
746            mMessage = message;
747            mIcon = icon;
748        }
749
750        public boolean isEnabled() {
751            return true;
752        }
753
754        public String getStatus() {
755            return null;
756        }
757
758        abstract public void onPress();
759
760        public CharSequence getLabelForAccessibility(Context context) {
761            if (mMessage != null) {
762                return mMessage;
763            } else {
764                return context.getString(mMessageResId);
765            }
766        }
767
768        public View create(
769                Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
770            View v = inflater.inflate(R.layout.global_actions_item, parent, false);
771
772            ImageView icon = (ImageView) v.findViewById(R.id.icon);
773            TextView messageView = (TextView) v.findViewById(R.id.message);
774
775            TextView statusView = (TextView) v.findViewById(R.id.status);
776            final String status = getStatus();
777            if (!TextUtils.isEmpty(status)) {
778                statusView.setText(status);
779            } else {
780                statusView.setVisibility(View.GONE);
781            }
782            if (mIcon != null) {
783                icon.setImageDrawable(mIcon);
784                icon.setScaleType(ScaleType.CENTER_CROP);
785            } else if (mIconResId != 0) {
786                icon.setImageDrawable(context.getDrawable(mIconResId));
787            }
788            if (mMessage != null) {
789                messageView.setText(mMessage);
790            } else {
791                messageView.setText(mMessageResId);
792            }
793
794            return v;
795        }
796    }
797
798    /**
799     * A toggle action knows whether it is on or off, and displays an icon
800     * and status message accordingly.
801     */
802    private static abstract class ToggleAction implements Action {
803
804        enum State {
805            Off(false),
806            TurningOn(true),
807            TurningOff(true),
808            On(false);
809
810            private final boolean inTransition;
811
812            State(boolean intermediate) {
813                inTransition = intermediate;
814            }
815
816            public boolean inTransition() {
817                return inTransition;
818            }
819        }
820
821        protected State mState = State.Off;
822
823        // prefs
824        protected int mEnabledIconResId;
825        protected int mDisabledIconResid;
826        protected int mMessageResId;
827        protected int mEnabledStatusMessageResId;
828        protected int mDisabledStatusMessageResId;
829
830        /**
831         * @param enabledIconResId The icon for when this action is on.
832         * @param disabledIconResid The icon for when this action is off.
833         * @param essage The general information message, e.g 'Silent Mode'
834         * @param enabledStatusMessageResId The on status message, e.g 'sound disabled'
835         * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled'
836         */
837        public ToggleAction(int enabledIconResId,
838                int disabledIconResid,
839                int message,
840                int enabledStatusMessageResId,
841                int disabledStatusMessageResId) {
842            mEnabledIconResId = enabledIconResId;
843            mDisabledIconResid = disabledIconResid;
844            mMessageResId = message;
845            mEnabledStatusMessageResId = enabledStatusMessageResId;
846            mDisabledStatusMessageResId = disabledStatusMessageResId;
847        }
848
849        /**
850         * Override to make changes to resource IDs just before creating the
851         * View.
852         */
853        void willCreate() {
854
855        }
856
857        @Override
858        public CharSequence getLabelForAccessibility(Context context) {
859            return context.getString(mMessageResId);
860        }
861
862        public View create(Context context, View convertView, ViewGroup parent,
863                LayoutInflater inflater) {
864            willCreate();
865
866            View v = inflater.inflate(R
867                            .layout.global_actions_item, parent, false);
868
869            ImageView icon = (ImageView) v.findViewById(R.id.icon);
870            TextView messageView = (TextView) v.findViewById(R.id.message);
871            TextView statusView = (TextView) v.findViewById(R.id.status);
872            final boolean enabled = isEnabled();
873
874            if (messageView != null) {
875                messageView.setText(mMessageResId);
876                messageView.setEnabled(enabled);
877            }
878
879            boolean on = ((mState == State.On) || (mState == State.TurningOn));
880            if (icon != null) {
881                icon.setImageDrawable(context.getDrawable(
882                        (on ? mEnabledIconResId : mDisabledIconResid)));
883                icon.setEnabled(enabled);
884            }
885
886            if (statusView != null) {
887                statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
888                statusView.setVisibility(View.VISIBLE);
889                statusView.setEnabled(enabled);
890            }
891            v.setEnabled(enabled);
892
893            return v;
894        }
895
896        public final void onPress() {
897            if (mState.inTransition()) {
898                Log.w(TAG, "shouldn't be able to toggle when in transition");
899                return;
900            }
901
902            final boolean nowOn = !(mState == State.On);
903            onToggle(nowOn);
904            changeStateFromPress(nowOn);
905        }
906
907        public boolean isEnabled() {
908            return !mState.inTransition();
909        }
910
911        /**
912         * Implementations may override this if their state can be in on of the intermediate
913         * states until some notification is received (e.g airplane mode is 'turning off' until
914         * we know the wireless connections are back online
915         * @param buttonOn Whether the button was turned on or off
916         */
917        protected void changeStateFromPress(boolean buttonOn) {
918            mState = buttonOn ? State.On : State.Off;
919        }
920
921        abstract void onToggle(boolean on);
922
923        public void updateState(State state) {
924            mState = state;
925        }
926    }
927
928    private class SilentModeToggleAction extends ToggleAction {
929        public SilentModeToggleAction() {
930            super(R.drawable.ic_audio_vol_mute,
931                    R.drawable.ic_audio_vol,
932                    R.string.global_action_toggle_silent_mode,
933                    R.string.global_action_silent_mode_on_status,
934                    R.string.global_action_silent_mode_off_status);
935        }
936
937        void onToggle(boolean on) {
938            if (on) {
939                mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
940            } else {
941                mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
942            }
943        }
944
945        public boolean showDuringKeyguard() {
946            return true;
947        }
948
949        public boolean showBeforeProvisioning() {
950            return false;
951        }
952    }
953
954    private static class SilentModeTriStateAction implements Action, View.OnClickListener {
955
956        private final int[] ITEM_IDS = { R.id.option1, R.id.option2, R.id.option3 };
957
958        private final AudioManager mAudioManager;
959        private final Handler mHandler;
960        private final Context mContext;
961
962        SilentModeTriStateAction(Context context, AudioManager audioManager, Handler handler) {
963            mAudioManager = audioManager;
964            mHandler = handler;
965            mContext = context;
966        }
967
968        private int ringerModeToIndex(int ringerMode) {
969            // They just happen to coincide
970            return ringerMode;
971        }
972
973        private int indexToRingerMode(int index) {
974            // They just happen to coincide
975            return index;
976        }
977
978        @Override
979        public CharSequence getLabelForAccessibility(Context context) {
980            return null;
981        }
982
983        public View create(Context context, View convertView, ViewGroup parent,
984                LayoutInflater inflater) {
985            View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false);
986
987            int selectedIndex = ringerModeToIndex(mAudioManager.getRingerMode());
988            for (int i = 0; i < 3; i++) {
989                View itemView = v.findViewById(ITEM_IDS[i]);
990                itemView.setSelected(selectedIndex == i);
991                // Set up click handler
992                itemView.setTag(i);
993                itemView.setOnClickListener(this);
994            }
995            return v;
996        }
997
998        public void onPress() {
999        }
1000
1001        public boolean showDuringKeyguard() {
1002            return true;
1003        }
1004
1005        public boolean showBeforeProvisioning() {
1006            return false;
1007        }
1008
1009        public boolean isEnabled() {
1010            return true;
1011        }
1012
1013        void willCreate() {
1014        }
1015
1016        public void onClick(View v) {
1017            if (!(v.getTag() instanceof Integer)) return;
1018
1019            int index = (Integer) v.getTag();
1020            mAudioManager.setRingerMode(indexToRingerMode(index));
1021            mHandler.sendEmptyMessageDelayed(MESSAGE_DISMISS, DIALOG_DISMISS_DELAY);
1022        }
1023    }
1024
1025    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
1026        public void onReceive(Context context, Intent intent) {
1027            String action = intent.getAction();
1028            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
1029                    || Intent.ACTION_SCREEN_OFF.equals(action)) {
1030                String reason = intent.getStringExtra(PhoneWindowManager.SYSTEM_DIALOG_REASON_KEY);
1031                if (!PhoneWindowManager.SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) {
1032                    mHandler.sendEmptyMessage(MESSAGE_DISMISS);
1033                }
1034            } else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) {
1035                // Airplane mode can be changed after ECM exits if airplane toggle button
1036                // is pressed during ECM mode
1037                if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) &&
1038                        mIsWaitingForEcmExit) {
1039                    mIsWaitingForEcmExit = false;
1040                    changeAirplaneModeSystemSetting(true);
1041                }
1042            }
1043        }
1044    };
1045
1046    PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
1047        @Override
1048        public void onServiceStateChanged(ServiceState serviceState) {
1049            if (!mHasTelephony) return;
1050            final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF;
1051            mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off;
1052            mAirplaneModeOn.updateState(mAirplaneState);
1053            mAdapter.notifyDataSetChanged();
1054        }
1055    };
1056
1057    private BroadcastReceiver mRingerModeReceiver = new BroadcastReceiver() {
1058        @Override
1059        public void onReceive(Context context, Intent intent) {
1060            if (intent.getAction().equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
1061                mHandler.sendEmptyMessage(MESSAGE_REFRESH);
1062            }
1063        }
1064    };
1065
1066    private ContentObserver mAirplaneModeObserver = new ContentObserver(new Handler()) {
1067        @Override
1068        public void onChange(boolean selfChange) {
1069            onAirplaneModeChanged();
1070        }
1071    };
1072
1073    private static final int MESSAGE_DISMISS = 0;
1074    private static final int MESSAGE_REFRESH = 1;
1075    private static final int MESSAGE_SHOW = 2;
1076    private static final int DIALOG_DISMISS_DELAY = 300; // ms
1077
1078    private Handler mHandler = new Handler() {
1079        public void handleMessage(Message msg) {
1080            switch (msg.what) {
1081            case MESSAGE_DISMISS:
1082                if (mDialog != null) {
1083                    mDialog.dismiss();
1084                    mDialog = null;
1085                }
1086                break;
1087            case MESSAGE_REFRESH:
1088                refreshSilentMode();
1089                mAdapter.notifyDataSetChanged();
1090                break;
1091            case MESSAGE_SHOW:
1092                handleShow();
1093                break;
1094            }
1095        }
1096    };
1097
1098    private void onAirplaneModeChanged() {
1099        // Let the service state callbacks handle the state.
1100        if (mHasTelephony) return;
1101
1102        boolean airplaneModeOn = Settings.Global.getInt(
1103                mContext.getContentResolver(),
1104                Settings.Global.AIRPLANE_MODE_ON,
1105                0) == 1;
1106        mAirplaneState = airplaneModeOn ? ToggleAction.State.On : ToggleAction.State.Off;
1107        mAirplaneModeOn.updateState(mAirplaneState);
1108    }
1109
1110    /**
1111     * Change the airplane mode system setting
1112     */
1113    private void changeAirplaneModeSystemSetting(boolean on) {
1114        Settings.Global.putInt(
1115                mContext.getContentResolver(),
1116                Settings.Global.AIRPLANE_MODE_ON,
1117                on ? 1 : 0);
1118        Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
1119        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
1120        intent.putExtra("state", on);
1121        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
1122        if (!mHasTelephony) {
1123            mAirplaneState = on ? ToggleAction.State.On : ToggleAction.State.Off;
1124        }
1125    }
1126
1127    private static final class GlobalActionsDialog extends Dialog implements DialogInterface {
1128        private final Context mContext;
1129        private final int mWindowTouchSlop;
1130        private final AlertController mAlert;
1131        private final MyAdapter mAdapter;
1132
1133        private EnableAccessibilityController mEnableAccessibilityController;
1134
1135        private boolean mIntercepted;
1136        private boolean mCancelOnUp;
1137
1138        public GlobalActionsDialog(Context context, AlertParams params) {
1139            super(context, getDialogTheme(context));
1140            mContext = getContext();
1141            mAlert = new AlertController(mContext, this, getWindow());
1142            mAdapter = (MyAdapter) params.mAdapter;
1143            mWindowTouchSlop = ViewConfiguration.get(context).getScaledWindowTouchSlop();
1144            params.apply(mAlert);
1145        }
1146
1147        private static int getDialogTheme(Context context) {
1148            TypedValue outValue = new TypedValue();
1149            context.getTheme().resolveAttribute(com.android.internal.R.attr.alertDialogTheme,
1150                    outValue, true);
1151            return outValue.resourceId;
1152        }
1153
1154        @Override
1155        protected void onStart() {
1156            // If global accessibility gesture can be performed, we will take care
1157            // of dismissing the dialog on touch outside. This is because the dialog
1158            // is dismissed on the first down while the global gesture is a long press
1159            // with two fingers anywhere on the screen.
1160            if (EnableAccessibilityController.canEnableAccessibilityViaGesture(mContext)) {
1161                mEnableAccessibilityController = new EnableAccessibilityController(mContext,
1162                        new Runnable() {
1163                    @Override
1164                    public void run() {
1165                        dismiss();
1166                    }
1167                });
1168                super.setCanceledOnTouchOutside(false);
1169            } else {
1170                mEnableAccessibilityController = null;
1171                super.setCanceledOnTouchOutside(true);
1172            }
1173
1174            super.onStart();
1175        }
1176
1177        @Override
1178        protected void onStop() {
1179            if (mEnableAccessibilityController != null) {
1180                mEnableAccessibilityController.onDestroy();
1181            }
1182            super.onStop();
1183        }
1184
1185        @Override
1186        public boolean dispatchTouchEvent(MotionEvent event) {
1187            if (mEnableAccessibilityController != null) {
1188                final int action = event.getActionMasked();
1189                if (action == MotionEvent.ACTION_DOWN) {
1190                    View decor = getWindow().getDecorView();
1191                    final int eventX = (int) event.getX();
1192                    final int eventY = (int) event.getY();
1193                    if (eventX < -mWindowTouchSlop
1194                            || eventY < -mWindowTouchSlop
1195                            || eventX >= decor.getWidth() + mWindowTouchSlop
1196                            || eventY >= decor.getHeight() + mWindowTouchSlop) {
1197                        mCancelOnUp = true;
1198                    }
1199                }
1200                try {
1201                    if (!mIntercepted) {
1202                        mIntercepted = mEnableAccessibilityController.onInterceptTouchEvent(event);
1203                        if (mIntercepted) {
1204                            final long now = SystemClock.uptimeMillis();
1205                            event = MotionEvent.obtain(now, now,
1206                                    MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
1207                            event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
1208                            mCancelOnUp = true;
1209                        }
1210                    } else {
1211                        return mEnableAccessibilityController.onTouchEvent(event);
1212                    }
1213                } finally {
1214                    if (action == MotionEvent.ACTION_UP) {
1215                        if (mCancelOnUp) {
1216                            cancel();
1217                        }
1218                        mCancelOnUp = false;
1219                        mIntercepted = false;
1220                    }
1221                }
1222            }
1223            return super.dispatchTouchEvent(event);
1224        }
1225
1226        public ListView getListView() {
1227            return mAlert.getListView();
1228        }
1229
1230        @Override
1231        protected void onCreate(Bundle savedInstanceState) {
1232            super.onCreate(savedInstanceState);
1233            mAlert.installContent();
1234        }
1235
1236        @Override
1237        public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
1238            if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
1239                for (int i = 0; i < mAdapter.getCount(); ++i) {
1240                    CharSequence label =
1241                            mAdapter.getItem(i).getLabelForAccessibility(getContext());
1242                    if (label != null) {
1243                        event.getText().add(label);
1244                    }
1245                }
1246            }
1247            return super.dispatchPopulateAccessibilityEvent(event);
1248        }
1249
1250        @Override
1251        public boolean onKeyDown(int keyCode, KeyEvent event) {
1252            if (mAlert.onKeyDown(keyCode, event)) {
1253                return true;
1254            }
1255            return super.onKeyDown(keyCode, event);
1256        }
1257
1258        @Override
1259        public boolean onKeyUp(int keyCode, KeyEvent event) {
1260            if (mAlert.onKeyUp(keyCode, event)) {
1261                return true;
1262            }
1263            return super.onKeyUp(keyCode, event);
1264        }
1265    }
1266}
1267