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