1 
2 /*
3  * Copyright (C) 2014 The Android Open Source Project
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.android.settings.password;
19 
20 import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
21 
22 import android.app.Activity;
23 import android.app.KeyguardManager;
24 import android.app.admin.DevicePolicyManager;
25 import android.app.trust.TrustManager;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.graphics.Color;
29 import android.hardware.biometrics.BiometricConstants;
30 import android.hardware.biometrics.BiometricManager;
31 import android.hardware.biometrics.BiometricPrompt;
32 import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback;
33 import android.os.Bundle;
34 import android.os.Handler;
35 import android.os.Looper;
36 import android.os.UserHandle;
37 import android.os.UserManager;
38 import android.util.Log;
39 import android.view.WindowManager;
40 
41 import androidx.annotation.NonNull;
42 import androidx.fragment.app.FragmentActivity;
43 
44 import com.android.internal.widget.LockPatternUtils;
45 import com.android.settings.R;
46 import com.android.settings.Utils;
47 
48 import java.util.concurrent.Executor;
49 
50 /**
51  * Launch this when you want to confirm the user is present by asking them to enter their
52  * PIN/password/pattern.
53  */
54 public class ConfirmDeviceCredentialActivity extends FragmentActivity {
55     public static final String TAG = ConfirmDeviceCredentialActivity.class.getSimpleName();
56 
57     /**
58      * If the intent is sent from {@link com.android.systemui.keyguard.WorkLockActivityController}
59      * then check for device policy management flags.
60      */
61     public static final String EXTRA_FROM_WORK_LOCK_ACTIVITY_CONTROLLER =
62             "from_work_lock_activity_controller";
63 
64     // The normal flow that apps go through
65     private static final int CREDENTIAL_NORMAL = 1;
66     // Unlocks the managed profile when the primary profile is unlocked
67     private static final int CREDENTIAL_MANAGED = 2;
68 
69     private static final String TAG_BIOMETRIC_FRAGMENT = "fragment";
70 
71     public static class InternalActivity extends ConfirmDeviceCredentialActivity {
72     }
73 
createIntent(CharSequence title, CharSequence details)74     public static Intent createIntent(CharSequence title, CharSequence details) {
75         Intent intent = new Intent();
76         intent.setClassName(SETTINGS_PACKAGE_NAME,
77                 ConfirmDeviceCredentialActivity.class.getName());
78         intent.putExtra(KeyguardManager.EXTRA_TITLE, title);
79         intent.putExtra(KeyguardManager.EXTRA_DESCRIPTION, details);
80         return intent;
81     }
82 
createIntent(CharSequence title, CharSequence details, long challenge)83     public static Intent createIntent(CharSequence title, CharSequence details, long challenge) {
84         Intent intent = new Intent();
85         intent.setClassName(SETTINGS_PACKAGE_NAME,
86                 ConfirmDeviceCredentialActivity.class.getName());
87         intent.putExtra(KeyguardManager.EXTRA_TITLE, title);
88         intent.putExtra(KeyguardManager.EXTRA_DESCRIPTION, details);
89         intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, challenge);
90         intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, true);
91         return intent;
92     }
93 
94     private BiometricManager mBiometricManager;
95     private BiometricFragment mBiometricFragment;
96     private DevicePolicyManager mDevicePolicyManager;
97     private LockPatternUtils mLockPatternUtils;
98     private UserManager mUserManager;
99     private TrustManager mTrustManager;
100     private ChooseLockSettingsHelper mChooseLockSettingsHelper;
101     private Handler mHandler = new Handler(Looper.getMainLooper());
102     private Context mContext;
103     private boolean mCheckDevicePolicyManager;
104 
105     private String mTitle;
106     private String mDetails;
107     private int mUserId;
108     private int mCredentialMode;
109     private boolean mGoingToBackground;
110     private boolean mWaitingForBiometricCallback;
111 
112     private Executor mExecutor = (runnable -> {
113         mHandler.post(runnable);
114     });
115 
116     private AuthenticationCallback mAuthenticationCallback = new AuthenticationCallback() {
117         @Override
118         public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
119             if (!mGoingToBackground) {
120                 mWaitingForBiometricCallback = false;
121                 if (errorCode == BiometricPrompt.BIOMETRIC_ERROR_USER_CANCELED
122                         || errorCode == BiometricPrompt.BIOMETRIC_ERROR_CANCELED) {
123                     finish();
124                 } else {
125                     // All other errors go to some version of CC
126                     showConfirmCredentials();
127                 }
128             } else if (mWaitingForBiometricCallback) { // mGoingToBackground is true
129                 mWaitingForBiometricCallback = false;
130                 finish();
131             }
132         }
133 
134         @Override
135         public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
136             mWaitingForBiometricCallback = false;
137             mTrustManager.setDeviceLockedForUser(mUserId, false);
138             final boolean isStrongAuth = result.getAuthenticationType()
139                     == BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL;
140             ConfirmDeviceCredentialUtils.reportSuccessfulAttempt(mLockPatternUtils, mUserManager,
141                     mDevicePolicyManager, mUserId, isStrongAuth);
142             ConfirmDeviceCredentialUtils.checkForPendingIntent(
143                     ConfirmDeviceCredentialActivity.this);
144 
145             setResult(Activity.RESULT_OK);
146             finish();
147         }
148 
149         @Override
150         public void onAuthenticationFailed() {
151             mWaitingForBiometricCallback = false;
152             mDevicePolicyManager.reportFailedBiometricAttempt(mUserId);
153         }
154 
155         @Override
156         public void onSystemEvent(int event) {
157             Log.d(TAG, "SystemEvent: " + event);
158             switch (event) {
159                 case BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL:
160                     finish();
161                     break;
162             }
163         }
164     };
165 
getStringForError(int errorCode)166     private String getStringForError(int errorCode) {
167         switch (errorCode) {
168             case BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED:
169                 return getString(com.android.internal.R.string.biometric_error_user_canceled);
170             case BiometricConstants.BIOMETRIC_ERROR_CANCELED:
171                 return getString(com.android.internal.R.string.biometric_error_canceled);
172             default:
173                 return null;
174         }
175     }
176 
177     @Override
onCreate(Bundle savedInstanceState)178     protected void onCreate(Bundle savedInstanceState) {
179         super.onCreate(savedInstanceState);
180 
181         getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
182         getWindow().setStatusBarColor(Color.TRANSPARENT);
183 
184         mBiometricManager = getSystemService(BiometricManager.class);
185         mDevicePolicyManager = getSystemService(DevicePolicyManager.class);
186         mUserManager = UserManager.get(this);
187         mTrustManager = getSystemService(TrustManager.class);
188         mLockPatternUtils = new LockPatternUtils(this);
189 
190         Intent intent = getIntent();
191         mContext = this;
192         mCheckDevicePolicyManager = intent
193                 .getBooleanExtra(BiometricPrompt.EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS, false);
194         mTitle = intent.getStringExtra(KeyguardManager.EXTRA_TITLE);
195         mDetails = intent.getStringExtra(KeyguardManager.EXTRA_DESCRIPTION);
196         String alternateButton = intent.getStringExtra(
197                 KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL);
198         boolean frp = KeyguardManager.ACTION_CONFIRM_FRP_CREDENTIAL.equals(intent.getAction());
199 
200         mUserId = UserHandle.myUserId();
201         if (isInternalActivity()) {
202             try {
203                 mUserId = Utils.getUserIdFromBundle(this, intent.getExtras());
204             } catch (SecurityException se) {
205                 Log.e(TAG, "Invalid intent extra", se);
206             }
207         }
208         final int effectiveUserId = mUserManager.getCredentialOwnerProfile(mUserId);
209         final boolean isManagedProfile = UserManager.get(this).isManagedProfile(mUserId);
210         // if the client app did not hand in a title and we are about to show the work challenge,
211         // check whether there is a policy setting the organization name and use that as title
212         if ((mTitle == null) && isManagedProfile) {
213             mTitle = getTitleFromOrganizationName(mUserId);
214         }
215 
216 
217         mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this);
218         final LockPatternUtils lockPatternUtils = new LockPatternUtils(this);
219 
220         final Bundle bpBundle = new Bundle();
221         bpBundle.putString(BiometricPrompt.KEY_TITLE, mTitle);
222         bpBundle.putString(BiometricPrompt.KEY_DESCRIPTION, mDetails);
223         bpBundle.putBoolean(BiometricPrompt.EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS,
224                 mCheckDevicePolicyManager);
225 
226         final @LockPatternUtils.CredentialType int credentialType = Utils.getCredentialType(
227                 mContext, effectiveUserId);
228         if (mTitle == null) {
229             bpBundle.putString(BiometricPrompt.KEY_DEVICE_CREDENTIAL_TITLE,
230                     getTitleFromCredentialType(credentialType, isManagedProfile));
231         }
232         if (mDetails == null) {
233             bpBundle.putString(BiometricPrompt.KEY_DEVICE_CREDENTIAL_SUBTITLE,
234                     getDetailsFromCredentialType(credentialType, isManagedProfile));
235         }
236 
237         boolean launchedBiometric = false;
238         boolean launchedCDC = false;
239         // If the target is a managed user and user key not unlocked yet, we will force unlock
240         // tied profile so it will enable work mode and unlock managed profile, when personal
241         // challenge is unlocked.
242         if (frp) {
243             launchedCDC = mChooseLockSettingsHelper.launchFrpConfirmationActivity(
244                     0, mTitle, mDetails, alternateButton);
245         } else if (isManagedProfile && isInternalActivity()
246                 && !lockPatternUtils.isSeparateProfileChallengeEnabled(mUserId)) {
247             mCredentialMode = CREDENTIAL_MANAGED;
248             if (isBiometricAllowed(effectiveUserId, mUserId)) {
249                 showBiometricPrompt(bpBundle);
250                 launchedBiometric = true;
251             } else {
252                 showConfirmCredentials();
253                 launchedCDC = true;
254             }
255         } else {
256             mCredentialMode = CREDENTIAL_NORMAL;
257             if (isBiometricAllowed(effectiveUserId, mUserId)) {
258                 // Don't need to check if biometrics / pin/pattern/pass are enrolled. It will go to
259                 // onAuthenticationError and do the right thing automatically.
260                 showBiometricPrompt(bpBundle);
261                 launchedBiometric = true;
262             } else {
263                 showConfirmCredentials();
264                 launchedCDC = true;
265             }
266         }
267 
268         if (launchedCDC) {
269             finish();
270         } else if (launchedBiometric) {
271             // Keep this activity alive until BiometricPrompt goes away
272             mWaitingForBiometricCallback = true;
273         } else {
274             Log.d(TAG, "No pattern, password or PIN set.");
275             setResult(Activity.RESULT_OK);
276             finish();
277         }
278     }
279 
getTitleFromCredentialType(@ockPatternUtils.CredentialType int credentialType, boolean isManagedProfile)280     private String getTitleFromCredentialType(@LockPatternUtils.CredentialType int credentialType,
281             boolean isManagedProfile) {
282         switch (credentialType) {
283             case LockPatternUtils.CREDENTIAL_TYPE_PIN:
284                 return isManagedProfile
285                         ? getString(R.string.lockpassword_confirm_your_work_pin_header)
286                         : getString(R.string.lockpassword_confirm_your_pin_header);
287             case LockPatternUtils.CREDENTIAL_TYPE_PATTERN:
288                 return isManagedProfile
289                         ? getString(R.string.lockpassword_confirm_your_work_pattern_header)
290                         : getString(R.string.lockpassword_confirm_your_pattern_header);
291             case LockPatternUtils.CREDENTIAL_TYPE_PASSWORD:
292                 return isManagedProfile
293                         ? getString(R.string.lockpassword_confirm_your_work_password_header)
294                         : getString(R.string.lockpassword_confirm_your_password_header);
295         }
296         return null;
297     }
298 
getDetailsFromCredentialType(@ockPatternUtils.CredentialType int credentialType, boolean isManagedProfile)299     private String getDetailsFromCredentialType(@LockPatternUtils.CredentialType int credentialType,
300             boolean isManagedProfile) {
301         switch (credentialType) {
302             case LockPatternUtils.CREDENTIAL_TYPE_PIN:
303                 return isManagedProfile
304                         ? getString(R.string.lockpassword_confirm_your_pin_generic_profile)
305                         : getString(R.string.lockpassword_confirm_your_pin_generic);
306             case LockPatternUtils.CREDENTIAL_TYPE_PATTERN:
307                 return isManagedProfile
308                         ? getString(R.string.lockpassword_confirm_your_pattern_generic_profile)
309                         : getString(R.string.lockpassword_confirm_your_pattern_generic);
310             case LockPatternUtils.CREDENTIAL_TYPE_PASSWORD:
311                 return isManagedProfile
312                         ? getString(R.string.lockpassword_confirm_your_password_generic_profile)
313                         : getString(R.string.lockpassword_confirm_your_password_generic);
314         }
315         return null;
316     }
317 
318     @Override
onStart()319     protected void onStart() {
320         super.onStart();
321         // Translucent activity that is "visible", so it doesn't complain about finish()
322         // not being called before onResume().
323         setVisible(true);
324     }
325 
326     @Override
onPause()327     public void onPause() {
328         super.onPause();
329         if (!isChangingConfigurations()) {
330             mGoingToBackground = true;
331             if (!mWaitingForBiometricCallback) {
332                 finish();
333             }
334         } else {
335             mGoingToBackground = false;
336         }
337     }
338 
339     // User could be locked while Effective user is unlocked even though the effective owns the
340     // credential. Otherwise, biometric can't unlock fbe/keystore through
341     // verifyTiedProfileChallenge. In such case, we also wanna show the user message that
342     // biometric is disabled due to device restart.
isStrongAuthRequired(int effectiveUserId)343     private boolean isStrongAuthRequired(int effectiveUserId) {
344         return !mLockPatternUtils.isBiometricAllowedForUser(effectiveUserId)
345                 || !mUserManager.isUserUnlocked(mUserId);
346     }
347 
isBiometricAllowed(int effectiveUserId, int realUserId)348     private boolean isBiometricAllowed(int effectiveUserId, int realUserId) {
349         return !isStrongAuthRequired(effectiveUserId) && !mLockPatternUtils
350                 .hasPendingEscrowToken(realUserId);
351     }
352 
showBiometricPrompt(Bundle bundle)353     private void showBiometricPrompt(Bundle bundle) {
354         mBiometricManager.setActiveUser(mUserId);
355 
356         mBiometricFragment = (BiometricFragment) getSupportFragmentManager()
357                 .findFragmentByTag(TAG_BIOMETRIC_FRAGMENT);
358         boolean newFragment = false;
359 
360         if (mBiometricFragment == null) {
361             mBiometricFragment = BiometricFragment.newInstance(bundle);
362             newFragment = true;
363         }
364         mBiometricFragment.setCallbacks(mExecutor, mAuthenticationCallback);
365         mBiometricFragment.setUser(mUserId);
366 
367         if (newFragment) {
368             getSupportFragmentManager().beginTransaction()
369                     .add(mBiometricFragment, TAG_BIOMETRIC_FRAGMENT).commit();
370         }
371     }
372 
373     /**
374      * Shows ConfirmDeviceCredentials for normal apps.
375      */
showConfirmCredentials()376     private void showConfirmCredentials() {
377         boolean launched = false;
378         // The only difference between CREDENTIAL_MANAGED and CREDENTIAL_NORMAL is that for
379         // CREDENTIAL_MANAGED, we launch the real confirm credential activity with an explicit
380         // but dummy challenge value (0L). This will result in ConfirmLockPassword calling
381         // verifyTiedProfileChallenge() (if it's a profile with unified challenge), due to the
382         // difference between ConfirmLockPassword.startVerifyPassword() and
383         // ConfirmLockPassword.startCheckPassword(). Calling verifyTiedProfileChallenge() here is
384         // necessary when this is part of the turning on work profile flow, because it forces
385         // unlocking the work profile even before the profile is running.
386         // TODO: Remove the duplication of checkPassword and verifyPassword in ConfirmLockPassword,
387         // LockPatternChecker and LockPatternUtils. verifyPassword should be the only API to use,
388         // which optionally accepts a challenge.
389         if (mCredentialMode == CREDENTIAL_MANAGED) {
390             launched = mChooseLockSettingsHelper
391                     .launchConfirmationActivityWithExternalAndChallenge(
392                             0 /* request code */, null /* title */, mTitle, mDetails,
393                             true /* isExternal */, 0L /* challenge */, mUserId);
394         } else if (mCredentialMode == CREDENTIAL_NORMAL) {
395             launched = mChooseLockSettingsHelper.launchConfirmationActivity(
396                     0 /* request code */, null /* title */,
397                     mTitle, mDetails, false /* returnCredentials */, true /* isExternal */,
398                     mUserId);
399         }
400         if (!launched) {
401             Log.d(TAG, "No pin/pattern/pass set");
402             setResult(Activity.RESULT_OK);
403         }
404         finish();
405     }
406 
isInternalActivity()407     private boolean isInternalActivity() {
408         return this instanceof ConfirmDeviceCredentialActivity.InternalActivity;
409     }
410 
getTitleFromOrganizationName(int userId)411     private String getTitleFromOrganizationName(int userId) {
412         DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(
413                 Context.DEVICE_POLICY_SERVICE);
414         CharSequence organizationNameForUser = (dpm != null)
415                 ? dpm.getOrganizationNameForUser(userId) : null;
416         return organizationNameForUser != null ? organizationNameForUser.toString() : null;
417     }
418 }
419