1/*
2 * Copyright (C) 2012 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 */
16package com.android.internal.policy.impl.keyguard;
17
18import android.app.Activity;
19import android.app.Dialog;
20import android.app.ProgressDialog;
21import android.content.Context;
22import android.os.RemoteException;
23import android.os.ServiceManager;
24import android.text.Editable;
25import android.text.InputType;
26import android.text.TextWatcher;
27import android.text.method.DigitsKeyListener;
28import android.util.AttributeSet;
29import android.view.View;
30import android.view.WindowManager;
31import android.widget.TextView.OnEditorActionListener;
32
33import com.android.internal.telephony.ITelephony;
34
35import com.android.internal.R;
36
37/**
38 * Displays a PIN pad for entering a PUK (Pin Unlock Kode) provided by a carrier.
39 */
40public class KeyguardSimPukView extends KeyguardAbsKeyInputView
41        implements KeyguardSecurityView, OnEditorActionListener, TextWatcher {
42
43    private ProgressDialog mSimUnlockProgressDialog = null;
44    private volatile boolean mCheckInProgress;
45    private String mPukText;
46    private String mPinText;
47    private StateMachine mStateMachine = new StateMachine();
48
49    private class StateMachine {
50        final int ENTER_PUK = 0;
51        final int ENTER_PIN = 1;
52        final int CONFIRM_PIN = 2;
53        final int DONE = 3;
54        private int state = ENTER_PUK;
55
56        public void next() {
57            int msg = 0;
58            if (state == ENTER_PUK) {
59                if (checkPuk()) {
60                    state = ENTER_PIN;
61                    msg = R.string.kg_puk_enter_pin_hint;
62                } else {
63                    msg = R.string.kg_invalid_sim_puk_hint;
64                }
65            } else if (state == ENTER_PIN) {
66                if (checkPin()) {
67                    state = CONFIRM_PIN;
68                    msg = R.string.kg_enter_confirm_pin_hint;
69                } else {
70                    msg = R.string.kg_invalid_sim_pin_hint;
71                }
72            } else if (state == CONFIRM_PIN) {
73                if (confirmPin()) {
74                    state = DONE;
75                    msg =
76                        com.android.internal.R.string.lockscreen_sim_unlock_progress_dialog_message;
77                    updateSim();
78                } else {
79                    state = ENTER_PIN; // try again?
80                    msg = R.string.kg_invalid_confirm_pin_hint;
81                }
82            }
83            mPasswordEntry.setText(null);
84            if (msg != 0) {
85                mSecurityMessageDisplay.setMessage(msg, true);
86            }
87        }
88
89        void reset() {
90            mPinText="";
91            mPukText="";
92            state = ENTER_PUK;
93            mSecurityMessageDisplay.setMessage(R.string.kg_puk_enter_puk_hint, true);
94            mPasswordEntry.requestFocus();
95        }
96    }
97
98    public KeyguardSimPukView(Context context) {
99        this(context, null);
100    }
101
102    public KeyguardSimPukView(Context context, AttributeSet attrs) {
103        super(context, attrs);
104    }
105
106    public void resetState() {
107        mStateMachine.reset();
108        mPasswordEntry.setEnabled(true);
109    }
110
111    @Override
112    protected int getPasswordTextViewId() {
113        return R.id.pinEntry;
114    }
115
116    @Override
117    protected void onFinishInflate() {
118        super.onFinishInflate();
119
120        final View ok = findViewById(R.id.key_enter);
121        if (ok != null) {
122            ok.setOnClickListener(new View.OnClickListener() {
123                @Override
124                public void onClick(View v) {
125                    doHapticKeyClick();
126                    verifyPasswordAndUnlock();
127                }
128            });
129        }
130
131        // The delete button is of the PIN keyboard itself in some (e.g. tablet) layouts,
132        // not a separate view
133        View pinDelete = findViewById(R.id.delete_button);
134        if (pinDelete != null) {
135            pinDelete.setVisibility(View.VISIBLE);
136            pinDelete.setOnClickListener(new OnClickListener() {
137                public void onClick(View v) {
138                    CharSequence str = mPasswordEntry.getText();
139                    if (str.length() > 0) {
140                        mPasswordEntry.setText(str.subSequence(0, str.length()-1));
141                    }
142                    doHapticKeyClick();
143                }
144            });
145            pinDelete.setOnLongClickListener(new View.OnLongClickListener() {
146                public boolean onLongClick(View v) {
147                    mPasswordEntry.setText("");
148                    doHapticKeyClick();
149                    return true;
150                }
151            });
152        }
153
154        mPasswordEntry.setKeyListener(DigitsKeyListener.getInstance());
155        mPasswordEntry.setInputType(InputType.TYPE_CLASS_NUMBER
156                | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
157
158        mPasswordEntry.requestFocus();
159
160        mSecurityMessageDisplay.setTimeout(0); // don't show ownerinfo/charging status by default
161    }
162
163    @Override
164    public void showUsabilityHint() {
165    }
166
167    @Override
168    public void onPause() {
169        // dismiss the dialog.
170        if (mSimUnlockProgressDialog != null) {
171            mSimUnlockProgressDialog.dismiss();
172            mSimUnlockProgressDialog = null;
173        }
174    }
175
176    /**
177     * Since the IPC can block, we want to run the request in a separate thread
178     * with a callback.
179     */
180    private abstract class CheckSimPuk extends Thread {
181
182        private final String mPin, mPuk;
183
184        protected CheckSimPuk(String puk, String pin) {
185            mPuk = puk;
186            mPin = pin;
187        }
188
189        abstract void onSimLockChangedResponse(boolean success);
190
191        @Override
192        public void run() {
193            try {
194                final boolean result = ITelephony.Stub.asInterface(ServiceManager
195                        .checkService("phone")).supplyPuk(mPuk, mPin);
196
197                post(new Runnable() {
198                    public void run() {
199                        onSimLockChangedResponse(result);
200                    }
201                });
202            } catch (RemoteException e) {
203                post(new Runnable() {
204                    public void run() {
205                        onSimLockChangedResponse(false);
206                    }
207                });
208            }
209        }
210    }
211
212    private Dialog getSimUnlockProgressDialog() {
213        if (mSimUnlockProgressDialog == null) {
214            mSimUnlockProgressDialog = new ProgressDialog(mContext);
215            mSimUnlockProgressDialog.setMessage(
216                    mContext.getString(R.string.kg_sim_unlock_progress_dialog_message));
217            mSimUnlockProgressDialog.setIndeterminate(true);
218            mSimUnlockProgressDialog.setCancelable(false);
219            if (!(mContext instanceof Activity)) {
220                mSimUnlockProgressDialog.getWindow().setType(
221                        WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
222            }
223        }
224        return mSimUnlockProgressDialog;
225    }
226
227    private boolean checkPuk() {
228        // make sure the puk is at least 8 digits long.
229        if (mPasswordEntry.getText().length() >= 8) {
230            mPukText = mPasswordEntry.getText().toString();
231            return true;
232        }
233        return false;
234    }
235
236    private boolean checkPin() {
237        // make sure the PIN is between 4 and 8 digits
238        int length = mPasswordEntry.getText().length();
239        if (length >= 4 && length <= 8) {
240            mPinText = mPasswordEntry.getText().toString();
241            return true;
242        }
243        return false;
244    }
245
246    public boolean confirmPin() {
247        return mPinText.equals(mPasswordEntry.getText().toString());
248    }
249
250    private void updateSim() {
251        getSimUnlockProgressDialog().show();
252
253        if (!mCheckInProgress) {
254            mCheckInProgress = true;
255            new CheckSimPuk(mPukText, mPinText) {
256                void onSimLockChangedResponse(final boolean success) {
257                    post(new Runnable() {
258                        public void run() {
259                            if (mSimUnlockProgressDialog != null) {
260                                mSimUnlockProgressDialog.hide();
261                            }
262                            if (success) {
263                                mCallback.dismiss(true);
264                            } else {
265                                mStateMachine.reset();
266                                mSecurityMessageDisplay.setMessage(R.string.kg_invalid_puk, true);
267                            }
268                            mCheckInProgress = false;
269                        }
270                    });
271                }
272            }.start();
273        }
274    }
275
276    @Override
277    protected void verifyPasswordAndUnlock() {
278        mStateMachine.next();
279    }
280}
281
282
283