• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.app.AlertDialog;
20 import android.app.AlertDialog.Builder;
21 import android.app.Dialog;
22 import android.app.ProgressDialog;
23 import android.content.Context;
24 import android.content.res.ColorStateList;
25 import android.content.res.Configuration;
26 import android.content.res.Resources;
27 import android.content.res.TypedArray;
28 import android.graphics.Color;
29 import android.os.RemoteException;
30 import android.os.ServiceManager;
31 import android.telephony.SubscriptionInfo;
32 import android.telephony.SubscriptionManager;
33 import android.telephony.TelephonyManager;
34 import android.util.AttributeSet;
35 import android.util.Log;
36 import android.view.View;
37 import android.view.WindowManager;
38 import android.widget.ImageView;
39 
40 import com.android.internal.telephony.ITelephony;
41 import com.android.internal.telephony.IccCardConstants;
42 import com.android.internal.telephony.IccCardConstants.State;
43 import com.android.internal.telephony.PhoneConstants;
44 
45 /**
46  * Displays a PIN pad for unlocking.
47  */
48 public class KeyguardSimPinView extends KeyguardPinBasedInputView {
49     private static final String LOG_TAG = "KeyguardSimPinView";
50     private static final boolean DEBUG = KeyguardConstants.DEBUG_SIM_STATES;
51     public static final String TAG = "KeyguardSimPinView";
52 
53     private ProgressDialog mSimUnlockProgressDialog = null;
54     private CheckSimPin mCheckSimPinThread;
55 
56     // Below flag is set to true during power-up or when a new SIM card inserted on device.
57     // When this is true and when SIM card is PIN locked state, on PIN lock screen, message would
58     // be displayed to inform user about the number of remaining PIN attempts left.
59     private boolean mShowDefaultMessage = true;
60     private int mRemainingAttempts = -1;
61     private AlertDialog mRemainingAttemptsDialog;
62     private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
63     private ImageView mSimImageView;
64 
65     KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() {
66         @Override
67         public void onSimStateChanged(int subId, int slotId, State simState) {
68             if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")");
69             switch(simState) {
70                 case READY: {
71                     mRemainingAttempts = -1;
72                     resetState();
73                     break;
74                 }
75                 default:
76                     resetState();
77             }
78         }
79     };
80 
KeyguardSimPinView(Context context)81     public KeyguardSimPinView(Context context) {
82         this(context, null);
83     }
84 
KeyguardSimPinView(Context context, AttributeSet attrs)85     public KeyguardSimPinView(Context context, AttributeSet attrs) {
86         super(context, attrs);
87     }
88 
89     @Override
resetState()90     public void resetState() {
91         super.resetState();
92         if (DEBUG) Log.v(TAG, "Resetting state");
93         handleSubInfoChangeIfNeeded();
94         if (mShowDefaultMessage) {
95             showDefaultMessage();
96         }
97         boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
98 
99         KeyguardEsimArea esimButton = findViewById(R.id.keyguard_esim_area);
100         esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE);
101     }
102 
setLockedSimMessage()103     private void setLockedSimMessage() {
104         boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
105         int count = TelephonyManager.getDefault().getSimCount();
106         Resources rez = getResources();
107         String msg;
108         TypedArray array = mContext.obtainStyledAttributes(new int[] { R.attr.wallpaperTextColor });
109         int color = array.getColor(0, Color.WHITE);
110         array.recycle();
111         if (count < 2) {
112             msg = rez.getString(R.string.kg_sim_pin_instructions);
113         } else {
114             SubscriptionInfo info = KeyguardUpdateMonitor.getInstance(mContext).
115                     getSubscriptionInfoForSubId(mSubId);
116             CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash
117             msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName);
118             if (info != null) {
119                 color = info.getIconTint();
120             }
121         }
122         if (isEsimLocked) {
123             msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg);
124         }
125 
126         if (mSecurityMessageDisplay != null) {
127             mSecurityMessageDisplay.setMessage(msg);
128         }
129         mSimImageView.setImageTintList(ColorStateList.valueOf(color));
130     }
131 
showDefaultMessage()132     private void showDefaultMessage() {
133         setLockedSimMessage();
134         if (mRemainingAttempts >= 0) {
135             return;
136         }
137 
138         // Sending empty PIN here to query the number of remaining PIN attempts
139         new CheckSimPin("", mSubId) {
140             void onSimCheckResponse(final int result, final int attemptsRemaining) {
141                 Log.d(LOG_TAG, "onSimCheckResponse " + " dummy One result" + result +
142                         " attemptsRemaining=" + attemptsRemaining);
143                 if (attemptsRemaining >= 0) {
144                     mRemainingAttempts = attemptsRemaining;
145                     setLockedSimMessage();
146                 }
147             }
148         }.start();
149     }
150 
handleSubInfoChangeIfNeeded()151     private void handleSubInfoChangeIfNeeded() {
152         KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
153         int subId = monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED);
154         if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) {
155             mSubId = subId;
156             mShowDefaultMessage = true;
157             mRemainingAttempts = -1;
158         }
159     }
160 
161     @Override
onConfigurationChanged(Configuration newConfig)162     protected void onConfigurationChanged(Configuration newConfig) {
163         super.onConfigurationChanged(newConfig);
164         resetState();
165     }
166 
167     @Override
getPromptReasonStringRes(int reason)168     protected int getPromptReasonStringRes(int reason) {
169         // No message on SIM Pin
170         return 0;
171     }
172 
getPinPasswordErrorMessage(int attemptsRemaining, boolean isDefault)173     private String getPinPasswordErrorMessage(int attemptsRemaining, boolean isDefault) {
174         String displayMessage;
175         int msgId;
176         if (attemptsRemaining == 0) {
177             displayMessage = getContext().getString(R.string.kg_password_wrong_pin_code_pukked);
178         } else if (attemptsRemaining > 0) {
179             msgId = isDefault ? R.plurals.kg_password_default_pin_message :
180                      R.plurals.kg_password_wrong_pin_code;
181             displayMessage = getContext().getResources()
182                     .getQuantityString(msgId, attemptsRemaining, attemptsRemaining);
183         } else {
184             msgId = isDefault ? R.string.kg_sim_pin_instructions : R.string.kg_password_pin_failed;
185             displayMessage = getContext().getString(msgId);
186         }
187         if (KeyguardEsimArea.isEsimLocked(mContext, mSubId)) {
188             displayMessage = getResources()
189                     .getString(R.string.kg_sim_lock_esim_instructions, displayMessage);
190         }
191         if (DEBUG) Log.d(LOG_TAG, "getPinPasswordErrorMessage:"
192                 + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage);
193         return displayMessage;
194     }
195 
196     @Override
shouldLockout(long deadline)197     protected boolean shouldLockout(long deadline) {
198         // SIM PIN doesn't have a timed lockout
199         return false;
200     }
201 
202     @Override
getPasswordTextViewId()203     protected int getPasswordTextViewId() {
204         return R.id.simPinEntry;
205     }
206 
207     @Override
onFinishInflate()208     protected void onFinishInflate() {
209         super.onFinishInflate();
210 
211         if (mEcaView instanceof EmergencyCarrierArea) {
212             ((EmergencyCarrierArea) mEcaView).setCarrierTextVisible(true);
213         }
214         mSimImageView = findViewById(R.id.keyguard_sim);
215     }
216 
217     @Override
showUsabilityHint()218     public void showUsabilityHint() {
219 
220     }
221 
222     @Override
onResume(int reason)223     public void onResume(int reason) {
224         super.onResume(reason);
225         KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
226         resetState();
227     }
228 
229     @Override
onPause()230     public void onPause() {
231         // dismiss the dialog.
232         if (mSimUnlockProgressDialog != null) {
233             mSimUnlockProgressDialog.dismiss();
234             mSimUnlockProgressDialog = null;
235         }
236         KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitorCallback);
237     }
238 
239     /**
240      * Since the IPC can block, we want to run the request in a separate thread
241      * with a callback.
242      */
243     private abstract class CheckSimPin extends Thread {
244         private final String mPin;
245         private int mSubId;
246 
CheckSimPin(String pin, int subId)247         protected CheckSimPin(String pin, int subId) {
248             mPin = pin;
249             mSubId = subId;
250         }
251 
onSimCheckResponse(final int result, final int attemptsRemaining)252         abstract void onSimCheckResponse(final int result, final int attemptsRemaining);
253 
254         @Override
run()255         public void run() {
256             try {
257                 if (DEBUG) {
258                     Log.v(TAG, "call supplyPinReportResultForSubscriber(subid=" + mSubId + ")");
259                 }
260                 final int[] result = ITelephony.Stub.asInterface(ServiceManager
261                         .checkService("phone")).supplyPinReportResultForSubscriber(mSubId, mPin);
262                 if (DEBUG) {
263                     Log.v(TAG, "supplyPinReportResult returned: " + result[0] + " " + result[1]);
264                 }
265                 post(new Runnable() {
266                     @Override
267                     public void run() {
268                         onSimCheckResponse(result[0], result[1]);
269                     }
270                 });
271             } catch (RemoteException e) {
272                 Log.e(TAG, "RemoteException for supplyPinReportResult:", e);
273                 post(new Runnable() {
274                     @Override
275                     public void run() {
276                         onSimCheckResponse(PhoneConstants.PIN_GENERAL_FAILURE, -1);
277                     }
278                 });
279             }
280         }
281     }
282 
getSimUnlockProgressDialog()283     private Dialog getSimUnlockProgressDialog() {
284         if (mSimUnlockProgressDialog == null) {
285             mSimUnlockProgressDialog = new ProgressDialog(mContext);
286             mSimUnlockProgressDialog.setMessage(
287                     mContext.getString(R.string.kg_sim_unlock_progress_dialog_message));
288             mSimUnlockProgressDialog.setIndeterminate(true);
289             mSimUnlockProgressDialog.setCancelable(false);
290             mSimUnlockProgressDialog.getWindow().setType(
291                     WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
292         }
293         return mSimUnlockProgressDialog;
294     }
295 
getSimRemainingAttemptsDialog(int remaining)296     private Dialog getSimRemainingAttemptsDialog(int remaining) {
297         String msg = getPinPasswordErrorMessage(remaining, false);
298         if (mRemainingAttemptsDialog == null) {
299             Builder builder = new AlertDialog.Builder(mContext);
300             builder.setMessage(msg);
301             builder.setCancelable(false);
302             builder.setNeutralButton(R.string.ok, null);
303             mRemainingAttemptsDialog = builder.create();
304             mRemainingAttemptsDialog.getWindow().setType(
305                     WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
306         } else {
307             mRemainingAttemptsDialog.setMessage(msg);
308         }
309         return mRemainingAttemptsDialog;
310     }
311 
312     @Override
verifyPasswordAndUnlock()313     protected void verifyPasswordAndUnlock() {
314         String entry = mPasswordEntry.getText();
315 
316         if (entry.length() < 4) {
317             // otherwise, display a message to the user, and don't submit.
318             mSecurityMessageDisplay.setMessage(R.string.kg_invalid_sim_pin_hint);
319             resetPasswordText(true /* animate */, true /* announce */);
320             mCallback.userActivity();
321             return;
322         }
323 
324         getSimUnlockProgressDialog().show();
325 
326         if (mCheckSimPinThread == null) {
327             mCheckSimPinThread = new CheckSimPin(mPasswordEntry.getText(), mSubId) {
328                 @Override
329                 void onSimCheckResponse(final int result, final int attemptsRemaining) {
330                     post(new Runnable() {
331                         @Override
332                         public void run() {
333                             mRemainingAttempts = attemptsRemaining;
334                             if (mSimUnlockProgressDialog != null) {
335                                 mSimUnlockProgressDialog.hide();
336                             }
337                             resetPasswordText(true /* animate */,
338                                     result != PhoneConstants.PIN_RESULT_SUCCESS /* announce */);
339                             if (result == PhoneConstants.PIN_RESULT_SUCCESS) {
340                                 KeyguardUpdateMonitor.getInstance(getContext())
341                                         .reportSimUnlocked(mSubId);
342                                 mRemainingAttempts = -1;
343                                 mShowDefaultMessage = true;
344                                 if (mCallback != null) {
345                                     mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
346                                 }
347                             } else {
348                                 mShowDefaultMessage = false;
349                                 if (result == PhoneConstants.PIN_PASSWORD_INCORRECT) {
350                                     if (attemptsRemaining <= 2) {
351                                         // this is getting critical - show dialog
352                                         getSimRemainingAttemptsDialog(attemptsRemaining).show();
353                                     } else {
354                                         // show message
355                                         mSecurityMessageDisplay.setMessage(
356                                                 getPinPasswordErrorMessage(attemptsRemaining, false));
357                                     }
358                                 } else {
359                                     // "PIN operation failed!" - no idea what this was and no way to
360                                     // find out. :/
361                                     mSecurityMessageDisplay.setMessage(getContext().getString(
362                                             R.string.kg_password_pin_failed));
363                                 }
364                                 if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock "
365                                         + " CheckSimPin.onSimCheckResponse: " + result
366                                         + " attemptsRemaining=" + attemptsRemaining);
367                             }
368                             mCallback.userActivity();
369                             mCheckSimPinThread = null;
370                         }
371                     });
372                 }
373             };
374             mCheckSimPinThread.start();
375         }
376     }
377 
378     @Override
startAppearAnimation()379     public void startAppearAnimation() {
380         // noop.
381     }
382 
383     @Override
startDisappearAnimation(Runnable finishRunnable)384     public boolean startDisappearAnimation(Runnable finishRunnable) {
385         return false;
386     }
387 
388     @Override
getTitle()389     public CharSequence getTitle() {
390         return getContext().getString(
391                 com.android.internal.R.string.keyguard_accessibility_sim_pin_unlock);
392     }
393 }
394 
395