Home | History | Annotate | Download | only in impl
      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      */
     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 + " out of "
    320                     + "range of showable actions, filtered count = "
    321                     + "= " + getCount() + ", keyguardshowing=" + mKeyguardShowing
    322                     + ", provisioned=" + mDeviceProvisioned);
    323         }
    324 
    325 
    326         public long getItemId(int position) {
    327             return position;
    328         }
    329 
    330         public View getView(int position, View convertView, ViewGroup parent) {
    331             Action action = getItem(position);
    332             return action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
    333         }
    334     }
    335 
    336     // note: the scheme below made more sense when we were planning on having
    337     // 8 different things in the global actions dialog.  seems overkill with
    338     // only 3 items now, but may as well keep this flexible approach so it will
    339     // be easy should someone decide at the last minute to include something
    340     // else, such as 'enable wifi', or 'enable bluetooth'
    341 
    342     /**
    343      * What each item in the global actions dialog must be able to support.
    344      */
    345     private interface Action {
    346         View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater);
    347 
    348         void onPress();
    349 
    350         /**
    351          * @return whether this action should appear in the dialog when the keygaurd
    352          *    is showing.
    353          */
    354         boolean showDuringKeyguard();
    355 
    356         /**
    357          * @return whether this action should appear in the dialog before the
    358          *   device is provisioned.
    359          */
    360         boolean showBeforeProvisioning();
    361 
    362         boolean isEnabled();
    363     }
    364 
    365     /**
    366      * A single press action maintains no state, just responds to a press
    367      * and takes an action.
    368      */
    369     private static abstract class SinglePressAction implements Action {
    370         private final int mIconResId;
    371         private final int mMessageResId;
    372 
    373         protected SinglePressAction(int iconResId, int messageResId) {
    374             mIconResId = iconResId;
    375             mMessageResId = messageResId;
    376         }
    377 
    378         public boolean isEnabled() {
    379             return true;
    380         }
    381 
    382         abstract public void onPress();
    383 
    384         public View create(
    385                 Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
    386             View v = (convertView != null) ?
    387                     convertView :
    388                     inflater.inflate(R.layout.global_actions_item, parent, false);
    389 
    390             ImageView icon = (ImageView) v.findViewById(R.id.icon);
    391             TextView messageView = (TextView) v.findViewById(R.id.message);
    392 
    393             v.findViewById(R.id.status).setVisibility(View.GONE);
    394 
    395             icon.setImageDrawable(context.getResources().getDrawable(mIconResId));
    396             messageView.setText(mMessageResId);
    397 
    398             return v;
    399         }
    400     }
    401 
    402     /**
    403      * A toggle action knows whether it is on or off, and displays an icon
    404      * and status message accordingly.
    405      */
    406     private static abstract class ToggleAction implements Action {
    407 
    408         enum State {
    409             Off(false),
    410             TurningOn(true),
    411             TurningOff(true),
    412             On(false);
    413 
    414             private final boolean inTransition;
    415 
    416             State(boolean intermediate) {
    417                 inTransition = intermediate;
    418             }
    419 
    420             public boolean inTransition() {
    421                 return inTransition;
    422             }
    423         }
    424 
    425         protected State mState = State.Off;
    426 
    427         // prefs
    428         protected int mEnabledIconResId;
    429         protected int mDisabledIconResid;
    430         protected int mMessageResId;
    431         protected int mEnabledStatusMessageResId;
    432         protected int mDisabledStatusMessageResId;
    433 
    434         /**
    435          * @param enabledIconResId The icon for when this action is on.
    436          * @param disabledIconResid The icon for when this action is off.
    437          * @param essage The general information message, e.g 'Silent Mode'
    438          * @param enabledStatusMessageResId The on status message, e.g 'sound disabled'
    439          * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled'
    440          */
    441         public ToggleAction(int enabledIconResId,
    442                 int disabledIconResid,
    443                 int essage,
    444                 int enabledStatusMessageResId,
    445                 int disabledStatusMessageResId) {
    446             mEnabledIconResId = enabledIconResId;
    447             mDisabledIconResid = disabledIconResid;
    448             mMessageResId = essage;
    449             mEnabledStatusMessageResId = enabledStatusMessageResId;
    450             mDisabledStatusMessageResId = disabledStatusMessageResId;
    451         }
    452 
    453         /**
    454          * Override to make changes to resource IDs just before creating the
    455          * View.
    456          */
    457         void willCreate() {
    458 
    459         }
    460 
    461         public View create(Context context, View convertView, ViewGroup parent,
    462                 LayoutInflater inflater) {
    463             willCreate();
    464 
    465             View v = (convertView != null) ?
    466                     convertView :
    467                     inflater.inflate(R
    468                             .layout.global_actions_item, parent, false);
    469 
    470             ImageView icon = (ImageView) v.findViewById(R.id.icon);
    471             TextView messageView = (TextView) v.findViewById(R.id.message);
    472             TextView statusView = (TextView) v.findViewById(R.id.status);
    473 
    474             messageView.setText(mMessageResId);
    475 
    476             boolean on = ((mState == State.On) || (mState == State.TurningOn));
    477             icon.setImageDrawable(context.getResources().getDrawable(
    478                     (on ? mEnabledIconResId : mDisabledIconResid)));
    479             statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
    480             statusView.setVisibility(View.VISIBLE);
    481 
    482             final boolean enabled = isEnabled();
    483             messageView.setEnabled(enabled);
    484             statusView.setEnabled(enabled);
    485             icon.setEnabled(enabled);
    486             v.setEnabled(enabled);
    487 
    488             return v;
    489         }
    490 
    491         public final void onPress() {
    492             if (mState.inTransition()) {
    493                 Log.w(TAG, "shouldn't be able to toggle when in transition");
    494                 return;
    495             }
    496 
    497             final boolean nowOn = !(mState == State.On);
    498             onToggle(nowOn);
    499             changeStateFromPress(nowOn);
    500         }
    501 
    502         public boolean isEnabled() {
    503             return !mState.inTransition();
    504         }
    505 
    506         /**
    507          * Implementations may override this if their state can be in on of the intermediate
    508          * states until some notification is received (e.g airplane mode is 'turning off' until
    509          * we know the wireless connections are back online
    510          * @param buttonOn Whether the button was turned on or off
    511          */
    512         protected void changeStateFromPress(boolean buttonOn) {
    513             mState = buttonOn ? State.On : State.Off;
    514         }
    515 
    516         abstract void onToggle(boolean on);
    517 
    518         public void updateState(State state) {
    519             mState = state;
    520         }
    521     }
    522 
    523     private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
    524         public void onReceive(Context context, Intent intent) {
    525             String action = intent.getAction();
    526             if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
    527                     || Intent.ACTION_SCREEN_OFF.equals(action)) {
    528                 String reason = intent.getStringExtra(PhoneWindowManager.SYSTEM_DIALOG_REASON_KEY);
    529                 if (!PhoneWindowManager.SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) {
    530                     mHandler.sendEmptyMessage(MESSAGE_DISMISS);
    531                 }
    532             } else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) {
    533                 // Airplane mode can be changed after ECM exits if airplane toggle button
    534                 // is pressed during ECM mode
    535                 if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) &&
    536                         mIsWaitingForEcmExit) {
    537                     mIsWaitingForEcmExit = false;
    538                     changeAirplaneModeSystemSetting(true);
    539                 }
    540             }
    541         }
    542     };
    543 
    544     PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
    545         @Override
    546         public void onServiceStateChanged(ServiceState serviceState) {
    547             final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF;
    548             mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off;
    549             mAirplaneModeOn.updateState(mAirplaneState);
    550             mAdapter.notifyDataSetChanged();
    551         }
    552     };
    553 
    554     private static final int MESSAGE_DISMISS = 0;
    555     private Handler mHandler = new Handler() {
    556         public void handleMessage(Message msg) {
    557             if (msg.what == MESSAGE_DISMISS) {
    558                 if (mDialog != null) {
    559                     mDialog.dismiss();
    560                 }
    561             }
    562         }
    563     };
    564 
    565     /**
    566      * Change the airplane mode system setting
    567      */
    568     private void changeAirplaneModeSystemSetting(boolean on) {
    569         Settings.System.putInt(
    570                 mContext.getContentResolver(),
    571                 Settings.System.AIRPLANE_MODE_ON,
    572                 on ? 1 : 0);
    573         Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
    574         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
    575         intent.putExtra("state", on);
    576         mContext.sendBroadcast(intent);
    577     }
    578 }
    579