1/*
2 * Copyright (C) 2008 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.policy.impl;
18
19import android.app.Dialog;
20import android.app.ProgressDialog;
21import android.content.Context;
22import android.content.res.Configuration;
23import android.os.RemoteException;
24import android.os.ServiceManager;
25
26import com.android.internal.telephony.ITelephony;
27import com.android.internal.widget.LockPatternUtils;
28
29import android.text.Editable;
30import android.util.Log;
31import android.view.KeyEvent;
32import android.view.LayoutInflater;
33import android.view.View;
34import android.view.WindowManager;
35import android.widget.Button;
36import android.widget.LinearLayout;
37import android.widget.TextView;
38import com.android.internal.R;
39
40/**
41 * Displays a dialer like interface to unlock the SIM PUK.
42 */
43public class SimPukUnlockScreen extends LinearLayout implements KeyguardScreen,
44        View.OnClickListener, View.OnFocusChangeListener {
45
46    private static final int DIGIT_PRESS_WAKE_MILLIS = 5000;
47
48    private final KeyguardUpdateMonitor mUpdateMonitor;
49    private final KeyguardScreenCallback mCallback;
50    private KeyguardStatusViewManager mKeyguardStatusViewManager;
51
52    private TextView mHeaderText;
53    private TextView mPukText;
54    private TextView mPinText;
55    private TextView mFocusedEntry;
56
57    private View mOkButton;
58    private View mDelPukButton;
59    private View mDelPinButton;
60
61    private ProgressDialog mSimUnlockProgressDialog = null;
62
63    private LockPatternUtils mLockPatternUtils;
64
65    private int mCreationOrientation;
66
67    private int mKeyboardHidden;
68
69    private static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
70
71    public SimPukUnlockScreen(Context context, Configuration configuration,
72            KeyguardUpdateMonitor updateMonitor, KeyguardScreenCallback callback,
73            LockPatternUtils lockpatternutils) {
74        super(context);
75        mUpdateMonitor = updateMonitor;
76        mCallback = callback;;
77
78        mCreationOrientation = configuration.orientation;
79        mKeyboardHidden = configuration.hardKeyboardHidden;
80        mLockPatternUtils = lockpatternutils;
81
82        LayoutInflater inflater = LayoutInflater.from(context);
83        if (mKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) {
84            inflater.inflate(
85                    R.layout.keyguard_screen_sim_puk_landscape, this, true);
86        } else {
87            inflater.inflate(
88                    R.layout.keyguard_screen_sim_puk_portrait, this, true);
89            new TouchInput();
90        }
91
92        mHeaderText = (TextView) findViewById(R.id.headerText);
93
94        mPukText = (TextView) findViewById(R.id.pukDisplay);
95        mPinText = (TextView) findViewById(R.id.pinDisplay);
96        mDelPukButton = findViewById(R.id.pukDel);
97        mDelPinButton = findViewById(R.id.pinDel);
98        mOkButton = findViewById(R.id.ok);
99
100        mDelPinButton.setOnClickListener(this);
101        mDelPukButton.setOnClickListener(this);
102        mOkButton.setOnClickListener(this);
103
104        mHeaderText.setText(R.string.keyguard_password_enter_puk_code);
105        // To make marquee work
106        mHeaderText.setSelected(true);
107
108        mKeyguardStatusViewManager = new KeyguardStatusViewManager(this, updateMonitor,
109                lockpatternutils, callback, true);
110
111        mPinText.setFocusableInTouchMode(true);
112        mPinText.setOnFocusChangeListener(this);
113        mPukText.setFocusableInTouchMode(true);
114        mPukText.setOnFocusChangeListener(this);
115    }
116
117    /** {@inheritDoc} */
118    public boolean needsInput() {
119        return false;
120    }
121
122    /** {@inheritDoc} */
123    public void onPause() {
124        mKeyguardStatusViewManager.onPause();
125    }
126
127    /** {@inheritDoc} */
128    public void onResume() {
129        // start fresh
130        mHeaderText.setText(R.string.keyguard_password_enter_puk_code);
131        mKeyguardStatusViewManager.onResume();
132    }
133
134    /** {@inheritDoc} */
135    public void cleanUp() {
136        // dismiss the dialog.
137        if (mSimUnlockProgressDialog != null) {
138            mSimUnlockProgressDialog.dismiss();
139            mSimUnlockProgressDialog = null;
140        }
141        mUpdateMonitor.removeCallback(this);
142    }
143
144
145    /**
146     * Since the IPC can block, we want to run the request in a separate thread
147     * with a callback.
148     */
149    private abstract class CheckSimPuk extends Thread {
150
151        private final String mPin, mPuk;
152
153        protected CheckSimPuk(String puk, String pin) {
154            mPuk = puk;
155            mPin = pin;
156        }
157
158        abstract void onSimLockChangedResponse(boolean success);
159
160        @Override
161        public void run() {
162            try {
163                final boolean result = ITelephony.Stub.asInterface(ServiceManager
164                        .checkService("phone")).supplyPuk(mPuk, mPin);
165
166                post(new Runnable() {
167                    public void run() {
168                        onSimLockChangedResponse(result);
169                    }
170                });
171            } catch (RemoteException e) {
172                post(new Runnable() {
173                    public void run() {
174                        onSimLockChangedResponse(false);
175                    }
176                });
177            }
178        }
179    }
180
181    public void onClick(View v) {
182        if (v == mDelPukButton) {
183            if (mFocusedEntry != mPukText)
184                mPukText.requestFocus();
185            final Editable digits = mPukText.getEditableText();
186            final int len = digits.length();
187            if (len > 0) {
188                digits.delete(len-1, len);
189            }
190        } else if (v == mDelPinButton) {
191            if (mFocusedEntry != mPinText)
192                mPinText.requestFocus();
193            final Editable digits = mPinText.getEditableText();
194            final int len = digits.length();
195            if (len > 0) {
196                digits.delete(len-1, len);
197            }
198        } else if (v == mOkButton) {
199            checkPuk();
200        }
201        mCallback.pokeWakelock(DIGIT_PRESS_WAKE_MILLIS);
202
203    }
204
205    @Override
206    public void onFocusChange(View v, boolean hasFocus) {
207        if (hasFocus)
208            mFocusedEntry = (TextView)v;
209    }
210
211    private Dialog getSimUnlockProgressDialog() {
212        if (mSimUnlockProgressDialog == null) {
213            mSimUnlockProgressDialog = new ProgressDialog(mContext);
214            mSimUnlockProgressDialog.setMessage(
215                    mContext.getString(R.string.lockscreen_sim_unlock_progress_dialog_message));
216            mSimUnlockProgressDialog.setIndeterminate(true);
217            mSimUnlockProgressDialog.setCancelable(false);
218            mSimUnlockProgressDialog.getWindow().setType(
219                    WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
220        }
221        return mSimUnlockProgressDialog;
222    }
223
224    private void checkPuk() {
225        // make sure that the puk is at least 8 digits long.
226        if (mPukText.getText().length() < 8) {
227            // otherwise, display a message to the user, and don't submit.
228            mHeaderText.setText(R.string.invalidPuk);
229            mPukText.setText("");
230            return;
231        }
232
233        if (mPinText.getText().length() < 4
234                || mPinText.getText().length() > 8) {
235            // otherwise, display a message to the user, and don't submit.
236            mHeaderText.setText(R.string.invalidPin);
237            mPinText.setText("");
238            return;
239        }
240
241        getSimUnlockProgressDialog().show();
242
243        new CheckSimPuk(mPukText.getText().toString(),
244                mPinText.getText().toString()) {
245            void onSimLockChangedResponse(final boolean success) {
246                mPinText.post(new Runnable() {
247                    public void run() {
248                        if (mSimUnlockProgressDialog != null) {
249                            mSimUnlockProgressDialog.hide();
250                        }
251                        if (success) {
252                            // before closing the keyguard, report back that
253                            // the sim is unlocked so it knows right away
254                            mUpdateMonitor.reportSimUnlocked();
255                            mCallback.goToUnlockScreen();
256                        } else {
257                            mHeaderText.setText(R.string.badPuk);
258                            mPukText.setText("");
259                            mPinText.setText("");
260                        }
261                    }
262                });
263            }
264        }.start();
265    }
266
267
268    public boolean onKeyDown(int keyCode, KeyEvent event) {
269        if (keyCode == KeyEvent.KEYCODE_BACK) {
270            mCallback.goToLockScreen();
271            return true;
272        }
273        final char match = event.getMatch(DIGITS);
274        if (match != 0) {
275            reportDigit(match - '0');
276            return true;
277        }
278        if (keyCode == KeyEvent.KEYCODE_DEL) {
279            mFocusedEntry.onKeyDown(keyCode, event);
280            final Editable digits = mFocusedEntry.getEditableText();
281            final int len = digits.length();
282            if (len > 0) {
283                digits.delete(len-1, len);
284            }
285            mCallback.pokeWakelock(DIGIT_PRESS_WAKE_MILLIS);
286            return true;
287        }
288
289        if (keyCode == KeyEvent.KEYCODE_ENTER) {
290            checkPuk();
291            return true;
292        }
293
294        return false;
295    }
296
297    private void reportDigit(int digit) {
298        mFocusedEntry.append(Integer.toString(digit));
299    }
300
301    void updateConfiguration() {
302        Configuration newConfig = getResources().getConfiguration();
303        if (newConfig.orientation != mCreationOrientation) {
304            mCallback.recreateMe(newConfig);
305        } else if (newConfig.hardKeyboardHidden != mKeyboardHidden) {
306            mKeyboardHidden = newConfig.hardKeyboardHidden;
307        }
308
309    }
310
311    @Override
312    protected void onAttachedToWindow() {
313        super.onAttachedToWindow();
314        updateConfiguration();
315    }
316
317    /** {@inheritDoc} */
318    @Override
319    protected void onConfigurationChanged(Configuration newConfig) {
320        super.onConfigurationChanged(newConfig);
321        updateConfiguration();
322    }
323
324    /**
325     * Helper class to handle input from touch dialer.  Only relevant when
326     * the keyboard is shut.
327     */
328    private class TouchInput implements View.OnClickListener {
329        private TextView mZero;
330        private TextView mOne;
331        private TextView mTwo;
332        private TextView mThree;
333        private TextView mFour;
334        private TextView mFive;
335        private TextView mSix;
336        private TextView mSeven;
337        private TextView mEight;
338        private TextView mNine;
339        private TextView mCancelButton;
340
341        private TouchInput() {
342            mZero = (TextView) findViewById(R.id.zero);
343            mOne = (TextView) findViewById(R.id.one);
344            mTwo = (TextView) findViewById(R.id.two);
345            mThree = (TextView) findViewById(R.id.three);
346            mFour = (TextView) findViewById(R.id.four);
347            mFive = (TextView) findViewById(R.id.five);
348            mSix = (TextView) findViewById(R.id.six);
349            mSeven = (TextView) findViewById(R.id.seven);
350            mEight = (TextView) findViewById(R.id.eight);
351            mNine = (TextView) findViewById(R.id.nine);
352            mCancelButton = (TextView) findViewById(R.id.cancel);
353
354            mZero.setText("0");
355            mOne.setText("1");
356            mTwo.setText("2");
357            mThree.setText("3");
358            mFour.setText("4");
359            mFive.setText("5");
360            mSix.setText("6");
361            mSeven.setText("7");
362            mEight.setText("8");
363            mNine.setText("9");
364
365            mZero.setOnClickListener(this);
366            mOne.setOnClickListener(this);
367            mTwo.setOnClickListener(this);
368            mThree.setOnClickListener(this);
369            mFour.setOnClickListener(this);
370            mFive.setOnClickListener(this);
371            mSix.setOnClickListener(this);
372            mSeven.setOnClickListener(this);
373            mEight.setOnClickListener(this);
374            mNine.setOnClickListener(this);
375            mCancelButton.setOnClickListener(this);
376        }
377
378
379        public void onClick(View v) {
380            if (v == mCancelButton) {
381                // clear the PIN/PUK entry fields if the user cancels
382                mPinText.setText("");
383                mPukText.setText("");
384                mCallback.goToLockScreen();
385                return;
386            }
387
388            final int digit = checkDigit(v);
389            if (digit >= 0) {
390                mCallback.pokeWakelock(DIGIT_PRESS_WAKE_MILLIS);
391                reportDigit(digit);
392            }
393        }
394
395        private int checkDigit(View v) {
396            int digit = -1;
397            if (v == mZero) {
398                digit = 0;
399            } else if (v == mOne) {
400                digit = 1;
401            } else if (v == mTwo) {
402                digit = 2;
403            } else if (v == mThree) {
404                digit = 3;
405            } else if (v == mFour) {
406                digit = 4;
407            } else if (v == mFive) {
408                digit = 5;
409            } else if (v == mSix) {
410                digit = 6;
411            } else if (v == mSeven) {
412                digit = 7;
413            } else if (v == mEight) {
414                digit = 8;
415            } else if (v == mNine) {
416                digit = 9;
417            }
418            return digit;
419        }
420    }
421
422}
423