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