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