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