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 com.android.internal.app.AlertController;
     20 import com.android.internal.app.AlertController.AlertParams;
     21 import com.android.internal.telephony.TelephonyIntents;
     22 import com.android.internal.telephony.TelephonyProperties;
     23 import com.android.internal.R;
     24 
     25 import android.app.ActivityManagerNative;
     26 import android.app.AlertDialog;
     27 import android.app.Dialog;
     28 import android.content.BroadcastReceiver;
     29 import android.content.Context;
     30 import android.content.DialogInterface;
     31 import android.content.Intent;
     32 import android.content.IntentFilter;
     33 import android.content.pm.UserInfo;
     34 import android.database.ContentObserver;
     35 import android.graphics.drawable.Drawable;
     36 import android.media.AudioManager;
     37 import android.net.ConnectivityManager;
     38 import android.os.Bundle;
     39 import android.os.Handler;
     40 import android.os.Message;
     41 import android.os.RemoteException;
     42 import android.os.ServiceManager;
     43 import android.os.SystemClock;
     44 import android.os.SystemProperties;
     45 import android.os.UserHandle;
     46 import android.os.UserManager;
     47 import android.os.Vibrator;
     48 import android.provider.Settings;
     49 import android.service.dreams.DreamService;
     50 import android.service.dreams.IDreamManager;
     51 import android.telephony.PhoneStateListener;
     52 import android.telephony.ServiceState;
     53 import android.telephony.TelephonyManager;
     54 import android.util.Log;
     55 import android.util.TypedValue;
     56 import android.view.InputDevice;
     57 import android.view.KeyEvent;
     58 import android.view.LayoutInflater;
     59 import android.view.MotionEvent;
     60 import android.view.View;
     61 import android.view.ViewConfiguration;
     62 import android.view.ViewGroup;
     63 import android.view.WindowManager;
     64 import android.view.WindowManagerPolicy.WindowManagerFuncs;
     65 import android.widget.AdapterView;
     66 import android.widget.BaseAdapter;
     67 import android.widget.ImageView;
     68 import android.widget.ImageView.ScaleType;
     69 import android.widget.ListView;
     70 import android.widget.TextView;
     71 
     72 import java.util.ArrayList;
     73 import java.util.List;
     74 
     75 /**
     76  * Helper to show the global actions dialog.  Each item is an {@link Action} that
     77  * may show depending on whether the keyguard is showing, and whether the device
     78  * is provisioned.
     79  */
     80 class GlobalActions implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener  {
     81 
     82     private static final String TAG = "GlobalActions";
     83 
     84     private static final boolean SHOW_SILENT_TOGGLE = true;
     85 
     86     private final Context mContext;
     87     private final WindowManagerFuncs mWindowManagerFuncs;
     88     private final AudioManager mAudioManager;
     89     private final IDreamManager mDreamManager;
     90 
     91     private ArrayList<Action> mItems;
     92     private GlobalActionsDialog mDialog;
     93 
     94     private Action mSilentModeAction;
     95     private ToggleAction mAirplaneModeOn;
     96 
     97     private MyAdapter mAdapter;
     98 
     99     private boolean mKeyguardShowing = false;
    100     private boolean mDeviceProvisioned = false;
    101     private ToggleAction.State mAirplaneState = ToggleAction.State.Off;
    102     private boolean mIsWaitingForEcmExit = false;
    103     private boolean mHasTelephony;
    104     private boolean mHasVibrator;
    105     private final boolean mShowSilentToggle;
    106 
    107     /**
    108      * @param context everything needs a context :(
    109      */
    110     public GlobalActions(Context context, WindowManagerFuncs windowManagerFuncs) {
    111         mContext = context;
    112         mWindowManagerFuncs = windowManagerFuncs;
    113         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
    114         mDreamManager = IDreamManager.Stub.asInterface(
    115                 ServiceManager.getService(DreamService.DREAM_SERVICE));
    116 
    117         // receive broadcasts
    118         IntentFilter filter = new IntentFilter();
    119         filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
    120         filter.addAction(Intent.ACTION_SCREEN_OFF);
    121         filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
    122         context.registerReceiver(mBroadcastReceiver, filter);
    123 
    124         // get notified of phone state changes
    125         TelephonyManager telephonyManager =
    126                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
    127         telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
    128         ConnectivityManager cm = (ConnectivityManager)
    129                 context.getSystemService(Context.CONNECTIVITY_SERVICE);
    130         mHasTelephony = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
    131         mContext.getContentResolver().registerContentObserver(
    132                 Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
    133                 mAirplaneModeObserver);
    134         Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
    135         mHasVibrator = vibrator != null && vibrator.hasVibrator();
    136 
    137         mShowSilentToggle = SHOW_SILENT_TOGGLE && !mContext.getResources().getBoolean(
    138                 com.android.internal.R.bool.config_useFixedVolume);
    139     }
    140 
    141     /**
    142      * Show the global actions dialog (creating if necessary)
    143      * @param keyguardShowing True if keyguard is showing
    144      */
    145     public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
    146         mKeyguardShowing = keyguardShowing;
    147         mDeviceProvisioned = isDeviceProvisioned;
    148         if (mDialog != null) {
    149             mDialog.dismiss();
    150             mDialog = null;
    151             // Show delayed, so that the dismiss of the previous dialog completes
    152             mHandler.sendEmptyMessage(MESSAGE_SHOW);
    153         } else {
    154             handleShow();
    155         }
    156     }
    157 
    158     private void awakenIfNecessary() {
    159         if (mDreamManager != null) {
    160             try {
    161                 if (mDreamManager.isDreaming()) {
    162                     mDreamManager.awaken();
    163                 }
    164             } catch (RemoteException e) {
    165                 // we tried
    166             }
    167         }
    168     }
    169 
    170     private void handleShow() {
    171         awakenIfNecessary();
    172         mDialog = createDialog();
    173         prepareDialog();
    174 
    175         WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
    176         attrs.setTitle("GlobalActions");
    177         mDialog.getWindow().setAttributes(attrs);
    178         mDialog.show();
    179         mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND);
    180     }
    181 
    182     /**
    183      * Create the global actions dialog.
    184      * @return A new dialog.
    185      */
    186     private GlobalActionsDialog createDialog() {
    187         // Simple toggle style if there's no vibrator, otherwise use a tri-state
    188         if (!mHasVibrator) {
    189             mSilentModeAction = new SilentModeToggleAction();
    190         } else {
    191             mSilentModeAction = new SilentModeTriStateAction(mContext, mAudioManager, mHandler);
    192         }
    193         mAirplaneModeOn = new ToggleAction(
    194                 R.drawable.ic_lock_airplane_mode,
    195                 R.drawable.ic_lock_airplane_mode_off,
    196                 R.string.global_actions_toggle_airplane_mode,
    197                 R.string.global_actions_airplane_mode_on_status,
    198                 R.string.global_actions_airplane_mode_off_status) {
    199 
    200             void onToggle(boolean on) {
    201                 if (mHasTelephony && Boolean.parseBoolean(
    202                         SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) {
    203                     mIsWaitingForEcmExit = true;
    204                     // Launch ECM exit dialog
    205                     Intent ecmDialogIntent =
    206                             new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);
    207                     ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    208                     mContext.startActivity(ecmDialogIntent);
    209                 } else {
    210                     changeAirplaneModeSystemSetting(on);
    211                 }
    212             }
    213 
    214             @Override
    215             protected void changeStateFromPress(boolean buttonOn) {
    216                 if (!mHasTelephony) return;
    217 
    218                 // In ECM mode airplane state cannot be changed
    219                 if (!(Boolean.parseBoolean(
    220                         SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)))) {
    221                     mState = buttonOn ? State.TurningOn : State.TurningOff;
    222                     mAirplaneState = mState;
    223                 }
    224             }
    225 
    226             public boolean showDuringKeyguard() {
    227                 return true;
    228             }
    229 
    230             public boolean showBeforeProvisioning() {
    231                 return false;
    232             }
    233         };
    234         onAirplaneModeChanged();
    235 
    236         mItems = new ArrayList<Action>();
    237 
    238         // first: power off
    239         mItems.add(
    240             new SinglePressAction(
    241                     com.android.internal.R.drawable.ic_lock_power_off,
    242                     R.string.global_action_power_off) {
    243 
    244                 public void onPress() {
    245                     // shutdown by making sure radio and power are handled accordingly.
    246                     mWindowManagerFuncs.shutdown(true);
    247                 }
    248 
    249                 public boolean onLongPress() {
    250                     mWindowManagerFuncs.rebootSafeMode(true);
    251                     return true;
    252                 }
    253 
    254                 public boolean showDuringKeyguard() {
    255                     return true;
    256                 }
    257 
    258                 public boolean showBeforeProvisioning() {
    259                     return true;
    260                 }
    261             });
    262 
    263         // next: airplane mode
    264         mItems.add(mAirplaneModeOn);
    265 
    266         // next: bug report, if enabled
    267         if (Settings.Global.getInt(mContext.getContentResolver(),
    268                 Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) {
    269             mItems.add(
    270                 new SinglePressAction(com.android.internal.R.drawable.stat_sys_adb,
    271                         R.string.global_action_bug_report) {
    272 
    273                     public void onPress() {
    274                         AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
    275                         builder.setTitle(com.android.internal.R.string.bugreport_title);
    276                         builder.setMessage(com.android.internal.R.string.bugreport_message);
    277                         builder.setNegativeButton(com.android.internal.R.string.cancel, null);
    278                         builder.setPositiveButton(com.android.internal.R.string.report,
    279                                 new DialogInterface.OnClickListener() {
    280                                     @Override
    281                                     public void onClick(DialogInterface dialog, int which) {
    282                                         // Add a little delay before executing, to give the
    283                                         // dialog a chance to go away before it takes a
    284                                         // screenshot.
    285                                         mHandler.postDelayed(new Runnable() {
    286                                             @Override public void run() {
    287                                                 try {
    288                                                     ActivityManagerNative.getDefault()
    289                                                             .requestBugReport();
    290                                                 } catch (RemoteException e) {
    291                                                 }
    292                                             }
    293                                         }, 500);
    294                                     }
    295                                 });
    296                         AlertDialog dialog = builder.create();
    297                         dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
    298                         dialog.show();
    299                     }
    300 
    301                     public boolean onLongPress() {
    302                         return false;
    303                     }
    304 
    305                     public boolean showDuringKeyguard() {
    306                         return true;
    307                     }
    308 
    309                     public boolean showBeforeProvisioning() {
    310                         return false;
    311                     }
    312                 });
    313         }
    314 
    315         // last: silent mode
    316         if (mShowSilentToggle) {
    317             mItems.add(mSilentModeAction);
    318         }
    319 
    320         // one more thing: optionally add a list of users to switch to
    321         if (SystemProperties.getBoolean("fw.power_user_switcher", false)) {
    322             addUsersToMenu(mItems);
    323         }
    324 
    325         mAdapter = new MyAdapter();
    326 
    327         AlertParams params = new AlertParams(mContext);
    328         params.mAdapter = mAdapter;
    329         params.mOnClickListener = this;
    330         params.mForceInverseBackground = true;
    331 
    332         GlobalActionsDialog dialog = new GlobalActionsDialog(mContext, params);
    333         dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
    334 
    335         dialog.getListView().setItemsCanFocus(true);
    336         dialog.getListView().setLongClickable(true);
    337         dialog.getListView().setOnItemLongClickListener(
    338                 new AdapterView.OnItemLongClickListener() {
    339                     @Override
    340                     public boolean onItemLongClick(AdapterView<?> parent, View view, int position,
    341                             long id) {
    342                         return mAdapter.getItem(position).onLongPress();
    343                     }
    344         });
    345         dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
    346 
    347         dialog.setOnDismissListener(this);
    348 
    349         return dialog;
    350     }
    351 
    352     private UserInfo getCurrentUser() {
    353         try {
    354             return ActivityManagerNative.getDefault().getCurrentUser();
    355         } catch (RemoteException re) {
    356             return null;
    357         }
    358     }
    359 
    360     private boolean isCurrentUserOwner() {
    361         UserInfo currentUser = getCurrentUser();
    362         return currentUser == null || currentUser.isPrimary();
    363     }
    364 
    365     private void addUsersToMenu(ArrayList<Action> items) {
    366         List<UserInfo> users = ((UserManager) mContext.getSystemService(Context.USER_SERVICE))
    367                 .getUsers();
    368         if (users.size() > 1) {
    369             UserInfo currentUser = getCurrentUser();
    370             for (final UserInfo user : users) {
    371                 boolean isCurrentUser = currentUser == null
    372                         ? user.id == 0 : (currentUser.id == user.id);
    373                 Drawable icon = user.iconPath != null ? Drawable.createFromPath(user.iconPath)
    374                         : null;
    375                 SinglePressAction switchToUser = new SinglePressAction(
    376                         com.android.internal.R.drawable.ic_menu_cc, icon,
    377                         (user.name != null ? user.name : "Primary")
    378                         + (isCurrentUser ? " \u2714" : "")) {
    379                     public void onPress() {
    380                         try {
    381                             ActivityManagerNative.getDefault().switchUser(user.id);
    382                         } catch (RemoteException re) {
    383                             Log.e(TAG, "Couldn't switch user " + re);
    384                         }
    385                     }
    386 
    387                     public boolean showDuringKeyguard() {
    388                         return true;
    389                     }
    390 
    391                     public boolean showBeforeProvisioning() {
    392                         return false;
    393                     }
    394                 };
    395                 items.add(switchToUser);
    396             }
    397         }
    398     }
    399 
    400     private void prepareDialog() {
    401         refreshSilentMode();
    402         mAirplaneModeOn.updateState(mAirplaneState);
    403         mAdapter.notifyDataSetChanged();
    404         mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
    405         if (mShowSilentToggle) {
    406             IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
    407             mContext.registerReceiver(mRingerModeReceiver, filter);
    408         }
    409     }
    410 
    411     private void refreshSilentMode() {
    412         if (!mHasVibrator) {
    413             final boolean silentModeOn =
    414                     mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
    415             ((ToggleAction)mSilentModeAction).updateState(
    416                     silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off);
    417         }
    418     }
    419 
    420     /** {@inheritDoc} */
    421     public void onDismiss(DialogInterface dialog) {
    422         if (mShowSilentToggle) {
    423             try {
    424                 mContext.unregisterReceiver(mRingerModeReceiver);
    425             } catch (IllegalArgumentException ie) {
    426                 // ignore this
    427                 Log.w(TAG, ie);
    428             }
    429         }
    430     }
    431 
    432     /** {@inheritDoc} */
    433     public void onClick(DialogInterface dialog, int which) {
    434         if (!(mAdapter.getItem(which) instanceof SilentModeTriStateAction)) {
    435             dialog.dismiss();
    436         }
    437         mAdapter.getItem(which).onPress();
    438     }
    439 
    440     /**
    441      * The adapter used for the list within the global actions dialog, taking
    442      * into account whether the keyguard is showing via
    443      * {@link GlobalActions#mKeyguardShowing} and whether the device is provisioned
    444      * via {@link GlobalActions#mDeviceProvisioned}.
    445      */
    446     private class MyAdapter extends BaseAdapter {
    447 
    448         public int getCount() {
    449             int count = 0;
    450 
    451             for (int i = 0; i < mItems.size(); i++) {
    452                 final Action action = mItems.get(i);
    453 
    454                 if (mKeyguardShowing && !action.showDuringKeyguard()) {
    455                     continue;
    456                 }
    457                 if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
    458                     continue;
    459                 }
    460                 count++;
    461             }
    462             return count;
    463         }
    464 
    465         @Override
    466         public boolean isEnabled(int position) {
    467             return getItem(position).isEnabled();
    468         }
    469 
    470         @Override
    471         public boolean areAllItemsEnabled() {
    472             return false;
    473         }
    474 
    475         public Action getItem(int position) {
    476 
    477             int filteredPos = 0;
    478             for (int i = 0; i < mItems.size(); i++) {
    479                 final Action action = mItems.get(i);
    480                 if (mKeyguardShowing && !action.showDuringKeyguard()) {
    481                     continue;
    482                 }
    483                 if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
    484                     continue;
    485                 }
    486                 if (filteredPos == position) {
    487                     return action;
    488                 }
    489                 filteredPos++;
    490             }
    491 
    492             throw new IllegalArgumentException("position " + position
    493                     + " out of range of showable actions"
    494                     + ", filtered count=" + getCount()
    495                     + ", keyguardshowing=" + mKeyguardShowing
    496                     + ", provisioned=" + mDeviceProvisioned);
    497         }
    498 
    499 
    500         public long getItemId(int position) {
    501             return position;
    502         }
    503 
    504         public View getView(int position, View convertView, ViewGroup parent) {
    505             Action action = getItem(position);
    506             return action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
    507         }
    508     }
    509 
    510     // note: the scheme below made more sense when we were planning on having
    511     // 8 different things in the global actions dialog.  seems overkill with
    512     // only 3 items now, but may as well keep this flexible approach so it will
    513     // be easy should someone decide at the last minute to include something
    514     // else, such as 'enable wifi', or 'enable bluetooth'
    515 
    516     /**
    517      * What each item in the global actions dialog must be able to support.
    518      */
    519     private interface Action {
    520         View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater);
    521 
    522         void onPress();
    523 
    524         public boolean onLongPress();
    525 
    526         /**
    527          * @return whether this action should appear in the dialog when the keygaurd
    528          *    is showing.
    529          */
    530         boolean showDuringKeyguard();
    531 
    532         /**
    533          * @return whether this action should appear in the dialog before the
    534          *   device is provisioned.
    535          */
    536         boolean showBeforeProvisioning();
    537 
    538         boolean isEnabled();
    539     }
    540 
    541     /**
    542      * A single press action maintains no state, just responds to a press
    543      * and takes an action.
    544      */
    545     private static abstract class SinglePressAction implements Action {
    546         private final int mIconResId;
    547         private final Drawable mIcon;
    548         private final int mMessageResId;
    549         private final CharSequence mMessage;
    550 
    551         protected SinglePressAction(int iconResId, int messageResId) {
    552             mIconResId = iconResId;
    553             mMessageResId = messageResId;
    554             mMessage = null;
    555             mIcon = null;
    556         }
    557 
    558         protected SinglePressAction(int iconResId, Drawable icon, CharSequence message) {
    559             mIconResId = iconResId;
    560             mMessageResId = 0;
    561             mMessage = message;
    562             mIcon = icon;
    563         }
    564 
    565         protected SinglePressAction(int iconResId, CharSequence message) {
    566             mIconResId = iconResId;
    567             mMessageResId = 0;
    568             mMessage = message;
    569             mIcon = null;
    570         }
    571 
    572         public boolean isEnabled() {
    573             return true;
    574         }
    575 
    576         abstract public void onPress();
    577 
    578         public boolean onLongPress() {
    579             return false;
    580         }
    581 
    582         public View create(
    583                 Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
    584             View v = inflater.inflate(R.layout.global_actions_item, parent, false);
    585 
    586             ImageView icon = (ImageView) v.findViewById(R.id.icon);
    587             TextView messageView = (TextView) v.findViewById(R.id.message);
    588 
    589             v.findViewById(R.id.status).setVisibility(View.GONE);
    590             if (mIcon != null) {
    591                 icon.setImageDrawable(mIcon);
    592                 icon.setScaleType(ScaleType.CENTER_CROP);
    593             } else if (mIconResId != 0) {
    594                 icon.setImageDrawable(context.getResources().getDrawable(mIconResId));
    595             }
    596             if (mMessage != null) {
    597                 messageView.setText(mMessage);
    598             } else {
    599                 messageView.setText(mMessageResId);
    600             }
    601 
    602             return v;
    603         }
    604     }
    605 
    606     /**
    607      * A toggle action knows whether it is on or off, and displays an icon
    608      * and status message accordingly.
    609      */
    610     private static abstract class ToggleAction implements Action {
    611 
    612         enum State {
    613             Off(false),
    614             TurningOn(true),
    615             TurningOff(true),
    616             On(false);
    617 
    618             private final boolean inTransition;
    619 
    620             State(boolean intermediate) {
    621                 inTransition = intermediate;
    622             }
    623 
    624             public boolean inTransition() {
    625                 return inTransition;
    626             }
    627         }
    628 
    629         protected State mState = State.Off;
    630 
    631         // prefs
    632         protected int mEnabledIconResId;
    633         protected int mDisabledIconResid;
    634         protected int mMessageResId;
    635         protected int mEnabledStatusMessageResId;
    636         protected int mDisabledStatusMessageResId;
    637 
    638         /**
    639          * @param enabledIconResId The icon for when this action is on.
    640          * @param disabledIconResid The icon for when this action is off.
    641          * @param essage The general information message, e.g 'Silent Mode'
    642          * @param enabledStatusMessageResId The on status message, e.g 'sound disabled'
    643          * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled'
    644          */
    645         public ToggleAction(int enabledIconResId,
    646                 int disabledIconResid,
    647                 int message,
    648                 int enabledStatusMessageResId,
    649                 int disabledStatusMessageResId) {
    650             mEnabledIconResId = enabledIconResId;
    651             mDisabledIconResid = disabledIconResid;
    652             mMessageResId = message;
    653             mEnabledStatusMessageResId = enabledStatusMessageResId;
    654             mDisabledStatusMessageResId = disabledStatusMessageResId;
    655         }
    656 
    657         /**
    658          * Override to make changes to resource IDs just before creating the
    659          * View.
    660          */
    661         void willCreate() {
    662 
    663         }
    664 
    665         public View create(Context context, View convertView, ViewGroup parent,
    666                 LayoutInflater inflater) {
    667             willCreate();
    668 
    669             View v = inflater.inflate(R
    670                             .layout.global_actions_item, parent, false);
    671 
    672             ImageView icon = (ImageView) v.findViewById(R.id.icon);
    673             TextView messageView = (TextView) v.findViewById(R.id.message);
    674             TextView statusView = (TextView) v.findViewById(R.id.status);
    675             final boolean enabled = isEnabled();
    676 
    677             if (messageView != null) {
    678                 messageView.setText(mMessageResId);
    679                 messageView.setEnabled(enabled);
    680             }
    681 
    682             boolean on = ((mState == State.On) || (mState == State.TurningOn));
    683             if (icon != null) {
    684                 icon.setImageDrawable(context.getResources().getDrawable(
    685                         (on ? mEnabledIconResId : mDisabledIconResid)));
    686                 icon.setEnabled(enabled);
    687             }
    688 
    689             if (statusView != null) {
    690                 statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
    691                 statusView.setVisibility(View.VISIBLE);
    692                 statusView.setEnabled(enabled);
    693             }
    694             v.setEnabled(enabled);
    695 
    696             return v;
    697         }
    698 
    699         public final void onPress() {
    700             if (mState.inTransition()) {
    701                 Log.w(TAG, "shouldn't be able to toggle when in transition");
    702                 return;
    703             }
    704 
    705             final boolean nowOn = !(mState == State.On);
    706             onToggle(nowOn);
    707             changeStateFromPress(nowOn);
    708         }
    709 
    710         public boolean onLongPress() {
    711             return false;
    712         }
    713 
    714         public boolean isEnabled() {
    715             return !mState.inTransition();
    716         }
    717 
    718         /**
    719          * Implementations may override this if their state can be in on of the intermediate
    720          * states until some notification is received (e.g airplane mode is 'turning off' until
    721          * we know the wireless connections are back online
    722          * @param buttonOn Whether the button was turned on or off
    723          */
    724         protected void changeStateFromPress(boolean buttonOn) {
    725             mState = buttonOn ? State.On : State.Off;
    726         }
    727 
    728         abstract void onToggle(boolean on);
    729 
    730         public void updateState(State state) {
    731             mState = state;
    732         }
    733     }
    734 
    735     private class SilentModeToggleAction extends ToggleAction {
    736         public SilentModeToggleAction() {
    737             super(R.drawable.ic_audio_vol_mute,
    738                     R.drawable.ic_audio_vol,
    739                     R.string.global_action_toggle_silent_mode,
    740                     R.string.global_action_silent_mode_on_status,
    741                     R.string.global_action_silent_mode_off_status);
    742         }
    743 
    744         void onToggle(boolean on) {
    745             if (on) {
    746                 mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
    747             } else {
    748                 mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
    749             }
    750         }
    751 
    752         public boolean showDuringKeyguard() {
    753             return true;
    754         }
    755 
    756         public boolean showBeforeProvisioning() {
    757             return false;
    758         }
    759     }
    760 
    761     private static class SilentModeTriStateAction implements Action, View.OnClickListener {
    762 
    763         private final int[] ITEM_IDS = { R.id.option1, R.id.option2, R.id.option3 };
    764 
    765         private final AudioManager mAudioManager;
    766         private final Handler mHandler;
    767         private final Context mContext;
    768 
    769         SilentModeTriStateAction(Context context, AudioManager audioManager, Handler handler) {
    770             mAudioManager = audioManager;
    771             mHandler = handler;
    772             mContext = context;
    773         }
    774 
    775         private int ringerModeToIndex(int ringerMode) {
    776             // They just happen to coincide
    777             return ringerMode;
    778         }
    779 
    780         private int indexToRingerMode(int index) {
    781             // They just happen to coincide
    782             return index;
    783         }
    784 
    785         public View create(Context context, View convertView, ViewGroup parent,
    786                 LayoutInflater inflater) {
    787             View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false);
    788 
    789             int selectedIndex = ringerModeToIndex(mAudioManager.getRingerMode());
    790             for (int i = 0; i < 3; i++) {
    791                 View itemView = v.findViewById(ITEM_IDS[i]);
    792                 itemView.setSelected(selectedIndex == i);
    793                 // Set up click handler
    794                 itemView.setTag(i);
    795                 itemView.setOnClickListener(this);
    796             }
    797             return v;
    798         }
    799 
    800         public void onPress() {
    801         }
    802 
    803         public boolean onLongPress() {
    804             return false;
    805         }
    806 
    807         public boolean showDuringKeyguard() {
    808             return true;
    809         }
    810 
    811         public boolean showBeforeProvisioning() {
    812             return false;
    813         }
    814 
    815         public boolean isEnabled() {
    816             return true;
    817         }
    818 
    819         void willCreate() {
    820         }
    821 
    822         public void onClick(View v) {
    823             if (!(v.getTag() instanceof Integer)) return;
    824 
    825             int index = (Integer) v.getTag();
    826             mAudioManager.setRingerMode(indexToRingerMode(index));
    827             mHandler.sendEmptyMessageDelayed(MESSAGE_DISMISS, DIALOG_DISMISS_DELAY);
    828         }
    829     }
    830 
    831     private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
    832         public void onReceive(Context context, Intent intent) {
    833             String action = intent.getAction();
    834             if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
    835                     || Intent.ACTION_SCREEN_OFF.equals(action)) {
    836                 String reason = intent.getStringExtra(PhoneWindowManager.SYSTEM_DIALOG_REASON_KEY);
    837                 if (!PhoneWindowManager.SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) {
    838                     mHandler.sendEmptyMessage(MESSAGE_DISMISS);
    839                 }
    840             } else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) {
    841                 // Airplane mode can be changed after ECM exits if airplane toggle button
    842                 // is pressed during ECM mode
    843                 if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) &&
    844                         mIsWaitingForEcmExit) {
    845                     mIsWaitingForEcmExit = false;
    846                     changeAirplaneModeSystemSetting(true);
    847                 }
    848             }
    849         }
    850     };
    851 
    852     PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
    853         @Override
    854         public void onServiceStateChanged(ServiceState serviceState) {
    855             if (!mHasTelephony) return;
    856             final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF;
    857             mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off;
    858             mAirplaneModeOn.updateState(mAirplaneState);
    859             mAdapter.notifyDataSetChanged();
    860         }
    861     };
    862 
    863     private BroadcastReceiver mRingerModeReceiver = new BroadcastReceiver() {
    864         @Override
    865         public void onReceive(Context context, Intent intent) {
    866             if (intent.getAction().equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
    867                 mHandler.sendEmptyMessage(MESSAGE_REFRESH);
    868             }
    869         }
    870     };
    871 
    872     private ContentObserver mAirplaneModeObserver = new ContentObserver(new Handler()) {
    873         @Override
    874         public void onChange(boolean selfChange) {
    875             onAirplaneModeChanged();
    876         }
    877     };
    878 
    879     private static final int MESSAGE_DISMISS = 0;
    880     private static final int MESSAGE_REFRESH = 1;
    881     private static final int MESSAGE_SHOW = 2;
    882     private static final int DIALOG_DISMISS_DELAY = 300; // ms
    883 
    884     private Handler mHandler = new Handler() {
    885         public void handleMessage(Message msg) {
    886             switch (msg.what) {
    887             case MESSAGE_DISMISS:
    888                 if (mDialog != null) {
    889                     mDialog.dismiss();
    890                 }
    891                 break;
    892             case MESSAGE_REFRESH:
    893                 refreshSilentMode();
    894                 mAdapter.notifyDataSetChanged();
    895                 break;
    896             case MESSAGE_SHOW:
    897                 handleShow();
    898                 break;
    899             }
    900         }
    901     };
    902 
    903     private void onAirplaneModeChanged() {
    904         // Let the service state callbacks handle the state.
    905         if (mHasTelephony) return;
    906 
    907         boolean airplaneModeOn = Settings.Global.getInt(
    908                 mContext.getContentResolver(),
    909                 Settings.Global.AIRPLANE_MODE_ON,
    910                 0) == 1;
    911         mAirplaneState = airplaneModeOn ? ToggleAction.State.On : ToggleAction.State.Off;
    912         mAirplaneModeOn.updateState(mAirplaneState);
    913     }
    914 
    915     /**
    916      * Change the airplane mode system setting
    917      */
    918     private void changeAirplaneModeSystemSetting(boolean on) {
    919         Settings.Global.putInt(
    920                 mContext.getContentResolver(),
    921                 Settings.Global.AIRPLANE_MODE_ON,
    922                 on ? 1 : 0);
    923         Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
    924         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
    925         intent.putExtra("state", on);
    926         mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
    927         if (!mHasTelephony) {
    928             mAirplaneState = on ? ToggleAction.State.On : ToggleAction.State.Off;
    929         }
    930     }
    931 
    932     private static final class GlobalActionsDialog extends Dialog implements DialogInterface {
    933         private final Context mContext;
    934         private final int mWindowTouchSlop;
    935         private final AlertController mAlert;
    936 
    937         private EnableAccessibilityController mEnableAccessibilityController;
    938 
    939         private boolean mIntercepted;
    940         private boolean mCancelOnUp;
    941 
    942         public GlobalActionsDialog(Context context, AlertParams params) {
    943             super(context, getDialogTheme(context));
    944             mContext = context;
    945             mAlert = new AlertController(mContext, this, getWindow());
    946             mWindowTouchSlop = ViewConfiguration.get(context).getScaledWindowTouchSlop();
    947             params.apply(mAlert);
    948         }
    949 
    950         private static int getDialogTheme(Context context) {
    951             TypedValue outValue = new TypedValue();
    952             context.getTheme().resolveAttribute(com.android.internal.R.attr.alertDialogTheme,
    953                     outValue, true);
    954             return outValue.resourceId;
    955         }
    956 
    957         @Override
    958         protected void onStart() {
    959             // If global accessibility gesture can be performed, we will take care
    960             // of dismissing the dialog on touch outside. This is because the dialog
    961             // is dismissed on the first down while the global gesture is a long press
    962             // with two fingers anywhere on the screen.
    963             if (EnableAccessibilityController.canEnableAccessibilityViaGesture(mContext)) {
    964                 mEnableAccessibilityController = new EnableAccessibilityController(mContext);
    965                 super.setCanceledOnTouchOutside(false);
    966             } else {
    967                 mEnableAccessibilityController = null;
    968                 super.setCanceledOnTouchOutside(true);
    969             }
    970             super.onStart();
    971         }
    972 
    973         @Override
    974         protected void onStop() {
    975             if (mEnableAccessibilityController != null) {
    976                 mEnableAccessibilityController.onDestroy();
    977             }
    978             super.onStop();
    979         }
    980 
    981         @Override
    982         public boolean dispatchTouchEvent(MotionEvent event) {
    983             if (mEnableAccessibilityController != null) {
    984                 final int action = event.getActionMasked();
    985                 if (action == MotionEvent.ACTION_DOWN) {
    986                     View decor = getWindow().getDecorView();
    987                     final int eventX = (int) event.getX();
    988                     final int eventY = (int) event.getY();
    989                     if (eventX < -mWindowTouchSlop
    990                             || eventY < -mWindowTouchSlop
    991                             || eventX >= decor.getWidth() + mWindowTouchSlop
    992                             || eventY >= decor.getHeight() + mWindowTouchSlop) {
    993                         mCancelOnUp = true;
    994                     }
    995                 }
    996                 try {
    997                     if (!mIntercepted) {
    998                         mIntercepted = mEnableAccessibilityController.onInterceptTouchEvent(event);
    999                         if (mIntercepted) {
   1000                             final long now = SystemClock.uptimeMillis();
   1001                             event = MotionEvent.obtain(now, now,
   1002                                     MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
   1003                             event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
   1004                             mCancelOnUp = true;
   1005                         }
   1006                     } else {
   1007                         return mEnableAccessibilityController.onTouchEvent(event);
   1008                     }
   1009                 } finally {
   1010                     if (action == MotionEvent.ACTION_UP) {
   1011                         if (mCancelOnUp) {
   1012                             cancel();
   1013                         }
   1014                         mCancelOnUp = false;
   1015                         mIntercepted = false;
   1016                     }
   1017                 }
   1018             }
   1019             return super.dispatchTouchEvent(event);
   1020         }
   1021 
   1022         public ListView getListView() {
   1023             return mAlert.getListView();
   1024         }
   1025 
   1026         @Override
   1027         protected void onCreate(Bundle savedInstanceState) {
   1028             super.onCreate(savedInstanceState);
   1029             mAlert.installContent();
   1030         }
   1031 
   1032         @Override
   1033         public boolean onKeyDown(int keyCode, KeyEvent event) {
   1034             if (mAlert.onKeyDown(keyCode, event)) {
   1035                 return true;
   1036             }
   1037             return super.onKeyDown(keyCode, event);
   1038         }
   1039 
   1040         @Override
   1041         public boolean onKeyUp(int keyCode, KeyEvent event) {
   1042             if (mAlert.onKeyUp(keyCode, event)) {
   1043                 return true;
   1044             }
   1045             return super.onKeyUp(keyCode, event);
   1046         }
   1047     }
   1048 }
   1049