Home | History | Annotate | Download | only in settings
      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.settings;
     18 
     19 import android.content.BroadcastReceiver;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.IntentFilter;
     23 import android.content.res.Resources;
     24 import android.os.AsyncResult;
     25 import android.os.Bundle;
     26 import android.os.Handler;
     27 import android.os.Message;
     28 import android.preference.CheckBoxPreference;
     29 import android.preference.Preference;
     30 import android.preference.PreferenceActivity;
     31 import android.preference.PreferenceScreen;
     32 import android.util.Log;
     33 import android.widget.Toast;
     34 
     35 import com.android.internal.telephony.Phone;
     36 import com.android.internal.telephony.PhoneFactory;
     37 import com.android.internal.telephony.TelephonyIntents;
     38 
     39 /**
     40  * Implements the preference screen to enable/disable ICC lock and
     41  * also the dialogs to change the ICC PIN. In the former case, enabling/disabling
     42  * the ICC lock will prompt the user for the current PIN.
     43  * In the Change PIN case, it prompts the user for old pin, new pin and new pin
     44  * again before attempting to change it. Calls the SimCard interface to execute
     45  * these operations.
     46  *
     47  */
     48 public class IccLockSettings extends PreferenceActivity
     49         implements EditPinPreference.OnPinEnteredListener {
     50     private static final String TAG = "IccLockSettings";
     51     private static final boolean DBG = true;
     52 
     53     private static final int OFF_MODE = 0;
     54     // State when enabling/disabling ICC lock
     55     private static final int ICC_LOCK_MODE = 1;
     56     // State when entering the old pin
     57     private static final int ICC_OLD_MODE = 2;
     58     // State when entering the new pin - first time
     59     private static final int ICC_NEW_MODE = 3;
     60     // State when entering the new pin - second time
     61     private static final int ICC_REENTER_MODE = 4;
     62 
     63     // Keys in xml file
     64     private static final String PIN_DIALOG = "sim_pin";
     65     private static final String PIN_TOGGLE = "sim_toggle";
     66     // Keys in icicle
     67     private static final String DIALOG_STATE = "dialogState";
     68     private static final String DIALOG_PIN = "dialogPin";
     69     private static final String DIALOG_ERROR = "dialogError";
     70     private static final String ENABLE_TO_STATE = "enableState";
     71 
     72     // Save and restore inputted PIN code when configuration changed
     73     // (ex. portrait<-->landscape) during change PIN code
     74     private static final String OLD_PINCODE = "oldPinCode";
     75     private static final String NEW_PINCODE = "newPinCode";
     76 
     77     private static final int MIN_PIN_LENGTH = 4;
     78     private static final int MAX_PIN_LENGTH = 8;
     79     // Which dialog to show next when popped up
     80     private int mDialogState = OFF_MODE;
     81 
     82     private String mPin;
     83     private String mOldPin;
     84     private String mNewPin;
     85     private String mError;
     86     // Are we trying to enable or disable ICC lock?
     87     private boolean mToState;
     88 
     89     private Phone mPhone;
     90 
     91     private EditPinPreference mPinDialog;
     92     private CheckBoxPreference mPinToggle;
     93 
     94     private Resources mRes;
     95 
     96     // For async handler to identify request type
     97     private static final int MSG_ENABLE_ICC_PIN_COMPLETE = 100;
     98     private static final int MSG_CHANGE_ICC_PIN_COMPLETE = 101;
     99     private static final int MSG_SIM_STATE_CHANGED = 102;
    100 
    101     // For replies from IccCard interface
    102     private Handler mHandler = new Handler() {
    103         public void handleMessage(Message msg) {
    104             AsyncResult ar = (AsyncResult) msg.obj;
    105             switch (msg.what) {
    106                 case MSG_ENABLE_ICC_PIN_COMPLETE:
    107                     iccLockChanged(ar.exception == null, msg.arg1);
    108                     break;
    109                 case MSG_CHANGE_ICC_PIN_COMPLETE:
    110                     iccPinChanged(ar.exception == null, msg.arg1);
    111                     break;
    112                 case MSG_SIM_STATE_CHANGED:
    113                     updatePreferences();
    114                     break;
    115             }
    116 
    117             return;
    118         }
    119     };
    120 
    121     private final BroadcastReceiver mSimStateReceiver = new BroadcastReceiver() {
    122         public void onReceive(Context context, Intent intent) {
    123             final String action = intent.getAction();
    124             if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(action)) {
    125                 mHandler.sendMessage(mHandler.obtainMessage(MSG_SIM_STATE_CHANGED));
    126             }
    127         }
    128     };
    129 
    130     // For top-level settings screen to query
    131     static boolean isIccLockEnabled() {
    132         return PhoneFactory.getDefaultPhone().getIccCard().getIccLockEnabled();
    133     }
    134 
    135     static String getSummary(Context context) {
    136         Resources res = context.getResources();
    137         String summary = isIccLockEnabled()
    138                 ? res.getString(R.string.sim_lock_on)
    139                 : res.getString(R.string.sim_lock_off);
    140         return summary;
    141     }
    142 
    143     @Override
    144     protected void onCreate(Bundle savedInstanceState) {
    145         super.onCreate(savedInstanceState);
    146 
    147         if (Utils.isMonkeyRunning()) {
    148             finish();
    149             return;
    150         }
    151 
    152         addPreferencesFromResource(R.xml.sim_lock_settings);
    153 
    154         mPinDialog = (EditPinPreference) findPreference(PIN_DIALOG);
    155         mPinToggle = (CheckBoxPreference) findPreference(PIN_TOGGLE);
    156         if (savedInstanceState != null && savedInstanceState.containsKey(DIALOG_STATE)) {
    157             mDialogState = savedInstanceState.getInt(DIALOG_STATE);
    158             mPin = savedInstanceState.getString(DIALOG_PIN);
    159             mError = savedInstanceState.getString(DIALOG_ERROR);
    160             mToState = savedInstanceState.getBoolean(ENABLE_TO_STATE);
    161 
    162             // Restore inputted PIN code
    163             switch (mDialogState) {
    164                 case ICC_NEW_MODE:
    165                     mOldPin = savedInstanceState.getString(OLD_PINCODE);
    166                     break;
    167 
    168                 case ICC_REENTER_MODE:
    169                     mOldPin = savedInstanceState.getString(OLD_PINCODE);
    170                     mNewPin = savedInstanceState.getString(NEW_PINCODE);
    171                     break;
    172 
    173                 case ICC_LOCK_MODE:
    174                 case ICC_OLD_MODE:
    175                 default:
    176                     break;
    177             }
    178         }
    179 
    180         mPinDialog.setOnPinEnteredListener(this);
    181 
    182         // Don't need any changes to be remembered
    183         getPreferenceScreen().setPersistent(false);
    184 
    185         mPhone = PhoneFactory.getDefaultPhone();
    186         mRes = getResources();
    187         updatePreferences();
    188     }
    189 
    190     private void updatePreferences() {
    191         mPinToggle.setChecked(mPhone.getIccCard().getIccLockEnabled());
    192     }
    193 
    194     @Override
    195     protected void onResume() {
    196         super.onResume();
    197 
    198         // ACTION_SIM_STATE_CHANGED is sticky, so we'll receive current state after this call,
    199         // which will call updatePreferences().
    200         final IntentFilter filter = new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
    201         registerReceiver(mSimStateReceiver, filter);
    202 
    203         if (mDialogState != OFF_MODE) {
    204             showPinDialog();
    205         } else {
    206             // Prep for standard click on "Change PIN"
    207             resetDialogState();
    208         }
    209     }
    210 
    211     @Override
    212     protected void onPause() {
    213         super.onPause();
    214         unregisterReceiver(mSimStateReceiver);
    215     }
    216 
    217     @Override
    218     protected void onSaveInstanceState(Bundle out) {
    219         // Need to store this state for slider open/close
    220         // There is one case where the dialog is popped up by the preference
    221         // framework. In that case, let the preference framework store the
    222         // dialog state. In other cases, where this activity manually launches
    223         // the dialog, store the state of the dialog.
    224         if (mPinDialog.isDialogOpen()) {
    225             out.putInt(DIALOG_STATE, mDialogState);
    226             out.putString(DIALOG_PIN, mPinDialog.getEditText().getText().toString());
    227             out.putString(DIALOG_ERROR, mError);
    228             out.putBoolean(ENABLE_TO_STATE, mToState);
    229 
    230             // Save inputted PIN code
    231             switch (mDialogState) {
    232                 case ICC_NEW_MODE:
    233                     out.putString(OLD_PINCODE, mOldPin);
    234                     break;
    235 
    236                 case ICC_REENTER_MODE:
    237                     out.putString(OLD_PINCODE, mOldPin);
    238                     out.putString(NEW_PINCODE, mNewPin);
    239                     break;
    240 
    241                 case ICC_LOCK_MODE:
    242                 case ICC_OLD_MODE:
    243                 default:
    244                     break;
    245             }
    246         } else {
    247             super.onSaveInstanceState(out);
    248         }
    249     }
    250 
    251     private void showPinDialog() {
    252         if (mDialogState == OFF_MODE) {
    253             return;
    254         }
    255         setDialogValues();
    256 
    257         mPinDialog.showPinDialog();
    258     }
    259 
    260     private void setDialogValues() {
    261         mPinDialog.setText(mPin);
    262         String message = "";
    263         switch (mDialogState) {
    264             case ICC_LOCK_MODE:
    265                 message = mRes.getString(R.string.sim_enter_pin);
    266                 mPinDialog.setDialogTitle(mToState
    267                         ? mRes.getString(R.string.sim_enable_sim_lock)
    268                         : mRes.getString(R.string.sim_disable_sim_lock));
    269                 break;
    270             case ICC_OLD_MODE:
    271                 message = mRes.getString(R.string.sim_enter_old);
    272                 mPinDialog.setDialogTitle(mRes.getString(R.string.sim_change_pin));
    273                 break;
    274             case ICC_NEW_MODE:
    275                 message = mRes.getString(R.string.sim_enter_new);
    276                 mPinDialog.setDialogTitle(mRes.getString(R.string.sim_change_pin));
    277                 break;
    278             case ICC_REENTER_MODE:
    279                 message = mRes.getString(R.string.sim_reenter_new);
    280                 mPinDialog.setDialogTitle(mRes.getString(R.string.sim_change_pin));
    281                 break;
    282         }
    283         if (mError != null) {
    284             message = mError + "\n" + message;
    285             mError = null;
    286         }
    287         mPinDialog.setDialogMessage(message);
    288     }
    289 
    290     public void onPinEntered(EditPinPreference preference, boolean positiveResult) {
    291         if (!positiveResult) {
    292             resetDialogState();
    293             return;
    294         }
    295 
    296         mPin = preference.getText();
    297         if (!reasonablePin(mPin)) {
    298             // inject error message and display dialog again
    299             mError = mRes.getString(R.string.sim_bad_pin);
    300             showPinDialog();
    301             return;
    302         }
    303         switch (mDialogState) {
    304             case ICC_LOCK_MODE:
    305                 tryChangeIccLockState();
    306                 break;
    307             case ICC_OLD_MODE:
    308                 mOldPin = mPin;
    309                 mDialogState = ICC_NEW_MODE;
    310                 mError = null;
    311                 mPin = null;
    312                 showPinDialog();
    313                 break;
    314             case ICC_NEW_MODE:
    315                 mNewPin = mPin;
    316                 mDialogState = ICC_REENTER_MODE;
    317                 mPin = null;
    318                 showPinDialog();
    319                 break;
    320             case ICC_REENTER_MODE:
    321                 if (!mPin.equals(mNewPin)) {
    322                     mError = mRes.getString(R.string.sim_pins_dont_match);
    323                     mDialogState = ICC_NEW_MODE;
    324                     mPin = null;
    325                     showPinDialog();
    326                 } else {
    327                     mError = null;
    328                     tryChangePin();
    329                 }
    330                 break;
    331         }
    332     }
    333 
    334     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
    335         if (preference == mPinToggle) {
    336             // Get the new, preferred state
    337             mToState = mPinToggle.isChecked();
    338             // Flip it back and pop up pin dialog
    339             mPinToggle.setChecked(!mToState);
    340             mDialogState = ICC_LOCK_MODE;
    341             showPinDialog();
    342         } else if (preference == mPinDialog) {
    343             mDialogState = ICC_OLD_MODE;
    344             return false;
    345         }
    346         return true;
    347     }
    348 
    349     private void tryChangeIccLockState() {
    350         // Try to change icc lock. If it succeeds, toggle the lock state and
    351         // reset dialog state. Else inject error message and show dialog again.
    352         Message callback = Message.obtain(mHandler, MSG_ENABLE_ICC_PIN_COMPLETE);
    353         mPhone.getIccCard().setIccLockEnabled(mToState, mPin, callback);
    354         // Disable the setting till the response is received.
    355         mPinToggle.setEnabled(false);
    356     }
    357 
    358     private void iccLockChanged(boolean success, int attemptsRemaining) {
    359         if (success) {
    360             mPinToggle.setChecked(mToState);
    361         } else {
    362             Toast.makeText(this, getPinPasswordErrorMessage(attemptsRemaining), Toast.LENGTH_LONG)
    363                     .show();
    364         }
    365         mPinToggle.setEnabled(true);
    366         resetDialogState();
    367     }
    368 
    369     private void iccPinChanged(boolean success, int attemptsRemaining) {
    370         if (!success) {
    371             Toast.makeText(this, getPinPasswordErrorMessage(attemptsRemaining),
    372                     Toast.LENGTH_LONG)
    373                     .show();
    374         } else {
    375             Toast.makeText(this, mRes.getString(R.string.sim_change_succeeded),
    376                     Toast.LENGTH_SHORT)
    377                     .show();
    378 
    379         }
    380         resetDialogState();
    381     }
    382 
    383     private void tryChangePin() {
    384         Message callback = Message.obtain(mHandler, MSG_CHANGE_ICC_PIN_COMPLETE);
    385         mPhone.getIccCard().changeIccLockPassword(mOldPin,
    386                 mNewPin, callback);
    387     }
    388 
    389     private String getPinPasswordErrorMessage(int attemptsRemaining) {
    390         String displayMessage;
    391 
    392         if (attemptsRemaining == 0) {
    393             displayMessage = mRes.getString(R.string.wrong_pin_code_pukked);
    394         } else if (attemptsRemaining > 0) {
    395             displayMessage = mRes
    396                     .getQuantityString(R.plurals.wrong_pin_code, attemptsRemaining,
    397                             attemptsRemaining);
    398         } else {
    399             displayMessage = mRes.getString(R.string.pin_failed);
    400         }
    401         if (DBG) Log.d(TAG, "getPinPasswordErrorMessage:"
    402                 + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage);
    403         return displayMessage;
    404     }
    405 
    406     private boolean reasonablePin(String pin) {
    407         if (pin == null || pin.length() < MIN_PIN_LENGTH || pin.length() > MAX_PIN_LENGTH) {
    408             return false;
    409         } else {
    410             return true;
    411         }
    412     }
    413 
    414     private void resetDialogState() {
    415         mError = null;
    416         mDialogState = ICC_OLD_MODE; // Default for when Change PIN is clicked
    417         mPin = "";
    418         setDialogValues();
    419         mDialogState = OFF_MODE;
    420     }
    421 }
    422