1 /* 2 * Copyright (C) 2023 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.biometrics2.ui.view; 18 19 import static android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL; 20 21 import android.annotation.RawRes; 22 import android.app.Activity; 23 import android.content.Context; 24 import android.content.res.Configuration; 25 import android.hardware.fingerprint.FingerprintManager; 26 import android.os.Bundle; 27 import android.text.TextUtils; 28 import android.util.DisplayMetrics; 29 import android.util.Log; 30 import android.view.LayoutInflater; 31 import android.view.Surface; 32 import android.view.View; 33 import android.view.ViewGroup; 34 import android.widget.Button; 35 import android.widget.ImageView; 36 import android.widget.RelativeLayout; 37 import android.widget.TextView; 38 39 import androidx.activity.OnBackPressedCallback; 40 import androidx.annotation.NonNull; 41 import androidx.fragment.app.Fragment; 42 import androidx.fragment.app.FragmentActivity; 43 import androidx.lifecycle.Observer; 44 import androidx.lifecycle.ViewModelProvider; 45 46 import com.android.settings.R; 47 import com.android.settings.biometrics.BiometricUtils; 48 import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog; 49 import com.android.settings.biometrics2.ui.model.EnrollmentProgress; 50 import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage; 51 import com.android.settings.biometrics2.ui.viewmodel.DeviceRotationViewModel; 52 import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel; 53 import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel; 54 import com.android.settings.biometrics2.ui.widget.UdfpsEnrollView; 55 import com.android.settingslib.display.DisplayDensityUtils; 56 57 import com.airbnb.lottie.LottieAnimationView; 58 import com.airbnb.lottie.LottieCompositionFactory; 59 60 /** 61 * Fragment is used to handle enrolling process for udfps 62 */ 63 public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment { 64 65 private static final String TAG = FingerprintEnrollEnrollingUdfpsFragment.class.getSimpleName(); 66 67 private static final int PROGRESS_BAR_MAX = 10000; 68 69 private static final int STAGE_UNKNOWN = -1; 70 private static final int STAGE_CENTER = 0; 71 private static final int STAGE_GUIDED = 1; 72 private static final int STAGE_FINGERTIP = 2; 73 private static final int STAGE_LEFT_EDGE = 3; 74 private static final int STAGE_RIGHT_EDGE = 4; 75 76 private FingerprintEnrollEnrollingViewModel mEnrollingViewModel; 77 private DeviceRotationViewModel mRotationViewModel; 78 private FingerprintEnrollProgressViewModel mProgressViewModel; 79 80 private LottieAnimationView mIllustrationLottie; 81 private boolean mHaveShownUdfpsTipLottie; 82 private boolean mHaveShownUdfpsLeftEdgeLottie; 83 private boolean mHaveShownUdfpsRightEdgeLottie; 84 private boolean mHaveShownUdfpsCenterLottie; 85 private boolean mHaveShownUdfpsGuideLottie; 86 87 private TextView mTitleText; 88 private TextView mSubTitleText; 89 private UdfpsEnrollView mUdfpsEnrollView; 90 private Button mSkipBtn; 91 private ImageView mIcon; 92 93 private boolean mShouldShowLottie; 94 private boolean mIsAccessibilityEnabled; 95 96 private int mRotation = -1; 97 98 private final View.OnClickListener mOnSkipClickListener = 99 (v) -> mEnrollingViewModel.onCancelledDueToOnSkipPressed(); 100 101 private final Observer<EnrollmentProgress> mProgressObserver = progress -> { 102 if (progress != null) { 103 onEnrollmentProgressChange(progress); 104 } 105 }; 106 private final Observer<EnrollmentStatusMessage> mHelpMessageObserver = helpMessage -> { 107 if (helpMessage != null) { 108 onEnrollmentHelp(helpMessage); 109 } 110 }; 111 private final Observer<EnrollmentStatusMessage> mErrorMessageObserver = errorMessage -> { 112 if (errorMessage != null) { 113 onEnrollmentError(errorMessage); 114 } 115 }; 116 private final Observer<Boolean> mAcquireObserver = isAcquiredGood -> { 117 if (isAcquiredGood != null) { 118 onAcquired(isAcquiredGood); 119 } 120 }; 121 private final Observer<Integer> mPointerDownObserver = sensorId -> { 122 if (sensorId != null) { 123 onPointerDown(sensorId); 124 } 125 }; 126 private final Observer<Integer> mPointerUpObserver = sensorId -> { 127 if (sensorId != null) { 128 onPointerUp(sensorId); 129 } 130 }; 131 132 private final Observer<Integer> mRotationObserver = rotation -> { 133 if (rotation != null) { 134 onRotationChanged(rotation); 135 } 136 }; 137 138 private final OnBackPressedCallback mOnBackPressedCallback = new OnBackPressedCallback(true) { 139 @Override 140 public void handleOnBackPressed() { 141 setEnabled(false); 142 mEnrollingViewModel.setOnBackPressed(); 143 cancelEnrollment(); 144 } 145 }; 146 147 @Override onAttach(@onNull Context context)148 public void onAttach(@NonNull Context context) { 149 final FragmentActivity activity = getActivity(); 150 final ViewModelProvider provider = new ViewModelProvider(activity); 151 mEnrollingViewModel = provider.get(FingerprintEnrollEnrollingViewModel.class); 152 mRotationViewModel = provider.get(DeviceRotationViewModel.class); 153 mProgressViewModel = provider.get(FingerprintEnrollProgressViewModel.class); 154 super.onAttach(context); 155 activity.getOnBackPressedDispatcher().addCallback(mOnBackPressedCallback); 156 } 157 158 @Override onDetach()159 public void onDetach() { 160 mOnBackPressedCallback.setEnabled(false); 161 super.onDetach(); 162 } 163 164 @Override onCreate(Bundle savedInstanceState)165 public void onCreate(Bundle savedInstanceState) { 166 super.onCreate(savedInstanceState); 167 mIsAccessibilityEnabled = mEnrollingViewModel.isAccessibilityEnabled(); 168 } 169 170 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)171 public View onCreateView(LayoutInflater inflater, ViewGroup container, 172 Bundle savedInstanceState) { 173 final RelativeLayout containView = (RelativeLayout) inflater.inflate( 174 R.layout.udfps_enroll_enrolling_v2, container, false); 175 176 final Activity activity = getActivity(); 177 mIcon = containView.findViewById(R.id.sud_layout_icon); 178 mTitleText = containView.findViewById(R.id.suc_layout_title); 179 mSubTitleText = containView.findViewById(R.id.sud_layout_subtitle); 180 mSkipBtn = containView.findViewById(R.id.skip_btn); 181 mSkipBtn.setOnClickListener(mOnSkipClickListener); 182 mUdfpsEnrollView = containView.findViewById(R.id.udfps_animation_view); 183 mUdfpsEnrollView.setSensorProperties( 184 mEnrollingViewModel.getFirstFingerprintSensorPropertiesInternal()); 185 mShouldShowLottie = shouldShowLottie(); 186 final boolean isLandscape = BiometricUtils.isReverseLandscape(activity) 187 || BiometricUtils.isLandscape(activity); 188 updateOrientation(containView, (isLandscape 189 ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT)); 190 191 mRotation = mRotationViewModel.getLiveData().getValue(); 192 configLayout(mRotation); 193 return containView; 194 } 195 196 @Override onStart()197 public void onStart() { 198 super.onStart(); 199 startEnrollment(); 200 updateProgress(false /* animate */, mProgressViewModel.getProgressLiveData().getValue()); 201 final EnrollmentStatusMessage msg = mProgressViewModel.getHelpMessageLiveData().getValue(); 202 if (msg != null) { 203 onEnrollmentHelp(msg); 204 } else { 205 updateTitleAndDescription(); 206 } 207 } 208 209 @Override onResume()210 public void onResume() { 211 super.onResume(); 212 mRotationViewModel.getLiveData().observe(this, mRotationObserver); 213 } 214 215 @Override onPause()216 public void onPause() { 217 mRotationViewModel.getLiveData().removeObserver(mRotationObserver); 218 super.onPause(); 219 } 220 221 @Override onStop()222 public void onStop() { 223 removeEnrollmentObservers(); 224 if (!getActivity().isChangingConfigurations() && mProgressViewModel.isEnrolling()) { 225 mProgressViewModel.cancelEnrollment(); 226 } 227 super.onStop(); 228 } 229 removeEnrollmentObservers()230 private void removeEnrollmentObservers() { 231 preRemoveEnrollmentObservers(); 232 mProgressViewModel.getErrorMessageLiveData().removeObserver(mErrorMessageObserver); 233 } 234 preRemoveEnrollmentObservers()235 private void preRemoveEnrollmentObservers() { 236 mProgressViewModel.getProgressLiveData().removeObserver(mProgressObserver); 237 mProgressViewModel.getHelpMessageLiveData().removeObserver(mHelpMessageObserver); 238 mProgressViewModel.getAcquireLiveData().removeObserver(mAcquireObserver); 239 mProgressViewModel.getPointerDownLiveData().removeObserver(mPointerDownObserver); 240 mProgressViewModel.getPointerUpLiveData().removeObserver(mPointerUpObserver); 241 } 242 cancelEnrollment()243 private void cancelEnrollment() { 244 preRemoveEnrollmentObservers(); 245 mProgressViewModel.cancelEnrollment(); 246 } 247 startEnrollment()248 private void startEnrollment() { 249 final boolean startResult = mProgressViewModel.startEnrollment(ENROLL_ENROLL); 250 if (!startResult) { 251 Log.e(TAG, "startEnrollment(), failed"); 252 } 253 mProgressViewModel.getProgressLiveData().observe(this, mProgressObserver); 254 mProgressViewModel.getHelpMessageLiveData().observe(this, mHelpMessageObserver); 255 mProgressViewModel.getErrorMessageLiveData().observe(this, mErrorMessageObserver); 256 mProgressViewModel.getAcquireLiveData().observe(this, mAcquireObserver); 257 mProgressViewModel.getPointerDownLiveData().observe(this, mPointerDownObserver); 258 mProgressViewModel.getPointerUpLiveData().observe(this, mPointerUpObserver); 259 } 260 updateProgress(boolean animate, @NonNull EnrollmentProgress enrollmentProgress)261 private void updateProgress(boolean animate, @NonNull EnrollmentProgress enrollmentProgress) { 262 if (!mProgressViewModel.isEnrolling()) { 263 Log.d(TAG, "Enrollment not started yet"); 264 return; 265 } 266 267 final int progress = getProgress(enrollmentProgress); 268 269 if (mProgressViewModel.getProgressLiveData().getValue().getSteps() != -1) { 270 mUdfpsEnrollView.onEnrollmentProgress(enrollmentProgress.getRemaining(), 271 enrollmentProgress.getSteps()); 272 } 273 274 if (animate) { 275 animateProgress(progress); 276 } else if (progress >= PROGRESS_BAR_MAX) { 277 mDelayedFinishRunnable.run(); 278 } 279 } 280 getProgress(@onNull EnrollmentProgress progress)281 private int getProgress(@NonNull EnrollmentProgress progress) { 282 if (progress.getSteps() == -1) { 283 return 0; 284 } 285 int displayProgress = Math.max(0, progress.getSteps() + 1 - progress.getRemaining()); 286 return PROGRESS_BAR_MAX * displayProgress / (progress.getSteps() + 1); 287 } 288 animateProgress(int progress)289 private void animateProgress(int progress) { 290 // UDFPS animations are owned by SystemUI 291 if (progress >= PROGRESS_BAR_MAX) { 292 // Wait for any animations in SysUI to finish, then proceed to next page 293 getActivity().getMainThreadHandler().postDelayed(mDelayedFinishRunnable, 400L); 294 } 295 } 296 updateTitleAndDescription()297 private void updateTitleAndDescription() { 298 switch (getCurrentStage()) { 299 case STAGE_CENTER: 300 mTitleText.setText(R.string.security_settings_fingerprint_enroll_repeat_title); 301 if (mIsAccessibilityEnabled || mIllustrationLottie == null) { 302 mSubTitleText.setText(R.string.security_settings_udfps_enroll_start_message); 303 } else if (!mHaveShownUdfpsCenterLottie) { 304 mHaveShownUdfpsCenterLottie = true; 305 // Note: Update string reference when differentiate in between udfps & sfps 306 mIllustrationLottie.setContentDescription( 307 getString(R.string.security_settings_sfps_enroll_finger_center_title) 308 ); 309 configureEnrollmentStage(R.raw.udfps_center_hint_lottie); 310 } 311 break; 312 313 case STAGE_GUIDED: 314 mTitleText.setText(R.string.security_settings_fingerprint_enroll_repeat_title); 315 if (mIsAccessibilityEnabled || mIllustrationLottie == null) { 316 mSubTitleText.setText( 317 R.string.security_settings_udfps_enroll_repeat_a11y_message); 318 } else if (!mHaveShownUdfpsGuideLottie) { 319 mHaveShownUdfpsGuideLottie = true; 320 mIllustrationLottie.setContentDescription( 321 getString(R.string.security_settings_fingerprint_enroll_repeat_message) 322 ); 323 // TODO(b/228100413) Could customize guided lottie animation 324 configureEnrollmentStage(R.raw.udfps_center_hint_lottie); 325 } 326 break; 327 case STAGE_FINGERTIP: 328 mTitleText.setText(R.string.security_settings_udfps_enroll_fingertip_title); 329 if (!mHaveShownUdfpsTipLottie && mIllustrationLottie != null) { 330 mHaveShownUdfpsTipLottie = true; 331 mIllustrationLottie.setContentDescription( 332 getString(R.string.security_settings_udfps_tip_fingerprint_help) 333 ); 334 configureEnrollmentStage(R.raw.udfps_tip_hint_lottie); 335 } 336 break; 337 case STAGE_LEFT_EDGE: 338 mTitleText.setText(R.string.security_settings_udfps_enroll_left_edge_title); 339 if (!mHaveShownUdfpsLeftEdgeLottie && mIllustrationLottie != null) { 340 mHaveShownUdfpsLeftEdgeLottie = true; 341 mIllustrationLottie.setContentDescription( 342 getString(R.string.security_settings_udfps_side_fingerprint_help) 343 ); 344 configureEnrollmentStage(R.raw.udfps_left_edge_hint_lottie); 345 } else if (mIllustrationLottie == null) { 346 if (isStageHalfCompleted()) { 347 mSubTitleText.setText( 348 R.string.security_settings_fingerprint_enroll_repeat_message); 349 } else { 350 mSubTitleText.setText(R.string.security_settings_udfps_enroll_edge_message); 351 } 352 } 353 break; 354 case STAGE_RIGHT_EDGE: 355 mTitleText.setText(R.string.security_settings_udfps_enroll_right_edge_title); 356 if (!mHaveShownUdfpsRightEdgeLottie && mIllustrationLottie != null) { 357 mHaveShownUdfpsRightEdgeLottie = true; 358 mIllustrationLottie.setContentDescription( 359 getString(R.string.security_settings_udfps_side_fingerprint_help) 360 ); 361 configureEnrollmentStage(R.raw.udfps_right_edge_hint_lottie); 362 363 } else if (mIllustrationLottie == null) { 364 if (isStageHalfCompleted()) { 365 mSubTitleText.setText( 366 R.string.security_settings_fingerprint_enroll_repeat_message); 367 } else { 368 mSubTitleText.setText(R.string.security_settings_udfps_enroll_edge_message); 369 } 370 } 371 break; 372 373 case STAGE_UNKNOWN: 374 default: 375 mTitleText.setText(R.string.security_settings_fingerprint_enroll_udfps_title); 376 mSubTitleText.setText(R.string.security_settings_udfps_enroll_start_message); 377 final CharSequence description = getString( 378 R.string.security_settings_udfps_enroll_a11y); 379 getActivity().setTitle(description); 380 break; 381 } 382 } 383 shouldShowLottie()384 private boolean shouldShowLottie() { 385 DisplayDensityUtils displayDensity = new DisplayDensityUtils(getContext()); 386 int currentDensityIndex = displayDensity.getCurrentIndexForDefaultDisplay(); 387 final int currentDensity = displayDensity.getDefaultDisplayDensityValues() 388 [currentDensityIndex]; 389 final int defaultDensity = displayDensity.getDefaultDensityForDefaultDisplay(); 390 return defaultDensity == currentDensity; 391 } 392 updateOrientation(@onNull RelativeLayout content, int orientation)393 private void updateOrientation(@NonNull RelativeLayout content, int orientation) { 394 switch (orientation) { 395 case Configuration.ORIENTATION_LANDSCAPE: { 396 mIllustrationLottie = null; 397 break; 398 } 399 case Configuration.ORIENTATION_PORTRAIT: { 400 if (mShouldShowLottie) { 401 mIllustrationLottie = content.findViewById(R.id.illustration_lottie); 402 } 403 break; 404 } 405 default: 406 Log.e(TAG, "Error unhandled configuration change"); 407 break; 408 } 409 } 410 getCurrentStage()411 private int getCurrentStage() { 412 EnrollmentProgress progressLiveData = mProgressViewModel.getProgressLiveData().getValue(); 413 414 if (progressLiveData == null || progressLiveData.getSteps() == -1) { 415 return STAGE_UNKNOWN; 416 } 417 418 final int progressSteps = progressLiveData.getSteps() - progressLiveData.getRemaining(); 419 if (progressSteps < getStageThresholdSteps(0)) { 420 return STAGE_CENTER; 421 } else if (progressSteps < getStageThresholdSteps(1)) { 422 return STAGE_GUIDED; 423 } else if (progressSteps < getStageThresholdSteps(2)) { 424 return STAGE_FINGERTIP; 425 } else if (progressSteps < getStageThresholdSteps(3)) { 426 return STAGE_LEFT_EDGE; 427 } else { 428 return STAGE_RIGHT_EDGE; 429 } 430 } 431 isStageHalfCompleted()432 private boolean isStageHalfCompleted() { 433 EnrollmentProgress progressLiveData = mProgressViewModel.getProgressLiveData().getValue(); 434 if (progressLiveData == null || progressLiveData.getSteps() == -1) { 435 return false; 436 } 437 438 final int progressSteps = progressLiveData.getSteps() - progressLiveData.getRemaining(); 439 int prevThresholdSteps = 0; 440 for (int i = 0; i < mEnrollingViewModel.getEnrollStageCount(); i++) { 441 final int thresholdSteps = getStageThresholdSteps(i); 442 if (progressSteps >= prevThresholdSteps && progressSteps < thresholdSteps) { 443 final int adjustedProgress = progressSteps - prevThresholdSteps; 444 final int adjustedThreshold = thresholdSteps - prevThresholdSteps; 445 return adjustedProgress >= adjustedThreshold / 2; 446 } 447 prevThresholdSteps = thresholdSteps; 448 } 449 450 // After last enrollment step. 451 return true; 452 } 453 getStageThresholdSteps(int index)454 private int getStageThresholdSteps(int index) { 455 456 EnrollmentProgress progressLiveData = mProgressViewModel.getProgressLiveData().getValue(); 457 458 if (progressLiveData == null || progressLiveData.getSteps() == -1) { 459 Log.w(TAG, "getStageThresholdSteps: Enrollment not started yet"); 460 return 1; 461 } 462 return Math.round(progressLiveData.getSteps() 463 * mEnrollingViewModel.getEnrollStageThreshold(index)); 464 } 465 configureEnrollmentStage(@awRes int lottie)466 private void configureEnrollmentStage(@RawRes int lottie) { 467 mSubTitleText.setText(""); 468 LottieCompositionFactory.fromRawRes(getActivity(), lottie) 469 .addListener((c) -> { 470 mIllustrationLottie.setComposition(c); 471 mIllustrationLottie.setVisibility(View.VISIBLE); 472 mIllustrationLottie.playAnimation(); 473 }); 474 } 475 onEnrollmentProgressChange(@onNull EnrollmentProgress progress)476 private void onEnrollmentProgressChange(@NonNull EnrollmentProgress progress) { 477 updateProgress(true /* animate */, progress); 478 479 updateTitleAndDescription(); 480 481 if (mIsAccessibilityEnabled) { 482 final int steps = progress.getSteps(); 483 final int remaining = progress.getRemaining(); 484 final int percent = (int) (((float) (steps - remaining) / (float) steps) * 100); 485 CharSequence announcement = getActivity().getString( 486 R.string.security_settings_udfps_enroll_progress_a11y_message, percent); 487 mEnrollingViewModel.sendAccessibilityEvent(announcement); 488 } 489 490 } 491 onEnrollmentHelp(@onNull EnrollmentStatusMessage helpMessage)492 private void onEnrollmentHelp(@NonNull EnrollmentStatusMessage helpMessage) { 493 final CharSequence helpStr = helpMessage.getStr(); 494 if (!TextUtils.isEmpty(helpStr)) { 495 showError(helpStr); 496 mUdfpsEnrollView.onEnrollmentHelp(); 497 } 498 } onEnrollmentError(@onNull EnrollmentStatusMessage errorMessage)499 private void onEnrollmentError(@NonNull EnrollmentStatusMessage errorMessage) { 500 removeEnrollmentObservers(); 501 502 if (mEnrollingViewModel.getOnBackPressed() 503 && errorMessage.getMsgId() == FingerprintManager.FINGERPRINT_ERROR_CANCELED) { 504 mEnrollingViewModel.onCancelledDueToOnBackPressed(); 505 } else if (mEnrollingViewModel.getOnSkipPressed() 506 && errorMessage.getMsgId() == FingerprintManager.FINGERPRINT_ERROR_CANCELED) { 507 mEnrollingViewModel.onCancelledDueToOnSkipPressed(); 508 } else { 509 final int errMsgId = errorMessage.getMsgId(); 510 mEnrollingViewModel.showErrorDialog( 511 new FingerprintEnrollEnrollingViewModel.ErrorDialogData( 512 getString(FingerprintErrorDialog.getErrorMessage(errMsgId)), 513 getString(FingerprintErrorDialog.getErrorTitle(errMsgId)), 514 errMsgId 515 )); 516 mProgressViewModel.cancelEnrollment(); 517 } 518 } 519 onAcquired(boolean isAcquiredGood)520 private void onAcquired(boolean isAcquiredGood) { 521 if (mUdfpsEnrollView != null) { 522 mUdfpsEnrollView.onAcquired(isAcquiredGood); 523 } 524 } 525 onPointerDown(int sensorId)526 private void onPointerDown(int sensorId) { 527 if (mUdfpsEnrollView != null) { 528 mUdfpsEnrollView.onPointerDown(sensorId); 529 } 530 } 531 onPointerUp(int sensorId)532 private void onPointerUp(int sensorId) { 533 if (mUdfpsEnrollView != null) { 534 mUdfpsEnrollView.onPointerUp(sensorId); 535 } 536 } 537 showError(CharSequence error)538 private void showError(CharSequence error) { 539 mTitleText.setText(error); 540 mTitleText.setContentDescription(error); 541 mSubTitleText.setContentDescription(""); 542 } 543 onRotationChanged(int newRotation)544 private void onRotationChanged(int newRotation) { 545 if( (newRotation +2) % 4 == mRotation) { 546 mRotation = newRotation; 547 configLayout(newRotation); 548 } 549 } 550 configLayout(int newRotation)551 private void configLayout(int newRotation) { 552 final Activity activity = getActivity(); 553 if (newRotation == Surface.ROTATION_270) { 554 RelativeLayout.LayoutParams iconLP = new RelativeLayout.LayoutParams(-2, -2); 555 iconLP.addRule(RelativeLayout.ALIGN_PARENT_TOP); 556 iconLP.addRule(RelativeLayout.END_OF, R.id.udfps_animation_view); 557 iconLP.topMargin = (int) convertDpToPixel(76.64f, activity); 558 iconLP.leftMargin = (int) convertDpToPixel(151.54f, activity); 559 mIcon.setLayoutParams(iconLP); 560 561 RelativeLayout.LayoutParams titleLP = new RelativeLayout.LayoutParams(-1, -2); 562 titleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP); 563 titleLP.addRule(RelativeLayout.END_OF, R.id.udfps_animation_view); 564 titleLP.topMargin = (int) convertDpToPixel(138f, activity); 565 titleLP.leftMargin = (int) convertDpToPixel(144f, activity); 566 mTitleText.setLayoutParams(titleLP); 567 568 RelativeLayout.LayoutParams subtitleLP = new RelativeLayout.LayoutParams(-1, -2); 569 subtitleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP); 570 subtitleLP.addRule(RelativeLayout.END_OF, R.id.udfps_animation_view); 571 subtitleLP.topMargin = (int) convertDpToPixel(198f, activity); 572 subtitleLP.leftMargin = (int) convertDpToPixel(144f, activity); 573 mSubTitleText.setLayoutParams(subtitleLP); 574 } else if (newRotation == Surface.ROTATION_90) { 575 DisplayMetrics metrics = activity.getResources().getDisplayMetrics(); 576 RelativeLayout.LayoutParams iconLP = new RelativeLayout.LayoutParams(-2, -2); 577 iconLP.addRule(RelativeLayout.ALIGN_PARENT_TOP); 578 iconLP.addRule(RelativeLayout.ALIGN_PARENT_START); 579 iconLP.topMargin = (int) convertDpToPixel(76.64f, activity); 580 iconLP.leftMargin = (int) convertDpToPixel(71.99f, activity); 581 mIcon.setLayoutParams(iconLP); 582 583 RelativeLayout.LayoutParams titleLP = new RelativeLayout.LayoutParams( 584 metrics.widthPixels / 2, -2); 585 titleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP); 586 titleLP.addRule(RelativeLayout.ALIGN_PARENT_START, R.id.udfps_animation_view); 587 titleLP.topMargin = (int) convertDpToPixel(138f, activity); 588 titleLP.leftMargin = (int) convertDpToPixel(66f, activity); 589 mTitleText.setLayoutParams(titleLP); 590 591 RelativeLayout.LayoutParams subtitleLP = new RelativeLayout.LayoutParams( 592 metrics.widthPixels / 2, -2); 593 subtitleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP); 594 subtitleLP.addRule(RelativeLayout.ALIGN_PARENT_START); 595 subtitleLP.topMargin = (int) convertDpToPixel(198f, activity); 596 subtitleLP.leftMargin = (int) convertDpToPixel(66f, activity); 597 mSubTitleText.setLayoutParams(subtitleLP); 598 } 599 600 if (newRotation == Surface.ROTATION_90 || newRotation == Surface.ROTATION_270) { 601 RelativeLayout.LayoutParams skipBtnLP = 602 (RelativeLayout.LayoutParams) mSkipBtn.getLayoutParams(); 603 skipBtnLP.topMargin = (int) convertDpToPixel(26f, activity); 604 skipBtnLP.leftMargin = (int) convertDpToPixel(54f, activity); 605 mSkipBtn.requestLayout(); 606 } 607 } 608 convertDpToPixel(float dp, Context context)609 private float convertDpToPixel(float dp, Context context) { 610 return dp * getDensity(context); 611 } 612 getDensity(Context context)613 private float getDensity(Context context) { 614 DisplayMetrics metrics = context.getResources().getDisplayMetrics(); 615 return metrics.density; 616 } 617 618 // Give the user a chance to see progress completed before jumping to the next stage. 619 private final Runnable mDelayedFinishRunnable = () -> mEnrollingViewModel.onEnrollingDone(); 620 } 621