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