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