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.util.Log; 34 import android.view.LayoutInflater; 35 import android.view.View; 36 import android.view.ViewGroup; 37 import android.widget.ListView; 38 import android.widget.TabHost; 39 import android.widget.TabHost.OnTabChangeListener; 40 import android.widget.TabHost.TabContentFactory; 41 import android.widget.TabHost.TabSpec; 42 import android.widget.TabWidget; 43 import android.widget.Toast; 44 import com.android.internal.logging.MetricsProto.MetricsEvent; 45 import com.android.internal.telephony.Phone; 46 import com.android.internal.telephony.PhoneFactory; 47 import 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 */ 58 public 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 mPinDialog.setEnabled(mPhone != null); 251 mPinToggle.setEnabled(mPhone != null); 252 253 if (mPhone != null) { 254 mPinToggle.setChecked(mPhone.getIccCard().getIccLockEnabled()); 255 } 256 } 257 258 @Override 259 protected int getMetricsCategory() { 260 return MetricsEvent.ICC_LOCK; 261 } 262 263 @Override 264 public void onResume() { 265 super.onResume(); 266 267 // ACTION_SIM_STATE_CHANGED is sticky, so we'll receive current state after this call, 268 // which will call updatePreferences(). 269 final IntentFilter filter = new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED); 270 getContext().registerReceiver(mSimStateReceiver, filter); 271 272 if (mDialogState != OFF_MODE) { 273 showPinDialog(); 274 } else { 275 // Prep for standard click on "Change PIN" 276 resetDialogState(); 277 } 278 } 279 280 @Override 281 public void onPause() { 282 super.onPause(); 283 getContext().unregisterReceiver(mSimStateReceiver); 284 } 285 286 @Override 287 public void onSaveInstanceState(Bundle out) { 288 // Need to store this state for slider open/close 289 // There is one case where the dialog is popped up by the preference 290 // framework. In that case, let the preference framework store the 291 // dialog state. In other cases, where this activity manually launches 292 // the dialog, store the state of the dialog. 293 if (mPinDialog.isDialogOpen()) { 294 out.putInt(DIALOG_STATE, mDialogState); 295 out.putString(DIALOG_PIN, mPinDialog.getEditText().getText().toString()); 296 out.putString(DIALOG_ERROR, mError); 297 out.putBoolean(ENABLE_TO_STATE, mToState); 298 299 // Save inputted PIN code 300 switch (mDialogState) { 301 case ICC_NEW_MODE: 302 out.putString(OLD_PINCODE, mOldPin); 303 break; 304 305 case ICC_REENTER_MODE: 306 out.putString(OLD_PINCODE, mOldPin); 307 out.putString(NEW_PINCODE, mNewPin); 308 break; 309 310 case ICC_LOCK_MODE: 311 case ICC_OLD_MODE: 312 default: 313 break; 314 } 315 } else { 316 super.onSaveInstanceState(out); 317 } 318 } 319 320 private void showPinDialog() { 321 if (mDialogState == OFF_MODE) { 322 return; 323 } 324 setDialogValues(); 325 326 mPinDialog.showPinDialog(); 327 } 328 329 private void setDialogValues() { 330 mPinDialog.setText(mPin); 331 String message = ""; 332 switch (mDialogState) { 333 case ICC_LOCK_MODE: 334 message = mRes.getString(R.string.sim_enter_pin); 335 mPinDialog.setDialogTitle(mToState 336 ? mRes.getString(R.string.sim_enable_sim_lock) 337 : mRes.getString(R.string.sim_disable_sim_lock)); 338 break; 339 case ICC_OLD_MODE: 340 message = mRes.getString(R.string.sim_enter_old); 341 mPinDialog.setDialogTitle(mRes.getString(R.string.sim_change_pin)); 342 break; 343 case ICC_NEW_MODE: 344 message = mRes.getString(R.string.sim_enter_new); 345 mPinDialog.setDialogTitle(mRes.getString(R.string.sim_change_pin)); 346 break; 347 case ICC_REENTER_MODE: 348 message = mRes.getString(R.string.sim_reenter_new); 349 mPinDialog.setDialogTitle(mRes.getString(R.string.sim_change_pin)); 350 break; 351 } 352 if (mError != null) { 353 message = mError + "\n" + message; 354 mError = null; 355 } 356 mPinDialog.setDialogMessage(message); 357 } 358 359 @Override 360 public void onPinEntered(EditPinPreference preference, boolean positiveResult) { 361 if (!positiveResult) { 362 resetDialogState(); 363 return; 364 } 365 366 mPin = preference.getText(); 367 if (!reasonablePin(mPin)) { 368 // inject error message and display dialog again 369 mError = mRes.getString(R.string.sim_bad_pin); 370 showPinDialog(); 371 return; 372 } 373 switch (mDialogState) { 374 case ICC_LOCK_MODE: 375 tryChangeIccLockState(); 376 break; 377 case ICC_OLD_MODE: 378 mOldPin = mPin; 379 mDialogState = ICC_NEW_MODE; 380 mError = null; 381 mPin = null; 382 showPinDialog(); 383 break; 384 case ICC_NEW_MODE: 385 mNewPin = mPin; 386 mDialogState = ICC_REENTER_MODE; 387 mPin = null; 388 showPinDialog(); 389 break; 390 case ICC_REENTER_MODE: 391 if (!mPin.equals(mNewPin)) { 392 mError = mRes.getString(R.string.sim_pins_dont_match); 393 mDialogState = ICC_NEW_MODE; 394 mPin = null; 395 showPinDialog(); 396 } else { 397 mError = null; 398 tryChangePin(); 399 } 400 break; 401 } 402 } 403 404 @Override 405 public boolean onPreferenceTreeClick(Preference preference) { 406 if (preference == mPinToggle) { 407 // Get the new, preferred state 408 mToState = mPinToggle.isChecked(); 409 // Flip it back and pop up pin dialog 410 mPinToggle.setChecked(!mToState); 411 mDialogState = ICC_LOCK_MODE; 412 showPinDialog(); 413 } else if (preference == mPinDialog) { 414 mDialogState = ICC_OLD_MODE; 415 return false; 416 } 417 return true; 418 } 419 420 private void tryChangeIccLockState() { 421 // Try to change icc lock. If it succeeds, toggle the lock state and 422 // reset dialog state. Else inject error message and show dialog again. 423 Message callback = Message.obtain(mHandler, MSG_ENABLE_ICC_PIN_COMPLETE); 424 mPhone.getIccCard().setIccLockEnabled(mToState, mPin, callback); 425 // Disable the setting till the response is received. 426 mPinToggle.setEnabled(false); 427 } 428 429 private void iccLockChanged(boolean success, int attemptsRemaining) { 430 if (success) { 431 mPinToggle.setChecked(mToState); 432 } else { 433 Toast.makeText(getContext(), getPinPasswordErrorMessage(attemptsRemaining), 434 Toast.LENGTH_LONG).show(); 435 } 436 mPinToggle.setEnabled(true); 437 resetDialogState(); 438 } 439 440 private void iccPinChanged(boolean success, int attemptsRemaining) { 441 if (!success) { 442 Toast.makeText(getContext(), getPinPasswordErrorMessage(attemptsRemaining), 443 Toast.LENGTH_LONG) 444 .show(); 445 } else { 446 Toast.makeText(getContext(), mRes.getString(R.string.sim_change_succeeded), 447 Toast.LENGTH_SHORT) 448 .show(); 449 450 } 451 resetDialogState(); 452 } 453 454 private void tryChangePin() { 455 Message callback = Message.obtain(mHandler, MSG_CHANGE_ICC_PIN_COMPLETE); 456 mPhone.getIccCard().changeIccLockPassword(mOldPin, 457 mNewPin, callback); 458 } 459 460 private String getPinPasswordErrorMessage(int attemptsRemaining) { 461 String displayMessage; 462 463 if (attemptsRemaining == 0) { 464 displayMessage = mRes.getString(R.string.wrong_pin_code_pukked); 465 } else if (attemptsRemaining > 0) { 466 displayMessage = mRes 467 .getQuantityString(R.plurals.wrong_pin_code, attemptsRemaining, 468 attemptsRemaining); 469 } else { 470 displayMessage = mRes.getString(R.string.pin_failed); 471 } 472 if (DBG) Log.d(TAG, "getPinPasswordErrorMessage:" 473 + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage); 474 return displayMessage; 475 } 476 477 private boolean reasonablePin(String pin) { 478 if (pin == null || pin.length() < MIN_PIN_LENGTH || pin.length() > MAX_PIN_LENGTH) { 479 return false; 480 } else { 481 return true; 482 } 483 } 484 485 private void resetDialogState() { 486 mError = null; 487 mDialogState = ICC_OLD_MODE; // Default for when Change PIN is clicked 488 mPin = ""; 489 setDialogValues(); 490 mDialogState = OFF_MODE; 491 } 492 493 private OnTabChangeListener mTabListener = new OnTabChangeListener() { 494 @Override 495 public void onTabChanged(String tabId) { 496 final int slotId = Integer.parseInt(tabId); 497 final SubscriptionInfo sir = SubscriptionManager.from(getActivity().getBaseContext()) 498 .getActiveSubscriptionInfoForSimSlotIndex(slotId); 499 500 mPhone = (sir == null) ? null 501 : PhoneFactory.getPhone(SubscriptionManager.getPhoneId(sir.getSubscriptionId())); 502 503 // The User has changed tab; update the body. 504 updatePreferences(); 505 } 506 }; 507 508 private TabContentFactory mEmptyTabContent = new TabContentFactory() { 509 @Override 510 public View createTabContent(String tag) { 511 return new View(mTabHost.getContext()); 512 } 513 }; 514 515 private TabSpec buildTabSpec(String tag, String title) { 516 return mTabHost.newTabSpec(tag).setIndicator(title).setContent( 517 mEmptyTabContent); 518 } 519 } 520