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