1 /*
2  * Copyright (C) 2019 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.preference.cts;
18 
19 import static android.preference.PreferenceActivity.EXTRA_NO_HEADERS;
20 import static android.preference.PreferenceActivity.EXTRA_SHOW_FRAGMENT;
21 import static android.preference.PreferenceActivity.EXTRA_SHOW_FRAGMENT_TITLE;
22 
23 import static org.junit.Assert.assertEquals;
24 import static org.junit.Assert.assertFalse;
25 import static org.junit.Assert.assertTrue;
26 import static org.junit.Assert.fail;
27 import static org.junit.Assume.assumeTrue;
28 
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.pm.PackageManager;
32 import android.graphics.Bitmap;
33 import android.os.Environment;
34 import android.util.Log;
35 
36 import androidx.test.platform.app.InstrumentationRegistry;
37 
38 import com.android.compatibility.common.util.BitmapUtils;
39 
40 import org.junit.Rule;
41 import org.junit.rules.TestName;
42 
43 import java.io.File;
44 import java.io.IOException;
45 
46 /**
47  * This test suite covers {@link android.preference.PreferenceActivity} to ensure its correct
48  * behavior in orientation and multi-window changes together with navigation between different
49  * screens and going back in the history. It should also cover any possible transition between
50  * single and multi pane modes. Some tests are designed to run only on large or small screen devices
51  * and some can run both. These tests are run from {@link PreferenceActivityFlowLandscapeTest} and
52  * {@link PreferenceActivityFlowPortraitTest} to ensure that both display configurations are
53  * checked.
54  */
55 public abstract class PreferenceActivityFlowTest {
56 
57     private static final String TAG = "PreferenceFlowTest";
58 
59     // Helper strings to ensure that some parts of preferences are visible or not.
60     private static final String PREFS1_HEADER_TITLE = "Prefs 1";
61     private static final String PREFS2_HEADER_TITLE = "Prefs 2";
62     private static final String PREFS1_PANEL_TITLE = "Preferences panel 1";
63     private static final String PREFS2_PANEL_TITLE = "Preferences panel 2";
64     private static final String INNER_FRAGMENT_PREF_BUTTON = "Fragment preference";
65     private static final String INNER_FRAGMENT_PREF_TITLE = "Inner fragment";
66     private static final String LIST_PREF_TITLE = "List preference";
67     private static final String LIST_PREF_OPTION = "Alpha Option 01";
68 
69     private static final int INITIAL_TITLE_RES_ID = R.string.test_title;
70     private static final int EXPECTED_HEADERS_COUNT = 3;
71 
72     private static final String LOCAL_DIRECTORY = Environment.getExternalStorageDirectory()
73             + "/CtsPreferenceTestCases";
74 
75     @Rule
76     public final TestName mTestName = new TestName();
77 
78     TestUtils mTestUtils;
79     protected PreferenceWithHeaders mActivity;
80     protected final Context mContext = InstrumentationRegistry.getInstrumentation()
81             .getTargetContext();
82     private boolean mIsMultiPane;
83 
switchHeadersInner()84     void switchHeadersInner() {
85         launchActivity();
86         if (shouldRunLargeDeviceTest()) {
87             largeScreenSwitchHeadersInner();
88         } else {
89             smallScreenSwitchHeadersInner();
90         }
91     }
92 
93     /**
94      * For: Large screen (multi-pane).
95      * Scenario: Tests that tapping on header changes to its proper preference panel and that the
96      * headers are still visible.
97      */
largeScreenSwitchHeadersInner()98     private void largeScreenSwitchHeadersInner() {
99         assertTrue(shouldRunLargeDeviceTest());
100         assertInitialState();
101 
102         tapOnPrefs2Header();
103 
104         // Headers and panel Prefs2 must be shown.
105         assertHeadersShown();
106         assertPanelPrefs1Hidden();
107         assertPanelPrefs2Shown();
108 
109         tapOnPrefs1Header();
110 
111         // Headers and panel Prefs1 must be shown.
112         assertHeadersShown();
113         assertPanelPrefs1Shown();
114         assertPanelPrefs2Hidden();
115     }
116 
117     /**
118      * For: Small screen (single-pane).
119      * Scenario: Tests that tapping on header changes to its proper preference panel and that the
120      * headers are hidden and pressing back button after that shows them again.
121      */
smallScreenSwitchHeadersInner()122     private void smallScreenSwitchHeadersInner() {
123         assertTrue(shouldRunSmallDeviceTest());
124         assertInitialState();
125 
126         tapOnPrefs2Header();
127 
128         // Only Prefs2 must be shown.
129         assertHeadersHidden();
130         assertPanelPrefs1Hidden();
131         assertPanelPrefs2Shown();
132 
133         pressBack();
134 
135         tapOnPrefs1Header();
136 
137         // Only Prefs1 must be shown.
138         assertHeadersHidden();
139         assertPanelPrefs1Shown();
140         assertPanelPrefs2Hidden();
141     }
142 
143     /**
144      * For: Small screen (single-pane).
145      * Scenario: Tests that after navigating back to the headers list there will be no header
146      * highlighted and that the title was properly restored..
147      */
smallScreenNoHighlightInHeadersListInner()148     void smallScreenNoHighlightInHeadersListInner() {
149         launchActivity();
150         if (!shouldRunSmallDeviceTest()) {
151             return;
152         }
153 
154         assertInitialState();
155 
156         CharSequence title = mActivity.getTitle();
157 
158         tapOnPrefs2Header();
159         assertHeadersHidden();
160 
161         pressBack();
162         assertHeadersShown();
163 
164         // Verify that no headers are focused.
165         assertHeadersNotFocused();
166 
167         // Verify that the title was properly restored.
168         assertEquals(title, mActivity.getTitle());
169 
170         // Verify that everything restores back to initial state again.
171         assertInitialState();
172     }
173 
backPressToExitInner()174     void backPressToExitInner() {
175         launchActivity();
176         if (shouldRunLargeDeviceTest()) {
177             largeScreenBackPressToExitInner();
178         } else {
179             smallScreenBackPressToExitInner();
180         }
181     }
182 
183     /**
184      * For: Small screen (single-pane).
185      * Scenario: Tests that pressing back button twice after having preference panel opened will
186      * exit the app when running single-pane.
187      */
smallScreenBackPressToExitInner()188     private void smallScreenBackPressToExitInner() {
189         assertTrue(shouldRunSmallDeviceTest());
190         assertInitialState();
191 
192         tapOnPrefs2Header();
193 
194         // Only Prefs2 must be shown - covered by smallScreenSwitchHeadersTest
195 
196         pressBack();
197         pressBack();
198 
199         // Now we should be out of the activity
200         assertHeadersHidden();
201         assertPanelPrefs1Hidden();
202         assertPanelPrefs2Hidden();
203     }
204 
205     /**
206      * For: Large screen (multi-pane).
207      * Scenario: Selects a header and then leaves the activity by pressing back button. Tests that
208      * we don't transition to the previous header or list of header like in single-pane.
209      */
largeScreenBackPressToExitInner()210     private void largeScreenBackPressToExitInner() {
211         assertTrue(shouldRunLargeDeviceTest());
212         assertInitialState();
213 
214         tapOnPrefs2Header();
215 
216         // Headers and panel Prefs2 must be shown - covered by largeScreenSwitchHeadersInner.
217 
218         pressBack();
219 
220         assertHeadersHidden();
221     }
222 
goToFragmentInner()223     void goToFragmentInner() {
224         launchActivity();
225         if (shouldRunLargeDeviceTest()) {
226             largeScreenGoToFragmentInner();
227         } else {
228             smallScreenGoToFragmentInner();
229         }
230     }
231 
232     /**
233      * For: Large screen (multi-pane).
234      * Scenario: Navigates to inner fragment. Test that the fragment was opened correctly and
235      * headers are still visible. Also tests that back press doesn't close the app but navigates
236      * back from the fragment.
237      */
largeScreenGoToFragmentInner()238     private void largeScreenGoToFragmentInner() {
239         assertTrue(shouldRunLargeDeviceTest());
240         assertInitialState();
241 
242         tapOnPrefs1Header();
243 
244         // Go to preferences inner fragment.
245         mTestUtils.tapOnViewWithText(INNER_FRAGMENT_PREF_BUTTON);
246 
247         // Headers and inner fragment must be shown.
248         assertHeadersShown();
249         assertPanelPrefs1Hidden();
250         assertInnerFragmentShown();
251 
252         pressBack();
253 
254         // Headers and panel Prefs1 must be shown.
255         assertHeadersShown();
256         assertPanelPrefs1Shown();
257         assertPanelPrefs2Hidden();
258         assertInnerFragmentHidden();
259     }
260 
261     /**
262      * For: Small screen (single-pane).
263      * Scenario: Navigates to inner fragment. Tests that the fragment was opened correctly and
264      * headers are hidden. Also tests that back press doesn't close the app but navigates back from
265      * the fragment.
266      */
smallScreenGoToFragmentInner()267     private void smallScreenGoToFragmentInner() {
268         assertTrue(shouldRunSmallDeviceTest());
269         assertInitialState();
270 
271         tapOnPrefs1Header();
272 
273         // Go to preferences inner fragment.
274         mTestUtils.tapOnViewWithText(INNER_FRAGMENT_PREF_BUTTON);
275 
276         // Only inner fragment must be shown.
277         assertHeadersHidden();
278         assertPanelPrefs1Hidden();
279         assertInnerFragmentShown();
280 
281         pressBack();
282 
283         // Prefs1 must be shown.
284         assertHeadersHidden();
285         assertPanelPrefs1Shown();
286         assertPanelPrefs2Hidden();
287         assertInnerFragmentHidden();
288     }
289 
290     /**
291      * For: Any screen (single or multi-pane).
292      * Scenario: Tests that opening specific preference fragment directly via intent works properly.
293      */
startWithFragmentInner()294     void startWithFragmentInner() {
295         launchActivityWithExtras(PreferenceWithHeaders.PrefsTwoFragment.class,
296                 false /* noHeaders */, -1 /* initialTitle */);
297 
298         assertInitialStateForFragment();
299     }
300 
301     /**
302      * For: Any screen (single or multi-pane).
303      * Scenario: Tests that preference fragment opened directly survives recreation (via screenshot
304      * tests).
305      */
startWithFragmentAndRecreateInner()306     void startWithFragmentAndRecreateInner() {
307         launchActivityWithExtras(PreferenceWithHeaders.PrefsTwoFragment.class,
308                 false /* noHeaders */, -1 /* initialTitle */);
309 
310         assertInitialStateForFragment();
311 
312         // Take screenshot
313         Bitmap before = mTestUtils.takeScreenshot();
314 
315         // Force recreate
316         recreate();
317 
318         assertInitialStateForFragment();
319 
320         // Compare screenshots
321         Bitmap after = mTestUtils.takeScreenshot();
322         assertScreenshotsAreEqual(before, after);
323     }
324 
325     /**
326      * For: Any screen (single or multi-pane).
327      * Scenario: Starts preference fragment directly with the given initial title and tests that
328      * multi-pane does not show it and single-pane does.
329      */
startWithFragmentAndInitTitleInner()330     void startWithFragmentAndInitTitleInner() {
331         launchActivityWithExtras(PreferenceWithHeaders.PrefsTwoFragment.class,
332                 false /* noHeaders */, INITIAL_TITLE_RES_ID);
333 
334         assertInitialStateForFragment();
335 
336         if (mIsMultiPane) {
337             String testTitle = mActivity.getResources().getString(INITIAL_TITLE_RES_ID);
338             // Title should not be shown.
339             assertTextHidden(testTitle);
340         } else {
341             // Title should be shown.
342             assertTitleShown();
343         }
344     }
345 
346     /**
347      * For: Any screen (single or multi-pane).
348      * Scenario: Tests that EXTRA_NO_HEADERS intent arg that prevents showing headers in multi-pane
349      * is applied correctly.
350      */
startWithFragmentNoHeadersInner()351     void startWithFragmentNoHeadersInner() {
352         launchActivityWithExtras(PreferenceWithHeaders.PrefsTwoFragment.class,
353                 true /* noHeaders */, -1 /* initialTitle */);
354 
355         assertInitialStateForFragment();
356         // Only Prefs2 should be shown.
357         assertHeadersHidden();
358         assertPanelPrefs1Hidden();
359         assertPanelPrefs2Shown();
360     }
361 
362     /**
363      * For: Any screen (single or multi-pane).
364      * Scenario: Tests that EXTRA_NO_HEADERS intent arg that prevents showing headers in multi-pane
365      * is applied correctly plus initial title is displayed.
366      */
startWithFragmentNoHeadersButInitTitleInner()367     void startWithFragmentNoHeadersButInitTitleInner() {
368         launchActivityWithExtras(PreferenceWithHeaders.PrefsTwoFragment.class,
369                 true /* noHeaders */, INITIAL_TITLE_RES_ID);
370 
371         assertInitialStateForFragment();
372         // Only Prefs2 should be shown.
373         assertHeadersHidden();
374         assertPanelPrefs1Hidden();
375         assertPanelPrefs2Shown();
376 
377         assertTitleShown();
378     }
379 
380     /**
381      * For: Any screen (single or multi-pane).
382      * Scenario: Tests that list preference opens correctly and that back press correctly closes it.
383      */
listDialogTest()384     void listDialogTest() {
385         launchActivity();
386 
387         assertInitialState();
388         if (!mIsMultiPane) {
389             tapOnPrefs1Header();
390         }
391 
392         mTestUtils.tapOnViewWithText(LIST_PREF_TITLE);
393 
394         assertTextShown(LIST_PREF_OPTION);
395 
396         pressBack();
397 
398         if (mIsMultiPane) {
399             // Headers and Prefs1 should be shown.
400             assertHeadersShown();
401             assertPanelPrefs1Shown();
402         } else {
403             // Only Prefs1 should be shown.
404             assertHeadersHidden();
405             assertPanelPrefs1Shown();
406         }
407     }
408 
409     /**
410      * For: Any screen (single or multi-pane).
411      * Scenario: Tests that the PreferenceActivity properly restores its state after recreation.
412      * Test done via screenshots.
413      */
recreateTest()414     void recreateTest() {
415         launchActivity();
416 
417         assertInitialState();
418         tapOnPrefs2Header();
419 
420         assertPanelPrefs2Shown();
421 
422         // Take screenshot
423         Bitmap before = mTestUtils.takeScreenshot();
424 
425         recreate();
426 
427         assertPanelPrefs2Shown();
428 
429         // Compare screenshots
430         Bitmap after = mTestUtils.takeScreenshot();
431         assertScreenshotsAreEqual(before, after);
432     }
433 
434     /**
435      * For: Any screen (single or multi-pane).
436      * Scenario: Tests that the PreferenceActivity properly restores its state after recreation
437      * while an inner fragment is shown. Test done via screenshots.
438      */
recreateInnerFragmentTest()439     void recreateInnerFragmentTest() {
440         launchActivity();
441 
442         assertInitialState();
443 
444         if (!mIsMultiPane) {
445             tapOnPrefs1Header();
446         }
447 
448         // Go to preferences inner fragment.
449         mTestUtils.tapOnViewWithText(INNER_FRAGMENT_PREF_BUTTON);
450 
451         // Only inner fragment must be shown.
452         if (shouldRunLargeDeviceTest()) {
453             assertHeadersShown();
454         } else {
455             assertHeadersHidden();
456         }
457         assertPanelPrefs1Hidden();
458         assertInnerFragmentShown();
459 
460         // Take screenshot
461         Log.v(TAG, "taking screenshot before");
462         Bitmap before = mTestUtils.takeScreenshot();
463         Log.v(TAG, "screenshot taken");
464 
465         recreate();
466 
467         // Only inner fragment must be shown.
468         if (shouldRunLargeDeviceTest()) {
469             assertHeadersShown();
470         } else {
471             assertHeadersHidden();
472         }
473         assertPanelPrefs1Hidden();
474         assertInnerFragmentShown();
475 
476         // Compare screenshots
477         Log.v(TAG, "taking screenshot after");
478         Bitmap after = mTestUtils.takeScreenshot();
479         Log.v(TAG, "screenshot taken");
480         assertScreenshotsAreEqual(before, after);
481     }
482 
assertScreenshotsAreEqual(Bitmap before, Bitmap after)483     private void assertScreenshotsAreEqual(Bitmap before, Bitmap after) {
484         // TODO(b/134080964): remove the precision=0.99 arg so it does a pixel-by-pixel check
485         if (!BitmapUtils.compareBitmaps(before, after, 0.99)) {
486             String testName = getClass().getSimpleName() + "." + mTestName.getMethodName();
487             File beforeFile = null;
488             File afterFile = null;
489             try {
490                 beforeFile = dumpBitmap(before, testName + "-before.png");
491                 afterFile = dumpBitmap(after, testName + "-after.png");
492             } catch (IOException e) {
493                 Log.e(TAG,  "Error dumping bitmap", e);
494             }
495             fail("Screenshots do not match (check " + beforeFile + " and " + afterFile + ")");
496         }
497     }
498 
499     // TODO: copied from Autofill; move to common CTS code
dumpBitmap(Bitmap bitmap, String filename)500     private File dumpBitmap(Bitmap bitmap, String filename) throws IOException {
501         File file = createFile(filename);
502         if (file == null) return null;
503         Log.i(TAG, "Dumping bitmap at " + file);
504         BitmapUtils.saveBitmap(bitmap, file.getParent(), file.getName());
505         return file;
506 
507     }
508 
createFile(String filename)509     private static File createFile(String filename) throws IOException {
510         File dir = getLocalDirectory();
511         File file = new File(dir, filename);
512         if (file.exists()) {
513             Log.v(TAG, "Deleting file " + file);
514             file.delete();
515         }
516         if (!file.createNewFile()) {
517             Log.e(TAG, "Could not create file " + file);
518             return null;
519         }
520         return file;
521     }
522 
getLocalDirectory()523     private static File getLocalDirectory() {
524         File dir = new File(LOCAL_DIRECTORY);
525         dir.mkdirs();
526         if (!dir.exists()) {
527             Log.e(TAG, "Could not create directory " + dir);
528             return null;
529         }
530         return dir;
531     }
532 
533     // TODO: move to common code
requirePortraitModeSupport()534     void requirePortraitModeSupport() {
535         String testName = mTestName.getMethodName();
536         PackageManager pm = mContext.getPackageManager();
537 
538         boolean hasPortrait = pm.hasSystemFeature(PackageManager.FEATURE_SCREEN_PORTRAIT);
539         boolean hasLandscape = pm.hasSystemFeature(PackageManager.FEATURE_SCREEN_LANDSCAPE);
540 
541         // From the javadoc: For backwards compatibility, you can assume that if neither
542         // FEATURE_SCREEN_PORTRAIT nor FEATURE_SCREEN_LANDSCAPE is set then the device
543         // supports both portrait and landscape.
544         boolean supportsPortrait = hasPortrait || !hasLandscape;
545 
546         Log.v(TAG, "requirePortraitModeSupport(): FEATURE_SCREEN_PORTRAIT=" + hasPortrait
547                 + ", FEATURE_SCREEN_LANDSCAPE=" + hasLandscape
548                 + ", supportsPortrait=" + supportsPortrait);
549 
550         assumeTrue(testName + ": device does not support portrait mode", supportsPortrait);
551     }
552 
553     // TODO: move to common code
requireLandscapeModeSupport()554     void requireLandscapeModeSupport() {
555         String testName = mTestName.getMethodName();
556         PackageManager pm = mContext.getPackageManager();
557 
558         boolean hasPortrait = pm.hasSystemFeature(PackageManager.FEATURE_SCREEN_PORTRAIT);
559         boolean hasLandscape = pm.hasSystemFeature(PackageManager.FEATURE_SCREEN_LANDSCAPE);
560 
561         // From the javadoc: For backwards compatibility, you can assume that if neither
562         // FEATURE_SCREEN_PORTRAIT nor FEATURE_SCREEN_LANDSCAPE is set then the device
563         // supports both portrait and landscape.
564         boolean supportsLandscape = hasLandscape || !hasPortrait;
565 
566         Log.v(TAG, "requireLandscapeModeSupport(): FEATURE_SCREEN_PORTRAIT=" + hasPortrait
567                 + ", FEATURE_SCREEN_LANDSCAPE=" + hasLandscape
568                 + ", supportsLandscape=" + supportsLandscape);
569 
570         assumeTrue(testName + ": device does not support portrait mode", supportsLandscape);
571     }
572 
assertInitialState()573     private void assertInitialState() {
574         if (mIsMultiPane) {
575             // Headers and panel Prefs1 must be shown.
576             assertHeadersShown();
577             runOnUiThread(() -> assertTrue(mActivity.hasHeaders()));
578             assertPanelPrefs1Shown();
579             assertPanelPrefs2Hidden();
580         } else {
581             // Headers must be shown and nothing else.
582             assertHeadersShown();
583             runOnUiThread(() -> assertTrue(mActivity.hasHeaders()));
584             assertPanelPrefs1Hidden();
585             assertPanelPrefs2Hidden();
586         }
587         assertHeadersAreLoaded();
588     }
589 
assertInitialStateForFragment()590     private void assertInitialStateForFragment() {
591         if (mIsMultiPane) {
592             // Headers and Prefs2 should be shown.
593             assertHeadersShown();
594             runOnUiThread(() -> assertTrue(mActivity.hasHeaders()));
595             assertPanelPrefs1Hidden();
596             assertPanelPrefs2Shown();
597         } else {
598             // Only Prefs2 should be shown.
599             assertHeadersHidden();
600             runOnUiThread(() -> assertFalse(mActivity.hasHeaders()));
601             assertPanelPrefs1Hidden();
602             assertPanelPrefs2Shown();
603         }
604     }
605 
shouldRunLargeDeviceTest()606     private boolean shouldRunLargeDeviceTest() {
607         if (mActivity.onIsMultiPane()) {
608             return true;
609         }
610 
611         Log.d(TAG, "Skipping a large device test.");
612         return false;
613     }
614 
shouldRunSmallDeviceTest()615     private boolean shouldRunSmallDeviceTest() {
616         if (!mActivity.onIsMultiPane()) {
617             return true;
618         }
619 
620         Log.d(TAG, "Skipping a small device test.");
621         return false;
622     }
623 
tapOnPrefs1Header()624     private void tapOnPrefs1Header() {
625         mTestUtils.tapOnViewWithText(PREFS1_HEADER_TITLE);
626     }
627 
tapOnPrefs2Header()628     private void tapOnPrefs2Header() {
629         mTestUtils.tapOnViewWithText(PREFS2_HEADER_TITLE);
630     }
631 
assertHeadersAreLoaded()632     private void assertHeadersAreLoaded() {
633         runOnUiThread(() -> assertEquals(EXPECTED_HEADERS_COUNT,
634                 mActivity.loadedHeaders == null
635                         ? 0
636                         : mActivity.loadedHeaders.size()));
637     }
638 
assertHeadersShown()639     private void assertHeadersShown() {
640         assertTextShown(PREFS1_HEADER_TITLE);
641         assertTextShown(PREFS2_HEADER_TITLE);
642     }
643 
assertHeadersNotFocused()644     private void assertHeadersNotFocused() {
645         assertFalse(mTestUtils.isTextFocused(PREFS1_HEADER_TITLE));
646         assertFalse(mTestUtils.isTextFocused(PREFS2_HEADER_TITLE));
647     }
648 
assertHeadersHidden()649     private void assertHeadersHidden() {
650         // We check that at least one is hidden instead of each individual one separately because
651         // these headers are also part of individual preference panels breadcrumbs so it would fail
652         // if we only checked for one.
653         assertTrue(mTestUtils.isTextHidden(PREFS1_HEADER_TITLE)
654                 || mTestUtils.isTextHidden(PREFS2_HEADER_TITLE));
655     }
656 
assertPanelPrefs1Shown()657     private void assertPanelPrefs1Shown() {
658         assertTextShown(PREFS1_PANEL_TITLE);
659     }
660 
assertPanelPrefs1Hidden()661     private void assertPanelPrefs1Hidden() {
662         assertTextHidden(PREFS1_PANEL_TITLE);
663     }
664 
assertPanelPrefs2Shown()665     private void assertPanelPrefs2Shown() {
666         assertTextShown(PREFS2_PANEL_TITLE);
667     }
668 
assertPanelPrefs2Hidden()669     private void assertPanelPrefs2Hidden() {
670         assertTextHidden(PREFS2_PANEL_TITLE);
671     }
672 
assertInnerFragmentShown()673     private void assertInnerFragmentShown() {
674         assertTextShown(INNER_FRAGMENT_PREF_TITLE);
675     }
676 
assertInnerFragmentHidden()677     private void assertInnerFragmentHidden() {
678         assertTextHidden(INNER_FRAGMENT_PREF_TITLE);
679     }
680 
assertTextShown(String text)681     private void assertTextShown(String text) {
682         assertTrue(mTestUtils.isTextShown(text));
683     }
684 
assertTextHidden(String text)685     private void assertTextHidden(String text) {
686         assertTrue(mTestUtils.isTextHidden(text));
687     }
688 
assertTitleShown()689     private void assertTitleShown() {
690         if (!mTestUtils.isOnWatchUiMode()) {
691             // On watch, activity title is not shown by default.
692             String testTitle = mActivity.getResources().getString(INITIAL_TITLE_RES_ID);
693             assertTextShown(testTitle);
694         }
695     }
696 
recreate()697     private void recreate() {
698         runOnUiThread(() -> mActivity.recreate());
699         mTestUtils.waitForIdle();
700     }
701 
pressBack()702     private void pressBack() {
703         mTestUtils.mDevice.pressBack();
704         mTestUtils.waitForIdle();
705     }
706 
launchActivity()707     private void launchActivity() {
708         mActivity = launchActivity(null);
709         mTestUtils.waitForIdle();
710         runOnUiThread(() -> mIsMultiPane = mActivity.isMultiPane());
711     }
712 
launchActivityWithExtras(Class extraFragment, boolean noHeaders, int initialTitle)713     private void launchActivityWithExtras(Class extraFragment, boolean noHeaders,
714             int initialTitle) {
715         Intent intent = new Intent(Intent.ACTION_MAIN);
716 
717         if (extraFragment != null) {
718             intent.putExtra(EXTRA_SHOW_FRAGMENT, extraFragment.getName());
719         }
720         if (noHeaders) {
721             intent.putExtra(EXTRA_NO_HEADERS, true);
722         }
723         if (initialTitle != -1) {
724             intent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE, initialTitle);
725         }
726 
727         mActivity = launchActivity(intent);
728         mTestUtils.waitForIdle();
729         runOnUiThread(() -> mIsMultiPane = mActivity.isMultiPane());
730     }
731 
launchActivity(Intent intent)732     protected abstract PreferenceWithHeaders launchActivity(Intent intent);
733 
runOnUiThread(Runnable runnable)734     protected abstract void runOnUiThread(Runnable runnable);
735 }
736