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