1 /* 2 * Copyright (C) 2024 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 android.server.biometrics; 18 19 import static com.android.server.biometrics.nano.BiometricServiceStateProto.STATE_AUTH_IDLE; 20 21 import static com.google.common.truth.Truth.assertThat; 22 import static com.google.common.truth.Truth.assertWithMessage; 23 24 import static org.junit.Assert.assertThrows; 25 import static org.junit.Assume.assumeFalse; 26 import static org.junit.Assume.assumeTrue; 27 import static org.mockito.Mockito.mock; 28 29 import android.Manifest; 30 import android.content.DialogInterface; 31 import android.hardware.biometrics.BiometricPrompt; 32 import android.hardware.biometrics.BiometricTestSession; 33 import android.hardware.biometrics.PromptContentItemBulletedText; 34 import android.hardware.biometrics.PromptContentItemPlainText; 35 import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton; 36 import android.hardware.biometrics.PromptVerticalListContentView; 37 import android.hardware.biometrics.SensorProperties; 38 import android.os.CancellationSignal; 39 import android.os.Handler; 40 import android.os.Looper; 41 import android.platform.test.annotations.Presubmit; 42 import android.server.biometrics.util.Utils; 43 import android.util.Log; 44 45 import androidx.test.uiautomator.UiObject2; 46 47 import com.android.compatibility.common.util.ApiTest; 48 import com.android.compatibility.common.util.CddTest; 49 50 import org.junit.Test; 51 52 import java.util.ArrayList; 53 import java.util.List; 54 import java.util.Random; 55 import java.util.concurrent.CountDownLatch; 56 import java.util.concurrent.Executor; 57 import java.util.concurrent.TimeUnit; 58 59 /** 60 * Basic test cases for content view on biometric prompt. 61 */ 62 @Presubmit 63 public class BiometricPromptContentViewTest extends BiometricTestBase { 64 private static final String TAG = "BiometricTests/PromptContentView"; 65 private static final String VERTICAL_LIST_LAST_ITEM_TEXT = "last item"; 66 private static final String MORE_OPTIONS_BUTTON_VIEW = "customized_view_more_options_button"; 67 68 /** 69 * Tests that the values specified through the public APIs are shown on the BiometricPrompt UI 70 * when biometric auth is requested. When {@link BiometricPrompt.Builder#setContentView} is 71 * called with {@link PromptContentViewWithMoreOptionsButton}, 72 * {@link BiometricPrompt.Builder#setDescription} is overridden. 73 * 74 * Upon successful authentication, checks that the result is 75 * {@link BiometricPrompt#AUTHENTICATION_RESULT_TYPE_BIOMETRIC} 76 */ 77 @CddTest(requirements = {"7.3.10/C-4-2", "7.3.10/C-4-4"}) 78 @ApiTest(apis = { 79 "android.hardware.biometrics." 80 + "BiometricManager#canAuthenticate", 81 "android.hardware.biometrics." 82 + "BiometricPrompt.Builder#setTitle", 83 "android.hardware.biometrics." 84 + "BiometricPrompt.Builder#setSubtitle", 85 "android.hardware.biometrics." 86 + "BiometricPrompt.Builder#setContentView", 87 "android.hardware.biometrics." 88 + "BiometricPrompt.Builder#setNegativeButton", 89 "android.hardware.biometrics." 90 + "BiometricPrompt#authenticate", 91 "android.hardware.biometrics." 92 + "BiometricPrompt.AuthenticationCallback#onAuthenticationSucceeded", 93 "android.hardware.biometrics." 94 + "PromptContentViewWithMoreOptionsButton.Builder#setDescription", 95 "android.hardware.biometrics.PromptContentViewWithMoreOptionsButton" 96 + ".Builder#setMoreOptionsButtonListener"}) 97 @Test testMoreOptionsButton_simpleBiometricAuth()98 public void testMoreOptionsButton_simpleBiometricAuth() throws Exception { 99 assumeTrue(Utils.isFirstApiLevel29orGreater()); 100 for (SensorProperties props : mSensorProperties) { 101 if (props.getSensorStrength() == SensorProperties.STRENGTH_CONVENIENCE) { 102 continue; 103 } 104 105 Log.d(TAG, "testMoreOptionsButton_simpleBiometricAuth, sensor: " 106 + props.getSensorId()); 107 108 try (BiometricTestSession session = 109 mBiometricManager.createTestSession(props.getSensorId())) { 110 111 setUpNonConvenienceSensorEnrollment(props, session); 112 113 // Set up title, subtitle, description, negative button text. 114 final Random random = new Random(); 115 final String randomTitle = String.valueOf(random.nextInt(10000)); 116 final String randomSubtitle = String.valueOf(random.nextInt(10000)); 117 final String randomDescription = String.valueOf(random.nextInt(10000)); 118 final String randomNegativeButtonText = String.valueOf(random.nextInt(10000)); 119 120 // Set up content view with more options button. 121 final String randomContentViewDescription = 122 String.valueOf(random.nextInt(10000)); 123 final PromptContentViewWithMoreOptionsButton randomContentView = 124 createContentViewWithMoreOptionsButton(randomContentViewDescription); 125 // Show biometric prompt 126 BiometricPrompt.AuthenticationCallback callback = 127 mock(BiometricPrompt.AuthenticationCallback.class); 128 showDefaultBiometricPromptWithContents( 129 props.getSensorId(), 130 Utils.getUserId(), 131 true /* requireConfirmation */, 132 callback, 133 randomTitle, 134 randomSubtitle, 135 randomDescription, 136 randomContentView, 137 randomNegativeButtonText); 138 139 // Check all views except content view. 140 checkTopViews(true /*checkLogo*/, randomTitle, randomSubtitle, 141 randomNegativeButtonText); 142 // Check content view with more options button. 143 checkDescriptionViewInContentView(randomContentViewDescription); 144 checkMoreOptionsButton(false /*checkClickEvent*/); 145 146 // Finish auth 147 successfullyAuthenticate(session, Utils.getUserId(), callback); 148 } 149 } 150 } 151 152 /** 153 * Test the button click event on {@link PromptContentViewWithMoreOptionsButton} should dismiss 154 * BiometricPrompt UI. 155 */ 156 @ApiTest(apis = { 157 "android.hardware.biometrics.PromptContentViewWithMoreOptionsButton" 158 + ".Builder#setMoreOptionsButtonListener"}) 159 @Test testMoreOptionsButton_clickButton()160 public void testMoreOptionsButton_clickButton() throws Exception { 161 assumeTrue(Utils.isFirstApiLevel29orGreater()); 162 for (SensorProperties props : mSensorProperties) { 163 if (props.getSensorStrength() == SensorProperties.STRENGTH_CONVENIENCE) { 164 continue; 165 } 166 167 Log.d(TAG, "testMoreOptionsButton_clickButton, sensor: " 168 + props.getSensorId()); 169 170 try (BiometricTestSession session = 171 mBiometricManager.createTestSession(props.getSensorId())) { 172 173 setUpNonConvenienceSensorEnrollment(props, session); 174 175 final PromptContentViewWithMoreOptionsButton contentView = 176 createContentViewWithMoreOptionsButton(); 177 // Show biometric prompt 178 BiometricPrompt.AuthenticationCallback callback = 179 mock(BiometricPrompt.AuthenticationCallback.class); 180 showDefaultBiometricPromptWithContents( 181 props.getSensorId(), 182 Utils.getUserId(), 183 true /* requireConfirmation */, 184 callback, 185 "title", 186 "subtitle", 187 "description", 188 contentView, 189 "negative button"); 190 191 // Check content view with more options button. 192 checkMoreOptionsButton(true /*checkClickEvent*/); 193 } 194 } 195 } 196 197 /** 198 * Test without SET_BIOMETRIC_DIALOG_ADVANCED permission, authentication with 199 * {@link PromptContentViewWithMoreOptionsButton} should throw security exception. 200 */ 201 @ApiTest(apis = { 202 "android.hardware.biometrics.PromptContentViewWithMoreOptionsButton" 203 + ".Builder#setMoreOptionsButtonListener"}) 204 @Test testMoreOptionsButton_withoutPermissionException()205 public void testMoreOptionsButton_withoutPermissionException() throws Exception { 206 mInstrumentation.getUiAutomation().dropShellPermissionIdentity(); 207 mInstrumentation.getUiAutomation().adoptShellPermissionIdentity( 208 android.Manifest.permission.WAKE_LOCK, Manifest.permission.TEST_BIOMETRIC, 209 android.Manifest.permission.USE_BIOMETRIC); 210 211 assumeTrue(Utils.isFirstApiLevel29orGreater()); 212 for (SensorProperties props : mSensorProperties) { 213 if (props.getSensorStrength() == SensorProperties.STRENGTH_CONVENIENCE) { 214 continue; 215 } 216 217 Log.d(TAG, "testMoreOptionsButton_withoutPermissionException, sensor: " 218 + props.getSensorId()); 219 220 try (BiometricTestSession session = 221 mBiometricManager.createTestSession(props.getSensorId())) { 222 223 setUpNonConvenienceSensorEnrollment(props, session); 224 225 final PromptContentViewWithMoreOptionsButton contentView = 226 createContentViewWithMoreOptionsButton(); 227 228 SecurityException e = 229 assertThrows( 230 SecurityException.class, 231 () -> 232 showDefaultBiometricPromptWithContents( 233 props.getSensorId(), 234 Utils.getUserId(), 235 true /* requireConfirmation */, 236 mock(BiometricPrompt.AuthenticationCallback.class), 237 "title", 238 "subtitle", 239 "description", 240 contentView, 241 "negative button")); 242 243 assertThat(e).hasMessageThat().contains( 244 "android.permission.SET_BIOMETRIC_DIALOG_ADVANCED"); 245 } 246 } 247 } 248 249 /** 250 * Test without setting 251 * {@link PromptContentViewWithMoreOptionsButton.Builder#setMoreOptionsButtonListener(Executor, 252 * DialogInterface.OnClickListener)}, 253 * {@link PromptContentViewWithMoreOptionsButton.Builder#build()} should throw illegal argument 254 * exception. 255 */ 256 @ApiTest(apis = { 257 "android.hardware.biometrics.PromptContentViewWithMoreOptionsButton" 258 + ".Builder#setMoreOptionsButtonListener"}) 259 @Test testMoreOptionsButton_withoutSettingListenerException()260 public void testMoreOptionsButton_withoutSettingListenerException() throws Exception { 261 mInstrumentation.getUiAutomation().dropShellPermissionIdentity(); 262 mInstrumentation.getUiAutomation().adoptShellPermissionIdentity( 263 android.Manifest.permission.WAKE_LOCK, Manifest.permission.TEST_BIOMETRIC, 264 android.Manifest.permission.USE_BIOMETRIC); 265 266 assumeTrue(Utils.isFirstApiLevel29orGreater()); 267 for (SensorProperties props : mSensorProperties) { 268 if (props.getSensorStrength() == SensorProperties.STRENGTH_CONVENIENCE) { 269 continue; 270 } 271 272 Log.d(TAG, "testMoreOptionsButton_withoutSettingListenerException, sensor: " 273 + props.getSensorId()); 274 275 try (BiometricTestSession session = 276 mBiometricManager.createTestSession(props.getSensorId())) { 277 setUpNonConvenienceSensorEnrollment(props, session); 278 279 final PromptContentViewWithMoreOptionsButton.Builder contentViewBuilder = 280 new PromptContentViewWithMoreOptionsButton.Builder(); 281 282 assertThrows(IllegalArgumentException.class, contentViewBuilder::build); 283 } 284 } 285 } 286 287 /** 288 * Tests that if {@link PromptContentViewWithMoreOptionsButton} is set and device credential is 289 * the only available authenticator, the values specified through the public APIs are shown on 290 * the BiometricPrompt UI, and "More Options" button click should dismiss the UI. 291 */ 292 @ApiTest(apis = { 293 "android.hardware.biometrics.PromptContentViewWithMoreOptionsButton" 294 + ".Builder#setMoreOptionsButtonListener"}) 295 @Test testMoreOptionsButton_onlyCredential_clickButton()296 public void testMoreOptionsButton_onlyCredential_clickButton() throws Exception { 297 assumeTrue(Utils.isFirstApiLevel29orGreater()); 298 //TODO: b/331955301 need to update Auto biometric UI 299 assumeFalse(isCar()); 300 try (CredentialSession session = new CredentialSession()) { 301 session.setCredential(); 302 303 final Random random = new Random(); 304 final String randomTitle = String.valueOf(random.nextInt(10000)); 305 final String randomSubtitle = String.valueOf(random.nextInt(10000)); 306 final String randomDescription = String.valueOf(random.nextInt(10000)); 307 final String randomContentViewDescription = 308 String.valueOf(random.nextInt(10000)); 309 310 final PromptContentViewWithMoreOptionsButton contentView = 311 createContentViewWithMoreOptionsButton(randomContentViewDescription); 312 313 CountDownLatch latch = new CountDownLatch(1); 314 BiometricPrompt.AuthenticationCallback callback = 315 new BiometricPrompt.AuthenticationCallback() { 316 @Override 317 public void onAuthenticationSucceeded( 318 BiometricPrompt.AuthenticationResult result) { 319 assertWithMessage("Must be TYPE_CREDENTIAL").that( 320 result.getAuthenticationType()).isEqualTo( 321 BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL); 322 latch.countDown(); 323 } 324 }; 325 showCredentialOnlyBiometricPromptWithContents(callback, new CancellationSignal(), 326 true /* shouldShow */, randomTitle, randomSubtitle, randomDescription, 327 contentView); 328 329 // Check title, subtitle, description. 330 checkTopViews(false /*checkLogo*/, randomTitle, randomSubtitle, 331 null /*expectedNegativeButtonText*/); 332 // Check content view with more options button. 333 checkDescriptionViewInContentView(randomContentViewDescription); 334 checkMoreOptionsButton(true /*checkClickEvent*/); 335 } 336 } 337 338 /** 339 * Tests that the values specified through the public APIs are shown on the BiometricPrompt UI 340 * when biometric auth is requested. When {@link BiometricPrompt.Builder#setContentView} is 341 * called with {@link PromptVerticalListContentView}, 342 * {@link BiometricPrompt.Builder#setDescription} is overridden. 343 * 344 * Upon successful authentication, checks that the result is 345 * {@link BiometricPrompt#AUTHENTICATION_RESULT_TYPE_BIOMETRIC} 346 */ 347 @CddTest(requirements = {"7.3.10/C-4-2", "7.3.10/C-4-4"}) 348 @ApiTest(apis = { 349 "android.hardware.biometrics." 350 + "BiometricManager#canAuthenticate", 351 "android.hardware.biometrics." 352 + "BiometricPrompt.Builder#setTitle", 353 "android.hardware.biometrics." 354 + "BiometricPrompt.Builder#setSubtitle", 355 "android.hardware.biometrics." 356 + "BiometricPrompt.Builder#setContentView", 357 "android.hardware.biometrics." 358 + "BiometricPrompt.Builder#setNegativeButton", 359 "android.hardware.biometrics." 360 + "BiometricPrompt#authenticate", 361 "android.hardware.biometrics." 362 + "BiometricPrompt.AuthenticationCallback#onAuthenticationSucceeded", 363 "android.hardware.biometrics." 364 + "PromptVerticalListContentView.Builder#addListItem", 365 "android.hardware.biometrics." 366 + "PromptVerticalListContentView.Builder#setDescription"}) 367 @Test testVerticalList_simpleBiometricAuth()368 public void testVerticalList_simpleBiometricAuth() throws Exception { 369 assumeTrue(Utils.isFirstApiLevel29orGreater()); 370 for (SensorProperties props : mSensorProperties) { 371 if (props.getSensorStrength() == SensorProperties.STRENGTH_CONVENIENCE) { 372 continue; 373 } 374 375 Log.d(TAG, "testVerticalList_simpleBiometricAuth, sensor: " 376 + props.getSensorId()); 377 378 try (BiometricTestSession session = 379 mBiometricManager.createTestSession(props.getSensorId())) { 380 381 setUpNonConvenienceSensorEnrollment(props, session); 382 383 // Set up title, subtitle, description, negative button text. 384 final Random random = new Random(); 385 final String randomTitle = String.valueOf(random.nextInt(10000)); 386 final String randomSubtitle = String.valueOf(random.nextInt(10000)); 387 final String randomDescription = String.valueOf(random.nextInt(10000)); 388 final String randomNegativeButtonText = String.valueOf(random.nextInt(10000)); 389 390 // Set up vertical list content. 391 final String randomContentViewDescription = 392 String.valueOf(random.nextInt(10000)); 393 final PromptVerticalListContentView.Builder contentViewBuilder = 394 new PromptVerticalListContentView.Builder().setDescription( 395 randomContentViewDescription); 396 final List<String> randomContentItemTexts = addVerticalListItems(isWatch() ? 5 : 15, 397 10, contentViewBuilder); 398 final PromptVerticalListContentView randomContentView = contentViewBuilder.build(); 399 400 // Show biometric prompt 401 BiometricPrompt.AuthenticationCallback callback = 402 mock(BiometricPrompt.AuthenticationCallback.class); 403 showDefaultBiometricPromptWithContents( 404 props.getSensorId(), 405 Utils.getUserId(), 406 true /* requireConfirmation */, 407 callback, 408 randomTitle, 409 randomSubtitle, 410 randomDescription, 411 randomContentView, 412 randomNegativeButtonText); 413 414 // Check logo, title, subtitle, description, negative button. 415 checkTopViews(true /*checkLogo*/, randomTitle, randomSubtitle, 416 randomNegativeButtonText); 417 // Check vertical content view. 418 checkDescriptionViewInContentView(randomContentViewDescription); 419 checkVerticalListContentViewItems(randomContentItemTexts); 420 421 // Finish auth 422 successfullyAuthenticate(session, Utils.getUserId(), callback); 423 } 424 } 425 } 426 427 /** 428 * Tests that if {@link PromptVerticalListContentView} is set and device credential is the only 429 * available authenticator, two-step ui (biometric prompt without sensor and credential view) 430 * should show. 431 * 432 * Upon successful authentication, checks that the result is 433 * {@link BiometricPrompt#AUTHENTICATION_RESULT_TYPE_BIOMETRIC} 434 */ 435 @CddTest(requirements = {"7.3.10/C-4-2", "7.3.10/C-4-4"}) 436 @ApiTest(apis = { 437 "android.hardware.biometrics." 438 + "BiometricManager#canAuthenticate", 439 "android.hardware.biometrics." 440 + "BiometricPrompt#authenticate", 441 "android.hardware.biometrics." 442 + "BiometricPrompt.Builder#setContentView", 443 "android.hardware.biometrics." 444 + "PromptVerticalListContentView.Builder#addListItem"}) 445 @Test testVerticalList_onlyCredential_showsTwoStep()446 public void testVerticalList_onlyCredential_showsTwoStep() throws Exception { 447 assumeTrue(Utils.isFirstApiLevel29orGreater()); 448 //TODO: b/331955301 need to update Auto biometric UI 449 assumeFalse(isCar()); 450 try (CredentialSession session = new CredentialSession()) { 451 session.setCredential(); 452 453 final Random random = new Random(); 454 final String randomTitle = String.valueOf(random.nextInt(10000)); 455 final String randomSubtitle = String.valueOf(random.nextInt(10000)); 456 final String randomDescription = String.valueOf(random.nextInt(10000)); 457 458 CountDownLatch latch = new CountDownLatch(1); 459 BiometricPrompt.AuthenticationCallback callback = 460 new BiometricPrompt.AuthenticationCallback() { 461 @Override 462 public void onAuthenticationSucceeded( 463 BiometricPrompt.AuthenticationResult result) { 464 assertWithMessage("Must be TYPE_CREDENTIAL").that( 465 result.getAuthenticationType()).isEqualTo( 466 BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL); 467 latch.countDown(); 468 } 469 }; 470 showCredentialOnlyBiometricPromptWithContents(callback, new CancellationSignal(), 471 true /* shouldShow */, randomTitle, randomSubtitle, randomDescription, 472 new PromptVerticalListContentView.Builder().build()); 473 474 final UiObject2 actualTitle = findView(TITLE_VIEW); 475 final UiObject2 actualSubtitle = findView(SUBTITLE_VIEW); 476 final UiObject2 actualDescription = findView(DESCRIPTION_VIEW); 477 assertThat(actualTitle.getText()).isEqualTo(randomTitle); 478 assertWithMessage( 479 "Subtitle should be hidden on credential view with vertical list content set" 480 + ".").that( 481 actualSubtitle).isNull(); 482 assertWithMessage( 483 "Description should be hidden on credential view with vertical list content " 484 + "set.").that( 485 actualDescription).isNull(); 486 487 // Finish auth 488 successfullyEnterCredential(); 489 latch.await(3, TimeUnit.SECONDS); 490 } 491 } 492 createContentViewWithMoreOptionsButton()493 private PromptContentViewWithMoreOptionsButton createContentViewWithMoreOptionsButton() { 494 return createContentViewWithMoreOptionsButton(null); 495 } 496 createContentViewWithMoreOptionsButton( String contentViewDescription)497 private PromptContentViewWithMoreOptionsButton createContentViewWithMoreOptionsButton( 498 String contentViewDescription) { 499 final PromptContentViewWithMoreOptionsButton.Builder contentViewBuilder = 500 new PromptContentViewWithMoreOptionsButton.Builder(); 501 502 if (contentViewDescription != null) { 503 contentViewBuilder.setDescription(contentViewDescription); 504 } 505 506 final Handler handler = new Handler(Looper.getMainLooper()); 507 final Executor executor = handler::post; 508 final DialogInterface.OnClickListener listener = (dialog, which) -> { 509 // Do nothing. 510 }; 511 contentViewBuilder.setMoreOptionsButtonListener(executor, listener); 512 513 return contentViewBuilder.build(); 514 } 515 addVerticalListItems(int itemCountBesidesLastItem, int charNum, PromptVerticalListContentView.Builder contentViewBuilder)516 private List<String> addVerticalListItems(int itemCountBesidesLastItem, int charNum, 517 PromptVerticalListContentView.Builder contentViewBuilder) { 518 final Random random = new Random(); 519 final List<String> itemList = new ArrayList<>(); 520 521 for (int i = 0; i < itemCountBesidesLastItem; i++) { 522 final StringBuilder longString = new StringBuilder(charNum); 523 for (int j = 0; j < charNum; j++) { 524 longString.append(random.nextInt(10)); 525 } 526 itemList.add(longString.toString()); 527 } 528 itemList.forEach( 529 text -> contentViewBuilder.addListItem(new PromptContentItemBulletedText(text))); 530 531 itemList.add(VERTICAL_LIST_LAST_ITEM_TEXT); 532 // For testing API addListItem(PromptContentItem, int) 533 contentViewBuilder.addListItem( 534 new PromptContentItemPlainText(VERTICAL_LIST_LAST_ITEM_TEXT), 535 itemCountBesidesLastItem); 536 return itemList; 537 } 538 539 /** 540 * Check logo, title, subtitle, description, negative button. 541 */ checkTopViews(boolean checkLogo, String expectedTitle, String expectedSubtitle, String expectedNegativeButtonText)542 private void checkTopViews(boolean checkLogo, 543 String expectedTitle, String expectedSubtitle, String expectedNegativeButtonText) { 544 final UiObject2 actualLogo = waitForView(LOGO_VIEW); 545 final UiObject2 actualLogoDescription = findView(LOGO_DESCRIPTION_VIEW); 546 final UiObject2 actualTitle = findView(TITLE_VIEW); 547 final UiObject2 actualSubtitle = findView(SUBTITLE_VIEW); 548 final UiObject2 actualNegativeButton = findView(BUTTON_ID_NEGATIVE); 549 550 if (checkLogo) { 551 assertThat(actualLogo.getVisibleBounds()).isNotNull(); 552 assertThat(actualLogoDescription.getText()).isEqualTo("CtsBiometricsTestCases"); 553 } 554 assertThat(actualTitle.getText()).isEqualTo(expectedTitle); 555 assertThat(actualSubtitle.getText()).isEqualTo(expectedSubtitle); 556 if (expectedNegativeButtonText != null) { 557 assertThat(actualNegativeButton.getText()).isEqualTo(expectedNegativeButtonText); 558 } 559 } 560 561 /** 562 * Check description view shown on custom content view. Scroll the body content if needed. 563 * 564 * @param expectedDescription Expected description shown on custom content view. 565 */ checkDescriptionViewInContentView(String expectedDescription)566 private void checkDescriptionViewInContentView(String expectedDescription) { 567 final UiObject2 actualContentViewDescription = findViewByText(expectedDescription); 568 assertWithMessage("Description on content view should be shown.").that( 569 actualContentViewDescription).isNotNull(); 570 } 571 572 /** 573 * Check more options button shown on custom content view. Scroll the body content if needed. 574 * 575 * @param checkClickEvent Whether to check click event. 576 */ checkMoreOptionsButton(boolean checkClickEvent)577 private void checkMoreOptionsButton(boolean checkClickEvent) throws Exception { 578 final UiObject2 actualMoreOptionsButton = findView(MORE_OPTIONS_BUTTON_VIEW); 579 assertWithMessage("More options button should be clickable.").that( 580 actualMoreOptionsButton.isClickable()).isTrue(); 581 582 if (checkClickEvent) { 583 actualMoreOptionsButton.click(); 584 mInstrumentation.waitForIdleSync(); 585 // Clicking more options button should dismiss bp ui. 586 waitForState(STATE_AUTH_IDLE); 587 } 588 } 589 590 /** 591 * Check list items view shown on custom content view. Scroll the body content if needed. 592 * 593 * @param expectedContentItemTexts Expected list items shown on custom content view. 594 */ checkVerticalListContentViewItems( List<String> expectedContentItemTexts)595 private void checkVerticalListContentViewItems( 596 List<String> expectedContentItemTexts) { 597 for (String itemText : expectedContentItemTexts) { 598 final UiObject2 actualContentViewItem = findViewByText(itemText); 599 assertWithMessage("Item " + itemText + "should be shown").that( 600 actualContentViewItem).isNotNull(); 601 } 602 } 603 } 604