1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.policy.impl;
18
19import com.android.internal.R;
20import com.android.internal.app.ShutdownThread;
21import com.google.android.collect.Lists;
22
23import android.app.AlertDialog;
24import android.app.StatusBarManager;
25import android.content.Context;
26import android.content.BroadcastReceiver;
27import android.content.Intent;
28import android.content.IntentFilter;
29import android.content.DialogInterface;
30import android.media.AudioManager;
31import android.os.LocalPowerManager;
32import android.os.Handler;
33import android.os.Message;
34import android.os.SystemClock;
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.LinearLayout;
42import android.widget.TextView;
43
44import java.util.ArrayList;
45
46/**
47 * Helper to show the global actions dialog.  Each item is an {@link Action} that
48 * may show depending on whether the keyguard is showing, and whether the device
49 * is provisioned.
50 */
51class GlobalActions implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener  {
52
53    private StatusBarManager mStatusBar;
54
55    private final Context mContext;
56    private final LocalPowerManager mPowerManager;
57    private final AudioManager mAudioManager;
58    private ArrayList<Action> mItems;
59    private AlertDialog mDialog;
60
61    private ToggleAction mSilentModeToggle;
62
63    private MyAdapter mAdapter;
64
65    private boolean mKeyguardShowing = false;
66    private boolean mDeviceProvisioned = false;
67
68    /**
69     * @param context everything needs a context :)
70     * @param powerManager used to turn the screen off (the lock action).
71     */
72    public GlobalActions(Context context, LocalPowerManager powerManager) {
73        mContext = context;
74        mPowerManager = powerManager;
75        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
76
77        // receive broadcasts
78        IntentFilter filter = new IntentFilter();
79        filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
80        context.registerReceiver(mBroadcastReceiver, filter);
81    }
82
83    /**
84     * Show the global actions dialog (creating if necessary)
85     * @param keyguardShowing True if keyguard is showing
86     */
87    public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
88        mKeyguardShowing = keyguardShowing;
89        mDeviceProvisioned = isDeviceProvisioned;
90        if (mDialog == null) {
91            mStatusBar = (StatusBarManager)mContext.getSystemService(Context.STATUS_BAR_SERVICE);
92            mDialog = createDialog();
93        }
94        prepareDialog();
95
96        mStatusBar.disable(StatusBarManager.DISABLE_EXPAND);
97        mDialog.show();
98    }
99
100    /**
101     * Create the global actions dialog.
102     * @return A new dialog.
103     */
104    private AlertDialog createDialog() {
105
106        mSilentModeToggle = new ToggleAction(
107                R.drawable.ic_lock_silent_mode,
108                R.drawable.ic_lock_silent_mode_off,
109                R.string.global_action_toggle_silent_mode,
110                R.string.global_action_silent_mode_on_status,
111                R.string.global_action_silent_mode_off_status) {
112
113            void onToggle(boolean on) {
114                mAudioManager.setRingerMode(on ? AudioManager.RINGER_MODE_SILENT
115                        : AudioManager.RINGER_MODE_NORMAL);
116            }
117
118            public boolean showDuringKeyguard() {
119                return true;
120            }
121
122            public boolean showBeforeProvisioning() {
123                return false;
124            }
125        };
126
127        mItems = Lists.newArrayList(
128                // first: lock screen
129                new SinglePressAction(com.android.internal.R.drawable.ic_lock_lock, R.string.global_action_lock) {
130
131                    public void onPress() {
132                        mPowerManager.goToSleep(SystemClock.uptimeMillis() + 1);
133                    }
134
135                    public boolean showDuringKeyguard() {
136                        return false;
137                    }
138
139                    public boolean showBeforeProvisioning() {
140                        return false;
141                    }
142                },
143                // next: silent mode
144                mSilentModeToggle,
145                // last: power off
146                new SinglePressAction(com.android.internal.R.drawable.ic_lock_power_off, R.string.global_action_power_off) {
147
148                    public void onPress() {
149                        // shutdown by making sure radio and power are handled accordingly.
150                        ShutdownThread.shutdown(mContext, true);
151                    }
152
153                    public boolean showDuringKeyguard() {
154                        return true;
155                    }
156
157                    public boolean showBeforeProvisioning() {
158                        return true;
159                    }
160                });
161
162        mAdapter = new MyAdapter();
163
164        final AlertDialog.Builder ab = new AlertDialog.Builder(mContext);
165
166        ab.setAdapter(mAdapter, this)
167                .setInverseBackgroundForced(true)
168                .setTitle(R.string.global_actions);
169
170        final AlertDialog dialog = ab.create();
171        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
172        dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND,
173                WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
174
175        dialog.setOnDismissListener(this);
176
177        return dialog;
178    }
179
180    private void prepareDialog() {
181        // TODO: May need another 'Vibrate' toggle button, but for now treat them the same
182        final boolean silentModeOn =
183                mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
184        mSilentModeToggle.updateState(silentModeOn);
185        mAdapter.notifyDataSetChanged();
186        if (mKeyguardShowing) {
187            mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
188        } else {
189            mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
190        }
191    }
192
193    /** {@inheritDoc} */
194    public void onDismiss(DialogInterface dialog) {
195        mStatusBar.disable(StatusBarManager.DISABLE_NONE);
196    }
197
198    /** {@inheritDoc} */
199    public void onClick(DialogInterface dialog, int which) {
200        dialog.dismiss();
201        mAdapter.getItem(which).onPress();
202    }
203
204
205    /**
206     * The adapter used for the list within the global actions dialog, taking
207     * into account whether the keyguard is showing via
208     * {@link GlobalActions#mKeyguardShowing} and whether the device is provisioned
209     * via {@link GlobalActions#mDeviceProvisioned}.
210     */
211    private class MyAdapter extends BaseAdapter {
212
213        public int getCount() {
214            int count = 0;
215
216            for (int i = 0; i < mItems.size(); i++) {
217                final Action action = mItems.get(i);
218
219                if (mKeyguardShowing && !action.showDuringKeyguard()) {
220                    continue;
221                }
222                if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
223                    continue;
224                }
225                count++;
226            }
227            return count;
228        }
229
230        public Action getItem(int position) {
231
232            int filteredPos = 0;
233            for (int i = 0; i < mItems.size(); i++) {
234                final Action action = mItems.get(i);
235                if (mKeyguardShowing && !action.showDuringKeyguard()) {
236                    continue;
237                }
238                if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
239                    continue;
240                }
241                if (filteredPos == position) {
242                    return action;
243                }
244                filteredPos++;
245            }
246
247            throw new IllegalArgumentException("position " + position + " out of "
248                    + "range of showable actions, filtered count = "
249                    + "= " + getCount() + ", keyguardshowing=" + mKeyguardShowing
250                    + ", provisioned=" + mDeviceProvisioned);
251        }
252
253
254        public long getItemId(int position) {
255            return position;
256        }
257
258        public View getView(int position, View convertView, ViewGroup parent) {
259            Action action = getItem(position);
260            return action.create(mContext, (LinearLayout) convertView, LayoutInflater.from(mContext));
261        }
262    }
263
264    // note: the scheme below made more sense when we were planning on having
265    // 8 different things in the global actions dialog.  seems overkill with
266    // only 3 items now, but may as well keep this flexible approach so it will
267    // be easy should someone decide at the last minute to include something
268    // else, such as 'enable wifi', or 'enable bluetooth'
269
270    /**
271     * What each item in the global actions dialog must be able to support.
272     */
273    private interface Action {
274        LinearLayout create(Context context, LinearLayout convertView, LayoutInflater inflater);
275
276        void onPress();
277
278        /**
279         * @return whether this action should appear in the dialog when the keygaurd
280         *    is showing.
281         */
282        boolean showDuringKeyguard();
283
284        /**
285         * @return whether this action should appear in the dialog before the
286         *   device is provisioned.
287         */
288        boolean showBeforeProvisioning();
289    }
290
291    /**
292     * A single press action maintains no state, just responds to a press
293     * and takes an action.
294     */
295    private static abstract class SinglePressAction implements Action {
296        private final int mIconResId;
297        private final int mMessageResId;
298
299        protected SinglePressAction(int iconResId, int messageResId) {
300            mIconResId = iconResId;
301            mMessageResId = messageResId;
302        }
303
304        abstract public void onPress();
305
306        public LinearLayout create(Context context, LinearLayout convertView, LayoutInflater inflater) {
307            LinearLayout v = (LinearLayout) ((convertView != null) ?
308                    convertView :
309                    inflater.inflate(R.layout.global_actions_item, null));
310
311            ImageView icon = (ImageView) v.findViewById(R.id.icon);
312            TextView messageView = (TextView) v.findViewById(R.id.message);
313
314            v.findViewById(R.id.status).setVisibility(View.GONE);
315
316            icon.setImageDrawable(context.getResources().getDrawable(mIconResId));
317            messageView.setText(mMessageResId);
318
319            return v;
320        }
321    }
322
323    /**
324     * A toggle action knows whether it is on or off, and displays an icon
325     * and status message accordingly.
326     */
327    static abstract class ToggleAction implements Action {
328
329        private boolean mOn = false;
330
331        // prefs
332        private final int mEnabledIconResId;
333        private final int mDisabledIconResid;
334        private final int mMessageResId;
335        private final int mEnabledStatusMessageResId;
336        private final int mDisabledStatusMessageResId;
337
338        /**
339         * @param enabledIconResId The icon for when this action is on.
340         * @param disabledIconResid The icon for when this action is off.
341         * @param essage The general information message, e.g 'Silent Mode'
342         * @param enabledStatusMessageResId The on status message, e.g 'sound disabled'
343         * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled'
344         */
345        public ToggleAction(int enabledIconResId,
346                int disabledIconResid,
347                int essage,
348                int enabledStatusMessageResId,
349                int disabledStatusMessageResId) {
350            mEnabledIconResId = enabledIconResId;
351            mDisabledIconResid = disabledIconResid;
352            mMessageResId = essage;
353            mEnabledStatusMessageResId = enabledStatusMessageResId;
354            mDisabledStatusMessageResId = disabledStatusMessageResId;
355        }
356
357        public LinearLayout create(Context context, LinearLayout convertView,
358                LayoutInflater inflater) {
359            LinearLayout v = (LinearLayout) ((convertView != null) ?
360                    convertView :
361                    inflater.inflate(R
362                            .layout.global_actions_item, null));
363
364            ImageView icon = (ImageView) v.findViewById(R.id.icon);
365            TextView messageView = (TextView) v.findViewById(R.id.message);
366            TextView statusView = (TextView) v.findViewById(R.id.status);
367
368            messageView.setText(mMessageResId);
369
370            icon.setImageDrawable(context.getResources().getDrawable(
371                    (mOn ? mEnabledIconResId : mDisabledIconResid)));
372            statusView.setText(mOn ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
373            statusView.setVisibility(View.VISIBLE);
374
375            return v;
376        }
377
378        public void onPress() {
379            updateState(!mOn);
380            onToggle(mOn);
381        }
382
383        abstract void onToggle(boolean on);
384
385        public void updateState(boolean on) {
386            mOn = on;
387        }
388    }
389
390    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
391        public void onReceive(Context context, Intent intent) {
392            String action = intent.getAction();
393            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
394                String reason = intent.getStringExtra(MidWindowManager.SYSTEM_DIALOG_REASON_KEY);
395                if (! MidWindowManager.SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) {
396                    mHandler.sendEmptyMessage(MESSAGE_DISMISS);
397                }
398            }
399        }
400    };
401
402    private static final int MESSAGE_DISMISS = 0;
403    private Handler mHandler = new Handler() {
404        public void handleMessage(Message msg) {
405            if (msg.what == MESSAGE_DISMISS) {
406                if (mDialog != null) {
407                    mDialog.dismiss();
408                }
409            }
410        }
411    };
412}
413