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