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