chromium/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/HomeSurfaceTestUtils.java

// Copyright 2021 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.browser.ntp;

import static org.chromium.chrome.browser.tabmodel.TestTabModelDirectory.M26_GOOGLE_COM;

import android.content.Intent;
import android.graphics.Bitmap;
import android.util.Base64;

import androidx.annotation.Nullable;

import org.junit.Assert;

import org.chromium.base.ContextUtils;
import org.chromium.base.StreamUtil;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabState;
import org.chromium.chrome.browser.tab.TabUtils;
import org.chromium.chrome.browser.tab_ui.TabContentManager;
import org.chromium.chrome.browser.tabmodel.TabModelUtils;
import org.chromium.chrome.browser.tabmodel.TabPersistentStore;
import org.chromium.chrome.browser.tabmodel.TabPersistentStore.ActiveTabState;
import org.chromium.chrome.browser.tabmodel.TabbedModeTabPersistencePolicy;
import org.chromium.chrome.browser.tabpersistence.TabStateDirectory;
import org.chromium.chrome.browser.tabpersistence.TabStateFileManager;
import org.chromium.chrome.browser.tasks.ReturnToChromeUtil;
import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference;

/** Utility methods and classes for testing home Surface. */
public class HomeSurfaceTestUtils {
    public static final String IMMEDIATE_RETURN_TEST_PARAMS =
            "force-fieldtrial-params=Study.Group:"
                    + ReturnToChromeUtil.HOME_SURFACE_RETURN_TIME_SECONDS_PARAM
                    + "/0";

    private static final long MAX_TIMEOUT_MS = 30000L;

    /**
     * Only launch Chrome without waiting for a current tab. This method could not use {@link
     * ChromeTabbedActivityTestRule#startMainActivityFromLauncher()} because of its {@link
     * org.chromium.chrome.browser.tab.Tab} dependency.
     */
    public static void startMainActivityFromLauncher(ChromeActivityTestRule activityTestRule) {
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_LAUNCHER);
        activityTestRule.prepareUrlIntent(intent, null);
        activityTestRule.launchActivity(intent);
    }

    /**
     * Wait for the tab state to be initialized.
     *
     * @param cta The ChromeTabbedActivity under test.
     */
    public static void waitForTabModel(ChromeTabbedActivity cta) {
        CriteriaHelper.pollUiThread(
                cta.getTabModelSelector()::isTabStateInitialized,
                MAX_TIMEOUT_MS,
                CriteriaHelper.DEFAULT_POLLING_INTERVAL);
    }

    /**
     * Create all the files so that tab models can be restored.
     *
     * @param tabIds all the Tab IDs in the normal tab model.
     */
    public static void createTabStatesAndMetadataFile(int[] tabIds) throws IOException {
        createTabStatesAndMetadataFile(tabIds, null, null, 0);
    }

    /**
     * Create all the files so that tab models can be restored.
     *
     * @param tabIds all the Tab IDs in the normal tab model.
     * @param rootIds all the root IDs in the normal tab model.
     */
    public static void createTabStatesAndMetadataFile(int[] tabIds, @Nullable int[] rootIds)
            throws IOException {
        createTabStatesAndMetadataFile(tabIds, rootIds, null, 0);
    }

    /**
     * Create all the files so that tab models can be restored.
     *
     * @param tabIds all the Tab IDs in the normal tab model.
     * @param rootIds all the root IDs in the normal tab model.
     * @param urls all of the URLs in the normal tab model.
     * @param selectedIndex the selected index of normal tab model.
     */
    public static void createTabStatesAndMetadataFile(
            int[] tabIds, @Nullable int[] rootIds, @Nullable String[] urls, int selectedIndex)
            throws IOException {
        createTabStatesAndMetadataFile(tabIds, rootIds, urls, selectedIndex, true);
    }

    private static void createTabStatesAndMetadataFile(
            int[] tabIds,
            int[] rootIds,
            @Nullable String[] urls,
            int selectedIndex,
            boolean createStateFile)
            throws IOException {
        TabPersistentStore.TabModelMetadata normalInfo =
                new TabPersistentStore.TabModelMetadata(selectedIndex);
        for (int i = 0; i < tabIds.length; i++) {
            normalInfo.ids.add(tabIds[i]);
            String url = urls != null ? urls[i] : "about:blank";
            normalInfo.urls.add(url);

            if (createStateFile) {
                int rootId = rootIds == null ? tabIds[i] : rootIds[i];
                saveTabState(tabIds[i], rootId, false);
            }
        }
        TabPersistentStore.TabModelMetadata incognitoInfo =
                new TabPersistentStore.TabModelMetadata(0);

        TabPersistentStore.TabModelSelectorMetadata selectorMetaData =
                new TabPersistentStore.TabModelSelectorMetadata(normalInfo, incognitoInfo);

        TabPersistentStore.saveTabModelPrefs(normalInfo, incognitoInfo, 0, ActiveTabState.OTHER);
        File metadataFile =
                new File(
                        TabStateDirectory.getOrCreateTabbedModeStateDirectory(),
                        TabbedModeTabPersistencePolicy.getMetadataFileNameForIndex(0));
        TabPersistentStore.saveListToFile(metadataFile, selectorMetaData);
    }

    /**
     * Creates a Tab state metadata file without creating Tab state files for the given Tab's info.
     *
     * @param tabIds All the Tab IDs in the normal tab model.
     * @param urls All the Tab URLs in the normal tab model.
     * @param selectedIndex The selected index of normal tab model.
     */
    public static void prepareTabStateMetadataFile(
            int[] tabIds, @Nullable String[] urls, int selectedIndex) throws IOException {
        createTabStatesAndMetadataFile(tabIds, null, urls, selectedIndex, false);
    }

    /**
     * Create thumbnail bitmap of the tab based on the given id and write it to file.
     *
     * @param tabId The id of the target tab.
     * @param browserControlsStateProvider For getting the top offset.
     * @return The bitmap created.
     */
    public static Bitmap createThumbnailBitmapAndWriteToFile(
            int tabId, BrowserControlsStateProvider browserControlsStateProvider) {
        final int height = 100;
        final int width =
                (int)
                        Math.round(
                                height
                                        * TabUtils.getTabThumbnailAspectRatio(
                                                ContextUtils.getApplicationContext(),
                                                browserControlsStateProvider));
        final Bitmap thumbnailBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);

        try {
            File thumbnailFile = TabContentManager.getTabThumbnailFileJpeg(tabId);
            if (thumbnailFile.exists()) {
                thumbnailFile.delete();
            }
            Assert.assertFalse(thumbnailFile.exists());

            FileOutputStream thumbnailFileOutputStream = new FileOutputStream(thumbnailFile);
            thumbnailBitmap.compress(Bitmap.CompressFormat.JPEG, 100, thumbnailFileOutputStream);
            thumbnailFileOutputStream.flush();
            thumbnailFileOutputStream.close();

            Assert.assertTrue(thumbnailFile.exists());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return thumbnailBitmap;
    }

    /**
     * Gets the current active Tab from UI thread.
     *
     * @param cta The ChromeTabbedActivity under test.
     */
    public static Tab getCurrentTabFromUIThread(ChromeTabbedActivity cta) {
        AtomicReference<Tab> tab = new AtomicReference<>();
        ThreadUtils.runOnUiThreadBlocking(
                () -> tab.set(TabModelUtils.getCurrentTab(cta.getCurrentTabModel())));
        return tab.get();
    }

    /**
     * Create a file so that a TabState can be restored later.
     *
     * @param tabId the Tab ID
     * @param tabId the Root ID
     * @param encrypted for Incognito mode
     */
    private static void saveTabState(int tabId, int rootId, boolean encrypted) {
        File file =
                TabStateFileManager.getTabStateFile(
                        TabStateDirectory.getOrCreateTabbedModeStateDirectory(),
                        tabId,
                        encrypted,
                        /* isFlatBuffer= */ false);
        writeFile(file, M26_GOOGLE_COM.encodedTabState);

        TabState tabState = TabStateFileManager.restoreTabStateInternal(file, false);
        tabState.rootId = rootId;
        TabStateFileManager.saveStateInternal(file, tabState, encrypted);
    }

    private static void writeFile(File file, String data) {
        FileOutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream(file);
            outputStream.write(Base64.decode(data, 0));
        } catch (Exception e) {
            assert false : "Failed to create " + file;
        } finally {
            StreamUtil.closeQuietly(outputStream);
        }
    }
}