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