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