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