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