2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.settings;
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.support.v7.preference.PreferenceScreen;
31 import android.telephony.SubscriptionInfo;
32 import android.telephony.SubscriptionManager;
33 import android.telephony.TelephonyManager;
34 import android.util.Log;
35 import android.view.View;
36 import android.widget.ListView;
37 import android.widget.TabHost;
38 import android.widget.TabHost.OnTabChangeListener;
39 import android.widget.TabHost.TabContentFactory;
40 import android.widget.TabHost.TabSpec;
41 import android.widget.TabWidget;
42 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;
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
58 public class IccLockSettings extends InstrumentedPreferenceActivity
59 implements EditPinPreference.OnPinEnteredListener {
60 private static final String TAG = "IccLockSettings";
61 private static final boolean DBG = true;
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;
74 private static final String PIN_DIALOG = "sim_pin";
75 private static final String PIN_TOGGLE = "sim_toggle";
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";
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";
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;
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;
99 private TabHost mTabHost;
100 private TabWidget mTabWidget;
101 private ListView mListView;
103 private Phone mPhone;
105 private EditPinPreference mPinDialog;
106 private SwitchPreference mPinToggle;
108 private Resources mRes;
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;
115 // For replies from IccCard interface
116 private Handler mHandler = new Handler() {
117 public void handleMessage(Message msg) {
118 AsyncResult ar = (AsyncResult) msg.obj;
120 case MSG_ENABLE_ICC_PIN_COMPLETE:
121 iccLockChanged(ar.exception == null, msg.arg1);
123 case MSG_CHANGE_ICC_PIN_COMPLETE:
124 iccPinChanged(ar.exception == null, msg.arg1);
126 case MSG_SIM_STATE_CHANGED:
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));
144 // For top-level settings screen to query
145 static boolean isIccLockEnabled() {
146 return PhoneFactory.getDefaultPhone().getIccCard().getIccLockEnabled();
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);
158 protected void onCreate(Bundle savedInstanceState) {
159 super.onCreate(savedInstanceState);
160 final Context context = getApplicationContext();
161 final TelephonyManager tm =
162 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
163 final int numSims = tm.getSimCount();
165 if (Utils.isMonkeyRunning()) {
170 addPreferencesFromResource(R.xml.sim_lock_settings);
172 mPinDialog = (EditPinPreference) findPreference(PIN_DIALOG);
173 mPinToggle = (SwitchPreference) findPreference(PIN_TOGGLE);
174 if (savedInstanceState != null && savedInstanceState.containsKey(DIALOG_STATE)) {
175 mDialogState = savedInstanceState.getInt(DIALOG_STATE);
176 mPin = savedInstanceState.getString(DIALOG_PIN);
177 mError = savedInstanceState.getString(DIALOG_ERROR);
178 mToState = savedInstanceState.getBoolean(ENABLE_TO_STATE);
180 // Restore inputted PIN code
181 switch (mDialogState) {
183 mOldPin = savedInstanceState.getString(OLD_PINCODE);
186 case ICC_REENTER_MODE:
187 mOldPin = savedInstanceState.getString(OLD_PINCODE);
188 mNewPin = savedInstanceState.getString(NEW_PINCODE);
198 mPinDialog.setOnPinEnteredListener(this);
200 // Don't need any changes to be remembered
201 getPreferenceScreen().setPersistent(false);
204 setContentView(R.layout.icc_lock_tabs);
206 mTabHost = (TabHost) findViewById(android.R.id.tabhost);
207 mTabWidget = (TabWidget) findViewById(android.R.id.tabs);
208 mListView = (ListView) findViewById(android.R.id.list);
211 mTabHost.setOnTabChangedListener(mTabListener);
212 mTabHost.clearAllTabs();
214 SubscriptionManager sm = SubscriptionManager.from(this);
215 for (int i = 0; i < numSims; ++i) {
216 final SubscriptionInfo subInfo = sm.getActiveSubscriptionInfoForSimSlotIndex(i);
217 mTabHost.addTab(buildTabSpec(String.valueOf(i),
218 String.valueOf(subInfo == null
219 ? context.getString(R.string.sim_editor_title, i + 1)
220 : subInfo.getDisplayName())));
222 final SubscriptionInfo sir = sm.getActiveSubscriptionInfoForSimSlotIndex(0);
224 mPhone = (sir == null) ? null
225 : PhoneFactory.getPhone(SubscriptionManager.getPhoneId(sir.getSubscriptionId()));
227 mPhone = PhoneFactory.getDefaultPhone();
229 mRes = getResources();
233 private void updatePreferences() {
234 mPinDialog.setEnabled(mPhone != null);
235 mPinToggle.setEnabled(mPhone != null);
237 if (mPhone != null) {
238 mPinToggle.setChecked(mPhone.getIccCard().getIccLockEnabled());
243 protected int getMetricsCategory() {
244 return MetricsEvent.ICC_LOCK;
248 protected void onResume() {
251 // ACTION_SIM_STATE_CHANGED is sticky, so we'll receive current state after this call,
252 // which will call updatePreferences().
253 final IntentFilter filter = new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
254 registerReceiver(mSimStateReceiver, filter);
256 if (mDialogState != OFF_MODE) {
259 // Prep for standard click on "Change PIN"
265 protected void onPause() {
267 unregisterReceiver(mSimStateReceiver);
271 protected void onSaveInstanceState(Bundle out) {
272 // Need to store this state for slider open/close
273 // There is one case where the dialog is popped up by the preference
274 // framework. In that case, let the preference framework store the
275 // dialog state. In other cases, where this activity manually launches
276 // the dialog, store the state of the dialog.
277 if (mPinDialog.isDialogOpen()) {
278 out.putInt(DIALOG_STATE, mDialogState);
279 out.putString(DIALOG_PIN, mPinDialog.getEditText().getText().toString());
280 out.putString(DIALOG_ERROR, mError);
281 out.putBoolean(ENABLE_TO_STATE, mToState);
283 // Save inputted PIN code
284 switch (mDialogState) {
286 out.putString(OLD_PINCODE, mOldPin);
289 case ICC_REENTER_MODE:
290 out.putString(OLD_PINCODE, mOldPin);
291 out.putString(NEW_PINCODE, mNewPin);
300 super.onSaveInstanceState(out);
304 private void showPinDialog() {
305 if (mDialogState == OFF_MODE) {
310 mPinDialog.showPinDialog();
313 private void setDialogValues() {
314 mPinDialog.setText(mPin);
316 switch (mDialogState) {
318 message = mRes.getString(R.string.sim_enter_pin);
319 mPinDialog.setDialogTitle(mToState
320 ? mRes.getString(R.string.sim_enable_sim_lock)
321 : mRes.getString(R.string.sim_disable_sim_lock));
324 message = mRes.getString(R.string.sim_enter_old);
325 mPinDialog.setDialogTitle(mRes.getString(R.string.sim_change_pin));
328 message = mRes.getString(R.string.sim_enter_new);
329 mPinDialog.setDialogTitle(mRes.getString(R.string.sim_change_pin));
331 case ICC_REENTER_MODE:
332 message = mRes.getString(R.string.sim_reenter_new);
333 mPinDialog.setDialogTitle(mRes.getString(R.string.sim_change_pin));
336 if (mError != null) {
337 message = mError + "\n" + message;
340 mPinDialog.setDialogMessage(message);
343 public void onPinEntered(EditPinPreference preference, boolean positiveResult) {
344 if (!positiveResult) {
349 mPin = preference.getText();
350 if (!reasonablePin(mPin)) {
351 // inject error message and display dialog again
352 mError = mRes.getString(R.string.sim_bad_pin);
356 switch (mDialogState) {
358 tryChangeIccLockState();
362 mDialogState = ICC_NEW_MODE;
369 mDialogState = ICC_REENTER_MODE;
373 case ICC_REENTER_MODE:
374 if (!mPin.equals(mNewPin)) {
375 mError = mRes.getString(R.string.sim_pins_dont_match);
376 mDialogState = ICC_NEW_MODE;
387 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
388 if (preference == mPinToggle) {
389 // Get the new, preferred state
390 mToState = mPinToggle.isChecked();
391 // Flip it back and pop up pin dialog
392 mPinToggle.setChecked(!mToState);
393 mDialogState = ICC_LOCK_MODE;
395 } else if (preference == mPinDialog) {
396 mDialogState = ICC_OLD_MODE;
402 private void tryChangeIccLockState() {
403 // Try to change icc lock. If it succeeds, toggle the lock state and
404 // reset dialog state. Else inject error message and show dialog again.
405 Message callback = Message.obtain(mHandler, MSG_ENABLE_ICC_PIN_COMPLETE);
406 mPhone.getIccCard().setIccLockEnabled(mToState, mPin, callback);
407 // Disable the setting till the response is received.
408 mPinToggle.setEnabled(false);
411 private void iccLockChanged(boolean success, int attemptsRemaining) {
413 mPinToggle.setChecked(mToState);
415 Toast.makeText(this, getPinPasswordErrorMessage(attemptsRemaining), Toast.LENGTH_LONG)
418 mPinToggle.setEnabled(true);
422 private void iccPinChanged(boolean success, int attemptsRemaining) {
424 Toast.makeText(this, getPinPasswordErrorMessage(attemptsRemaining),
428 Toast.makeText(this, mRes.getString(R.string.sim_change_succeeded),
436 private void tryChangePin() {
437 Message callback = Message.obtain(mHandler, MSG_CHANGE_ICC_PIN_COMPLETE);
438 mPhone.getIccCard().changeIccLockPassword(mOldPin,
442 private String getPinPasswordErrorMessage(int attemptsRemaining) {
443 String displayMessage;
445 if (attemptsRemaining == 0) {
446 displayMessage = mRes.getString(R.string.wrong_pin_code_pukked);
447 } else if (attemptsRemaining > 0) {
448 displayMessage = mRes
449 .getQuantityString(R.plurals.wrong_pin_code, attemptsRemaining,
452 displayMessage = mRes.getString(R.string.pin_failed);
454 if (DBG) Log.d(TAG, "getPinPasswordErrorMessage:"
455 + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage);
456 return displayMessage;
459 private boolean reasonablePin(String pin) {
460 if (pin == null || pin.length() < MIN_PIN_LENGTH || pin.length() > MAX_PIN_LENGTH) {
467 private void resetDialogState() {
469 mDialogState = ICC_OLD_MODE; // Default for when Change PIN is clicked
472 mDialogState = OFF_MODE;
475 private OnTabChangeListener mTabListener = new OnTabChangeListener() {
477 public void onTabChanged(String tabId) {
478 final int slotId = Integer.parseInt(tabId);
479 final SubscriptionInfo sir = SubscriptionManager.from(getBaseContext())
480 .getActiveSubscriptionInfoForSimSlotIndex(slotId);
482 mPhone = (sir == null) ? null
483 : PhoneFactory.getPhone(SubscriptionManager.getPhoneId(sir.getSubscriptionId()));
485 // The User has changed tab; update the body.
490 private TabContentFactory mEmptyTabContent = new TabContentFactory() {
492 public View createTabContent(String tag) {
493 return new View(mTabHost.getContext());
497 private TabSpec buildTabSpec(String tag, String title) {
498 return mTabHost.newTabSpec(tag).setIndicator(title).setContent(