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.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         mPinDialog.setEnabled(mPhone != null);
    251         mPinToggle.setEnabled(mPhone != null);
    252 
    253         if (mPhone != null) {
    254             mPinToggle.setChecked(mPhone.getIccCard().getIccLockEnabled());
    255         }
    256     }
    257 
    258     @Override
    259     protected int getMetricsCategory() {
    260         return MetricsEvent.ICC_LOCK;
    261     }
    262 
    263     @Override
    264     public void onResume() {
    265         super.onResume();
    266 
    267         // ACTION_SIM_STATE_CHANGED is sticky, so we'll receive current state after this call,
    268         // which will call updatePreferences().
    269         final IntentFilter filter = new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
    270         getContext().registerReceiver(mSimStateReceiver, filter);
    271 
    272         if (mDialogState != OFF_MODE) {
    273             showPinDialog();
    274         } else {
    275             // Prep for standard click on "Change PIN"
    276             resetDialogState();
    277         }
    278     }
    279 
    280     @Override
    281     public void onPause() {
    282         super.onPause();
    283         getContext().unregisterReceiver(mSimStateReceiver);
    284     }
    285 
    286     @Override
    287     public void onSaveInstanceState(Bundle out) {
    288         // Need to store this state for slider open/close
    289         // There is one case where the dialog is popped up by the preference
    290         // framework. In that case, let the preference framework store the
    291         // dialog state. In other cases, where this activity manually launches
    292         // the dialog, store the state of the dialog.
    293         if (mPinDialog.isDialogOpen()) {
    294             out.putInt(DIALOG_STATE, mDialogState);
    295             out.putString(DIALOG_PIN, mPinDialog.getEditText().getText().toString());
    296             out.putString(DIALOG_ERROR, mError);
    297             out.putBoolean(ENABLE_TO_STATE, mToState);
    298 
    299             // Save inputted PIN code
    300             switch (mDialogState) {
    301                 case ICC_NEW_MODE:
    302                     out.putString(OLD_PINCODE, mOldPin);
    303                     break;
    304 
    305                 case ICC_REENTER_MODE:
    306                     out.putString(OLD_PINCODE, mOldPin);
    307                     out.putString(NEW_PINCODE, mNewPin);
    308                     break;
    309 
    310                 case ICC_LOCK_MODE:
    311                 case ICC_OLD_MODE:
    312                 default:
    313                     break;
    314             }
    315         } else {
    316             super.onSaveInstanceState(out);
    317         }
    318     }
    319 
    320     private void showPinDialog() {
    321         if (mDialogState == OFF_MODE) {
    322             return;
    323         }
    324         setDialogValues();
    325 
    326         mPinDialog.showPinDialog();
    327     }
    328 
    329     private void setDialogValues() {
    330         mPinDialog.setText(mPin);
    331         String message = "";
    332         switch (mDialogState) {
    333             case ICC_LOCK_MODE:
    334                 message = mRes.getString(R.string.sim_enter_pin);
    335                 mPinDialog.setDialogTitle(mToState
    336                         ? mRes.getString(R.string.sim_enable_sim_lock)
    337                         : mRes.getString(R.string.sim_disable_sim_lock));
    338                 break;
    339             case ICC_OLD_MODE:
    340                 message = mRes.getString(R.string.sim_enter_old);
    341                 mPinDialog.setDialogTitle(mRes.getString(R.string.sim_change_pin));
    342                 break;
    343             case ICC_NEW_MODE:
    344                 message = mRes.getString(R.string.sim_enter_new);
    345                 mPinDialog.setDialogTitle(mRes.getString(R.string.sim_change_pin));
    346                 break;
    347             case ICC_REENTER_MODE:
    348                 message = mRes.getString(R.string.sim_reenter_new);
    349                 mPinDialog.setDialogTitle(mRes.getString(R.string.sim_change_pin));
    350                 break;
    351         }
    352         if (mError != null) {
    353             message = mError + "\n" + message;
    354             mError = null;
    355         }
    356         mPinDialog.setDialogMessage(message);
    357     }
    358 
    359     @Override
    360     public void onPinEntered(EditPinPreference preference, boolean positiveResult) {
    361         if (!positiveResult) {
    362             resetDialogState();
    363             return;
    364         }
    365 
    366         mPin = preference.getText();
    367         if (!reasonablePin(mPin)) {
    368             // inject error message and display dialog again
    369             mError = mRes.getString(R.string.sim_bad_pin);
    370             showPinDialog();
    371             return;
    372         }
    373         switch (mDialogState) {
    374             case ICC_LOCK_MODE:
    375                 tryChangeIccLockState();
    376                 break;
    377             case ICC_OLD_MODE:
    378                 mOldPin = mPin;
    379                 mDialogState = ICC_NEW_MODE;
    380                 mError = null;
    381                 mPin = null;
    382                 showPinDialog();
    383                 break;
    384             case ICC_NEW_MODE:
    385                 mNewPin = mPin;
    386                 mDialogState = ICC_REENTER_MODE;
    387                 mPin = null;
    388                 showPinDialog();
    389                 break;
    390             case ICC_REENTER_MODE:
    391                 if (!mPin.equals(mNewPin)) {
    392                     mError = mRes.getString(R.string.sim_pins_dont_match);
    393                     mDialogState = ICC_NEW_MODE;
    394                     mPin = null;
    395                     showPinDialog();
    396                 } else {
    397                     mError = null;
    398                     tryChangePin();
    399                 }
    400                 break;
    401         }
    402     }
    403 
    404     @Override
    405     public boolean onPreferenceTreeClick(Preference preference) {
    406         if (preference == mPinToggle) {
    407             // Get the new, preferred state
    408             mToState = mPinToggle.isChecked();
    409             // Flip it back and pop up pin dialog
    410             mPinToggle.setChecked(!mToState);
    411             mDialogState = ICC_LOCK_MODE;
    412             showPinDialog();
    413         } else if (preference == mPinDialog) {
    414             mDialogState = ICC_OLD_MODE;
    415             return false;
    416         }
    417         return true;
    418     }
    419 
    420     private void tryChangeIccLockState() {
    421         // Try to change icc lock. If it succeeds, toggle the lock state and
    422         // reset dialog state. Else inject error message and show dialog again.
    423         Message callback = Message.obtain(mHandler, MSG_ENABLE_ICC_PIN_COMPLETE);
    424         mPhone.getIccCard().setIccLockEnabled(mToState, mPin, callback);
    425         // Disable the setting till the response is received.
    426         mPinToggle.setEnabled(false);
    427     }
    428 
    429     private void iccLockChanged(boolean success, int attemptsRemaining) {
    430         if (success) {
    431             mPinToggle.setChecked(mToState);
    432         } else {
    433             Toast.makeText(getContext(), getPinPasswordErrorMessage(attemptsRemaining),
    434                     Toast.LENGTH_LONG).show();
    435         }
    436         mPinToggle.setEnabled(true);
    437         resetDialogState();
    438     }
    439 
    440     private void iccPinChanged(boolean success, int attemptsRemaining) {
    441         if (!success) {
    442             Toast.makeText(getContext(), getPinPasswordErrorMessage(attemptsRemaining),
    443                     Toast.LENGTH_LONG)
    444                     .show();
    445         } else {
    446             Toast.makeText(getContext(), mRes.getString(R.string.sim_change_succeeded),
    447                     Toast.LENGTH_SHORT)
    448                     .show();
    449 
    450         }
    451         resetDialogState();
    452     }
    453 
    454     private void tryChangePin() {
    455         Message callback = Message.obtain(mHandler, MSG_CHANGE_ICC_PIN_COMPLETE);
    456         mPhone.getIccCard().changeIccLockPassword(mOldPin,
    457                 mNewPin, callback);
    458     }
    459 
    460     private String getPinPasswordErrorMessage(int attemptsRemaining) {
    461         String displayMessage;
    462 
    463         if (attemptsRemaining == 0) {
    464             displayMessage = mRes.getString(R.string.wrong_pin_code_pukked);
    465         } else if (attemptsRemaining > 0) {
    466             displayMessage = mRes
    467                     .getQuantityString(R.plurals.wrong_pin_code, attemptsRemaining,
    468                             attemptsRemaining);
    469         } else {
    470             displayMessage = mRes.getString(R.string.pin_failed);
    471         }
    472         if (DBG) Log.d(TAG, "getPinPasswordErrorMessage:"
    473                 + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage);
    474         return displayMessage;
    475     }
    476 
    477     private boolean reasonablePin(String pin) {
    478         if (pin == null || pin.length() < MIN_PIN_LENGTH || pin.length() > MAX_PIN_LENGTH) {
    479             return false;
    480         } else {
    481             return true;
    482         }
    483     }
    484 
    485     private void resetDialogState() {
    486         mError = null;
    487         mDialogState = ICC_OLD_MODE; // Default for when Change PIN is clicked
    488         mPin = "";
    489         setDialogValues();
    490         mDialogState = OFF_MODE;
    491     }
    492 
    493     private OnTabChangeListener mTabListener = new OnTabChangeListener() {
    494         @Override
    495         public void onTabChanged(String tabId) {
    496             final int slotId = Integer.parseInt(tabId);
    497             final SubscriptionInfo sir = SubscriptionManager.from(getActivity().getBaseContext())
    498                     .getActiveSubscriptionInfoForSimSlotIndex(slotId);
    499 
    500             mPhone = (sir == null) ? null
    501                 : PhoneFactory.getPhone(SubscriptionManager.getPhoneId(sir.getSubscriptionId()));
    502 
    503             // The User has changed tab; update the body.
    504             updatePreferences();
    505         }
    506     };
    507 
    508     private TabContentFactory mEmptyTabContent = new TabContentFactory() {
    509         @Override
    510         public View createTabContent(String tag) {
    511             return new View(mTabHost.getContext());
    512         }
    513     };
    514 
    515     private TabSpec buildTabSpec(String tag, String title) {
    516         return mTabHost.newTabSpec(tag).setIndicator(title).setContent(
    517                 mEmptyTabContent);
    518     }
    519 }
    520