Android: Factor out TabState API from Tab

Moves TabState-related stuff from Tab to TabState which is a collection
of static utility methods.

|SHOULD_PRESERVE| is not in use any more. Deleted the field from
TabState, and access to/from Bundle.

Bug: 925242
Change-Id: I675275d821cabcccc5e67eef67baa80a103073a0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1501952
Reviewed-by: Theresa <twellington@chromium.org>
Commit-Queue: Jinsuk Kim <jinsukkim@chromium.org>
Cr-Commit-Position: refs/heads/master@{#639704}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
index 28c03c7..025656d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
@@ -86,14 +86,12 @@
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.WebContentsAccessibility;
 import org.chromium.content_public.common.BrowserControlsState;
-import org.chromium.content_public.common.Referrer;
 import org.chromium.content_public.common.ResourceRequestBody;
 import org.chromium.ui.base.PageTransition;
 import org.chromium.ui.base.WindowAndroid;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.nio.ByteBuffer;
 
 /**
  * The basic Java representation of a tab.  Contains and manages a {@link ContentView}.
@@ -226,12 +224,6 @@
     private String mAppAssociatedWith;
 
     /**
-     * Keeps track of whether the Tab should be kept in the TabModel after the user hits "back".
-     * Used by Document mode to keep track of whether we want to remove the tab when user hits back.
-     */
-    private boolean mShouldPreserve;
-
-    /**
      * True while a page load is in progress.
      */
     private boolean mIsLoading;
@@ -411,7 +403,6 @@
         assert state != null;
         mAppAssociatedWith = state.openerAppId;
         mFrozenContentsState = state.contentsState;
-        mShouldPreserve = state.shouldPreserve;
         mTimestampMillis = state.timestampMillis;
         mUrl = state.getVirtualUrlFromState();
 
@@ -648,58 +639,11 @@
         return getActivity().getTabModelSelector();
     }
 
-    /** @return An opaque "state" object that can be persisted to storage. */
-    public TabState getState() {
-        if (!isInitialized()) return null;
-        TabState tabState = new TabState();
-        tabState.contentsState = getWebContentsState();
-        tabState.openerAppId = mAppAssociatedWith;
-        tabState.parentId = mParentId;
-        tabState.shouldPreserve = mShouldPreserve;
-        tabState.timestampMillis = mTimestampMillis;
-        tabState.tabLaunchTypeAtCreation = mLaunchTypeAtCreation;
-        // Don't save actual default theme color because it could change on night mode state
-        // changed.
-        tabState.themeColor = TabThemeColorHelper.isDefaultColorUsed(this)
-                ? TabState.UNSPECIFIED_THEME_COLOR
-                : TabThemeColorHelper.getColor(this);
-        tabState.rootId = mRootId;
-        return tabState;
-    }
-
     /** @return WebContentsState representing the state of the WebContents (navigations, etc.) */
-    private WebContentsState getFrozenContentsState() {
+    WebContentsState getFrozenContentsState() {
         return mFrozenContentsState;
     }
 
-    /** Returns an object representing the state of the Tab's WebContents. */
-    private TabState.WebContentsState getWebContentsState() {
-        if (mFrozenContentsState != null) return mFrozenContentsState;
-
-        // Native call returns null when buffer allocation needed to serialize the state failed.
-        ByteBuffer buffer = getWebContentsStateAsByteBuffer();
-        if (buffer == null) return null;
-
-        TabState.WebContentsState state = new TabState.WebContentsState(buffer);
-        state.setVersion(TabState.CONTENTS_STATE_CURRENT_VERSION);
-        return state;
-    }
-
-    /** Returns an ByteBuffer representing the state of the Tab's WebContents. */
-    private ByteBuffer getWebContentsStateAsByteBuffer() {
-        if (mPendingLoadParams == null) {
-            return TabState.getContentsStateAsByteBuffer(this);
-        } else {
-            Referrer referrer = mPendingLoadParams.getReferrer();
-            return TabState.createSingleNavigationStateAsByteBuffer(
-                    mPendingLoadParams.getUrl(),
-                    referrer != null ? referrer.getUrl() : null,
-                    // Policy will be ignored for null referrer url, 0 is just a placeholder.
-                    referrer != null ? referrer.getPolicy() : 0,
-                    isIncognito());
-        }
-    }
-
     /**
      * Reloads the current page content.
      */
@@ -1950,7 +1894,7 @@
     /**
      * @return Parameters that should be used for a lazily loaded Tab.  May be null.
      */
-    private LoadUrlParams getPendingLoadParams() {
+    LoadUrlParams getPendingLoadParams() {
         return mPendingLoadParams;
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabState.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabState.java
index 8c50b64b..e0fb454 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabState.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabState.java
@@ -17,7 +17,9 @@
 import org.chromium.chrome.browser.crypto.CipherFactory;
 import org.chromium.chrome.browser.tabmodel.TabLaunchType;
 import org.chromium.chrome.browser.util.ColorUtils;
+import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.WebContents;
+import org.chromium.content_public.common.Referrer;
 
 import java.io.BufferedOutputStream;
 import java.io.DataInputStream;
@@ -76,7 +78,6 @@
     private static final String PARENT_ID = TAB_STATE_BUNDLE_PREFIX + "parentId";
     private static final String OPENER_APP_ID = TAB_STATE_BUNDLE_PREFIX + "openerAppId";
     private static final String VERSION = TAB_STATE_BUNDLE_PREFIX + "version";
-    private static final String SHOULD_PRESERVE = TAB_STATE_BUNDLE_PREFIX + "shouldPreserve";
     private static final String THEME_COLOR = TAB_STATE_BUNDLE_PREFIX + "themeColor";
     private static final String IS_INCOGNITO = TAB_STATE_BUNDLE_PREFIX + "isIncognito";
 
@@ -144,7 +145,6 @@
 
     public long timestampMillis = TIMESTAMP_NOT_SET;
     public String openerAppId;
-    public boolean shouldPreserve;
 
     /**
      * The tab's brand theme color. Set this to {@link #UNSPECIFIED_THEME_COLOR} for an unspecified
@@ -224,7 +224,6 @@
         tabState.parentId = bundle.getInt(PARENT_ID);
         tabState.openerAppId = bundle.getString(OPENER_APP_ID);
         tabState.contentsState.setVersion(bundle.getInt(VERSION));
-        tabState.shouldPreserve = bundle.getBoolean(SHOULD_PRESERVE);
         tabState.themeColor = bundle.getInt(THEME_COLOR);
         tabState.mIsIncognito = bundle.getBoolean(IS_INCOGNITO);
 
@@ -300,10 +299,9 @@
             } catch (EOFException eof) {
             }
             try {
-                tabState.shouldPreserve = stream.readBoolean();
+                boolean shouldPreserveNotUsed = stream.readBoolean();
             } catch (EOFException eof) {
                 // Could happen if reading a version of TabState without this flag set.
-                tabState.shouldPreserve = false;
                 Log.w(TAG,
                         "Failed to read shouldPreserve flag from tab state. "
                                 + "Assuming shouldPreserve is false");
@@ -355,6 +353,47 @@
         return contentsStateBytes;
     }
 
+    /** @return An opaque "state" object that can be persisted to storage. */
+    public static TabState from(Tab tab) {
+        if (!tab.isInitialized()) return null;
+        TabState tabState = new TabState();
+        tabState.contentsState = getWebContentsState(tab);
+        tabState.openerAppId = tab.getAppAssociatedWith();
+        tabState.parentId = tab.getParentId();
+        tabState.timestampMillis = tab.getTimestampMillis();
+        tabState.tabLaunchTypeAtCreation = tab.getLaunchTypeAtInitialTabCreation();
+        tabState.themeColor = TabThemeColorHelper.getColor(tab);
+        tabState.rootId = tab.getRootId();
+        return tabState;
+    }
+
+    /** Returns an object representing the state of the Tab's WebContents. */
+    private static WebContentsState getWebContentsState(Tab tab) {
+        if (tab.getFrozenContentsState() != null) return tab.getFrozenContentsState();
+
+        // Native call returns null when buffer allocation needed to serialize the state failed.
+        ByteBuffer buffer = getWebContentsStateAsByteBuffer(tab);
+        if (buffer == null) return null;
+
+        WebContentsState state = new WebContentsState(buffer);
+        state.setVersion(CONTENTS_STATE_CURRENT_VERSION);
+        return state;
+    }
+
+    /** Returns an ByteBuffer representing the state of the Tab's WebContents. */
+    private static ByteBuffer getWebContentsStateAsByteBuffer(Tab tab) {
+        LoadUrlParams pendingLoadParams = tab.getPendingLoadParams();
+        if (pendingLoadParams == null) {
+            return getContentsStateAsByteBuffer(tab);
+        } else {
+            Referrer referrer = pendingLoadParams.getReferrer();
+            return createSingleNavigationStateAsByteBuffer(pendingLoadParams.getUrl(),
+                    referrer != null ? referrer.getUrl() : null,
+                    // Policy will be ignored for null referrer url, 0 is just a placeholder.
+                    referrer != null ? referrer.getPolicy() : 0, tab.isIncognito());
+        }
+    }
+
     /**
      * Writes the TabState to disk. This method may be called on either the UI or background thread.
      * @param file File to write the tab's state to.
@@ -397,7 +436,7 @@
             dataOutputStream.writeUTF(state.openerAppId != null ? state.openerAppId : "");
             dataOutputStream.writeInt(state.contentsState.version());
             dataOutputStream.writeLong(-1); // Obsolete sync ID.
-            dataOutputStream.writeBoolean(state.shouldPreserve);
+            dataOutputStream.writeBoolean(false); // Obsolete attribute |SHOULD_PRESERVE|.
             dataOutputStream.writeInt(state.themeColor);
             dataOutputStream.writeInt(
                     state.tabLaunchTypeAtCreation != null ? state.tabLaunchTypeAtCreation : -1);
@@ -434,7 +473,6 @@
         bundle.putInt(PARENT_ID, state.parentId);
         bundle.putString(OPENER_APP_ID, state.openerAppId);
         bundle.putInt(VERSION, state.contentsState.version());
-        bundle.putBoolean(SHOULD_PRESERVE, state.shouldPreserve);
         bundle.putInt(THEME_COLOR, state.themeColor);
         bundle.putBoolean(IS_INCOGNITO, state.isIncognito());
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java
index a7b0ae0..e92175a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java
@@ -317,7 +317,7 @@
                 int id = tab.getId();
                 boolean incognito = tab.isIncognito();
                 try {
-                    TabState state = tab.getState();
+                    TabState state = TabState.from(tab);
                     if (state != null) {
                         TabState.saveState(getTabStateFile(id, incognito), state, incognito);
                     }
@@ -1141,7 +1141,7 @@
         @Override
         protected void onPreExecute() {
             if (mDestroyed || isCancelled()) return;
-            mState = mTab.getState();
+            mState = TabState.from(mTab);
         }
 
         @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
index 6e3b34c..11cd4a3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
@@ -371,7 +371,7 @@
         StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
         try {
             long time = SystemClock.elapsedRealtime();
-            TabState.saveState(tabFile, getActivityTab().getState(), false);
+            TabState.saveState(tabFile, TabState.from(getActivityTab()), false);
             RecordHistogram.recordTimesHistogram(
                     "Android.StrictMode.WebappSaveState", SystemClock.elapsedRealtime() - time);
         } finally {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/privacy/ClearBrowsingDataPreferencesTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/privacy/ClearBrowsingDataPreferencesTest.java
index 9bb6462..5acb866 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/privacy/ClearBrowsingDataPreferencesTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/privacy/ClearBrowsingDataPreferencesTest.java
@@ -572,11 +572,12 @@
         Tab[] frozen = new Tab[1];
         WebContents[] restored = new WebContents[1];
         ThreadUtils.runOnUiThreadBlocking(() -> {
-            TabState state = tab.getState();
+            TabState state = TabState.from(tab);
             mActivityTestRule.getActivity().getCurrentTabModel().closeTab(tab);
             frozen[0] = mActivityTestRule.getActivity().getCurrentTabCreator().createFrozenTab(
                     state, tab.getId(), 1);
-            restored[0] = frozen[0].getState().contentsState.restoreContentsFromByteBuffer(false);
+            restored[0] =
+                    TabState.from(frozen[0]).contentsState.restoreContentsFromByteBuffer(false);
         });
 
         // Check content of frozen state.
@@ -594,7 +595,8 @@
 
         // Check that frozen state was cleaned up.
         ThreadUtils.runOnUiThreadBlocking(() -> {
-            restored[0] = frozen[0].getState().contentsState.restoreContentsFromByteBuffer(false);
+            restored[0] =
+                    TabState.from(frozen[0]).contentsState.restoreContentsFromByteBuffer(false);
         });
         controller = restored[0].getNavigationController();
         assertEquals(0, controller.getLastCommittedEntryIndex());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabStateTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabStateTest.java
index 254bb0a..93651ce 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabStateTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabStateTest.java
@@ -101,7 +101,6 @@
         tabState.parentId = 2;
         tabState.openerAppId = "app";
         tabState.contentsState.setVersion(TabState.CONTENTS_STATE_CURRENT_VERSION);
-        tabState.shouldPreserve = true;
         tabState.themeColor = Color.BLACK;
         tabState.mIsIncognito = true;
 
@@ -116,7 +115,6 @@
         Assert.assertEquals(tabState.timestampMillis, restoredState.timestampMillis);
         Assert.assertEquals(
                 tabState.contentsState.version(), restoredState.contentsState.version());
-        Assert.assertEquals(tabState.shouldPreserve, restoredState.shouldPreserve);
         Assert.assertEquals(tabState.themeColor, restoredState.themeColor);
         Assert.assertEquals(tabState.mIsIncognito, restoredState.mIsIncognito);
     }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/tabstate/TabStateUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/tabstate/TabStateUnitTest.java
index 44a53cd..0c1fd9a 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/tabstate/TabStateUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/tabstate/TabStateUnitTest.java
@@ -37,7 +37,6 @@
     private static final int PARENT_ID = 1;
     private static final int VERSION = 2;
     private static final int THEME_COLOR = 4;
-    private static final boolean SHOULD_PRESERVE = true;
     private static final String OPENER_APP_ID = "test";
     private static final @Nullable @TabLaunchType Integer LAUNCH_TYPE_AT_CREATION = null;
     private static final int ROOT_ID = 1;
@@ -67,7 +66,6 @@
             state.timestampMillis = TIMESTAMP;
             state.parentId = PARENT_ID;
             state.themeColor = THEME_COLOR;
-            state.shouldPreserve = SHOULD_PRESERVE;
             state.openerAppId = OPENER_APP_ID;
             state.tabLaunchTypeAtCreation = LAUNCH_TYPE_AT_CREATION;
             state.rootId = ROOT_ID;
@@ -82,7 +80,6 @@
         assertEquals(PARENT_ID, state.parentId);
         assertEquals(OPENER_APP_ID, state.openerAppId);
         assertEquals(VERSION, state.contentsState.version());
-        assertEquals(SHOULD_PRESERVE, state.shouldPreserve);
         assertEquals(THEME_COLOR, state.getThemeColor());
         assertEquals(LAUNCH_TYPE_AT_CREATION, state.tabLaunchTypeAtCreation);
         assertEquals(ROOT_ID, state.rootId);
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java
index 6edd39c..aa326fe 100644
--- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java
@@ -153,7 +153,7 @@
         if (tab == null || tab.getUrl() == null || tab.getUrl().isEmpty()) return;
         long time = SystemClock.elapsedRealtime();
         outState.putInt(BUNDLE_TAB_ID, tab.getId());
-        TabState.saveState(outState, tab.getState());
+        TabState.saveState(outState, TabState.from(tab));
         RecordHistogram.recordTimesHistogram("Android.StrictMode.NoTouchActivitySaveState",
                 SystemClock.elapsedRealtime() - time);
     }