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