1 /*
2  * Copyright (C) 2020 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 static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
20 
21 import android.annotation.NonNull;
22 import android.app.AlertDialog;
23 import android.app.AlertDialog.Builder;
24 import android.app.Dialog;
25 import android.app.ProgressDialog;
26 import android.content.res.ColorStateList;
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.Log;
35 import android.view.View;
36 import android.view.WindowManager;
37 import android.widget.ImageView;
38 
39 import com.android.internal.util.LatencyTracker;
40 import com.android.internal.widget.LockPatternUtils;
41 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
42 import com.android.systemui.R;
43 import com.android.systemui.classifier.FalsingCollector;
44 import com.android.systemui.flags.FeatureFlags;
45 
46 public class KeyguardSimPinViewController
47         extends KeyguardPinBasedInputViewController<KeyguardSimPinView> {
48     public static final String TAG = "KeyguardSimPinView";
49     private static final String LOG_TAG = "KeyguardSimPinView";
50     private static final boolean DEBUG = KeyguardConstants.DEBUG_SIM_STATES;
51     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
52     private final TelephonyManager mTelephonyManager;
53 
54     private ProgressDialog mSimUnlockProgressDialog;
55     private CheckSimPin mCheckSimPinThread;
56     private int mRemainingAttempts;
57     // Below flag is set to true during power-up or when a new SIM card inserted on device.
58     // When this is true and when SIM card is PIN locked state, on PIN lock screen, message would
59     // be displayed to inform user about the number of remaining PIN attempts left.
60     private boolean mShowDefaultMessage;
61     private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
62     private AlertDialog mRemainingAttemptsDialog;
63     private ImageView mSimImageView;
64 
65     KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() {
66         @Override
67         public void onSimStateChanged(int subId, int slotId, int simState) {
68             if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")");
69             if (simState == TelephonyManager.SIM_STATE_READY) {
70                 mRemainingAttempts = -1;
71                 resetState();
72             } else {
73                 resetState();
74             }
75         }
76     };
77 
KeyguardSimPinViewController(KeyguardSimPinView view, KeyguardUpdateMonitor keyguardUpdateMonitor, SecurityMode securityMode, LockPatternUtils lockPatternUtils, KeyguardSecurityCallback keyguardSecurityCallback, KeyguardMessageAreaController.Factory messageAreaControllerFactory, LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, TelephonyManager telephonyManager, FalsingCollector falsingCollector, EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags)78     protected KeyguardSimPinViewController(KeyguardSimPinView view,
79             KeyguardUpdateMonitor keyguardUpdateMonitor,
80             SecurityMode securityMode, LockPatternUtils lockPatternUtils,
81             KeyguardSecurityCallback keyguardSecurityCallback,
82             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
83             LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
84             TelephonyManager telephonyManager, FalsingCollector falsingCollector,
85             EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags) {
86         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
87                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
88                 emergencyButtonController, falsingCollector, featureFlags);
89         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
90         mTelephonyManager = telephonyManager;
91         mSimImageView = mView.findViewById(R.id.keyguard_sim);
92     }
93 
94     @Override
onViewAttached()95     protected void onViewAttached() {
96         super.onViewAttached();
97     }
98 
99     @Override
resetState()100     void resetState() {
101         super.resetState();
102         if (DEBUG) Log.v(TAG, "Resetting state");
103         handleSubInfoChangeIfNeeded();
104         mMessageAreaController.setMessage("");
105         if (mShowDefaultMessage) {
106             showDefaultMessage();
107         }
108 
109         mView.setESimLocked(KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId), mSubId);
110     }
111 
112     @Override
startDisappearAnimation(Runnable finishRunnable)113     public boolean startDisappearAnimation(Runnable finishRunnable) {
114         return false;
115     }
116 
117     @Override
onResume(int reason)118     public void onResume(int reason) {
119         super.onResume(reason);
120         mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
121         mView.resetState();
122     }
123 
124     @Override
onPause()125     public void onPause() {
126         super.onPause();
127         mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
128 
129         // dismiss the dialog.
130         if (mSimUnlockProgressDialog != null) {
131             mSimUnlockProgressDialog.dismiss();
132             mSimUnlockProgressDialog = null;
133         }
134     }
135 
136     @Override
verifyPasswordAndUnlock()137     protected void verifyPasswordAndUnlock() {
138         String entry = mPasswordEntry.getText();
139 
140         // A SIM PIN is 4 to 8 decimal digits according to
141         // GSM 02.17 version 5.0.1, Section 5.6 PIN Management
142         if ((entry.length() < 4) || (entry.length() > 8)) {
143             // otherwise, display a message to the user, and don't submit.
144             mMessageAreaController.setMessage(
145                     com.android.systemui.R.string.kg_invalid_sim_pin_hint);
146             mView.resetPasswordText(true /* animate */, true /* announce */);
147             getKeyguardSecurityCallback().userActivity();
148             return;
149         }
150 
151         getSimUnlockProgressDialog().show();
152 
153         if (mCheckSimPinThread == null) {
154             mCheckSimPinThread = new CheckSimPin(mPasswordEntry.getText(), mSubId) {
155                 @Override
156                 void onSimCheckResponse(final PinResult result) {
157                     mView.post(() -> {
158                         mRemainingAttempts = result.getAttemptsRemaining();
159                         if (mSimUnlockProgressDialog != null) {
160                             mSimUnlockProgressDialog.hide();
161                         }
162                         mView.resetPasswordText(true /* animate */,
163                                 /* announce */
164                                 result.getResult() != PinResult.PIN_RESULT_TYPE_SUCCESS);
165                         if (result.getResult() == PinResult.PIN_RESULT_TYPE_SUCCESS) {
166                             mKeyguardUpdateMonitor.reportSimUnlocked(mSubId);
167                             mRemainingAttempts = -1;
168                             mShowDefaultMessage = true;
169                             getKeyguardSecurityCallback().dismiss(
170                                     true, KeyguardUpdateMonitor.getCurrentUser(),
171                                     SecurityMode.SimPin);
172                         } else {
173                             mShowDefaultMessage = false;
174                             if (result.getResult() == PinResult.PIN_RESULT_TYPE_INCORRECT) {
175                                 if (result.getAttemptsRemaining() <= 2) {
176                                     // this is getting critical - show dialog
177                                     getSimRemainingAttemptsDialog(
178                                             result.getAttemptsRemaining()).show();
179                                 } else {
180                                     // show message
181                                     mMessageAreaController.setMessage(
182                                             getPinPasswordErrorMessage(
183                                                     result.getAttemptsRemaining(), false));
184                                 }
185                             } else {
186                                 // "PIN operation failed!" - no idea what this was and no way to
187                                 // find out. :/
188                                 mMessageAreaController.setMessage(mView.getResources().getString(
189                                         R.string.kg_password_pin_failed));
190                             }
191                             if (DEBUG) {
192                                 Log.d(LOG_TAG, "verifyPasswordAndUnlock "
193                                         + " CheckSimPin.onSimCheckResponse: " + result
194                                         + " attemptsRemaining=" + result.getAttemptsRemaining());
195                             }
196                         }
197                         getKeyguardSecurityCallback().userActivity();
198                         mCheckSimPinThread = null;
199                     });
200                 }
201             };
202             mCheckSimPinThread.start();
203         }
204     }
205 
getSimUnlockProgressDialog()206     private Dialog getSimUnlockProgressDialog() {
207         if (mSimUnlockProgressDialog == null) {
208             mSimUnlockProgressDialog = new ProgressDialog(mView.getContext());
209             mSimUnlockProgressDialog.setMessage(
210                     mView.getResources().getString(R.string.kg_sim_unlock_progress_dialog_message));
211             mSimUnlockProgressDialog.setIndeterminate(true);
212             mSimUnlockProgressDialog.setCancelable(false);
213             mSimUnlockProgressDialog.getWindow().setType(
214                     WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
215         }
216         return mSimUnlockProgressDialog;
217     }
218 
219 
getSimRemainingAttemptsDialog(int remaining)220     private Dialog getSimRemainingAttemptsDialog(int remaining) {
221         String msg = getPinPasswordErrorMessage(remaining, false);
222         if (mRemainingAttemptsDialog == null) {
223             Builder builder = new AlertDialog.Builder(mView.getContext());
224             builder.setMessage(msg);
225             builder.setCancelable(false);
226             builder.setNeutralButton(R.string.ok, null);
227             mRemainingAttemptsDialog = builder.create();
228             mRemainingAttemptsDialog.getWindow().setType(
229                     WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
230         } else {
231             mRemainingAttemptsDialog.setMessage(msg);
232         }
233         return mRemainingAttemptsDialog;
234     }
235 
236 
getPinPasswordErrorMessage(int attemptsRemaining, boolean isDefault)237     private String getPinPasswordErrorMessage(int attemptsRemaining, boolean isDefault) {
238         String displayMessage;
239         int msgId;
240         if (attemptsRemaining == 0) {
241             displayMessage = mView.getResources().getString(
242                     R.string.kg_password_wrong_pin_code_pukked);
243         } else if (attemptsRemaining > 0) {
244             msgId = isDefault ? R.string.kg_password_default_pin_message :
245                     R.string.kg_password_wrong_pin_code;
246             displayMessage = icuMessageFormat(mView.getResources(), msgId, attemptsRemaining);
247         } else {
248             msgId = isDefault ? R.string.kg_sim_pin_instructions : R.string.kg_password_pin_failed;
249             displayMessage = mView.getResources().getString(msgId);
250         }
251         if (KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId)) {
252             displayMessage = mView.getResources()
253                     .getString(R.string.kg_sim_lock_esim_instructions, displayMessage);
254         }
255         if (DEBUG) {
256             Log.d(LOG_TAG, "getPinPasswordErrorMessage: attemptsRemaining="
257                     + attemptsRemaining + " displayMessage=" + displayMessage);
258         }
259         return displayMessage;
260     }
261 
showDefaultMessage()262     private void showDefaultMessage() {
263         setLockedSimMessage();
264         if (mRemainingAttempts >= 0) {
265             return;
266         }
267 
268         // Sending empty PIN here to query the number of remaining PIN attempts
269         new CheckSimPin("", mSubId) {
270             void onSimCheckResponse(final PinResult result) {
271                 Log.d(LOG_TAG, "onSimCheckResponse " + " empty One result "
272                         + result.toString());
273                 if (result.getAttemptsRemaining() >= 0) {
274                     mRemainingAttempts = result.getAttemptsRemaining();
275                     setLockedSimMessage();
276                 }
277             }
278         }.start();
279     }
280 
281     /**
282      * Since the IPC can block, we want to run the request in a separate thread
283      * with a callback.
284      */
285     private abstract class CheckSimPin extends Thread {
286         private final String mPin;
287         private int mSubId;
288 
CheckSimPin(String pin, int subId)289         protected CheckSimPin(String pin, int subId) {
290             mPin = pin;
291             mSubId = subId;
292         }
293 
onSimCheckResponse(@onNull PinResult result)294         abstract void onSimCheckResponse(@NonNull PinResult result);
295 
296         @Override
run()297         public void run() {
298             if (DEBUG) {
299                 Log.v(TAG, "call supplyIccLockPin(subid=" + mSubId + ")");
300             }
301             TelephonyManager telephonyManager = mTelephonyManager.createForSubscriptionId(mSubId);
302             final PinResult result = telephonyManager.supplyIccLockPin(mPin);
303             if (DEBUG) {
304                 Log.v(TAG, "supplyIccLockPin returned: " + result.toString());
305             }
306             mView.post(() -> onSimCheckResponse(result));
307         }
308     }
309 
setLockedSimMessage()310     private void setLockedSimMessage() {
311         boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId);
312         int count = 1;
313         if (mTelephonyManager != null) {
314             count = mTelephonyManager.getActiveModemCount();
315         }
316         Resources rez = mView.getResources();
317         String msg;
318         TypedArray array = mView.getContext().obtainStyledAttributes(
319                 new int[] { android.R.attr.textColor });
320         int color = array.getColor(0, Color.WHITE);
321         array.recycle();
322         if (count < 2) {
323             msg = rez.getString(R.string.kg_sim_pin_instructions);
324         } else {
325             SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId);
326             CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash
327             msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName);
328             if (info != null) {
329                 color = info.getIconTint();
330             }
331         }
332         if (isEsimLocked) {
333             msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg);
334         }
335 
336         if (mView.getVisibility() == View.VISIBLE) {
337             mMessageAreaController.setMessage(msg);
338         }
339         mSimImageView.setImageTintList(ColorStateList.valueOf(color));
340     }
341 
handleSubInfoChangeIfNeeded()342     private void handleSubInfoChangeIfNeeded() {
343         int subId = mKeyguardUpdateMonitor
344                 .getNextSubIdForState(TelephonyManager.SIM_STATE_PIN_REQUIRED);
345         if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) {
346             mSubId = subId;
347             mShowDefaultMessage = true;
348             mRemainingAttempts = -1;
349         }
350     }
351 }
352