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.phone;
18
19import android.app.Activity;
20import android.app.AlertDialog;
21import android.content.Intent;
22import android.content.res.Resources;
23import android.os.AsyncResult;
24import android.os.Bundle;
25import android.os.Handler;
26import android.os.Message;
27import android.text.method.DigitsKeyListener;
28import android.util.Log;
29import android.view.View;
30import android.widget.Button;
31import android.widget.EditText;
32import android.widget.LinearLayout;
33import android.widget.ScrollView;
34import android.widget.TextView;
35import android.widget.Toast;
36
37import com.android.internal.telephony.CommandException;
38import com.android.internal.telephony.IccCard;
39import com.android.internal.telephony.Phone;
40
41/**
42 * "Change ICC PIN" UI for the Phone app.
43 */
44public class ChangeIccPinScreen extends Activity {
45    private static final String LOG_TAG = PhoneGlobals.LOG_TAG;
46    private static final boolean DBG = false;
47
48    private static final int EVENT_PIN_CHANGED = 100;
49
50    private enum EntryState {
51        ES_PIN,
52        ES_PUK
53    }
54
55    private EntryState mState;
56
57    private static final int NO_ERROR = 0;
58    private static final int PIN_MISMATCH = 1;
59    private static final int PIN_INVALID_LENGTH = 2;
60
61    private static final int MIN_PIN_LENGTH = 4;
62    private static final int MAX_PIN_LENGTH = 8;
63
64    private Phone mPhone;
65    private boolean mChangePin2;
66    private TextView mBadPinError;
67    private TextView mMismatchError;
68    private EditText mOldPin;
69    private EditText mNewPin1;
70    private EditText mNewPin2;
71    private EditText mPUKCode;
72    private Button mButton;
73    private Button mPUKSubmit;
74    private ScrollView mScrollView;
75
76    private LinearLayout mIccPUKPanel;
77
78    private Handler mHandler = new Handler() {
79        public void handleMessage(Message msg) {
80            switch (msg.what) {
81                case EVENT_PIN_CHANGED:
82                    AsyncResult ar = (AsyncResult) msg.obj;
83                    handleResult(ar);
84                    break;
85            }
86
87            return;
88        }
89    };
90
91    public void onCreate(Bundle icicle) {
92        super.onCreate(icicle);
93
94        mPhone = PhoneGlobals.getPhone();
95
96        resolveIntent();
97
98        setContentView(R.layout.change_sim_pin_screen);
99
100        mOldPin = (EditText) findViewById(R.id.old_pin);
101        mOldPin.setKeyListener(DigitsKeyListener.getInstance());
102        mOldPin.setMovementMethod(null);
103        mOldPin.setOnClickListener(mClicked);
104
105        mNewPin1 = (EditText) findViewById(R.id.new_pin1);
106        mNewPin1.setKeyListener(DigitsKeyListener.getInstance());
107        mNewPin1.setMovementMethod(null);
108        mNewPin1.setOnClickListener(mClicked);
109
110        mNewPin2 = (EditText) findViewById(R.id.new_pin2);
111        mNewPin2.setKeyListener(DigitsKeyListener.getInstance());
112        mNewPin2.setMovementMethod(null);
113        mNewPin2.setOnClickListener(mClicked);
114
115        mBadPinError = (TextView) findViewById(R.id.bad_pin);
116        mMismatchError = (TextView) findViewById(R.id.mismatch);
117
118        mButton = (Button) findViewById(R.id.button);
119        mButton.setOnClickListener(mClicked);
120
121        mScrollView = (ScrollView) findViewById(R.id.scroll);
122
123        mPUKCode = (EditText) findViewById(R.id.puk_code);
124        mPUKCode.setKeyListener(DigitsKeyListener.getInstance());
125        mPUKCode.setMovementMethod(null);
126        mPUKCode.setOnClickListener(mClicked);
127
128        mPUKSubmit = (Button) findViewById(R.id.puk_submit);
129        mPUKSubmit.setOnClickListener(mClicked);
130
131        mIccPUKPanel = (LinearLayout) findViewById(R.id.puk_panel);
132
133        int id = mChangePin2 ? R.string.change_pin2 : R.string.change_pin;
134        setTitle(getResources().getText(id));
135
136        mState = EntryState.ES_PIN;
137    }
138
139    private void resolveIntent() {
140        Intent intent = getIntent();
141        mChangePin2 = intent.getBooleanExtra("pin2", mChangePin2);
142    }
143
144    private void reset() {
145        mScrollView.scrollTo(0, 0);
146        mBadPinError.setVisibility(View.GONE);
147        mMismatchError.setVisibility(View.GONE);
148    }
149
150    private int validateNewPin(String p1, String p2) {
151        if (p1 == null) {
152            return PIN_INVALID_LENGTH;
153        }
154
155        if (!p1.equals(p2)) {
156            return PIN_MISMATCH;
157        }
158
159        int len1 = p1.length();
160
161        if (len1 < MIN_PIN_LENGTH || len1 > MAX_PIN_LENGTH) {
162            return PIN_INVALID_LENGTH;
163        }
164
165        return NO_ERROR;
166    }
167
168    private View.OnClickListener mClicked = new View.OnClickListener() {
169        public void onClick(View v) {
170            if (v == mOldPin) {
171                mNewPin1.requestFocus();
172            } else if (v == mNewPin1) {
173                mNewPin2.requestFocus();
174            } else if (v == mNewPin2) {
175                mButton.requestFocus();
176            } else if (v == mButton) {
177                IccCard iccCardInterface = mPhone.getIccCard();
178                if (iccCardInterface != null) {
179                    String oldPin = mOldPin.getText().toString();
180                    String newPin1 = mNewPin1.getText().toString();
181                    String newPin2 = mNewPin2.getText().toString();
182
183                    int error = validateNewPin(newPin1, newPin2);
184
185                    switch (error) {
186                        case PIN_INVALID_LENGTH:
187                        case PIN_MISMATCH:
188                            mNewPin1.getText().clear();
189                            mNewPin2.getText().clear();
190                            mMismatchError.setVisibility(View.VISIBLE);
191
192                            Resources r = getResources();
193                            CharSequence text;
194
195                            if (error == PIN_MISMATCH) {
196                                text = r.getString(R.string.mismatchPin);
197                            } else {
198                                text = r.getString(R.string.invalidPin);
199                            }
200
201                            mMismatchError.setText(text);
202                            break;
203
204                        default:
205                            Message callBack = Message.obtain(mHandler,
206                                    EVENT_PIN_CHANGED);
207
208                            if (DBG) log("change pin attempt: old=" + oldPin +
209                                    ", newPin=" + newPin1);
210
211                            reset();
212
213                            if (mChangePin2) {
214                                iccCardInterface.changeIccFdnPassword(oldPin,
215                                        newPin1, callBack);
216                            } else {
217                                iccCardInterface.changeIccLockPassword(oldPin,
218                                        newPin1, callBack);
219                            }
220
221                            // TODO: show progress panel
222                    }
223                }
224            } else if (v == mPUKCode) {
225                mPUKSubmit.requestFocus();
226            } else if (v == mPUKSubmit) {
227                mPhone.getIccCard().supplyPuk2(mPUKCode.getText().toString(),
228                        mNewPin1.getText().toString(),
229                        Message.obtain(mHandler, EVENT_PIN_CHANGED));
230            }
231        }
232    };
233
234    private void handleResult(AsyncResult ar) {
235        if (ar.exception == null) {
236            if (DBG) log("handleResult: success!");
237
238            if (mState == EntryState.ES_PUK) {
239                mScrollView.setVisibility(View.VISIBLE);
240                mIccPUKPanel.setVisibility(View.GONE);
241            }
242            // TODO: show success feedback
243            showConfirmation();
244
245            mHandler.postDelayed(new Runnable() {
246                public void run() {
247                    finish();
248                }
249            }, 3000);
250
251        } else if (ar.exception instanceof CommandException
252           /*  && ((CommandException)ar.exception).getCommandError() ==
253           CommandException.Error.PASSWORD_INCORRECT */ ) {
254            if (mState == EntryState.ES_PIN) {
255                if (DBG) log("handleResult: pin failed!");
256                mOldPin.getText().clear();
257                mBadPinError.setVisibility(View.VISIBLE);
258                CommandException ce = (CommandException) ar.exception;
259                if (ce.getCommandError() == CommandException.Error.SIM_PUK2) {
260                    if (DBG) log("handleResult: puk requested!");
261                    mState = EntryState.ES_PUK;
262                    displayPUKAlert();
263                    mScrollView.setVisibility(View.GONE);
264                    mIccPUKPanel.setVisibility(View.VISIBLE);
265                    mPUKCode.requestFocus();
266                }
267            } else if (mState == EntryState.ES_PUK) {
268                //should really check to see if the error is CommandException.PASSWORD_INCORRECT...
269                if (DBG) log("handleResult: puk2 failed!");
270                displayPUKAlert();
271                mPUKCode.getText().clear();
272                mPUKCode.requestFocus();
273            }
274        }
275    }
276
277    private AlertDialog mPUKAlert;
278    private void displayPUKAlert () {
279        if (mPUKAlert == null) {
280            mPUKAlert = new AlertDialog.Builder(this)
281            .setMessage (R.string.puk_requested)
282            .setCancelable(false)
283            .show();
284        } else {
285            mPUKAlert.show();
286        }
287        //TODO: The 3 second delay here is somewhat arbitrary, reflecting the values
288        //used elsewhere for similar code.  This should get revisited with the framework
289        //crew to see if there is some standard we should adhere to.
290        mHandler.postDelayed(new Runnable() {
291            public void run() {
292                mPUKAlert.dismiss();
293            }
294        }, 3000);
295    }
296
297    private void showConfirmation() {
298        int id = mChangePin2 ? R.string.pin2_changed : R.string.pin_changed;
299        Toast.makeText(this, id, Toast.LENGTH_SHORT).show();
300    }
301
302    private void log(String msg) {
303        String prefix = mChangePin2 ? "[ChgPin2]" : "[ChgPin]";
304        Log.d(LOG_TAG, prefix + msg);
305    }
306}
307