CdmaMmiCode.java revision 3522c54a64f577f2b657a775dae9b4eb2d8003d5
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
17package com.android.internal.telephony.cdma;
18
19import android.content.Context;
20
21import com.android.internal.telephony.CommandException;
22import com.android.internal.telephony.uicc.UiccCardApplication;
23import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState;
24import com.android.internal.telephony.MmiCode;
25
26import android.os.AsyncResult;
27import android.os.Handler;
28import android.os.Message;
29import android.telephony.Rlog;
30
31import java.util.regex.Pattern;
32import java.util.regex.Matcher;
33
34/**
35 * This class can handle Puk code Mmi
36 *
37 * {@hide}
38 *
39 */
40public 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}
361