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