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