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