1 /* 2 * Copyright (C) 2021 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.biometrics.face; 18 19 import static android.app.admin.DevicePolicyResources.Strings.Settings.FACE_UNLOCK_DISABLED; 20 21 import static com.android.settings.biometrics.BiometricUtils.GatekeeperCredentialNotMatchException; 22 23 import android.app.admin.DevicePolicyManager; 24 import android.app.settings.SettingsEnums; 25 import android.content.Intent; 26 import android.content.res.Configuration; 27 import android.hardware.SensorPrivacyManager; 28 import android.hardware.biometrics.BiometricAuthenticator; 29 import android.hardware.biometrics.SensorProperties; 30 import android.hardware.face.FaceManager; 31 import android.hardware.face.FaceSensorPropertiesInternal; 32 import android.hardware.face.IFaceAuthenticatorsRegisteredCallback; 33 import android.os.Bundle; 34 import android.os.UserHandle; 35 import android.text.Html; 36 import android.text.method.LinkMovementMethod; 37 import android.util.Log; 38 import android.view.View; 39 import android.widget.ImageView; 40 import android.widget.LinearLayout; 41 import android.widget.TextView; 42 43 import androidx.annotation.NonNull; 44 import androidx.annotation.Nullable; 45 import androidx.annotation.StringRes; 46 import androidx.annotation.VisibleForTesting; 47 48 import com.android.settings.R; 49 import com.android.settings.Settings; 50 import com.android.settings.Utils; 51 import com.android.settings.biometrics.BiometricEnrollActivity; 52 import com.android.settings.biometrics.BiometricEnrollIntroduction; 53 import com.android.settings.biometrics.BiometricUtils; 54 import com.android.settings.biometrics.MultiBiometricEnrollHelper; 55 import com.android.settings.password.ChooseLockSettingsHelper; 56 import com.android.settings.password.SetupSkipDialog; 57 import com.android.settings.utils.SensorPrivacyManagerHelper; 58 import com.android.settingslib.RestrictedLockUtilsInternal; 59 import com.android.systemui.unfold.compat.ScreenSizeFoldProvider; 60 import com.android.systemui.unfold.updates.FoldProvider; 61 62 import com.google.android.setupcompat.template.FooterButton; 63 import com.google.android.setupcompat.util.WizardManagerHelper; 64 import com.google.android.setupdesign.span.LinkSpan; 65 66 import java.util.List; 67 68 /** 69 * Provides introductory info about face unlock and prompts the user to agree before starting face 70 * enrollment. 71 */ 72 public class FaceEnrollIntroduction extends BiometricEnrollIntroduction { 73 private static final String TAG = "FaceEnrollIntroduction"; 74 75 private FaceManager mFaceManager; 76 @Nullable private FooterButton mPrimaryFooterButton; 77 @Nullable private FooterButton mSecondaryFooterButton; 78 @Nullable private SensorPrivacyManager mSensorPrivacyManager; 79 private boolean mIsFaceStrong; 80 81 @Override onCancelButtonClick(View view)82 protected void onCancelButtonClick(View view) { 83 if (!BiometricUtils.tryStartingNextBiometricEnroll(this, ENROLL_NEXT_BIOMETRIC_REQUEST, 84 "cancel")) { 85 super.onCancelButtonClick(view); 86 } 87 } 88 89 @Override onSkipButtonClick(View view)90 protected void onSkipButtonClick(View view) { 91 if (!BiometricUtils.tryStartingNextBiometricEnroll(this, ENROLL_NEXT_BIOMETRIC_REQUEST, 92 "skip")) { 93 super.onSkipButtonClick(view); 94 } 95 } 96 97 @Override onEnrollmentSkipped(@ullable Intent data)98 protected void onEnrollmentSkipped(@Nullable Intent data) { 99 if (!BiometricUtils.tryStartingNextBiometricEnroll(this, ENROLL_NEXT_BIOMETRIC_REQUEST, 100 "skipped")) { 101 super.onEnrollmentSkipped(data); 102 } 103 } 104 105 @Override onFinishedEnrolling(@ullable Intent data)106 protected void onFinishedEnrolling(@Nullable Intent data) { 107 if (!BiometricUtils.tryStartingNextBiometricEnroll(this, ENROLL_NEXT_BIOMETRIC_REQUEST, 108 "finished")) { 109 super.onFinishedEnrolling(data); 110 } 111 } 112 113 @Override shouldFinishWhenBackgrounded()114 protected boolean shouldFinishWhenBackgrounded() { 115 return super.shouldFinishWhenBackgrounded() && !BiometricUtils.isPostureGuidanceShowing( 116 mDevicePostureState, mLaunchedPostureGuidance); 117 } 118 119 @Override onCreate(Bundle savedInstanceState)120 protected void onCreate(Bundle savedInstanceState) { 121 mFaceManager = getFaceManager(); 122 123 super.onCreate(savedInstanceState); 124 125 if (savedInstanceState == null 126 && !WizardManagerHelper.isAnySetupWizard(getIntent()) 127 && !getIntent().getBooleanExtra(EXTRA_FROM_SETTINGS_SUMMARY, false) 128 && maxFacesEnrolled()) { 129 // from tips && maxEnrolled 130 Log.d(TAG, "launch face settings"); 131 launchFaceSettingsActivity(); 132 finish(); 133 } 134 135 // Wait super::onCreated() then return because SuperNotCalledExceptio will be thrown 136 // if we don't wait for it. 137 if (isFinishing()) { 138 return; 139 } 140 141 // Apply extracted theme color to icons. 142 final ImageView iconGlasses = findViewById(R.id.icon_glasses); 143 final ImageView iconLooking = findViewById(R.id.icon_looking); 144 iconGlasses.getBackground().setColorFilter(getIconColorFilter()); 145 iconLooking.getBackground().setColorFilter(getIconColorFilter()); 146 147 // Set text for views with multiple variations. 148 final TextView infoMessageGlasses = findViewById(R.id.info_message_glasses); 149 final TextView infoMessageLooking = findViewById(R.id.info_message_looking); 150 final TextView howMessage = findViewById(R.id.how_message); 151 final TextView inControlTitle = findViewById(R.id.title_in_control); 152 final TextView inControlMessage = findViewById(R.id.message_in_control); 153 final TextView lessSecure = findViewById(R.id.info_message_less_secure); 154 infoMessageGlasses.setText(getInfoMessageGlasses()); 155 infoMessageLooking.setText(getInfoMessageLooking()); 156 inControlTitle.setText(getInControlTitle()); 157 howMessage.setText(getHowMessage()); 158 inControlMessage.setText(Html.fromHtml(getString(getInControlMessage()), 159 Html.FROM_HTML_MODE_LEGACY)); 160 inControlMessage.setMovementMethod(LinkMovementMethod.getInstance()); 161 lessSecure.setText(getLessSecureMessage()); 162 163 // Set up and show the "require eyes" info section if necessary. 164 if (getResources().getBoolean(R.bool.config_face_intro_show_require_eyes)) { 165 final LinearLayout infoRowRequireEyes = findViewById(R.id.info_row_require_eyes); 166 final ImageView iconRequireEyes = findViewById(R.id.icon_require_eyes); 167 final TextView infoMessageRequireEyes = findViewById(R.id.info_message_require_eyes); 168 infoRowRequireEyes.setVisibility(View.VISIBLE); 169 iconRequireEyes.getBackground().setColorFilter(getIconColorFilter()); 170 infoMessageRequireEyes.setText(getInfoMessageRequireEyes()); 171 } 172 173 mFaceManager.addAuthenticatorsRegisteredCallback( 174 new IFaceAuthenticatorsRegisteredCallback.Stub() { 175 @Override 176 public void onAllAuthenticatorsRegistered( 177 @NonNull List<FaceSensorPropertiesInternal> sensors) { 178 if (sensors.isEmpty()) { 179 Log.e(TAG, "No sensors"); 180 return; 181 } 182 183 boolean isFaceStrong = sensors.get(0).sensorStrength 184 == SensorProperties.STRENGTH_STRONG; 185 mIsFaceStrong = isFaceStrong; 186 onFaceStrengthChanged(); 187 } 188 }); 189 190 // This path is an entry point for SetNewPasswordController, e.g. 191 // adb shell am start -a android.app.action.SET_NEW_PASSWORD 192 if (mToken == null && BiometricUtils.containsGatekeeperPasswordHandle(getIntent())) { 193 if (generateChallengeOnCreate()) { 194 mFooterBarMixin.getPrimaryButton().setEnabled(false); 195 // We either block on generateChallenge, or need to gray out the "next" button until 196 // the challenge is ready. Let's just do this for now. 197 mFaceManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> { 198 if (isFinishing()) { 199 // Do nothing if activity is finishing 200 Log.w(TAG, "activity finished before challenge callback launched."); 201 return; 202 } 203 204 try { 205 mToken = requestGatekeeperHat(challenge); 206 mSensorId = sensorId; 207 mChallenge = challenge; 208 mFooterBarMixin.getPrimaryButton().setEnabled(true); 209 } catch (GatekeeperCredentialNotMatchException e) { 210 // Let BiometricEnrollBase#onCreate() to trigger confirmLock() 211 getIntent().removeExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE); 212 recreate(); 213 } 214 }); 215 } 216 } 217 218 mSensorPrivacyManager = getApplicationContext() 219 .getSystemService(SensorPrivacyManager.class); 220 final SensorPrivacyManagerHelper helper = SensorPrivacyManagerHelper 221 .getInstance(getApplicationContext()); 222 final boolean cameraPrivacyEnabled = helper 223 .isSensorBlocked(SensorPrivacyManagerHelper.SENSOR_CAMERA); 224 Log.v(TAG, "cameraPrivacyEnabled : " + cameraPrivacyEnabled); 225 } 226 launchFaceSettingsActivity()227 private void launchFaceSettingsActivity() { 228 final Intent intent = new Intent(this, Settings.FaceSettingsInternalActivity.class); 229 final byte[] token = getIntent().getByteArrayExtra( 230 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN); 231 if (token != null) { 232 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token); 233 } 234 final int userId = getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId()); 235 if (userId != UserHandle.USER_NULL) { 236 intent.putExtra(Intent.EXTRA_USER_ID, userId); 237 } 238 BiometricUtils.copyMultiBiometricExtras(getIntent(), intent); 239 intent.putExtra(EXTRA_FROM_SETTINGS_SUMMARY, true); 240 intent.putExtra(EXTRA_KEY_CHALLENGE, getIntent().getLongExtra(EXTRA_KEY_CHALLENGE, -1L)); 241 intent.putExtra(EXTRA_KEY_SENSOR_ID, getIntent().getIntExtra(EXTRA_KEY_SENSOR_ID, -1)); 242 startActivity(intent); 243 } 244 245 @VisibleForTesting 246 @Nullable getFaceManager()247 protected FaceManager getFaceManager() { 248 return Utils.getFaceManagerOrNull(this); 249 } 250 251 @VisibleForTesting 252 @Nullable getPostureGuidanceIntent()253 protected Intent getPostureGuidanceIntent() { 254 return mPostureGuidanceIntent; 255 } 256 257 @VisibleForTesting 258 @Nullable getPostureCallback()259 protected FoldProvider.FoldCallback getPostureCallback() { 260 return mFoldCallback; 261 } 262 263 @VisibleForTesting 264 @BiometricUtils.DevicePostureInt getDevicePostureState()265 protected int getDevicePostureState() { 266 return mDevicePostureState; 267 } 268 269 @VisibleForTesting 270 @Nullable requestGatekeeperHat(long challenge)271 protected byte[] requestGatekeeperHat(long challenge) { 272 return BiometricUtils.requestGatekeeperHat(this, getIntent(), mUserId, challenge); 273 } 274 275 @Override onConfigurationChanged(@onNull Configuration newConfig)276 public void onConfigurationChanged(@NonNull Configuration newConfig) { 277 super.onConfigurationChanged(newConfig); 278 if (mScreenSizeFoldProvider != null && getPostureCallback() != null) { 279 mScreenSizeFoldProvider.onConfigurationChange(newConfig); 280 } 281 } 282 283 @Override onStart()284 protected void onStart() { 285 super.onStart(); 286 listenFoldEventForPostureGuidance(); 287 } 288 listenFoldEventForPostureGuidance()289 private void listenFoldEventForPostureGuidance() { 290 if (maxFacesEnrolled()) { 291 Log.d(TAG, "Device has enrolled face, do not show posture guidance"); 292 return; 293 } 294 295 if (getPostureGuidanceIntent() == null) { 296 Log.d(TAG, "Device do not support posture guidance"); 297 return; 298 } 299 300 BiometricUtils.setDevicePosturesAllowEnroll( 301 getResources().getInteger(R.integer.config_face_enroll_supported_posture)); 302 303 if (getPostureCallback() == null) { 304 mFoldCallback = isFolded -> { 305 mDevicePostureState = isFolded ? BiometricUtils.DEVICE_POSTURE_CLOSED 306 : BiometricUtils.DEVICE_POSTURE_OPENED; 307 if (BiometricUtils.shouldShowPostureGuidance(mDevicePostureState, 308 mLaunchedPostureGuidance) && !mNextLaunched) { 309 launchPostureGuidance(); 310 } 311 }; 312 } 313 314 if (mScreenSizeFoldProvider == null) { 315 mScreenSizeFoldProvider = new ScreenSizeFoldProvider(getApplicationContext()); 316 mScreenSizeFoldProvider.registerCallback(mFoldCallback, getMainExecutor()); 317 } 318 } 319 320 @Override onActivityResult(int requestCode, int resultCode, Intent data)321 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 322 if (requestCode == REQUEST_POSTURE_GUIDANCE) { 323 mLaunchedPostureGuidance = false; 324 if (resultCode == RESULT_CANCELED || resultCode == RESULT_SKIP) { 325 onSkipButtonClick(getCurrentFocus()); 326 } 327 return; 328 } 329 330 // If user has skipped or finished enrolling, don't restart enrollment. 331 final boolean isEnrollRequest = requestCode == BIOMETRIC_FIND_SENSOR_REQUEST 332 || requestCode == ENROLL_NEXT_BIOMETRIC_REQUEST; 333 final boolean isResultSkipOrFinished = resultCode == RESULT_SKIP 334 || resultCode == SetupSkipDialog.RESULT_SKIP || resultCode == RESULT_FINISHED; 335 boolean hasEnrolledFace = false; 336 if (data != null) { 337 hasEnrolledFace = data.getBooleanExtra(EXTRA_FINISHED_ENROLL_FACE, false); 338 } 339 340 if (resultCode == RESULT_CANCELED) { 341 if (hasEnrolledFace || !BiometricUtils.isPostureAllowEnrollment(mDevicePostureState)) { 342 setResult(resultCode, data); 343 finish(); 344 return; 345 } 346 } 347 348 if (isEnrollRequest && isResultSkipOrFinished || hasEnrolledFace) { 349 data = setSkipPendingEnroll(data); 350 } 351 super.onActivityResult(requestCode, resultCode, data); 352 } 353 generateChallengeOnCreate()354 protected boolean generateChallengeOnCreate() { 355 return true; 356 } 357 358 @StringRes getInfoMessageGlasses()359 protected int getInfoMessageGlasses() { 360 return R.string.security_settings_face_enroll_introduction_info_glasses; 361 } 362 363 @StringRes getInfoMessageLooking()364 protected int getInfoMessageLooking() { 365 return R.string.security_settings_face_enroll_introduction_info_looking; 366 } 367 368 @StringRes getInfoMessageRequireEyes()369 protected int getInfoMessageRequireEyes() { 370 return R.string.security_settings_face_enroll_introduction_info_gaze; 371 } 372 373 @StringRes getHowMessage()374 protected int getHowMessage() { 375 return R.string.security_settings_face_enroll_introduction_how_message; 376 } 377 378 @StringRes getInControlTitle()379 protected int getInControlTitle() { 380 return R.string.security_settings_face_enroll_introduction_control_title; 381 } 382 383 @StringRes getInControlMessage()384 protected int getInControlMessage() { 385 return R.string.security_settings_face_enroll_introduction_control_message; 386 } 387 388 @StringRes getLessSecureMessage()389 protected int getLessSecureMessage() { 390 return R.string.security_settings_face_enroll_introduction_info_less_secure; 391 } 392 393 @Override isDisabledByAdmin()394 protected boolean isDisabledByAdmin() { 395 return RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled( 396 this, DevicePolicyManager.KEYGUARD_DISABLE_FACE, mUserId) != null; 397 } 398 399 @Override getLayoutResource()400 protected int getLayoutResource() { 401 return R.layout.face_enroll_introduction; 402 } 403 404 @Override getHeaderResDisabledByAdmin()405 protected int getHeaderResDisabledByAdmin() { 406 return R.string.security_settings_face_enroll_introduction_title_unlock_disabled; 407 } 408 409 @Override getHeaderResDefault()410 protected int getHeaderResDefault() { 411 return R.string.security_settings_face_enroll_introduction_title; 412 } 413 414 @Override getDescriptionDisabledByAdmin()415 protected String getDescriptionDisabledByAdmin() { 416 DevicePolicyManager devicePolicyManager = getSystemService(DevicePolicyManager.class); 417 return devicePolicyManager.getResources().getString( 418 FACE_UNLOCK_DISABLED, 419 () -> getString(R.string.security_settings_face_enroll_introduction_message_unlock_disabled)); 420 } 421 422 @Override getCancelButton()423 protected FooterButton getCancelButton() { 424 if (mFooterBarMixin != null) { 425 return mFooterBarMixin.getSecondaryButton(); 426 } 427 return null; 428 } 429 430 @Override getNextButton()431 protected FooterButton getNextButton() { 432 if (mFooterBarMixin != null) { 433 return mFooterBarMixin.getPrimaryButton(); 434 } 435 return null; 436 } 437 438 @Override getErrorTextView()439 protected TextView getErrorTextView() { 440 return findViewById(R.id.error_text); 441 } 442 maxFacesEnrolled()443 private boolean maxFacesEnrolled() { 444 if (mFaceManager != null) { 445 // This will need to be updated for devices with multiple face sensors. 446 final int numEnrolledFaces = mFaceManager.getEnrolledFaces(mUserId).size(); 447 final int maxFacesEnrollable = getApplicationContext().getResources() 448 .getInteger(R.integer.suw_max_faces_enrollable); 449 return numEnrolledFaces >= maxFacesEnrollable; 450 } else { 451 return false; 452 } 453 } 454 455 //TODO: Refactor this to something that conveys it is used for getting a string ID. 456 @Override checkMaxEnrolled()457 protected int checkMaxEnrolled() { 458 if (mFaceManager != null) { 459 if (maxFacesEnrolled()) { 460 return R.string.face_intro_error_max; 461 } 462 } else { 463 return R.string.face_intro_error_unknown; 464 } 465 return 0; 466 } 467 468 @Override getChallenge(GenerateChallengeCallback callback)469 protected void getChallenge(GenerateChallengeCallback callback) { 470 mFaceManager = Utils.getFaceManagerOrNull(this); 471 if (mFaceManager == null) { 472 callback.onChallengeGenerated(0, 0, 0L); 473 return; 474 } 475 mFaceManager.generateChallenge(mUserId, callback::onChallengeGenerated); 476 } 477 478 @Override getExtraKeyForBiometric()479 protected String getExtraKeyForBiometric() { 480 return ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE; 481 } 482 483 @Override getEnrollingIntent()484 protected Intent getEnrollingIntent() { 485 Intent intent = new Intent(this, FaceEnrollEducation.class); 486 WizardManagerHelper.copyWizardManagerExtras(getIntent(), intent); 487 return intent; 488 } 489 490 @Override getConfirmLockTitleResId()491 protected int getConfirmLockTitleResId() { 492 return R.string.security_settings_face_preference_title; 493 } 494 495 @Override getMetricsCategory()496 public int getMetricsCategory() { 497 return SettingsEnums.FACE_ENROLL_INTRO; 498 } 499 500 @Override onClick(LinkSpan span)501 public void onClick(LinkSpan span) { 502 // TODO(b/110906762) 503 } 504 505 @Override getModality()506 public @BiometricAuthenticator.Modality int getModality() { 507 return BiometricAuthenticator.TYPE_FACE; 508 } 509 510 @Override onNextButtonClick(View view)511 protected void onNextButtonClick(View view) { 512 final boolean parentelConsentRequired = 513 getIntent() 514 .getBooleanExtra(BiometricEnrollActivity.EXTRA_REQUIRE_PARENTAL_CONSENT, false); 515 final boolean cameraPrivacyEnabled = SensorPrivacyManagerHelper 516 .getInstance(getApplicationContext()) 517 .isSensorBlocked(SensorPrivacyManagerHelper.SENSOR_CAMERA); 518 final boolean isSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent()); 519 final boolean isSettingUp = isSetupWizard || (parentelConsentRequired 520 && !WizardManagerHelper.isUserSetupComplete(this)); 521 if (cameraPrivacyEnabled && !isSettingUp) { 522 if (mSensorPrivacyManager == null) { 523 mSensorPrivacyManager = getApplicationContext() 524 .getSystemService(SensorPrivacyManager.class); 525 } 526 mSensorPrivacyManager.showSensorUseDialog(SensorPrivacyManager.Sensors.CAMERA); 527 } else { 528 super.onNextButtonClick(view); 529 } 530 } 531 532 @Override 533 @NonNull getPrimaryFooterButton()534 protected FooterButton getPrimaryFooterButton() { 535 if (mPrimaryFooterButton == null) { 536 mPrimaryFooterButton = new FooterButton.Builder(this) 537 .setText(R.string.security_settings_face_enroll_introduction_agree) 538 .setButtonType(FooterButton.ButtonType.OPT_IN) 539 .setListener(this::onNextButtonClick) 540 .setTheme(R.style.SudGlifButton_Primary) 541 .build(); 542 } 543 return mPrimaryFooterButton; 544 } 545 546 @Override 547 @NonNull getSecondaryFooterButton()548 protected FooterButton getSecondaryFooterButton() { 549 if (mSecondaryFooterButton == null) { 550 mSecondaryFooterButton = new FooterButton.Builder(this) 551 .setText(R.string.security_settings_face_enroll_introduction_no_thanks) 552 .setListener(this::onSkipButtonClick) 553 .setButtonType(FooterButton.ButtonType.NEXT) 554 .setTheme(R.style.SudGlifButton_Primary) 555 .build(); 556 } 557 return mSecondaryFooterButton; 558 } 559 560 @Override 561 @StringRes getAgreeButtonTextRes()562 protected int getAgreeButtonTextRes() { 563 return R.string.security_settings_fingerprint_enroll_introduction_agree; 564 } 565 566 @Override 567 @StringRes getMoreButtonTextRes()568 protected int getMoreButtonTextRes() { 569 return R.string.security_settings_face_enroll_introduction_more; 570 } 571 572 @Override updateDescriptionText()573 protected void updateDescriptionText() { 574 if (mIsFaceStrong) { 575 setDescriptionText(getString( 576 R.string.security_settings_face_enroll_introduction_message_class3)); 577 } 578 super.updateDescriptionText(); 579 } 580 581 @NonNull setSkipPendingEnroll(@ullable Intent data)582 protected static Intent setSkipPendingEnroll(@Nullable Intent data) { 583 if (data == null) { 584 data = new Intent(); 585 } 586 data.putExtra(MultiBiometricEnrollHelper.EXTRA_SKIP_PENDING_ENROLL, true); 587 return data; 588 } 589 isFaceStrong()590 protected boolean isFaceStrong() { 591 return mIsFaceStrong; 592 } 593 onFaceStrengthChanged()594 private void onFaceStrengthChanged() { 595 // Set up and show the "less secure" info section if necessary. 596 if (!mIsFaceStrong && getResources().getBoolean( 597 R.bool.config_face_intro_show_less_secure)) { 598 final LinearLayout infoRowLessSecure = findViewById(R.id.info_row_less_secure); 599 final ImageView iconLessSecure = findViewById(R.id.icon_less_secure); 600 infoRowLessSecure.setVisibility(View.VISIBLE); 601 iconLessSecure.getBackground().setColorFilter(getIconColorFilter()); 602 } 603 updateDescriptionText(); 604 } 605 } 606