• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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