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.MmiCode;
23
24import android.os.AsyncResult;
25import android.os.Handler;
26import android.os.Message;
27import android.util.Log;
28
29import java.util.regex.Pattern;
30import java.util.regex.Matcher;
31
32/**
33 * This class can handle Puk code Mmi
34 *
35 * {@hide}
36 *
37 */
38public final class CdmaMmiCode  extends Handler implements MmiCode {
39    static final String LOG_TAG = "CDMA_MMI";
40
41    // Constants
42
43    // From TS 22.030 6.5.2
44    static final String ACTION_REGISTER = "**";
45
46    // Supp Service codes from TS 22.030 Annex B
47    static final String SC_PUK          = "05";
48
49    // Event Constant
50
51    static final int EVENT_SET_COMPLETE = 1;
52
53    // Instance Variables
54
55    CDMAPhone phone;
56    Context context;
57
58    String action;              // ACTION_REGISTER
59    String sc;                  // Service Code
60    String sia, sib, sic;       // Service Info a,b,c
61    String poundString;         // Entire MMI string up to and including #
62    String dialingNumber;
63    String pwd;                 // For password registration
64
65    State state = State.PENDING;
66    CharSequence message;
67
68    // Class Variables
69
70    static Pattern sPatternSuppService = Pattern.compile(
71        "((\\*|#|\\*#|\\*\\*|##)(\\d{2,3})(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*))?)?)?)?#)(.*)");
72/*       1  2                    3          4  5       6   7         8    9     10  11             12
73
74         1 = Full string up to and including #
75         2 = action
76         3 = service code
77         5 = SIA
78         7 = SIB
79         9 = SIC
80         10 = dialing number
81*/
82
83    static final int MATCH_GROUP_POUND_STRING = 1;
84    static final int MATCH_GROUP_ACTION = 2;
85    static final int MATCH_GROUP_SERVICE_CODE = 3;
86    static final int MATCH_GROUP_SIA = 5;
87    static final int MATCH_GROUP_SIB = 7;
88    static final int MATCH_GROUP_SIC = 9;
89    static final int MATCH_GROUP_PWD_CONFIRM = 11;
90    static final int MATCH_GROUP_DIALING_NUMBER = 12;
91
92
93    // Public Class methods
94
95    /**
96     * Check if provided string contains Mmi code in it and create corresponding
97     * Mmi if it does
98     */
99
100    public static CdmaMmiCode
101    newFromDialString(String dialString, CDMAPhone phone) {
102        Matcher m;
103        CdmaMmiCode ret = null;
104
105        m = sPatternSuppService.matcher(dialString);
106
107        // Is this formatted like a standard supplementary service code?
108        if (m.matches()) {
109            ret = new CdmaMmiCode(phone);
110            ret.poundString = makeEmptyNull(m.group(MATCH_GROUP_POUND_STRING));
111            ret.action = makeEmptyNull(m.group(MATCH_GROUP_ACTION));
112            ret.sc = makeEmptyNull(m.group(MATCH_GROUP_SERVICE_CODE));
113            ret.sia = makeEmptyNull(m.group(MATCH_GROUP_SIA));
114            ret.sib = makeEmptyNull(m.group(MATCH_GROUP_SIB));
115            ret.sic = makeEmptyNull(m.group(MATCH_GROUP_SIC));
116            ret.pwd = makeEmptyNull(m.group(MATCH_GROUP_PWD_CONFIRM));
117            ret.dialingNumber = makeEmptyNull(m.group(MATCH_GROUP_DIALING_NUMBER));
118
119        }
120
121        return ret;
122    }
123
124    // Private Class methods
125
126    /** make empty strings be null.
127     *  Regexp returns empty strings for empty groups
128     */
129    private static String
130    makeEmptyNull (String s) {
131        if (s != null && s.length() == 0) return null;
132
133        return s;
134    }
135
136    // Constructor
137
138    CdmaMmiCode (CDMAPhone phone) {
139        super(phone.getHandler().getLooper());
140        this.phone = phone;
141        this.context = phone.getContext();
142    }
143
144    // MmiCode implementation
145
146    public State
147    getState() {
148        return state;
149    }
150
151    public CharSequence
152    getMessage() {
153        return message;
154    }
155
156    // inherited javadoc suffices
157    public void
158    cancel() {
159        // Complete or failed cannot be cancelled
160        if (state == State.COMPLETE || state == State.FAILED) {
161            return;
162        }
163
164        state = State.CANCELLED;
165        phone.onMMIDone (this);
166    }
167
168    public boolean isCancelable() {
169        return false;
170    }
171
172    // Instance Methods
173
174    /**
175     * @return true if the Service Code is PIN/PIN2/PUK/PUK2-related
176     */
177    boolean isPukCommand() {
178        return sc != null && sc.equals(SC_PUK);
179     }
180
181    boolean isRegister() {
182        return action != null && action.equals(ACTION_REGISTER);
183    }
184
185    public boolean isUssdRequest() {
186        Log.w(LOG_TAG, "isUssdRequest is not implemented in CdmaMmiCode");
187        return false;
188    }
189
190    /** Process a MMI PUK code */
191    void
192    processCode () {
193        try {
194            if (isPukCommand()) {
195                // sia = old PUK
196                // sib = new PIN
197                // sic = new PIN
198                String oldPinOrPuk = sia;
199                String newPin = sib;
200                int pinLen = newPin.length();
201                if (isRegister()) {
202                    if (!newPin.equals(sic)) {
203                        // password mismatch; return error
204                        handlePasswordError(com.android.internal.R.string.mismatchPin);
205                    } else if (pinLen < 4 || pinLen > 8 ) {
206                        // invalid length
207                        handlePasswordError(com.android.internal.R.string.invalidPin);
208                    } else {
209                        phone.mCM.supplyIccPuk(oldPinOrPuk, newPin,
210                                obtainMessage(EVENT_SET_COMPLETE, this));
211                    }
212                } else {
213                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
214                }
215            } else {
216                throw new RuntimeException ("Invalid or Unsupported MMI Code");
217            }
218        } catch (RuntimeException exc) {
219            state = State.FAILED;
220            message = context.getText(com.android.internal.R.string.mmiError);
221            phone.onMMIDone(this);
222        }
223    }
224
225    private void handlePasswordError(int res) {
226        state = State.FAILED;
227        StringBuilder sb = new StringBuilder(getScString());
228        sb.append("\n");
229        sb.append(context.getText(res));
230        message = sb;
231        phone.onMMIDone(this);
232    }
233
234    public void
235    handleMessage (Message msg) {
236        AsyncResult ar;
237
238        if (msg.what == EVENT_SET_COMPLETE) {
239            ar = (AsyncResult) (msg.obj);
240            onSetComplete(ar);
241        } else {
242            Log.e(LOG_TAG, "Unexpected reply");
243        }
244    }
245    // Private instance methods
246
247    private CharSequence getScString() {
248        if (sc != null) {
249            if (isPukCommand()) {
250                return context.getText(com.android.internal.R.string.PinMmi);
251            }
252        }
253
254        return "";
255    }
256
257    private void
258    onSetComplete(AsyncResult ar){
259        StringBuilder sb = new StringBuilder(getScString());
260        sb.append("\n");
261
262        if (ar.exception != null) {
263            state = State.FAILED;
264            if (ar.exception instanceof CommandException) {
265                CommandException.Error err = ((CommandException)(ar.exception)).getCommandError();
266                if (err == CommandException.Error.PASSWORD_INCORRECT) {
267                    if (isPukCommand()) {
268                        sb.append(context.getText(
269                                com.android.internal.R.string.badPuk));
270                    } else {
271                        sb.append(context.getText(
272                                com.android.internal.R.string.passwordIncorrect));
273                    }
274                } else {
275                    sb.append(context.getText(
276                            com.android.internal.R.string.mmiError));
277                }
278            } else {
279                sb.append(context.getText(
280                        com.android.internal.R.string.mmiError));
281            }
282        } else if (isRegister()) {
283            state = State.COMPLETE;
284            sb.append(context.getText(
285                    com.android.internal.R.string.serviceRegistered));
286        } else {
287            state = State.FAILED;
288            sb.append(context.getText(
289                    com.android.internal.R.string.mmiError));
290        }
291
292        message = sb;
293        phone.onMMIDone(this);
294    }
295
296}
297