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