1 /* 2 * Copyright (C) 2015 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.settings; 18 19 import android.annotation.Nullable; 20 import android.app.ActivityManager; 21 import android.app.ActivityManagerNative; 22 import android.app.ActivityOptions; 23 import android.app.AlertDialog; 24 import android.app.IActivityManager; 25 import android.app.admin.DevicePolicyManager; 26 import android.app.trust.TrustManager; 27 import android.content.Context; 28 import android.content.DialogInterface; 29 import android.content.DialogInterface.OnClickListener; 30 import android.content.Intent; 31 import android.content.IntentSender; 32 import android.graphics.Point; 33 import android.graphics.PorterDuff; 34 import android.graphics.drawable.ColorDrawable; 35 import android.graphics.drawable.Drawable; 36 import android.os.Bundle; 37 import android.os.Handler; 38 import android.os.RemoteException; 39 import android.os.UserManager; 40 import android.security.KeyStore; 41 import android.view.View; 42 import android.view.ViewGroup; 43 import android.widget.Button; 44 import android.widget.FrameLayout; 45 import android.widget.ImageView; 46 import android.widget.TextView; 47 48 import com.android.internal.widget.LockPatternUtils; 49 import com.android.settings.fingerprint.FingerprintUiHelper; 50 51 /** 52 * Base fragment to be shared for PIN/Pattern/Password confirmation fragments. 53 */ 54 public abstract class ConfirmDeviceCredentialBaseFragment extends OptionsMenuFragment 55 implements FingerprintUiHelper.Callback { 56 57 public static final String PACKAGE = "com.android.settings"; 58 public static final String TITLE_TEXT = PACKAGE + ".ConfirmCredentials.title"; 59 public static final String HEADER_TEXT = PACKAGE + ".ConfirmCredentials.header"; 60 public static final String DETAILS_TEXT = PACKAGE + ".ConfirmCredentials.details"; 61 public static final String ALLOW_FP_AUTHENTICATION = 62 PACKAGE + ".ConfirmCredentials.allowFpAuthentication"; 63 public static final String DARK_THEME = PACKAGE + ".ConfirmCredentials.darkTheme"; 64 public static final String SHOW_CANCEL_BUTTON = 65 PACKAGE + ".ConfirmCredentials.showCancelButton"; 66 public static final String SHOW_WHEN_LOCKED = 67 PACKAGE + ".ConfirmCredentials.showWhenLocked"; 68 69 private FingerprintUiHelper mFingerprintHelper; 70 protected boolean mIsStrongAuthRequired; 71 private boolean mAllowFpAuthentication; 72 protected boolean mReturnCredentials = false; 73 protected Button mCancelButton; 74 protected ImageView mFingerprintIcon; 75 protected int mEffectiveUserId; 76 protected int mUserId; 77 protected LockPatternUtils mLockPatternUtils; 78 protected TextView mErrorTextView; 79 protected final Handler mHandler = new Handler(); 80 81 @Override 82 public void onCreate(@Nullable Bundle savedInstanceState) { 83 super.onCreate(savedInstanceState); 84 mAllowFpAuthentication = getActivity().getIntent().getBooleanExtra( 85 ALLOW_FP_AUTHENTICATION, false); 86 mReturnCredentials = getActivity().getIntent().getBooleanExtra( 87 ChooseLockSettingsHelper.EXTRA_KEY_RETURN_CREDENTIALS, false); 88 // Only take this argument into account if it belongs to the current profile. 89 Intent intent = getActivity().getIntent(); 90 mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras()); 91 final UserManager userManager = UserManager.get(getActivity()); 92 mEffectiveUserId = userManager.getCredentialOwnerProfile(mUserId); 93 mLockPatternUtils = new LockPatternUtils(getActivity()); 94 mIsStrongAuthRequired = isFingerprintDisallowedByStrongAuth(); 95 mAllowFpAuthentication = mAllowFpAuthentication && !isFingerprintDisabledByAdmin() 96 && !mReturnCredentials && !mIsStrongAuthRequired; 97 } 98 99 @Override 100 public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 101 super.onViewCreated(view, savedInstanceState); 102 mCancelButton = (Button) view.findViewById(R.id.cancelButton); 103 mFingerprintIcon = (ImageView) view.findViewById(R.id.fingerprintIcon); 104 mFingerprintHelper = new FingerprintUiHelper( 105 mFingerprintIcon, 106 (TextView) view.findViewById(R.id.errorText), this, mEffectiveUserId); 107 boolean showCancelButton = getActivity().getIntent().getBooleanExtra( 108 SHOW_CANCEL_BUTTON, false); 109 mCancelButton.setVisibility(showCancelButton ? View.VISIBLE : View.GONE); 110 mCancelButton.setOnClickListener(new View.OnClickListener() { 111 @Override 112 public void onClick(View v) { 113 getActivity().finish(); 114 } 115 }); 116 int credentialOwnerUserId = Utils.getCredentialOwnerUserId( 117 getActivity(), 118 Utils.getUserIdFromBundle( 119 getActivity(), 120 getActivity().getIntent().getExtras())); 121 if (Utils.isManagedProfile(UserManager.get(getActivity()), credentialOwnerUserId)) { 122 setWorkChallengeBackground(view, credentialOwnerUserId); 123 } 124 } 125 126 private boolean isFingerprintDisabledByAdmin() { 127 DevicePolicyManager dpm = (DevicePolicyManager) getActivity().getSystemService( 128 Context.DEVICE_POLICY_SERVICE); 129 final int disabledFeatures = dpm.getKeyguardDisabledFeatures(null, mEffectiveUserId); 130 return (disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT) != 0; 131 } 132 133 // User could be locked while Effective user is unlocked even though the effective owns the 134 // credential. Otherwise, fingerprint can't unlock fbe/keystore through 135 // verifyTiedProfileChallenge. In such case, we also wanna show the user message that 136 // fingerprint is disabled due to device restart. 137 private boolean isFingerprintDisallowedByStrongAuth() { 138 return !(mLockPatternUtils.isFingerprintAllowedForUser(mEffectiveUserId) 139 && KeyStore.getInstance().state(mUserId) == KeyStore.State.UNLOCKED); 140 } 141 142 @Override 143 public void onResume() { 144 super.onResume(); 145 refreshLockScreen(); 146 } 147 148 protected void refreshLockScreen() { 149 if (mAllowFpAuthentication) { 150 mFingerprintHelper.startListening(); 151 } else { 152 if (mFingerprintHelper.isListening()) { 153 mFingerprintHelper.stopListening(); 154 } 155 } 156 if (isProfileChallenge()) { 157 updateErrorMessage(mLockPatternUtils.getCurrentFailedPasswordAttempts( 158 mEffectiveUserId)); 159 } 160 } 161 162 protected void setAccessibilityTitle(CharSequence supplementalText) { 163 Intent intent = getActivity().getIntent(); 164 if (intent != null) { 165 CharSequence titleText = intent.getCharSequenceExtra( 166 ConfirmDeviceCredentialBaseFragment.TITLE_TEXT); 167 if (titleText == null || supplementalText == null) { 168 return; 169 } 170 String accessibilityTitle = 171 new StringBuilder(titleText).append(",").append(supplementalText).toString(); 172 getActivity().setTitle(Utils.createAccessibleSequence(titleText, accessibilityTitle)); 173 } 174 } 175 176 @Override 177 public void onPause() { 178 super.onPause(); 179 if (mFingerprintHelper.isListening()) { 180 mFingerprintHelper.stopListening(); 181 } 182 } 183 184 @Override 185 public void onAuthenticated() { 186 // Check whether we are still active. 187 if (getActivity() != null && getActivity().isResumed()) { 188 TrustManager trustManager = 189 (TrustManager) getActivity().getSystemService(Context.TRUST_SERVICE); 190 trustManager.setDeviceLockedForUser(mEffectiveUserId, false); 191 authenticationSucceeded(); 192 authenticationSucceeded(); 193 checkForPendingIntent(); 194 } 195 } 196 197 protected abstract void authenticationSucceeded(); 198 199 @Override 200 public void onFingerprintIconVisibilityChanged(boolean visible) { 201 } 202 203 public void prepareEnterAnimation() { 204 } 205 206 public void startEnterAnimation() { 207 } 208 209 protected void checkForPendingIntent() { 210 int taskId = getActivity().getIntent().getIntExtra(Intent.EXTRA_TASK_ID, -1); 211 if (taskId != -1) { 212 try { 213 IActivityManager activityManager = ActivityManagerNative.getDefault(); 214 final ActivityOptions options = ActivityOptions.makeBasic(); 215 options.setLaunchStackId(ActivityManager.StackId.INVALID_STACK_ID); 216 activityManager.startActivityFromRecents(taskId, options.toBundle()); 217 return; 218 } catch (RemoteException e) { 219 // Do nothing. 220 } 221 } 222 IntentSender intentSender = getActivity().getIntent() 223 .getParcelableExtra(Intent.EXTRA_INTENT); 224 if (intentSender != null) { 225 try { 226 getActivity().startIntentSenderForResult(intentSender, -1, null, 0, 0, 0); 227 } catch (IntentSender.SendIntentException e) { 228 /* ignore */ 229 } 230 } 231 } 232 233 private void setWorkChallengeBackground(View baseView, int userId) { 234 View mainContent = getActivity().findViewById(com.android.settings.R.id.main_content); 235 if (mainContent != null) { 236 // Remove the main content padding so that the background image is full screen. 237 mainContent.setPadding(0, 0, 0, 0); 238 } 239 240 DevicePolicyManager dpm = (DevicePolicyManager) getActivity().getSystemService( 241 Context.DEVICE_POLICY_SERVICE); 242 baseView.setBackground(new ColorDrawable(dpm.getOrganizationColorForUser(userId))); 243 ImageView imageView = (ImageView) baseView.findViewById(R.id.background_image); 244 if (imageView != null) { 245 Drawable image = getResources().getDrawable(R.drawable.work_challenge_background); 246 image.setColorFilter( 247 getResources().getColor(R.color.confirm_device_credential_transparent_black), 248 PorterDuff.Mode.DARKEN); 249 imageView.setImageDrawable(image); 250 Point screenSize = new Point(); 251 getActivity().getWindowManager().getDefaultDisplay().getSize(screenSize); 252 imageView.setLayoutParams(new FrameLayout.LayoutParams( 253 ViewGroup.LayoutParams.MATCH_PARENT, 254 screenSize.y)); 255 } 256 } 257 258 protected boolean isProfileChallenge() { 259 return Utils.isManagedProfile(UserManager.get(getContext()), mEffectiveUserId); 260 } 261 262 protected void reportSuccessfullAttempt() { 263 if (isProfileChallenge()) { 264 mLockPatternUtils.reportSuccessfulPasswordAttempt(mEffectiveUserId); 265 // Keyguard is responsible to disable StrongAuth for primary user. Disable StrongAuth 266 // for work challenge only here. 267 mLockPatternUtils.userPresent(mEffectiveUserId); 268 } 269 } 270 271 protected void reportFailedAttempt() { 272 if (isProfileChallenge()) { 273 // + 1 for this attempt. 274 updateErrorMessage( 275 mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId) + 1); 276 mLockPatternUtils.reportFailedPasswordAttempt(mEffectiveUserId); 277 } 278 } 279 280 protected void updateErrorMessage(int numAttempts) { 281 final int maxAttempts = 282 mLockPatternUtils.getMaximumFailedPasswordsForWipe(mEffectiveUserId); 283 if (maxAttempts > 0 && numAttempts > 0) { 284 int remainingAttempts = maxAttempts - numAttempts; 285 if (remainingAttempts == 1) { 286 // Last try 287 final String title = getActivity().getString( 288 R.string.lock_profile_wipe_warning_title); 289 final String message = getActivity().getString(getLastTryErrorMessage()); 290 showDialog(title, message, android.R.string.ok, false /* dismiss */); 291 } else if (remainingAttempts <= 0) { 292 // Profile is wiped 293 final String message = getActivity().getString(R.string.lock_profile_wipe_content); 294 showDialog(null, message, R.string.lock_profile_wipe_dismiss, true /* dismiss */); 295 } 296 if (mErrorTextView != null) { 297 final String message = getActivity().getString(R.string.lock_profile_wipe_attempts, 298 numAttempts, maxAttempts); 299 showError(message, 0); 300 } 301 } 302 } 303 304 protected abstract int getLastTryErrorMessage(); 305 306 private final Runnable mResetErrorRunnable = new Runnable() { 307 @Override 308 public void run() { 309 mErrorTextView.setText(""); 310 } 311 }; 312 313 protected void showError(CharSequence msg, long timeout) { 314 mErrorTextView.setText(msg); 315 onShowError(); 316 mHandler.removeCallbacks(mResetErrorRunnable); 317 if (timeout != 0) { 318 mHandler.postDelayed(mResetErrorRunnable, timeout); 319 } 320 } 321 322 protected abstract void onShowError(); 323 324 protected void showError(int msg, long timeout) { 325 showError(getText(msg), timeout); 326 } 327 328 private void showDialog(String title, String message, int buttonString, final boolean dismiss) { 329 final AlertDialog dialog = new AlertDialog.Builder(getActivity()) 330 .setTitle(title) 331 .setMessage(message) 332 .setPositiveButton(buttonString, new OnClickListener() { 333 @Override 334 public void onClick(DialogInterface dialog, int which) { 335 if (dismiss) { 336 getActivity().finish(); 337 } 338 } 339 }) 340 .create(); 341 dialog.show(); 342 } 343 } 344