chromium/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TestTabModelDirectory.java

// Copyright 2015 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.tabmodel;

import android.content.Context;
import android.util.Base64;

import org.chromium.base.FileUtils;
import org.chromium.base.Log;
import org.chromium.base.StreamUtil;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;

/**
 * Creates and fills up a directory with data suitable for testing code related to {@link TabModel}.
 * Used for mocking out the real data directory.
 *
 * <p>Instead of storing the TabState and metadata files in the test data directories, this class
 * stores them as encoded Base64 strings and then writes them out to storage during the test. This
 * gets around an infrastructure bug with setting file permissions.
 */
public class TestTabModelDirectory {
    private static final String TAG = "tabmodel";

    /**
     * Information about an encoded TabState file. Although the Tab ID is _not_ encoded in the
     * TabState file, it is convenient to store it in this class for testing purposes.
     */
    public static final class TabStateInfo {
        public final int version;
        public final int tabId;
        public final String url;
        public final String title;
        public final String encodedTabState;
        public final String filename;

        public TabStateInfo(
                boolean incognito,
                int version,
                int tabId,
                String url,
                String title,
                String encodedTabState) {
            this.version = version;
            this.tabId = tabId;
            this.url = url;
            this.title = title;
            this.encodedTabState = encodedTabState;
            this.filename = (incognito ? "cryptonito" : "tab") + tabId;
        }
    }

    /** Information about a TabModel and all of the TabStates it needs to be restored. */
    public static final class TabModelMetaDataInfo {
        public final int selectedTabId;
        public final TabStateInfo[] contents;
        public final int numRegularTabs;
        public final int numIncognitoTabs;
        public final String encodedFile;

        TabModelMetaDataInfo(
                int version,
                int numIncognitoTabs,
                int selectedTabId,
                TabStateInfo[] contents,
                String encodedFile) {
            this.numRegularTabs = contents.length - numIncognitoTabs;
            this.numIncognitoTabs = numIncognitoTabs;
            this.selectedTabId = selectedTabId;
            this.contents = contents;
            this.encodedFile = encodedFile;
        }
    }

    /**
     * Contains information about an M18 NTP. When used for testing, the TabState class has to be
     * told that Chrome Stable is being used.
     */
    public static final TabStateInfo M18_NTP =
            new TabStateInfo(
                    false,
                    0,
                    0,
                    "chrome-native://newtab/",
                    "New tab",
                    "AAABPK1gIpkAAAQYFAQAAAAAAAACAAAAAAAAAB0AAABjaHJvbWU6Ly9uZXd0YWIvI21vc3RfdmlzaXRlZAAAAA"
                            + "AAAAAHAAAATgBlAHcAIAB0AGEAYgAAADQBAAAwAQAACwAAADoAAABjAGgAcgBvAG0AZQA6AC8ALwBuAGUAdw"
                            + "B0AGEAYgAvACMAbQBvAHMAdABfAHYAaQBzAGkAdABlAGQAAAA6AAAAYwBoAHIAbwBtAGUAOgAvAC8AbgBlAH"
                            + "cAdABhAGIALwAjAG0AbwBzAHQAXwB2AGkAcwBpAHQAZQBkAAAA/////wAAAAD//////////wgAAAAAAAAAAA"
                            + "AAAAAAAAAAAAAAAQAAAAAAAAD/////AAAAAAgAAAAAAAAAAAAAQMYvST4F1QQAxy9JPgXVBAABAAAAMgAAAP"
                            + "8BPwBvPwFTCGZvbGRlcklkPwFTATM/AVMRc2VsZWN0ZWRQYW5lSW5kZXg/AUkCewIAAAAAAAAA//////////"
                            + "8IAAAAAAAAAAAAAEABAAAAAAAAAAYAAAEVAAAAaHR0cDovL3d3dy5nb29nbGUuY2EvAAAAAAAAAAYAAABHAG"
                            + "8AbwBnAGwAZQAUAgAAEAIAAAsAAAAqAAAAaAB0AHQAcAA6AC8ALwB3AHcAdwAuAGcAbwBvAGcAbABlAC4AYw"
                            + "BhAC8AAAAiAAAAaAB0AHQAcAA6AC8ALwBnAG8AbwBnAGwAZQAuAGMAYQAvAAAA/////wAAAAAMAAAARwBvAG"
                            + "8AZwBsAGUA/////wgAAAAAAAAAAAAAAAAAAAACAAAAAQAAAAAAAAD/////AwAAAAYAAABjAHMAaQAAABAAAA"
                            + "B0AGUAeAB0AGEAcgBlAGEA/////wgAAAAAAAAAAAAAQHu+oD4F1QQAfL6gPgXVBAAAAAAAAAAAAP////////"
                            + "//CAAAAAAAAAAAAABAAQAAAAEAAAALAAAAFgAAAGEAYgBvAHUAdAA6AGIAbABhAG4AawAAABYAAABhAGIAbw"
                            + "B1AHQAOgBiAGwAYQBuAGsAAAAIAAAAdwBnAGoAZgD///////////////8IAAAAAAAAAAAAAAAAAAAAAAAAAA"
                            + "AAAAAAAAAAKgAAAGgAdAB0AHAAOgAvAC8AdwB3AHcALgBnAG8AbwBnAGwAZQAuAGMAYQAvAAAAAAAAAAgAAA"
                            + "AAAAAAAADwP32+oD4F1QQAfr6gPgXVBAAAAAAAAAAAAP////8qAAAAaAB0AHQAcAA6AC8ALwB3AHcAdwAuAG"
                            + "cAbwBvAGcAbABlAC4AYwBhAC8AAAAIAAAAAAAAAAAA8D8BAAAAAAAAAAEAAAAdAAAAY2hyb21lOi8vbmV3dG"
                            + "FiLyNtb3N0X3Zpc2l0ZWQAAAAAAAAAEQAAAGh0dHA6Ly9nb29nbGUuY2EvAAAAAAAAAP////8AAA==");

    /**
     * Contains information about an M18 tab for http://google.com. When used for testing, the
     * TabState class has to be told that Chrome Stable is being used.
     */
    public static final TabStateInfo M18_GOOGLE_COM =
            new TabStateInfo(
                    false,
                    0,
                    1,
                    "http://www.google.com/",
                    "Google",
                    "AAABPLD4wNkAAALk4AIAAAAAAAACAAAAAQAAAB0AAABjaHJvbWU6Ly9uZXd0YWIvI21vc3RfdmlzaXRlZAAAAA"
                            + "AAAAAHAAAATgBlAHcAIAB0AGEAYgAAADQBAAAwAQAACwAAADoAAABjAGgAcgBvAG0AZQA6AC8ALwBuAGUAdw"
                            + "B0AGEAYgAvACMAbQBvAHMAdABfAHYAaQBzAGkAdABlAGQAAAA6AAAAYwBoAHIAbwBtAGUAOgAvAC8AbgBlAH"
                            + "cAdABhAGIALwAjAG0AbwBzAHQAXwB2AGkAcwBpAHQAZQBkAAAA/////wAAAAD//////////wgAAAAAAAAAAA"
                            + "AAAAAAAAAAAAAAAQAAAAAAAAD/////AAAAAAgAAAAAAAAAAAAAQHuHvz0F1QQAfIe/PQXVBAABAAAAMgAAAP"
                            + "8BPwBvPwFTCGZvbGRlcklkPwFTATM/AVMRc2VsZWN0ZWRQYW5lSW5kZXg/AUkCewIAAAAAAAAA//////////"
                            + "8IAAAAAAAAAAAAAEABAAAAAAAAAAgAAAAWAAAAaHR0cDovL3d3dy5nb29nbGUuY29tLwAAAAAAAAYAAABHAG"
                            + "8AbwBnAGwAZQDcAAAA2AAAAAsAAAAsAAAAaAB0AHQAcAA6AC8ALwB3AHcAdwAuAGcAbwBvAGcAbABlAC4AYw"
                            + "BvAG0ALwAsAAAAaAB0AHQAcAA6AC8ALwB3AHcAdwAuAGcAbwBvAGcAbABlAC4AYwBvAG0ALwD/////AAAAAP"
                            + "//////////CAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAP////8AAAAACAAAAAAAAMCcguc/o8IqPgXVBA"
                            + "Ckwio+BdUEAAAAAAAAAAAA//////////8IAAAAAAAAwJyC5z8AAAAAAAAAAAgAAAAdAAAAY2hyb21lOi8vbm"
                            + "V3dGFiLyNtb3N0X3Zpc2l0ZWQAAAAAAAAAFgAAAGh0dHA6Ly93d3cuZ29vZ2xlLmNvbS8AAAAAAAD/////AA"
                            + "A=");

    public static final TabStateInfo M26_GOOGLE_COM =
            new TabStateInfo(
                    false,
                    1,
                    2,
                    "http://www.google.com/",
                    "Google",
                    "AAABPK2JhPQAAALg3AIAAAAAAAACAAAAAQAAAAAAAAAdAAAAY2hyb21lOi8vbmV3dGFiLyNtb3N0X3Zpc2l0ZW"
                            + "QAAAAHAAAATgBlAHcAIAB0AGEAYgAAACQBAAAgAQAADQAAADoAAABjAGgAcgBvAG0AZQA6AC8ALwBuAGUAdw"
                            + "B0AGEAYgAvACMAbQBvAHMAdABfAHYAaQBzAGkAdABlAGQAAAA6AAAAYwBoAHIAbwBtAGUAOgAvAC8AbgBlAH"
                            + "cAdABhAGIALwAjAG0AbwBzAHQAXwB2AGkAcwBpAHQAZQBkAAAA/////wAAAAD//////////wgAAAAAAAAAAA"
                            + "AAAAAAAAAAAAAAAQAAAAAAAAD/////AAAAAAgAAAAAAAAAAADwP2hSNuEF1QQAaVI24QXVBAABAAAAMgAAAP"
                            + "8CPwBvPwFTCGZvbGRlcklkPwFTATM/AVMRc2VsZWN0ZWRQYW5lSW5kZXg/AUkCewIAAAAAAAAA//////////"
                            + "8AAAAABgAAAAAAAAAAAAAAAQAAAB0AAABjaHJvbWU6Ly9uZXd0YWIvI21vc3RfdmlzaXRlZAAAAAAAAABaa9"
                            + "YpnDMuAAEAAAAWAAAAaHR0cDovL3d3dy5nb29nbGUuY29tLwAABgAAAEcAbwBvAGcAbABlAMQAAADAAAAADQ"
                            + "AAACwAAABoAHQAdABwADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUALgBjAG8AbQAvACQAAABoAHQAdABwAD"
                            + "oALwAvAGcAbwBvAGcAbABlAC4AYwBvAG0ALwD/////AAAAAP//////////CAAAAAAAAAAAAAAAAAAAAAAAAA"
                            + "ABAAAAAAAAAP////8AAAAACAAAAAAAAMCcgtc/XIjK4gXVBABdiMriBdUEAAAAAAAAAAAA//////////8AAA"
                            + "AAAQAAAgAAAAAAAAAAAQAAABIAAABodHRwOi8vZ29vZ2xlLmNvbS8AAAAAAABIAVIrnDMuAP////8AAA==");

    public static final TabStateInfo M26_GOOGLE_CA =
            new TabStateInfo(
                    false,
                    1,
                    3,
                    "http://www.google.ca/",
                    "Google",
                    "AAABPK2J90YAAALs6AIAAAAAAAACAAAAAQAAAAAAAAAdAAAAY2hyb21lOi8vbmV3dGFiLyNtb3N0X3Zpc2l0ZW"
                            + "QAAAAHAAAATgBlAHcAIAB0AGEAYgAAACQBAAAgAQAADQAAADoAAABjAGgAcgBvAG0AZQA6AC8ALwBuAGUAdw"
                            + "B0AGEAYgAvACMAbQBvAHMAdABfAHYAaQBzAGkAdABlAGQAAAA6AAAAYwBoAHIAbwBtAGUAOgAvAC8AbgBlAH"
                            + "cAdABhAGIALwAjAG0AbwBzAHQAXwB2AGkAcwBpAHQAZQBkAAAA/////wAAAAD//////////wgAAAAAAAAAAA"
                            + "AAAAAAAAAAAAAAAQAAAAAAAAD/////AAAAAAgAAAAAAAAAAADwP9eU9OIF1QQA2JT04gXVBAABAAAAMgAAAP"
                            + "8CPwBvPwFTCGZvbGRlcklkPwFTATM/AVMRc2VsZWN0ZWRQYW5lSW5kZXg/AUkCewIAAAAAAAAA//////////"
                            + "8AAAAABgAAAAAAAAAAAAAAAQAAAB0AAABjaHJvbWU6Ly9uZXd0YWIvI21vc3RfdmlzaXRlZAAAAAAAAADl8o"
                            + "QrnDMuAAEAAAAVAAAAaHR0cDovL3d3dy5nb29nbGUuY2EvAAAABgAAAEcAbwBvAGcAbABlAMwAAADIAAAADQ"
                            + "AAACoAAABoAHQAdABwADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUALgBjAGEALwAAACoAAABoAHQAdABwAD"
                            + "oALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUALgBjAGEALwAAAP////8AAAAA//////////8IAAAAAAAAAAAAAA"
                            + "AAAAAAAAAAAAEAAAAAAAAA/////wAAAAAIAAAAAAAAAAAA8D9VtDjjBdUEAFa0OOMF1QQAAAAAAAAAAAD///"
                            + "///////wAAAAABAAAAAAAAAAAAAAABAAAAFQAAAGh0dHA6Ly93d3cuZ29vZ2xlLmNhLwAAAAAAAAD8oBUsnD"
                            + "MuAP////8AAA==");

    public static final TabStateInfo V2_BAIDU =
            new TabStateInfo(
                    false,
                    2,
                    4,
                    "http://www.baidu.com/",
                    "百度一下",
                    "AAABTbBCEBcAAAFkYAEAAAAAAAABAAAAAAAAAFABAABMAQAAAAAAABUAAABodHRwOi8vd3d3LmJhaWR1LmNvbS"
                            + "8AAAAEAAAAfnamXgBOC07IAAAAxAAAABYAAAAAAAAAKgAAAGgAdAB0AHAAOgAvAC8AdwB3AHcALgBiAGEAaQ"
                            + "BkAHUALgBjAG8AbQAvAAAA/////wAAAAAAAAAAIgAAAGgAdAB0AHAAOgAvAC8AYgBhAGkAZAB1AC4AYwBvAG"
                            + "0ALwAAAAAAAAAIAAAAAAAAwJyC1z9MWRSCeBcFAE1ZFIJ4FwUAS1kUgngXBQABAAAACAAAAAAAAAAAAAAACA"
                            + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////wAAAAAAAAAIAAAAABEAAABodHRwOi8vYmFpZHUuY29tLwAAAA"
                            + "EAAAARAAAAaHR0cDovL2JhaWR1LmNvbS8AAAAAAAAA7Nyiyg52LgAAAAAAyAAAAAEAAAD/////AAAAAAACAA"
                            + "AAAAAAAAAA");

    public static final TabStateInfo V2_DUCK_DUCK_GO =
            new TabStateInfo(
                    false,
                    2,
                    5,
                    "https://duckduckgo.com/",
                    "DuckDuckGo",
                    "AAABTbBCExUAAAFAPAEAAAAAAAABAAAAAAAAACwBAAAoAQAAAAAAABcAAABodHRwczovL2R1Y2tkdWNrZ28uY2"
                            + "9tLwAKAAAARAB1AGMAawBEAHUAYwBrAEcAbwCoAAAApAAAABYAAAAAAAAALgAAAGgAdAB0AHAAcwA6AC8ALw"
                            + "BkAHUAYwBrAGQAdQBjAGsAZwBvAC4AYwBvAG0ALwAAAP////8AAAAAAAAAAP////8AAAAACAAAAAAAAAAAAA"
                            + "AAlVAhgngXBQCWUCGCeBcFAJdQIYJ4FwUAAQAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAA"
                            + "AAAP////8AAAAAAAAACAAAAAAAAAAAAQAAABYAAABodHRwOi8vZHVja2R1Y2tnby5jb20vAAAAAAAAmoGoyg"
                            + "52LgAAAAAAyAAAAAEAAAD/////AAAAAAACAAAAAAAAAAAA");

    public static final TabStateInfo V2_HAARETZ =
            new TabStateInfo(
                    false,
                    2,
                    6,
                    "http://www.haaretz.co.il/",
                    "חדשות, ידיעות מהארץ והעולם - עיתון הארץ",
                    "AAABTbBhcJQAAAD49AAAAAAAAAABAAAAAAAAAOQAAADgAAAAAAAAABkAAABodHRwOi8vd3d3LmhhYXJldHouY2"
                            + "8uaWwvAAAAJwAAANcF0wXpBdUF6gUsACAA2QXTBdkF4gXVBeoFIADeBdQF0AXoBeUFIADVBdQF4gXVBdwF3Q"
                            + "UgAC0AIADiBdkF6gXVBd8FIADUBdAF6AXlBQAAAAAAAAAAAAgAAAAAGQAAAGh0dHA6Ly93d3cuaGFhcmV0ei"
                            + "5jby5pbC8AAAABAAAAGQAAAGh0dHA6Ly93d3cuaGFhcmV0ei5jby5pbC8AAAAAAAAAJ7hFRQ92LgAAAAAAyA"
                            + "AAAAEAAAD/////AAAAAAACAAAAAAAAAAAA");

    public static final TabStateInfo V2_TEXTAREA =
            new TabStateInfo(
                    false,
                    2,
                    7,
                    "http://textarea.org/",
                    "textarea",
                    "AAABSPI9OA8AAALs6AIAAAAAAAACAAAAAQAAACABAAAcAQAAAAAAABcAAABjaHJvbWUtbmF0aXZlOi8vbmV3dG"
                            + "FiLwAHAAAATgBlAHcAIAB0AGEAYgAAAKQAAACgAAAAFQAAAAAAAAAuAAAAYwBoAHIAbwBtAGUALQBuAGEAdA"
                            + "BpAHYAZQA6AC8ALwBuAGUAdwB0AGEAYgAvAAAA/////wAAAAAAAAAA/////wAAAAAIAAAAAAAAAAAA8D8EkS"
                            + "Y/8gQFAAWRJj/yBAUABpEmP/IEBQABAAAACAAAAAAAAAAAAPC/CAAAAAAAAAAAAPC/AAAAAAAAAAD/////AA"
                            + "AAAAYAAAAAAAAAAAAAAAEAAAAXAAAAY2hyb21lLW5hdGl2ZTovL25ld3RhYi8AAAAAAMnUrIeIYy4AAAAAAA"
                            + "AAAAC0AQAAsAEAAAEAAAAUAAAAaHR0cDovL3RleHRhcmVhLm9yZy8IAAAAdABlAHgAdABhAHIAZQBhAEABAA"
                            + "A8AQAAFQAAAAAAAAAoAAAAaAB0AHQAcAA6AC8ALwB0AGUAeAB0AGEAcgBlAGEALgBvAHIAZwAvAP////8AAA"
                            + "AAAAAAAP////8HAAAAYAAAAAoADQA/ACUAIABXAGUAYgBLAGkAdAAgAHMAZQByAGkAYQBsAGkAegBlAGQAIA"
                            + "BmAG8AcgBtACAAcwB0AGEAdABlACAAdgBlAHIAcwBpAG8AbgAgADgAIAAKAA0APQAmABAAAABOAG8AIABvAH"
                            + "cAbgBlAHIAAgAAADEAAAAAAAAAEAAAAHQAZQB4AHQAYQByAGUAYQACAAAAMQAAAAAAAAAIAAAAAAAAAAAAAA"
                            + "AHkSY/8gQFAAiRJj/yBAUABpEmP/IEBQABAAAACAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAD///"
                            + "//AAAAAAEAAAIAAAAAAAAAAAEAAAAUAAAAaHR0cDovL3RleHRhcmVhLm9yZy8AAAAANKvVh4hjLgAAAAAAyA"
                            + "AAAP////8AAAAAAAIAAAAAAAAAAwE=");

    /**
     * Tab model metadata file containing information about multiple tabs, with Baidu selected.
     * Ideally we'd have the M18 NTP in here, too, but it's difficult to get Chrome to visit that
     * URL now that the page is gone.
     *
     * <p>This file was created by clearing Chrome's app data, turning off wi-fi, and then visiting
     * each of the pages in turn.
     */
    public static final TabModelMetaDataInfo TAB_MODEL_METADATA_V4 =
            new TabModelMetaDataInfo(
                    4,
                    0,
                    V2_BAIDU.tabId,
                    new TabStateInfo[] {
                        M18_GOOGLE_COM,
                        M26_GOOGLE_COM,
                        M26_GOOGLE_CA,
                        V2_BAIDU,
                        V2_DUCK_DUCK_GO,
                        V2_HAARETZ,
                        V2_TEXTAREA
                    },
                    "AAAABAAAAAf/////AAAAAwAAAAEAFmh0dHA6Ly93d3cuZ29vZ2xlLmNvbS8AAAACABZodHRw"
                            + "Oi8vd3d3Lmdvb2dsZS5jb20vAAAAAwAVaHR0cDovL3d3dy5nb29nbGUuY2EvAAAABAAVaHR0"
                            + "cDovL3d3dy5iYWlkdS5jb20vAAAABQAXaHR0cHM6Ly9kdWNrZHVja2dvLmNvbS8AAAAGABlo"
                            + "dHRwOi8vd3d3LmhhYXJldHouY28uaWwvAAAABwAUaHR0cDovL3RleHRhcmVhLm9yZy8=");

    /** Same as TAB_MODEL_METADATA_V4, but using the version 5 file format. */
    public static final TabModelMetaDataInfo TAB_MODEL_METADATA_V5 =
            new TabModelMetaDataInfo(
                    5,
                    0,
                    V2_BAIDU.tabId,
                    new TabStateInfo[] {
                        M18_GOOGLE_COM,
                        M26_GOOGLE_COM,
                        M26_GOOGLE_CA,
                        V2_BAIDU,
                        V2_DUCK_DUCK_GO,
                        V2_HAARETZ,
                        V2_TEXTAREA
                    },
                    "AAAABQAAAAcAAAAA/////wAAAAMAAAABABZodHRwOi8vd3d3Lmdvb2dsZS5jb20vAAAAAgAWaHR0"
                            + "cDovL3d3dy5nb29nbGUuY29tLwAAAAMAFWh0dHA6Ly93d3cuZ29vZ2xlLmNhLwAAAAQAFWh0dHA6"
                            + "Ly93d3cuYmFpZHUuY29tLwAAAAUAF2h0dHBzOi8vZHVja2R1Y2tnby5jb20vAAAABgAZaHR0cDov"
                            + "L3d3dy5oYWFyZXR6LmNvLmlsLwAAAAcAFGh0dHA6Ly90ZXh0YXJlYS5vcmcv");

    /**
     * Similar to TAB_MODEL_METADATA_V5, but has a single Incognito tab. The tab state can't be
     * restored (currently) because this Class doesn't support Incognito TabStates.
     */
    public static final TabModelMetaDataInfo TAB_MODEL_METADATA_V5_WITH_INCOGNITO =
            new TabModelMetaDataInfo(
                    5,
                    1,
                    V2_BAIDU.tabId,
                    new TabStateInfo[] {
                        null,
                        M18_GOOGLE_COM,
                        M26_GOOGLE_COM,
                        M26_GOOGLE_CA,
                        V2_BAIDU,
                        V2_DUCK_DUCK_GO,
                        V2_HAARETZ,
                        V2_TEXTAREA
                    },
                    "AAAABQAAAAgAAAABAAAAAAAAAAQAAAAIABRodHRwOi8vZXJmd29ybGQuY29tLwAAAAEAFmh0dHA6"
                            + "Ly93d3cuZ29vZ2xlLmNvbS8AAAACABZodHRwOi8vd3d3Lmdvb2dsZS5jb20vAAAAAwAVaHR0cDov"
                            + "L3d3dy5nb29nbGUuY2EvAAAABAAVaHR0cDovL3d3dy5iYWlkdS5jb20vAAAABQAXaHR0cHM6Ly9k"
                            + "dWNrZHVja2dvLmNvbS8AAAAGABlodHRwOi8vd3d3LmhhYXJldHouY28uaWwvAAAABwAUaHR0cDov"
                            + "L3RleHRhcmVhLm9yZy8=");

    /** Same as TAB_MODEL_METADATA_V4, but using the version 5 file format. */
    public static final TabModelMetaDataInfo TAB_MODEL_METADATA_V5_NO_M18 =
            new TabModelMetaDataInfo(
                    5,
                    0,
                    M26_GOOGLE_CA.tabId,
                    new TabStateInfo[] {
                        V2_TEXTAREA,
                        V2_BAIDU,
                        V2_DUCK_DUCK_GO,
                        V2_HAARETZ,
                        M26_GOOGLE_CA,
                        M26_GOOGLE_COM
                    },
                    "AAAABQAAAAYAAAAA/////wAAAAQAAAAHABRodHRwOi8vdGV4dGFyZWEub3JnLwAAAAQAFWh0dHA6"
                            + "Ly93d3cuYmFpZHUuY29tLwAAAAUAF2h0dHBzOi8vZHVja2R1Y2tnby5jb20vAAAABgAZaHR0cDov"
                            + "L3d3dy5oYWFyZXR6LmNvLmlsLwAAAAMAFWh0dHA6Ly93d3cuZ29vZ2xlLmNhLwAAAAIAFmh0dHA6"
                            + "Ly93d3cuZ29vZ2xlLmNvbS8=");

    // Active Tab is google.ca (M26_GOOGLE_CA) with Tab ID 1.
    // Other Tab in the Tab Model is google.com (M26_GOOGLE_COM) with Tab ID 3
    public static final TabModelMetaDataInfo GOOGLE_CA_GOOGLE_COM =
            new TabModelMetaDataInfo(
                    5,
                    0,
                    M26_GOOGLE_CA.tabId,
                    new TabStateInfo[] {M26_GOOGLE_CA, M26_GOOGLE_COM},
                    // The following can be obtained by
                    // 1. Open Chrome for Android, navigate to sites, open new Tabs, windows etc.
                    // 2. adb root
                    // 3. adb pull
                    // /data/data/com.google.android.apps.chrome/app_tabs/0/tab_state<index> .
                    // e.g. adb pull /data/data/com.google.android.apps.chrome/app_tabs/0/tab_state1
                    // .
                    // for second window selector
                    // 4. base64 tab_state<index> e.g. base64 tab_state1
                    "AAAABQAAAAIAAAAA/////wAAAAAAAAABABZodHRwczovL3d3dy5nb29nbGUuY2EvAAAABQAXaHR0"
                            + "cHM6Ly93d3cuZ29vZ2xlLmNvbS8=");

    // Active Tab is textarea.org (V2_TEXTAREA) with Tab ID 4.
    // Other Tab in the Tab Model is duckduckgo.com (V2_DUCK_DUCK_GO) with Tab ID 5
    // Tabs 6 and 7 are incognito Tabs.
    public static final TabModelMetaDataInfo TEXTAREA_DUCK_DUCK_GO =
            new TabModelMetaDataInfo(
                    5,
                    0,
                    V2_TEXTAREA.tabId,
                    new TabStateInfo[] {V2_TEXTAREA, V2_DUCK_DUCK_GO},
                    "AAAABQAAAAQAAAACAAAAAQAAAAIAAAAGABJodHRwczovL2hlbGxvLmNvbS8AAAAHAA9odHRwOi8v"
                            + "Ym9vLmNvbS8AAAADABVodHRwczovL3RleHRhcmVhLm9yZy8AAAAEABdodHRwczovL2R1Y2tkdWNr"
                            + "Z28uY29tLw==");

    private File mTestingDirectory;
    private File mDataDirectory;

    /**
     * Creates a temporary directory that stores {@link TabState} files and the metadata required to
     * restore the {@link TabModel}s from the {@link TabPersistentStore}.
     *
     * @param context Context to use.
     * @param baseDirectoryName Name of the directory to store test data in.
     * @param subdirectoryName Subdirectory of the base directory. May be null, in which case the
     *     baseDirectoryName will store the data. This mocks out how the ChromeTabbedActivity
     *     instances all store their data in different subdirectories of the base data directory.
     */
    public TestTabModelDirectory(
            Context context, String baseDirectoryName, String subdirectoryName) {
        mTestingDirectory = new File(context.getCacheDir(), baseDirectoryName);
        if (mTestingDirectory.exists()) {
            FileUtils.recursivelyDeleteFile(mTestingDirectory, FileUtils.DELETE_ALL);
        }
        if (!mTestingDirectory.mkdirs()) {
            Log.e(TAG, "Failed to create: " + mTestingDirectory.getName());
        }

        // Create the subdirectory.
        mDataDirectory = mTestingDirectory;
        if (subdirectoryName != null) {
            mDataDirectory = new File(mTestingDirectory, subdirectoryName);
            if (!mDataDirectory.exists() && !mDataDirectory.mkdirs()) {
                Log.e(TAG, "Failed to create subdirectory: " + mDataDirectory.getName());
            }
        }
    }

    /** Nukes all the testing data. */
    public void tearDown() {
        FileUtils.recursivelyDeleteFile(mTestingDirectory, FileUtils.DELETE_ALL);
    }

    /** Returns the base data directory. */
    public File getBaseDirectory() {
        return mTestingDirectory;
    }

    /** Returns the directory containing all the TabStates. */
    public File getDataDirectory() {
        return mDataDirectory;
    }

    /** Calls the three-param version of this method with index = 0. */
    public void writeTabModelFiles(TabModelMetaDataInfo info, boolean writeTabStates)
            throws Exception {
        writeTabModelFiles(info, writeTabStates, 0);
    }

    /**
     * Writes out data required to restore a TabModel to the data directories.
     *
     * @param info The info to write to the tab metadata file.
     * @param writeTabStates Whether or not to write the TabState files for each of the Tabs out.
     * @param index The TabModelSelectorIndex to write the metadata file for.
     */
    public void writeTabModelFiles(TabModelMetaDataInfo info, boolean writeTabStates, int index)
            throws Exception {
        writeFile(mDataDirectory, "tab_state" + Integer.toString(index), info.encodedFile);
        for (TabStateInfo tabStateInfo : info.contents) {
            writeTabStateFile(tabStateInfo);
        }
    }

    /** Writes out a specific TabState file to the data directories. */
    public void writeTabStateFile(TabStateInfo info) throws Exception {
        if (info != null) writeFile(mDataDirectory, info.filename, info.encodedTabState);
    }

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