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