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.Configuration;
27 import android.content.res.Resources;
28 import android.app.AlertDialog;
29 import android.app.AlertDialog.Builder;
30 import android.app.Dialog;
31 import android.app.ProgressDialog;
32 import android.graphics.Color;
33 import android.os.RemoteException;
34 import android.os.ServiceManager;
35 import android.telephony.SubscriptionInfo;
36 import android.telephony.SubscriptionManager;
37 import android.telephony.TelephonyManager;
38 import android.util.AttributeSet;
39 import android.util.Log;
40 import android.view.WindowManager;
41 import android.widget.ImageView;
42 
43 /**
44  * Displays a PIN pad for unlocking.
45  */
46 public 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 
KeyguardSimPinView(Context context)66     public KeyguardSimPinView(Context context) {
67         this(context, null);
68     }
69 
KeyguardSimPinView(Context context, AttributeSet attrs)70     public KeyguardSimPinView(Context context, AttributeSet attrs) {
71         super(context, attrs);
72     }
73 
74     @Override
resetState()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
onConfigurationChanged(Configuration newConfig)101     protected void onConfigurationChanged(Configuration newConfig) {
102         super.onConfigurationChanged(newConfig);
103         resetState();
104     }
105 
106     @Override
getPromtReasonStringRes(int reason)107     protected int getPromtReasonStringRes(int reason) {
108         // No message on SIM Pin
109         return 0;
110     }
111 
getPinPasswordErrorMessage(int attemptsRemaining)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
shouldLockout(long deadline)130     protected boolean shouldLockout(long deadline) {
131         // SIM PIN doesn't have a timed lockout
132         return false;
133     }
134 
135     @Override
getPasswordTextViewId()136     protected int getPasswordTextViewId() {
137         return R.id.simPinEntry;
138     }
139 
140     @Override
onFinishInflate()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
onAttachedToWindow()152     protected void onAttachedToWindow() {
153         super.onAttachedToWindow();
154         KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
155     }
156 
157     @Override
onDetachedFromWindow()158     protected void onDetachedFromWindow() {
159         super.onDetachedFromWindow();
160         KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitorCallback);
161     }
162 
163     @Override
showUsabilityHint()164     public void showUsabilityHint() {
165     }
166 
167     @Override
onPause()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 
CheckSimPin(String pin, int subId)184         protected CheckSimPin(String pin, int subId) {
185             mPin = pin;
186             mSubId = subId;
187         }
188 
onSimCheckResponse(final int result, final int attemptsRemaining)189         abstract void onSimCheckResponse(final int result, final int attemptsRemaining);
190 
191         @Override
run()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 
getSimUnlockProgressDialog()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 
getSimRemainingAttemptsDialog(int remaining)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
verifyPasswordAndUnlock()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
startAppearAnimation()310     public void startAppearAnimation() {
311         // noop.
312     }
313 
314     @Override
startDisappearAnimation(Runnable finishRunnable)315     public boolean startDisappearAnimation(Runnable finishRunnable) {
316         return false;
317     }
318 }
319 
320