chromium/chrome/test/android/javatests/src/org/chromium/chrome/test/util/BookmarkTestRule.java

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.test.util;

import static org.chromium.base.ThreadUtils.runOnUiThreadBlocking;

import androidx.annotation.Nullable;
import androidx.test.platform.app.InstrumentationRegistry;

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.ApplicationTestUtils;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.chrome.browser.app.ChromeActivity;
import org.chromium.chrome.browser.app.bookmarks.BookmarkActivity;
import org.chromium.chrome.browser.bookmarks.BookmarkManagerCoordinator;
import org.chromium.chrome.browser.bookmarks.BookmarkPage;
import org.chromium.chrome.browser.bookmarks.BookmarkUtils;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.ui.native_page.NativePage;
import org.chromium.components.embedder_support.util.UrlConstants;
import org.chromium.url.GURL;

import java.util.concurrent.TimeoutException;

/**
 * This test rule destroys BookmarkActivity opened with showBookmarkManager on phone.
 * On tablet, the bookmark manager is a native page so the manager will be destroyed
 * with the parent chromeActivity.
 */
public class BookmarkTestRule implements TestRule {
    @Nullable private BookmarkActivity mBookmarkActivity;

    @Override
    public Statement apply(Statement statement, Description description) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                try {
                    statement.evaluate();
                } finally {
                    tearDown();
                }
            }
        };
    }

    /**
     * Shows the bookmark manager on screen. On tablets, it should only be called if there's an
     * active tab to load the bookmarks manager in.
     */
    public BookmarkManagerCoordinator showBookmarkManager(ChromeActivity chromeActivity) {
        BookmarkManagerCoordinator coordinator;
        // BookmarkActivity is only opened on phone, it is a native page on tablet.
        if (chromeActivity.isTablet()) {
            // Wait for the bookmark native page to load to make sure that the reference to
            // BookmarkManagerCoordinator is valid.
            CallbackHelper callbackHelper = new CallbackHelper();
            EmptyTabObserver obs =
                    new EmptyTabObserver() {
                        @Override
                        public void onPageLoadFinished(Tab tab, GURL url) {
                            NativePage nativePage = tab.getNativePage();
                            if (nativePage != null
                                    && nativePage.getHost().equals(UrlConstants.BOOKMARKS_HOST)) {
                                callbackHelper.notifyCalled();
                            }
                        }
                    };
            Tab tab = chromeActivity.getActivityTab();
            assert tab != null;

            runOnUiThreadBlocking(() -> tab.addObserver(obs));
            showBookmarkManagerInternal(chromeActivity);
            try {
                callbackHelper.waitForOnly();
            } catch (TimeoutException e) {
                throw new RuntimeException(e);
            }
            runOnUiThreadBlocking(() -> tab.removeObserver(obs));

            coordinator = ((BookmarkPage) tab.getNativePage()).getManagerForTesting();

        } else {
            mBookmarkActivity =
                    ActivityTestUtils.waitForActivity(
                            InstrumentationRegistry.getInstrumentation(),
                            BookmarkActivity.class,
                            () -> showBookmarkManagerInternal(chromeActivity));
            coordinator = mBookmarkActivity.getManagerForTesting();
        }
        BookmarkTestUtil.waitForBookmarkModelLoaded();
        return coordinator;
    }

    /** Returns the bookmark activity. */
    public @Nullable BookmarkActivity getBookmarkActivity() {
        return mBookmarkActivity;
    }

    private void showBookmarkManagerInternal(ChromeActivity chromeActivity) {
        ThreadUtils.runOnUiThreadBlocking(
                () -> BookmarkUtils.showBookmarkManager(chromeActivity, /* isIncognito= */ false));
    }

    /**
     * Tears down the BookmarkActivity opened on phone. On tablet, it is
     * a native page so the bookmarks will finish with the chromeActivity.
     */
    private void tearDown() throws Exception {
        if (mBookmarkActivity != null) {
            ApplicationTestUtils.finishActivity(mBookmarkActivity);
        }
    }
}