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