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