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