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 */
16
17package com.android.keyguard;
18
19import com.android.internal.telephony.ITelephony;
20import com.android.internal.telephony.IccCardConstants;
21import com.android.internal.telephony.IccCardConstants.State;
22import com.android.internal.telephony.PhoneConstants;
23
24import android.content.Context;
25import android.content.res.ColorStateList;
26import android.content.res.Configuration;
27import android.content.res.Resources;
28import android.app.AlertDialog;
29import android.app.AlertDialog.Builder;
30import android.app.Dialog;
31import android.app.ProgressDialog;
32import android.graphics.Color;
33import android.os.RemoteException;
34import android.os.ServiceManager;
35import android.telephony.SubscriptionInfo;
36import android.telephony.SubscriptionManager;
37import android.telephony.TelephonyManager;
38import android.util.AttributeSet;
39import android.util.Log;
40import android.view.WindowManager;
41import android.widget.ImageView;
42
43/**
44 * Displays a PIN pad for unlocking.
45 */
46public class KeyguardSimPinView extends KeyguardPinBasedInputView {
47    private static final String LOG_TAG = "KeyguardSimPinView";
48    private static final boolean DEBUG = KeyguardConstants.DEBUG_SIM_STATES;
49    public static final String TAG = "KeyguardSimPinView";
50
51    private ProgressDialog mSimUnlockProgressDialog = null;
52    private CheckSimPin mCheckSimPinThread;
53
54    private AlertDialog mRemainingAttemptsDialog;
55    private int mSubId;
56    private ImageView mSimImageView;
57
58    KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() {
59        @Override
60        public void onSimStateChanged(int subId, int slotId, State simState) {
61           if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")");
62           resetState();
63       };
64    };
65
66    public KeyguardSimPinView(Context context) {
67        this(context, null);
68    }
69
70    public KeyguardSimPinView(Context context, AttributeSet attrs) {
71        super(context, attrs);
72    }
73
74    @Override
75    public void resetState() {
76        super.resetState();
77        if (DEBUG) Log.v(TAG, "Resetting state");
78        KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
79        mSubId = monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED);
80        if (SubscriptionManager.isValidSubscriptionId(mSubId)) {
81            int count = TelephonyManager.getDefault().getSimCount();
82            Resources rez = getResources();
83            final String msg;
84            int color = Color.WHITE;
85            if (count < 2) {
86                msg = rez.getString(R.string.kg_sim_pin_instructions);
87            } else {
88                SubscriptionInfo info = monitor.getSubscriptionInfoForSubId(mSubId);
89                CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash
90                msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName);
91                if (info != null) {
92                    color = info.getIconTint();
93                }
94            }
95            mSecurityMessageDisplay.setMessage(msg, true);
96            mSimImageView.setImageTintList(ColorStateList.valueOf(color));
97        }
98    }
99
100    @Override
101    protected void onConfigurationChanged(Configuration newConfig) {
102        super.onConfigurationChanged(newConfig);
103        resetState();
104    }
105
106    @Override
107    protected int getPromtReasonStringRes(int reason) {
108        // No message on SIM Pin
109        return 0;
110    }
111
112    private String getPinPasswordErrorMessage(int attemptsRemaining) {
113        String displayMessage;
114
115        if (attemptsRemaining == 0) {
116            displayMessage = getContext().getString(R.string.kg_password_wrong_pin_code_pukked);
117        } else if (attemptsRemaining > 0) {
118            displayMessage = getContext().getResources()
119                    .getQuantityString(R.plurals.kg_password_wrong_pin_code, attemptsRemaining,
120                            attemptsRemaining);
121        } else {
122            displayMessage = getContext().getString(R.string.kg_password_pin_failed);
123        }
124        if (DEBUG) Log.d(LOG_TAG, "getPinPasswordErrorMessage:"
125                + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage);
126        return displayMessage;
127    }
128
129    @Override
130    protected boolean shouldLockout(long deadline) {
131        // SIM PIN doesn't have a timed lockout
132        return false;
133    }
134
135    @Override
136    protected int getPasswordTextViewId() {
137        return R.id.simPinEntry;
138    }
139
140    @Override
141    protected void onFinishInflate() {
142        super.onFinishInflate();
143
144        mSecurityMessageDisplay.setTimeout(0); // don't show ownerinfo/charging status by default
145        if (mEcaView instanceof EmergencyCarrierArea) {
146            ((EmergencyCarrierArea) mEcaView).setCarrierTextVisible(true);
147        }
148        mSimImageView = (ImageView) findViewById(R.id.keyguard_sim);
149    }
150
151    @Override
152    protected void onAttachedToWindow() {
153        super.onAttachedToWindow();
154        KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
155    }
156
157    @Override
158    protected void onDetachedFromWindow() {
159        super.onDetachedFromWindow();
160        KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitorCallback);
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 CheckSimPin extends Thread {
181        private final String mPin;
182        private int mSubId;
183
184        protected CheckSimPin(String pin, int subId) {
185            mPin = pin;
186            mSubId = subId;
187        }
188
189        abstract void onSimCheckResponse(final int result, final int attemptsRemaining);
190
191        @Override
192        public void run() {
193            try {
194                if (DEBUG) {
195                    Log.v(TAG, "call supplyPinReportResultForSubscriber(subid=" + mSubId + ")");
196                }
197                final int[] result = ITelephony.Stub.asInterface(ServiceManager
198                        .checkService("phone")).supplyPinReportResultForSubscriber(mSubId, mPin);
199                if (DEBUG) {
200                    Log.v(TAG, "supplyPinReportResult returned: " + result[0] + " " + result[1]);
201                }
202                post(new Runnable() {
203                    @Override
204                    public void run() {
205                        onSimCheckResponse(result[0], result[1]);
206                    }
207                });
208            } catch (RemoteException e) {
209                Log.e(TAG, "RemoteException for supplyPinReportResult:", e);
210                post(new Runnable() {
211                    @Override
212                    public void run() {
213                        onSimCheckResponse(PhoneConstants.PIN_GENERAL_FAILURE, -1);
214                    }
215                });
216            }
217        }
218    }
219
220    private Dialog getSimUnlockProgressDialog() {
221        if (mSimUnlockProgressDialog == null) {
222            mSimUnlockProgressDialog = new ProgressDialog(mContext);
223            mSimUnlockProgressDialog.setMessage(
224                    mContext.getString(R.string.kg_sim_unlock_progress_dialog_message));
225            mSimUnlockProgressDialog.setIndeterminate(true);
226            mSimUnlockProgressDialog.setCancelable(false);
227            mSimUnlockProgressDialog.getWindow().setType(
228                    WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
229        }
230        return mSimUnlockProgressDialog;
231    }
232
233    private Dialog getSimRemainingAttemptsDialog(int remaining) {
234        String msg = getPinPasswordErrorMessage(remaining);
235        if (mRemainingAttemptsDialog == null) {
236            Builder builder = new AlertDialog.Builder(mContext);
237            builder.setMessage(msg);
238            builder.setCancelable(false);
239            builder.setNeutralButton(R.string.ok, null);
240            mRemainingAttemptsDialog = builder.create();
241            mRemainingAttemptsDialog.getWindow().setType(
242                    WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
243        } else {
244            mRemainingAttemptsDialog.setMessage(msg);
245        }
246        return mRemainingAttemptsDialog;
247    }
248
249    @Override
250    protected void verifyPasswordAndUnlock() {
251        String entry = mPasswordEntry.getText();
252
253        if (entry.length() < 4) {
254            // otherwise, display a message to the user, and don't submit.
255            mSecurityMessageDisplay.setMessage(R.string.kg_invalid_sim_pin_hint, true);
256            resetPasswordText(true /* animate */, true /* announce */);
257            mCallback.userActivity();
258            return;
259        }
260
261        getSimUnlockProgressDialog().show();
262
263        if (mCheckSimPinThread == null) {
264            mCheckSimPinThread = new CheckSimPin(mPasswordEntry.getText(), mSubId) {
265                @Override
266                void onSimCheckResponse(final int result, final int attemptsRemaining) {
267                    post(new Runnable() {
268                        @Override
269                        public void run() {
270                            if (mSimUnlockProgressDialog != null) {
271                                mSimUnlockProgressDialog.hide();
272                            }
273                            resetPasswordText(true /* animate */,
274                                    result != PhoneConstants.PIN_RESULT_SUCCESS /* announce */);
275                            if (result == PhoneConstants.PIN_RESULT_SUCCESS) {
276                                KeyguardUpdateMonitor.getInstance(getContext())
277                                        .reportSimUnlocked(mSubId);
278                                mCallback.dismiss(true);
279                            } else {
280                                if (result == PhoneConstants.PIN_PASSWORD_INCORRECT) {
281                                    if (attemptsRemaining <= 2) {
282                                        // this is getting critical - show dialog
283                                        getSimRemainingAttemptsDialog(attemptsRemaining).show();
284                                    } else {
285                                        // show message
286                                        mSecurityMessageDisplay.setMessage(
287                                                getPinPasswordErrorMessage(attemptsRemaining), true);
288                                    }
289                                } else {
290                                    // "PIN operation failed!" - no idea what this was and no way to
291                                    // find out. :/
292                                    mSecurityMessageDisplay.setMessage(getContext().getString(
293                                            R.string.kg_password_pin_failed), true);
294                                }
295                                if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock "
296                                        + " CheckSimPin.onSimCheckResponse: " + result
297                                        + " attemptsRemaining=" + attemptsRemaining);
298                            }
299                            mCallback.userActivity();
300                            mCheckSimPinThread = null;
301                        }
302                    });
303                }
304            };
305            mCheckSimPinThread.start();
306        }
307    }
308
309    @Override
310    public void startAppearAnimation() {
311        // noop.
312    }
313
314    @Override
315    public boolean startDisappearAnimation(Runnable finishRunnable) {
316        return false;
317    }
318}
319
320