1 /* 2 * Copyright (C) 2006 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.internal.telephony.cdma; 18 19 import android.content.Context; 20 21 import com.android.internal.telephony.CommandException; 22 import com.android.internal.telephony.uicc.UiccCardApplication; 23 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState; 24 import com.android.internal.telephony.MmiCode; 25 import com.android.internal.telephony.Phone; 26 27 import android.os.AsyncResult; 28 import android.os.Handler; 29 import android.os.Message; 30 import android.telephony.Rlog; 31 32 import java.util.regex.Pattern; 33 import java.util.regex.Matcher; 34 35 /** 36 * This class can handle Puk code Mmi 37 * 38 * {@hide} 39 * 40 */ 41 public final class CdmaMmiCode extends Handler implements MmiCode { 42 static final String LOG_TAG = "CdmaMmiCode"; 43 44 // Constants 45 46 // From TS 22.030 6.5.2 47 static final String ACTION_REGISTER = "**"; 48 49 // Supplementary Service codes for PIN/PIN2/PUK/PUK2 from TS 22.030 Annex B 50 static final String SC_PIN = "04"; 51 static final String SC_PIN2 = "042"; 52 static final String SC_PUK = "05"; 53 static final String SC_PUK2 = "052"; 54 55 // Event Constant 56 57 static final int EVENT_SET_COMPLETE = 1; 58 59 // Instance Variables 60 61 CDMAPhone mPhone; 62 Context mContext; 63 UiccCardApplication mUiccApplication; 64 65 String mAction; // ACTION_REGISTER 66 String mSc; // Service Code 67 String mSia, mSib, mSic; // Service Info a,b,c 68 String mPoundString; // Entire MMI string up to and including # 69 String mDialingNumber; 70 String mPwd; // For password registration 71 72 State mState = State.PENDING; 73 CharSequence mMessage; 74 75 // Class Variables 76 77 static Pattern sPatternSuppService = Pattern.compile( 78 "((\\*|#|\\*#|\\*\\*|##)(\\d{2,3})(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*))?)?)?)?#)(.*)"); 79 /* 1 2 3 4 5 6 7 8 9 10 11 12 80 81 1 = Full string up to and including # 82 2 = action 83 3 = service code 84 5 = SIA 85 7 = SIB 86 9 = SIC 87 10 = dialing number 88 */ 89 90 static final int MATCH_GROUP_POUND_STRING = 1; 91 static final int MATCH_GROUP_ACTION = 2; 92 static final int MATCH_GROUP_SERVICE_CODE = 3; 93 static final int MATCH_GROUP_SIA = 5; 94 static final int MATCH_GROUP_SIB = 7; 95 static final int MATCH_GROUP_SIC = 9; 96 static final int MATCH_GROUP_PWD_CONFIRM = 11; 97 static final int MATCH_GROUP_DIALING_NUMBER = 12; 98 99 100 // Public Class methods 101 102 /** 103 * Check if provided string contains Mmi code in it and create corresponding 104 * Mmi if it does 105 */ 106 107 public static CdmaMmiCode 108 newFromDialString(String dialString, CDMAPhone phone, UiccCardApplication app) { 109 Matcher m; 110 CdmaMmiCode ret = null; 111 112 m = sPatternSuppService.matcher(dialString); 113 114 // Is this formatted like a standard supplementary service code? 115 if (m.matches()) { 116 ret = new CdmaMmiCode(phone,app); 117 ret.mPoundString = makeEmptyNull(m.group(MATCH_GROUP_POUND_STRING)); 118 ret.mAction = makeEmptyNull(m.group(MATCH_GROUP_ACTION)); 119 ret.mSc = makeEmptyNull(m.group(MATCH_GROUP_SERVICE_CODE)); 120 ret.mSia = makeEmptyNull(m.group(MATCH_GROUP_SIA)); 121 ret.mSib = makeEmptyNull(m.group(MATCH_GROUP_SIB)); 122 ret.mSic = makeEmptyNull(m.group(MATCH_GROUP_SIC)); 123 ret.mPwd = makeEmptyNull(m.group(MATCH_GROUP_PWD_CONFIRM)); 124 ret.mDialingNumber = makeEmptyNull(m.group(MATCH_GROUP_DIALING_NUMBER)); 125 126 } 127 128 return ret; 129 } 130 131 // Private Class methods 132 133 /** make empty strings be null. 134 * Regexp returns empty strings for empty groups 135 */ 136 private static String 137 makeEmptyNull (String s) { 138 if (s != null && s.length() == 0) return null; 139 140 return s; 141 } 142 143 // Constructor 144 145 CdmaMmiCode (CDMAPhone phone, UiccCardApplication app) { 146 super(phone.getHandler().getLooper()); 147 mPhone = phone; 148 mContext = phone.getContext(); 149 mUiccApplication = app; 150 } 151 152 // MmiCode implementation 153 154 @Override 155 public State 156 getState() { 157 return mState; 158 } 159 160 @Override 161 public CharSequence 162 getMessage() { 163 return mMessage; 164 } 165 166 public Phone 167 getPhone() { 168 return ((Phone) mPhone); 169 } 170 171 // inherited javadoc suffices 172 @Override 173 public void 174 cancel() { 175 // Complete or failed cannot be cancelled 176 if (mState == State.COMPLETE || mState == State.FAILED) { 177 return; 178 } 179 180 mState = State.CANCELLED; 181 mPhone.onMMIDone (this); 182 } 183 184 @Override 185 public boolean isCancelable() { 186 return false; 187 } 188 189 // Instance Methods 190 191 /** 192 * @return true if the Service Code is PIN/PIN2/PUK/PUK2-related 193 */ 194 boolean isPinPukCommand() { 195 return mSc != null && (mSc.equals(SC_PIN) || mSc.equals(SC_PIN2) 196 || mSc.equals(SC_PUK) || mSc.equals(SC_PUK2)); 197 } 198 199 boolean isRegister() { 200 return mAction != null && mAction.equals(ACTION_REGISTER); 201 } 202 203 @Override 204 public boolean isUssdRequest() { 205 Rlog.w(LOG_TAG, "isUssdRequest is not implemented in CdmaMmiCode"); 206 return false; 207 } 208 209 /** Process a MMI PUK code */ 210 void 211 processCode() { 212 try { 213 if (isPinPukCommand()) { 214 // TODO: This is the same as the code in GsmMmiCode.java, 215 // MmiCode should be an abstract or base class and this and 216 // other common variables and code should be promoted. 217 218 // sia = old PIN or PUK 219 // sib = new PIN 220 // sic = new PIN 221 String oldPinOrPuk = mSia; 222 String newPinOrPuk = mSib; 223 int pinLen = newPinOrPuk.length(); 224 if (isRegister()) { 225 if (!newPinOrPuk.equals(mSic)) { 226 // password mismatch; return error 227 handlePasswordError(com.android.internal.R.string.mismatchPin); 228 } else if (pinLen < 4 || pinLen > 8 ) { 229 // invalid length 230 handlePasswordError(com.android.internal.R.string.invalidPin); 231 } else if (mSc.equals(SC_PIN) 232 && mUiccApplication != null 233 && mUiccApplication.getState() == AppState.APPSTATE_PUK) { 234 // Sim is puk-locked 235 handlePasswordError(com.android.internal.R.string.needPuk); 236 } else if (mUiccApplication != null) { 237 Rlog.d(LOG_TAG, "process mmi service code using UiccApp sc=" + mSc); 238 239 // We have an app and the pre-checks are OK 240 if (mSc.equals(SC_PIN)) { 241 mUiccApplication.changeIccLockPassword(oldPinOrPuk, newPinOrPuk, 242 obtainMessage(EVENT_SET_COMPLETE, this)); 243 } else if (mSc.equals(SC_PIN2)) { 244 mUiccApplication.changeIccFdnPassword(oldPinOrPuk, newPinOrPuk, 245 obtainMessage(EVENT_SET_COMPLETE, this)); 246 } else if (mSc.equals(SC_PUK)) { 247 mUiccApplication.supplyPuk(oldPinOrPuk, newPinOrPuk, 248 obtainMessage(EVENT_SET_COMPLETE, this)); 249 } else if (mSc.equals(SC_PUK2)) { 250 mUiccApplication.supplyPuk2(oldPinOrPuk, newPinOrPuk, 251 obtainMessage(EVENT_SET_COMPLETE, this)); 252 } else { 253 throw new RuntimeException("Unsupported service code=" + mSc); 254 } 255 } else { 256 throw new RuntimeException("No application mUiccApplicaiton is null"); 257 } 258 } else { 259 throw new RuntimeException ("Ivalid register/action=" + mAction); 260 } 261 } 262 } catch (RuntimeException exc) { 263 mState = State.FAILED; 264 mMessage = mContext.getText(com.android.internal.R.string.mmiError); 265 mPhone.onMMIDone(this); 266 } 267 } 268 269 private void handlePasswordError(int res) { 270 mState = State.FAILED; 271 StringBuilder sb = new StringBuilder(getScString()); 272 sb.append("\n"); 273 sb.append(mContext.getText(res)); 274 mMessage = sb; 275 mPhone.onMMIDone(this); 276 } 277 278 @Override 279 public void 280 handleMessage (Message msg) { 281 AsyncResult ar; 282 283 if (msg.what == EVENT_SET_COMPLETE) { 284 ar = (AsyncResult) (msg.obj); 285 onSetComplete(msg, ar); 286 } else { 287 Rlog.e(LOG_TAG, "Unexpected reply"); 288 } 289 } 290 // Private instance methods 291 292 private CharSequence getScString() { 293 if (mSc != null) { 294 if (isPinPukCommand()) { 295 return mContext.getText(com.android.internal.R.string.PinMmi); 296 } 297 } 298 299 return ""; 300 } 301 302 private void 303 onSetComplete(Message msg, AsyncResult ar){ 304 StringBuilder sb = new StringBuilder(getScString()); 305 sb.append("\n"); 306 307 if (ar.exception != null) { 308 mState = State.FAILED; 309 if (ar.exception instanceof CommandException) { 310 CommandException.Error err = ((CommandException)(ar.exception)).getCommandError(); 311 if (err == CommandException.Error.PASSWORD_INCORRECT) { 312 if (isPinPukCommand()) { 313 // look specifically for the PUK commands and adjust 314 // the message accordingly. 315 if (mSc.equals(SC_PUK) || mSc.equals(SC_PUK2)) { 316 sb.append(mContext.getText( 317 com.android.internal.R.string.badPuk)); 318 } else { 319 sb.append(mContext.getText( 320 com.android.internal.R.string.badPin)); 321 } 322 // Get the No. of retries remaining to unlock PUK/PUK2 323 int attemptsRemaining = msg.arg1; 324 if (attemptsRemaining <= 0) { 325 Rlog.d(LOG_TAG, "onSetComplete: PUK locked," 326 + " cancel as lock screen will handle this"); 327 mState = State.CANCELLED; 328 } else if (attemptsRemaining > 0) { 329 Rlog.d(LOG_TAG, "onSetComplete: attemptsRemaining="+attemptsRemaining); 330 sb.append(mContext.getResources().getQuantityString( 331 com.android.internal.R.plurals.pinpuk_attempts, 332 attemptsRemaining, attemptsRemaining)); 333 } 334 } else { 335 sb.append(mContext.getText( 336 com.android.internal.R.string.passwordIncorrect)); 337 } 338 } else if (err == CommandException.Error.SIM_PUK2) { 339 sb.append(mContext.getText( 340 com.android.internal.R.string.badPin)); 341 sb.append("\n"); 342 sb.append(mContext.getText( 343 com.android.internal.R.string.needPuk2)); 344 } else if (err == CommandException.Error.REQUEST_NOT_SUPPORTED) { 345 if (mSc.equals(SC_PIN)) { 346 sb.append(mContext.getText(com.android.internal.R.string.enablePin)); 347 } 348 } else { 349 sb.append(mContext.getText( 350 com.android.internal.R.string.mmiError)); 351 } 352 } else { 353 sb.append(mContext.getText( 354 com.android.internal.R.string.mmiError)); 355 } 356 } else if (isRegister()) { 357 mState = State.COMPLETE; 358 sb.append(mContext.getText( 359 com.android.internal.R.string.serviceRegistered)); 360 } else { 361 mState = State.FAILED; 362 sb.append(mContext.getText( 363 com.android.internal.R.string.mmiError)); 364 } 365 366 mMessage = sb; 367 mPhone.onMMIDone(this); 368 } 369 370 } 371