GlobalActions.java revision ded7c652d754751e6fbde729d66825c69394d1cb
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 android.app.Activity;
20import android.app.AlertDialog;
21import android.app.StatusBarManager;
22import android.content.BroadcastReceiver;
23import android.content.Context;
24import android.content.DialogInterface;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.media.AudioManager;
28import android.os.Handler;
29import android.os.Message;
30import android.os.SystemProperties;
31import android.provider.Settings;
32import android.telephony.PhoneStateListener;
33import android.telephony.ServiceState;
34import android.telephony.TelephonyManager;
35import android.util.Log;
36import android.view.LayoutInflater;
37import android.view.View;
38import android.view.ViewGroup;
39import android.view.WindowManager;
40import android.widget.BaseAdapter;
41import android.widget.ImageView;
42import android.widget.TextView;
43import com.android.internal.R;
44import com.android.internal.app.ShutdownThread;
45import com.android.internal.telephony.TelephonyIntents;
46import com.android.internal.telephony.TelephonyProperties;
47import com.google.android.collect.Lists;
48
49import java.util.ArrayList;
50
51/**
52 * Helper to show the global actions dialog.  Each item is an {@link Action} that
53 * may show depending on whether the keyguard is showing, and whether the device
54 * is provisioned.
55 */
56class GlobalActions implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener  {
57
58    private static final String TAG = "GlobalActions";
59
60    private StatusBarManager mStatusBar;
61
62    private final Context mContext;
63    private final AudioManager mAudioManager;
64
65    private ArrayList<Action> mItems;
66    private AlertDialog mDialog;
67
68    private ToggleAction mSilentModeToggle;
69    private ToggleAction mAirplaneModeOn;
70
71    private MyAdapter mAdapter;
72
73    private boolean mKeyguardShowing = false;
74    private boolean mDeviceProvisioned = false;
75    private ToggleAction.State mAirplaneState = ToggleAction.State.Off;
76    private boolean mIsWaitingForEcmExit = false;
77
78    /**
79     * @param context everything needs a context :(
80     */
81    public GlobalActions(Context context) {
82        mContext = context;
83        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
84
85        // receive broadcasts
86        IntentFilter filter = new IntentFilter();
87        filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
88        filter.addAction(Intent.ACTION_SCREEN_OFF);
89        filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
90        context.registerReceiver(mBroadcastReceiver, filter);
91
92        // get notified of phone state changes
93        TelephonyManager telephonyManager =
94                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
95        telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
96    }
97
98    /**
99     * Show the global actions dialog (creating if necessary)
100     * @param keyguardShowing True if keyguard is showing
101     */
102    public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
103        mKeyguardShowing = keyguardShowing;
104        mDeviceProvisioned = isDeviceProvisioned;
105        if (mDialog == null) {
106            mStatusBar = (StatusBarManager)mContext.getSystemService(Context.STATUS_BAR_SERVICE);
107            mDialog = createDialog();
108        }
109        prepareDialog();
110
111        mStatusBar.disable(StatusBarManager.DISABLE_EXPAND);
112        mDialog.show();
113    }
114
115    /**
116     * Create the global actions dialog.
117     * @return A new dialog.
118     */
119    private AlertDialog createDialog() {
120        mSilentModeToggle = new ToggleAction(
121                R.drawable.ic_lock_silent_mode,
122                R.drawable.ic_lock_silent_mode_off,
123                R.string.global_action_toggle_silent_mode,
124                R.string.global_action_silent_mode_on_status,
125                R.string.global_action_silent_mode_off_status) {
126
127            void willCreate() {
128                // XXX: FIXME: switch to ic_lock_vibrate_mode when available
129                mEnabledIconResId = (Settings.System.getInt(mContext.getContentResolver(),
130                        Settings.System.VIBRATE_IN_SILENT, 1) == 1)
131                    ? R.drawable.ic_lock_silent_mode_vibrate
132                    : R.drawable.ic_lock_silent_mode;
133            }
134
135            void onToggle(boolean on) {
136                if (on) {
137                    mAudioManager.setRingerMode((Settings.System.getInt(mContext.getContentResolver(),
138                        Settings.System.VIBRATE_IN_SILENT, 1) == 1)
139                        ? AudioManager.RINGER_MODE_VIBRATE
140                        : AudioManager.RINGER_MODE_SILENT);
141                } else {
142                    mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
143                }
144            }
145
146            public boolean showDuringKeyguard() {
147                return true;
148            }
149
150            public boolean showBeforeProvisioning() {
151                return false;
152            }
153        };
154
155        mAirplaneModeOn = new ToggleAction(
156                R.drawable.ic_lock_airplane_mode,
157                R.drawable.ic_lock_airplane_mode_off,
158                R.string.global_actions_toggle_airplane_mode,
159                R.string.global_actions_airplane_mode_on_status,
160                R.string.global_actions_airplane_mode_off_status) {
161
162            void onToggle(boolean on) {
163                if (Boolean.parseBoolean(
164                        SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) {
165                    mIsWaitingForEcmExit = true;
166                    // Launch ECM exit dialog
167                    Intent ecmDialogIntent =
168                            new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);
169                    ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
170                    mContext.startActivity(ecmDialogIntent);
171                } else {
172                    changeAirplaneModeSystemSetting(on);
173                }
174            }
175
176            @Override
177            protected void changeStateFromPress(boolean buttonOn) {
178                // In ECM mode airplane state cannot be changed
179                if (!(Boolean.parseBoolean(
180                        SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)))) {
181                    mState = buttonOn ? State.TurningOn : State.TurningOff;
182                    mAirplaneState = mState;
183                }
184            }
185
186            public boolean showDuringKeyguard() {
187                return true;
188            }
189
190            public boolean showBeforeProvisioning() {
191                return false;
192            }
193        };
194
195        mItems = Lists.newArrayList(
196                // silent mode
197                mSilentModeToggle,
198                // next: airplane mode
199                mAirplaneModeOn,
200                // last: power off
201                new SinglePressAction(
202                        com.android.internal.R.drawable.ic_lock_power_off,
203                        R.string.global_action_power_off) {
204
205                    public void onPress() {
206                        // shutdown by making sure radio and power are handled accordingly.
207                        ShutdownThread.shutdown(mContext, true);
208                    }
209
210                    public boolean showDuringKeyguard() {
211                        return true;
212                    }
213
214                    public boolean showBeforeProvisioning() {
215                        return true;
216                    }
217                });
218
219        mAdapter = new MyAdapter();
220
221        final AlertDialog.Builder ab = new AlertDialog.Builder(mContext);
222
223        ab.setAdapter(mAdapter, this)
224                .setInverseBackgroundForced(true)
225                .setTitle(R.string.global_actions);
226
227        final AlertDialog dialog = ab.create();
228        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
229        if (!mContext.getResources().getBoolean(
230                com.android.internal.R.bool.config_sf_slowBlur)) {
231            dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND,
232                    WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
233        }
234
235        dialog.setOnDismissListener(this);
236
237        return dialog;
238    }
239
240    private void prepareDialog() {
241        final boolean silentModeOn =
242                mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
243        mSilentModeToggle.updateState(
244                silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off);
245        mAirplaneModeOn.updateState(mAirplaneState);
246        mAdapter.notifyDataSetChanged();
247        if (mKeyguardShowing) {
248            mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
249        } else {
250            mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
251        }
252    }
253
254
255    /** {@inheritDoc} */
256    public void onDismiss(DialogInterface dialog) {
257        mStatusBar.disable(StatusBarManager.DISABLE_NONE);
258    }
259
260    /** {@inheritDoc} */
261    public void onClick(DialogInterface dialog, int which) {
262        dialog.dismiss();
263        mAdapter.getItem(which).onPress();
264    }
265
266
267    /**
268     * The adapter used for the list within the global actions dialog, taking
269     * into account whether the keyguard is showing via
270     * {@link GlobalActions#mKeyguardShowing} and whether the device is provisioned
271     * via {@link GlobalActions#mDeviceProvisioned}.
272     */
273    private class MyAdapter extends BaseAdapter {
274
275        public int getCount() {
276            int count = 0;
277
278            for (int i = 0; i < mItems.size(); i++) {
279                final Action action = mItems.get(i);
280
281                if (mKeyguardShowing && !action.showDuringKeyguard()) {
282                    continue;
283                }
284                if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
285                    continue;
286                }
287                count++;
288            }
289            return count;
290        }
291
292        @Override
293        public boolean isEnabled(int position) {
294            return getItem(position).isEnabled();
295        }
296
297        @Override
298        public boolean areAllItemsEnabled() {
299            return false;
300        }
301
302        public Action getItem(int position) {
303
304            int filteredPos = 0;
305            for (int i = 0; i < mItems.size(); i++) {
306                final Action action = mItems.get(i);
307                if (mKeyguardShowing && !action.showDuringKeyguard()) {
308                    continue;
309                }
310                if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
311                    continue;
312                }
313                if (filteredPos == position) {
314                    return action;
315                }
316                filteredPos++;
317            }
318
319            throw new IllegalArgumentException("position " + position
320                    + " out of range of showable actions"
321                    + ", filtered count=" + getCount()
322                    + ", keyguardshowing=" + mKeyguardShowing
323                    + ", provisioned=" + mDeviceProvisioned);
324        }
325
326
327        public long getItemId(int position) {
328            return position;
329        }
330
331        public View getView(int position, View convertView, ViewGroup parent) {
332            Action action = getItem(position);
333            return action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
334        }
335    }
336
337    // note: the scheme below made more sense when we were planning on having
338    // 8 different things in the global actions dialog.  seems overkill with
339    // only 3 items now, but may as well keep this flexible approach so it will
340    // be easy should someone decide at the last minute to include something
341    // else, such as 'enable wifi', or 'enable bluetooth'
342
343    /**
344     * What each item in the global actions dialog must be able to support.
345     */
346    private interface Action {
347        View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater);
348
349        void onPress();
350
351        /**
352         * @return whether this action should appear in the dialog when the keygaurd
353         *    is showing.
354         */
355        boolean showDuringKeyguard();
356
357        /**
358         * @return whether this action should appear in the dialog before the
359         *   device is provisioned.
360         */
361        boolean showBeforeProvisioning();
362
363        boolean isEnabled();
364    }
365
366    /**
367     * A single press action maintains no state, just responds to a press
368     * and takes an action.
369     */
370    private static abstract class SinglePressAction implements Action {
371        private final int mIconResId;
372        private final int mMessageResId;
373
374        protected SinglePressAction(int iconResId, int messageResId) {
375            mIconResId = iconResId;
376            mMessageResId = messageResId;
377        }
378
379        public boolean isEnabled() {
380            return true;
381        }
382
383        abstract public void onPress();
384
385        public View create(
386                Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
387            View v = (convertView != null) ?
388                    convertView :
389                    inflater.inflate(R.layout.global_actions_item, parent, false);
390
391            ImageView icon = (ImageView) v.findViewById(R.id.icon);
392            TextView messageView = (TextView) v.findViewById(R.id.message);
393
394            v.findViewById(R.id.status).setVisibility(View.GONE);
395
396            icon.setImageDrawable(context.getResources().getDrawable(mIconResId));
397            messageView.setText(mMessageResId);
398
399            return v;
400        }
401    }
402
403    /**
404     * A toggle action knows whether it is on or off, and displays an icon
405     * and status message accordingly.
406     */
407    private static abstract class ToggleAction implements Action {
408
409        enum State {
410            Off(false),
411            TurningOn(true),
412            TurningOff(true),
413            On(false);
414
415            private final boolean inTransition;
416
417            State(boolean intermediate) {
418                inTransition = intermediate;
419            }
420
421            public boolean inTransition() {
422                return inTransition;
423            }
424        }
425
426        protected State mState = State.Off;
427
428        // prefs
429        protected int mEnabledIconResId;
430        protected int mDisabledIconResid;
431        protected int mMessageResId;
432        protected int mEnabledStatusMessageResId;
433        protected int mDisabledStatusMessageResId;
434
435        /**
436         * @param enabledIconResId The icon for when this action is on.
437         * @param disabledIconResid The icon for when this action is off.
438         * @param essage The general information message, e.g 'Silent Mode'
439         * @param enabledStatusMessageResId The on status message, e.g 'sound disabled'
440         * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled'
441         */
442        public ToggleAction(int enabledIconResId,
443                int disabledIconResid,
444                int essage,
445                int enabledStatusMessageResId,
446                int disabledStatusMessageResId) {
447            mEnabledIconResId = enabledIconResId;
448            mDisabledIconResid = disabledIconResid;
449            mMessageResId = essage;
450            mEnabledStatusMessageResId = enabledStatusMessageResId;
451            mDisabledStatusMessageResId = disabledStatusMessageResId;
452        }
453
454        /**
455         * Override to make changes to resource IDs just before creating the
456         * View.
457         */
458        void willCreate() {
459
460        }
461
462        public View create(Context context, View convertView, ViewGroup parent,
463                LayoutInflater inflater) {
464            willCreate();
465
466            View v = (convertView != null) ?
467                    convertView :
468                    inflater.inflate(R
469                            .layout.global_actions_item, parent, false);
470
471            ImageView icon = (ImageView) v.findViewById(R.id.icon);
472            TextView messageView = (TextView) v.findViewById(R.id.message);
473            TextView statusView = (TextView) v.findViewById(R.id.status);
474
475            messageView.setText(mMessageResId);
476
477            boolean on = ((mState == State.On) || (mState == State.TurningOn));
478            icon.setImageDrawable(context.getResources().getDrawable(
479                    (on ? mEnabledIconResId : mDisabledIconResid)));
480            statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
481            statusView.setVisibility(View.VISIBLE);
482
483            final boolean enabled = isEnabled();
484            messageView.setEnabled(enabled);
485            statusView.setEnabled(enabled);
486            icon.setEnabled(enabled);
487            v.setEnabled(enabled);
488
489            return v;
490        }
491
492        public final void onPress() {
493            if (mState.inTransition()) {
494                Log.w(TAG, "shouldn't be able to toggle when in transition");
495                return;
496            }
497
498            final boolean nowOn = !(mState == State.On);
499            onToggle(nowOn);
500            changeStateFromPress(nowOn);
501        }
502
503        public boolean isEnabled() {
504            return !mState.inTransition();
505        }
506
507        /**
508         * Implementations may override this if their state can be in on of the intermediate
509         * states until some notification is received (e.g airplane mode is 'turning off' until
510         * we know the wireless connections are back online
511         * @param buttonOn Whether the button was turned on or off
512         */
513        protected void changeStateFromPress(boolean buttonOn) {
514            mState = buttonOn ? State.On : State.Off;
515        }
516
517        abstract void onToggle(boolean on);
518
519        public void updateState(State state) {
520            mState = state;
521        }
522    }
523
524    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
525        public void onReceive(Context context, Intent intent) {
526            String action = intent.getAction();
527            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
528                    || Intent.ACTION_SCREEN_OFF.equals(action)) {
529                String reason = intent.getStringExtra(PhoneWindowManager.SYSTEM_DIALOG_REASON_KEY);
530                if (!PhoneWindowManager.SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) {
531                    mHandler.sendEmptyMessage(MESSAGE_DISMISS);
532                }
533            } else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) {
534                // Airplane mode can be changed after ECM exits if airplane toggle button
535                // is pressed during ECM mode
536                if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) &&
537                        mIsWaitingForEcmExit) {
538                    mIsWaitingForEcmExit = false;
539                    changeAirplaneModeSystemSetting(true);
540                }
541            }
542        }
543    };
544
545    PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
546        @Override
547        public void onServiceStateChanged(ServiceState serviceState) {
548            final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF;
549            mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off;
550            mAirplaneModeOn.updateState(mAirplaneState);
551            mAdapter.notifyDataSetChanged();
552        }
553    };
554
555    private static final int MESSAGE_DISMISS = 0;
556    private Handler mHandler = new Handler() {
557        public void handleMessage(Message msg) {
558            if (msg.what == MESSAGE_DISMISS) {
559                if (mDialog != null) {
560                    mDialog.dismiss();
561                }
562            }
563        }
564    };
565
566    /**
567     * Change the airplane mode system setting
568     */
569    private void changeAirplaneModeSystemSetting(boolean on) {
570        Settings.System.putInt(
571                mContext.getContentResolver(),
572                Settings.System.AIRPLANE_MODE_ON,
573                on ? 1 : 0);
574        Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
575        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
576        intent.putExtra("state", on);
577        mContext.sendBroadcast(intent);
578    }
579}
580