Index: chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java |
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 9aea9801823bbc8d361841df358f332b9d6c876b..9a8bf23a227bcf67c6d964e0bd6b9e28b311c216 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 |
@@ -10,9 +10,11 @@ import android.os.AsyncTask; |
import android.os.StrictMode; |
import android.os.SystemClock; |
import android.support.annotation.Nullable; |
+import android.support.annotation.WorkerThread; |
import android.support.v4.util.AtomicFile; |
import android.text.TextUtils; |
import android.util.Pair; |
+import android.util.SparseArray; |
import android.util.SparseIntArray; |
import org.chromium.base.ContextUtils; |
@@ -44,6 +46,7 @@ import java.util.Arrays; |
import java.util.Deque; |
import java.util.List; |
import java.util.concurrent.ExecutionException; |
+import java.util.concurrent.Executor; |
import java.util.concurrent.TimeUnit; |
/** |
@@ -65,6 +68,10 @@ public class TabPersistentStore extends TabPersister { |
@VisibleForTesting |
public static final String SAVED_STATE_FILE = "tab_state"; |
+ /** The name of the directory where the state is saved. */ |
+ @VisibleForTesting |
+ static final String SAVED_STATE_DIRECTORY = "0"; |
+ |
@VisibleForTesting |
static final String PREF_ACTIVE_TAB_ID = |
"org.chromium.chrome.browser.tabmodel.TabPersistentStore.ACTIVE_TAB_ID"; |
@@ -73,6 +80,11 @@ public class TabPersistentStore extends TabPersister { |
static final String PREF_HAS_RUN_FILE_MIGRATION = |
"org.chromium.chrome.browser.tabmodel.TabPersistentStore.HAS_RUN_FILE_MIGRATION"; |
+ @VisibleForTesting |
+ static final String PREF_HAS_RUN_MULTI_INSTANCE_FILE_MIGRATION = |
+ "org.chromium.chrome.browser.tabmodel.TabPersistentStore." |
+ + "HAS_RUN_MULTI_INSTANCE_FILE_MIGRATION"; |
+ |
private static final String PREF_HAS_COMPUTED_MAX_ID = |
"org.chromium.chrome.browser.tabmodel.TabPersistentStore.HAS_COMPUTED_MAX_ID"; |
@@ -83,6 +95,15 @@ public class TabPersistentStore extends TabPersister { |
private static final Object SAVE_LIST_LOCK = new Object(); |
/** |
+ * Prevents two clean up tasks from getting created simultaneously. Also protects against |
+ * incorrectly interleaving create/run/cancel on the task. |
+ */ |
+ private static final Object CLEAN_UP_TASK_LOCK = new Object(); |
+ |
+ /** Prevents two state directories from getting created simultaneously */ |
+ private static final Object DIR_CREATION_LOCK = new Object(); |
+ |
+ /** |
* Callback interface to use while reading the persisted TabModelSelector info from disk. |
*/ |
public static interface OnTabStateReadCallback { |
@@ -146,11 +167,13 @@ public class TabPersistentStore extends TabPersister { |
} |
private static class BaseStateDirectoryHolder { |
- @VisibleForTesting |
+ // Not final for tests. |
private static File sDirectory = getOrCreateBaseStateDirectory(); |
} |
private static AsyncTask<Void, Void, Void> sMigrationTask = null; |
+ private static AsyncTask<Void, Void, CleanUpTabStateDataInfo> sCleanupTask = null; |
+ private static File sStateDirectory; |
private final TabModelSelector mTabModelSelector; |
private final TabCreatorManager mTabCreatorManager; |
@@ -171,8 +194,6 @@ public class TabPersistentStore extends TabPersister { |
private boolean mCancelNormalTabLoads = false; |
private boolean mCancelIncognitoTabLoads = false; |
- private File mStateDirectory; |
- |
// Keys are the original tab indexes, values are the tab ids. |
private SparseIntArray mNormalTabsRestored; |
private SparseIntArray mIncognitoTabsRestored; |
@@ -184,11 +205,10 @@ public class TabPersistentStore extends TabPersister { |
@VisibleForTesting |
AsyncTask<Void, Void, TabState> mPrefetchActiveTabTask; |
- |
/** |
* Creates an instance of a TabPersistentStore. |
* @param modelSelector The {@link TabModelSelector} to restore to and save from. |
- * @param selectorIndex The index that represents which sub folder to pull and save state to. |
+ * @param selectorIndex The index that represents which state file to pull and save state to. |
* This is used when there can be more than one TabModelSelector. |
* @param context A Context instance. |
* @param tabCreatorManager The {@link TabCreatorManager} to use. |
@@ -211,29 +231,32 @@ public class TabPersistentStore extends TabPersister { |
@Override |
protected File getStateDirectory() { |
- if (mStateDirectory == null) { |
- mStateDirectory = getOrCreateSelectorStateDirectory(mSelectorIndex); |
- } |
- return mStateDirectory; |
+ // TODO(twellington): remove this method as the super-class separation was only useful for |
+ // document mode. |
+ return getOrCreateStateDirectory(); |
} |
/** |
* The folder where the state should be saved to. |
- * @param index The TabModelSelector index. |
- * @return A file representing the directory that contains the TabModelSelector state. |
+ * @return A file representing the directory that contains TabModelSelector states. |
*/ |
- public static File getOrCreateSelectorStateDirectory(int index) { |
- File file = new File(BaseStateDirectoryHolder.sDirectory, Integer.toString(index)); |
- StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); |
- StrictMode.allowThreadDiskWrites(); |
- try { |
- if (!file.exists() && !file.mkdirs()) { |
- Log.e(TAG, "Failed to create state folder: " + file); |
+ public static File getOrCreateStateDirectory() { |
+ synchronized (DIR_CREATION_LOCK) { |
+ if (sStateDirectory == null) { |
+ sStateDirectory = new File( |
+ BaseStateDirectoryHolder.sDirectory, SAVED_STATE_DIRECTORY); |
+ StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); |
+ StrictMode.allowThreadDiskWrites(); |
+ try { |
+ if (!sStateDirectory.exists() && !sStateDirectory.mkdirs()) { |
+ Log.e(TAG, "Failed to create state folder: " + sStateDirectory); |
+ } |
+ } finally { |
+ StrictMode.setThreadPolicy(oldPolicy); |
+ } |
} |
- } finally { |
- StrictMode.setThreadPolicy(oldPolicy); |
} |
- return file; |
+ return sStateDirectory; |
} |
/** |
@@ -347,6 +370,12 @@ public class TabPersistentStore extends TabPersister { |
*/ |
public void loadState() { |
long time = SystemClock.uptimeMillis(); |
+ |
+ // If a cleanup task is in progress, cancel it before loading state. |
+ synchronized (CLEAN_UP_TASK_LOCK) { |
+ if (sCleanupTask != null) sCleanupTask.cancel(true); |
+ } |
+ |
waitForMigrationToFinish(); |
logExecutionTime("LoadStateTime", time); |
@@ -363,7 +392,7 @@ public class TabPersistentStore extends TabPersister { |
DataInputStream stream = mPrefetchTabListTask.get(); |
logExecutionTime("LoadStateInternalPrefetchTime", timeWaitingForPrefetch); |
readSavedStateFile(stream, |
- createOnTabStateReadCallback(mTabModelSelector.isIncognitoSelected())); |
+ createOnTabStateReadCallback(mTabModelSelector.isIncognitoSelected()), null); |
logExecutionTime("LoadStateInternalTime", time); |
} catch (Exception e) { |
// Catch generic exception to prevent a corrupted state from crashing app on startup. |
@@ -522,9 +551,23 @@ public class TabPersistentStore extends TabPersister { |
return mTabsToRestore.size(); |
} |
+ /** |
+ * Deletes all files in the tab state directory. |
+ */ |
public void clearState() { |
- deleteFileAsync(SAVED_STATE_FILE); |
- cleanUpPersistentData(); |
+ synchronized (CLEAN_UP_TASK_LOCK) { |
+ if (sCleanupTask != null) sCleanupTask.cancel(true); |
+ } |
+ |
+ // Delete state files first. These files are deleted serially, ensuring that new state |
+ // files aren't created before the state files are cleared. |
+ for (int i = 0; i < TabWindowManager.MAX_SIMULTANEOUS_SELECTORS; i++) { |
+ deleteFileAsync(getStateFileName(i)); |
+ } |
+ |
+ // Clean up the rest of the persistent store data. |
+ cleanUpPersistentData(true); |
+ |
onStateLoaded(); |
} |
@@ -720,7 +763,7 @@ public class TabPersistentStore extends TabPersister { |
private void saveListToFile(byte[] listData) { |
if (Arrays.equals(mLastSavedMetadata, listData)) return; |
- saveListToFile(getStateDirectory(), listData); |
+ saveListToFile(getStateDirectory(), mSelectorIndex, listData); |
mLastSavedMetadata = listData; |
if (LibraryLoader.isInitialized()) { |
RecordHistogram.recordCountHistogram( |
@@ -731,12 +774,13 @@ public class TabPersistentStore extends TabPersister { |
/** |
* Atomically writes the given serialized data out to disk. |
* @param stateDirectory Directory to save TabModel data into. |
+ * @param selectorIndex Index of the TabModelSelector to write out. |
* @param listData TabModel data in the form of a serialized byte array. |
*/ |
- public static void saveListToFile(File stateDirectory, byte[] listData) { |
+ public static void saveListToFile(File stateDirectory, int selectorIndex, byte[] listData) { |
synchronized (SAVE_LIST_LOCK) { |
// Save the index file containing the list of tabs to restore. |
- File metadataFile = new File(stateDirectory, SAVED_STATE_FILE); |
+ File metadataFile = new File(stateDirectory, getStateFileName(selectorIndex)); |
AtomicFile file = new AtomicFile(metadataFile); |
FileOutputStream stream = null; |
@@ -798,17 +842,15 @@ public class TabPersistentStore extends TabPersister { |
// critical patch to initializing the TabIdManager with the correct max tab ID. |
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); |
try { |
- File[] folders = BaseStateDirectoryHolder.sDirectory.listFiles(); |
- if (folders == null) return; |
- for (File folder : folders) { |
- if (!folder.isDirectory()) continue; |
- File stateFile = new File(folder, SAVED_STATE_FILE); |
+ for (int i = 0; i < TabWindowManager.MAX_SIMULTANEOUS_SELECTORS; i++) { |
+ File stateFile = new File(getStateDirectory(), getStateFileName(i)); |
if (!stateFile.exists()) continue; |
+ |
DataInputStream stream = null; |
try { |
stream = new DataInputStream(new BufferedInputStream(new FileInputStream( |
stateFile))); |
- maxId = Math.max(maxId, readSavedStateFile(stream, null)); |
+ maxId = Math.max(maxId, readSavedStateFile(stream, null, null)); |
} finally { |
StreamUtil.closeQuietly(stream); |
} |
@@ -820,8 +862,8 @@ public class TabPersistentStore extends TabPersister { |
mPreferences.edit().putBoolean(PREF_HAS_COMPUTED_MAX_ID, true).apply(); |
} |
- private int readSavedStateFile(DataInputStream stream, OnTabStateReadCallback callback) |
- throws IOException { |
+ private int readSavedStateFile(DataInputStream stream, OnTabStateReadCallback callback, |
+ SparseArray<Boolean> tabIds) throws IOException { |
long time = SystemClock.uptimeMillis(); |
int nextId = 0; |
boolean skipUrlRead = false; |
@@ -847,6 +889,7 @@ public class TabPersistentStore extends TabPersister { |
int id = stream.readInt(); |
String tabUrl = skipUrlRead ? "" : stream.readUTF(); |
if (id >= nextId) nextId = id + 1; |
+ if (tabIds != null) tabIds.append(id, true); |
Boolean isIncognito = (incognitoCount < 0) ? null : i < incognitoCount; |
if (callback != null) { |
@@ -954,7 +997,11 @@ public class TabPersistentStore extends TabPersister { |
if (mTabsToRestore.isEmpty()) { |
mNormalTabsRestored = null; |
mIncognitoTabsRestored = null; |
- cleanUpPersistentData(); |
+ // Only clean up persistent data on application cold start. |
+ if (mSelectorIndex == 0 |
+ && TabWindowManager.getInstance().getNumberOfAssignedTabModelSelectors() == 1) { |
+ cleanUpPersistentData(false); |
+ } |
onStateLoaded(); |
mLoadTabTask = null; |
Log.d(TAG, "Loaded tab lists; counts: " + mTabModelSelector.getModel(false).getCount() |
@@ -966,8 +1013,21 @@ public class TabPersistentStore extends TabPersister { |
} |
} |
- private void cleanUpPersistentData() { |
- new CleanUpTabStateDataTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
+ /** |
+ * Creates an asynchronous task to delete persistent data. The task is run using a thread pool |
+ * and may be executed in parallel with other tasks. The cleanup task use a combination of the |
+ * current model and the tab state files for other models to determine which tab files should |
+ * be deleted. The cleanup task should be canceled if a second tab model is created. |
+ * |
+ * @param deleteAllFiles Whether all tab files should be deleted regardless of whether |
+ * they are found in a tab model. |
+ */ |
+ private void cleanUpPersistentData(boolean deleteAllFiles) { |
+ synchronized (CLEAN_UP_TASK_LOCK) { |
+ if (sCleanupTask != null) sCleanupTask.cancel(true); |
+ sCleanupTask = new CleanUpTabStateDataTask(deleteAllFiles); |
+ sCleanupTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
+ } |
if (mTabContentManager != null) { |
mTabContentManager.cleanUpPersistentData(mTabModelSelector); |
@@ -991,27 +1051,54 @@ public class TabPersistentStore extends TabPersister { |
return null; |
} |
}.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); |
+ // TODO(twellington): delete tab files using the thread pool rather than the serial |
+ // executor. |
} |
- private class CleanUpTabStateDataTask extends AsyncTask<Void, Void, String[]> { |
+ private static class CleanUpTabStateDataInfo { |
+ private final String[] mTabFileNames; |
+ private final SparseArray<Boolean> mOtherTabIds; |
+ |
+ public CleanUpTabStateDataInfo(String[] tabFileNames, SparseArray<Boolean> otherTabIds) { |
+ mTabFileNames = tabFileNames; |
+ mOtherTabIds = otherTabIds; |
+ } |
+ } |
+ |
+ private class CleanUpTabStateDataTask extends AsyncTask<Void, Void, CleanUpTabStateDataInfo> { |
+ private final boolean mDeleteAllFiles; |
+ |
+ /** |
+ * Creates a new AsyncTask to delete tab files. If deleteAllFiles is true, all tab files |
+ * will be deleted regardless of whether they currently belong to a tab model. If it is |
+ * false, only tab files that no longer belong to a model will be deleted. Tab files for |
+ * custom tabs will be deleted regardless of the deleteAllFiles value. |
+ */ |
+ public CleanUpTabStateDataTask(boolean deleteAllFiles) { |
+ mDeleteAllFiles = deleteAllFiles; |
+ } |
+ |
@Override |
- protected String[] doInBackground(Void... voids) { |
+ protected CleanUpTabStateDataInfo doInBackground(Void... voids) { |
if (mDestroyed) { |
return null; |
} |
- return getStateDirectory().list(); |
+ SparseArray<Boolean> otherTabIds = new SparseArray<Boolean>(); |
+ if (!mDeleteAllFiles) getTabsFromOtherStateFiles(otherTabIds); |
+ |
+ return new CleanUpTabStateDataInfo(getStateDirectory().list(), otherTabIds); |
} |
@Override |
- protected void onPostExecute(String[] fileNames) { |
- if (mDestroyed || fileNames == null) { |
- return; |
- } |
- for (String fileName : fileNames) { |
+ protected void onPostExecute(CleanUpTabStateDataInfo tabStateInfo) { |
+ if (mDestroyed || tabStateInfo.mTabFileNames == null) return; |
+ |
+ for (String fileName : tabStateInfo.mTabFileNames) { |
Pair<Integer, Boolean> data = TabState.parseInfoFromFilename(fileName); |
if (data != null) { |
TabModel model = mTabModelSelector.getModel(data.second); |
- if (TabModelUtils.getTabById(model, data.first) == null) { |
+ if (mDeleteAllFiles || (TabModelUtils.getTabById(model, data.first) == null |
+ && tabStateInfo.mOtherTabIds.get(data.first) == null)) { |
// It might be more efficient to use a single task for all files, but |
// the number of files is expected to be very small. |
deleteFileAsync(fileName); |
@@ -1021,6 +1108,29 @@ public class TabPersistentStore extends TabPersister { |
} |
} |
+ /** |
+ * Gets the IDs of all tabs in TabModelSelectors other than the currently selected one. IDs for |
+ * custom tabs are excluded. |
+ * @param tabIds SparseArray to populate with TabIds. |
+ */ |
+ private void getTabsFromOtherStateFiles(SparseArray<Boolean> tabIds) { |
+ for (int i = 0; i < TabWindowManager.MAX_SIMULTANEOUS_SELECTORS; i++) { |
+ if (i == mSelectorIndex) continue; |
+ |
+ File metadataFile = new File(getStateDirectory(), getStateFileName(i)); |
+ if (metadataFile.exists()) { |
+ DataInputStream stream; |
+ try { |
+ stream = new DataInputStream( |
+ new BufferedInputStream(new FileInputStream(metadataFile))); |
+ readSavedStateFile(stream, null, tabIds); |
+ } catch (Exception e) { |
+ Log.e(TAG, "Unable to read state for " + metadataFile.getName() + ": " + e); |
+ } |
+ } |
+ } |
+ } |
+ |
private class LoadTabTask extends AsyncTask<Void, Void, TabState> { |
public final TabRestoreDetails mTabToRestore; |
@@ -1067,47 +1177,146 @@ public class TabPersistentStore extends TabPersister { |
} |
/** |
- * Users upgrading from an old version of Chrome when the state file was still in the root |
- * directory. |
+ * Creates a task to move and rename state files so that they are ultimately in the correct |
+ * directory. All state files are stored the same directory. |
*/ |
private void startMigrationTaskIfNecessary() { |
- if (mPreferences.getBoolean(PREF_HAS_RUN_FILE_MIGRATION, false)) return; |
+ final boolean hasRunLegacyMigration = |
+ mPreferences.getBoolean(PREF_HAS_RUN_FILE_MIGRATION, false); |
+ final boolean hasRunMultiInstanceMigration = |
+ mPreferences.getBoolean(PREF_HAS_RUN_MULTI_INSTANCE_FILE_MIGRATION, false); |
+ |
+ if (hasRunLegacyMigration && hasRunMultiInstanceMigration) return; |
synchronized (MIGRATION_LOCK) { |
if (sMigrationTask != null) return; |
sMigrationTask = new AsyncTask<Void, Void, Void>() { |
@Override |
protected Void doInBackground(Void... params) { |
- File newFolder = getStateDirectory(); |
- File[] newFiles = newFolder.listFiles(); |
- |
- // Attempt migration if we have no tab state file in the new directory. |
- if (newFiles == null || newFiles.length == 0) { |
- File oldFolder = mContext.getFilesDir(); |
- File modelFile = new File(oldFolder, SAVED_STATE_FILE); |
- if (modelFile.exists()) { |
- if (!modelFile.renameTo(new File(newFolder, SAVED_STATE_FILE))) { |
- Log.e(TAG, "Failed to rename file: " + modelFile); |
+ if (!hasRunLegacyMigration) { |
+ performLegacyMigration(); |
+ // If a legacy migration was performed, a multi-instance migration is not |
+ // necessary. |
+ setMultiInstanceFileMigrationPref(); |
+ } else { |
+ performMultiInstanceMigration(); |
+ } |
+ return null; |
+ } |
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); |
+ } |
+ } |
+ |
+ /** |
+ * Upgrades users from an old version of Chrome when the state file was still in the root |
+ * directory. |
+ */ |
+ @WorkerThread |
+ private void performLegacyMigration() { |
+ File newFolder = getStateDirectory(); |
+ File[] newFiles = newFolder.listFiles(); |
+ // Attempt migration if we have no tab state file in the new directory. |
+ if (newFiles == null || newFiles.length == 0) { |
+ File oldFolder = mContext.getFilesDir(); |
+ File modelFile = new File(oldFolder, SAVED_STATE_FILE); |
+ if (modelFile.exists()) { |
+ if (!modelFile.renameTo(new File(newFolder, |
+ getStateFileName(mSelectorIndex)))) { |
+ Log.e(TAG, "Failed to rename file: " + modelFile); |
+ } |
+ } |
+ |
+ File[] files = oldFolder.listFiles(); |
+ if (files != null) { |
+ for (File file : files) { |
+ if (TabState.parseInfoFromFilename(file.getName()) != null) { |
+ if (!file.renameTo(new File(newFolder, file.getName()))) { |
+ Log.e(TAG, "Failed to rename file: " + file); |
+ } |
+ } |
+ } |
+ } |
+ } |
+ setLegacyFileMigrationPref(); |
+ } |
+ |
+ /** |
+ * Upgrades users from an older version of Chrome when the state files for multi-instance |
+ * were each kept in separate subdirectories. |
+ */ |
+ @WorkerThread |
+ private void performMultiInstanceMigration() { |
+ // 1. Rename tab metadata file for tab directory "0". |
+ File stateDir = getStateDirectory(); |
+ File metadataFile = new File(stateDir, SAVED_STATE_FILE); |
+ if (metadataFile.exists()) { |
+ if (!metadataFile.renameTo(new File(stateDir, getStateFileName(mSelectorIndex)))) { |
+ Log.e(TAG, "Failed to rename file: " + metadataFile); |
+ } |
+ } |
+ |
+ // 2. Move files from other state directories. |
+ for (int i = TabModelSelectorImpl.CUSTOM_TABS_SELECTOR_INDEX; |
+ i < TabWindowManager.MAX_SIMULTANEOUS_SELECTORS; i++) { |
+ // Skip the directory we're migrating to. |
+ if (i == 0) continue; |
+ |
+ File otherStateDir = new File(BaseStateDirectoryHolder.sDirectory, Integer.toString(i)); |
+ if (otherStateDir == null || !otherStateDir.exists()) continue; |
+ |
+ // Rename tab state file. |
+ metadataFile = new File(otherStateDir, SAVED_STATE_FILE); |
+ if (metadataFile.exists()) { |
+ if (!metadataFile.renameTo(new File(stateDir, getStateFileName(i)))) { |
+ Log.e(TAG, "Failed to rename file: " + metadataFile); |
+ } |
+ } |
+ |
+ // Rename tab files. |
+ File[] files = otherStateDir.listFiles(); |
+ if (files != null) { |
+ for (File file : files) { |
+ if (TabState.parseInfoFromFilename(file.getName()) != null) { |
+ // Custom tabs does not currently use tab files. Delete them rather than |
+ // migrating. |
+ if (i == TabModelSelectorImpl.CUSTOM_TABS_SELECTOR_INDEX) { |
+ if (!file.delete()) { |
+ Log.e(TAG, "Failed to delete file: " + file); |
} |
+ continue; |
} |
- File[] files = oldFolder.listFiles(); |
- if (files != null) { |
- for (File file : files) { |
- if (TabState.parseInfoFromFilename(file.getName()) != null) { |
- if (!file.renameTo(new File(newFolder, file.getName()))) { |
- Log.e(TAG, "Failed to rename file: " + file); |
- } |
- } |
+ // If the tab was moved between windows in Android N multi-window, the tab |
+ // file may exist in both directories. Keep whichever was modified more |
+ // recently. |
+ File newFileName = new File(stateDir, file.getName()); |
+ if (newFileName.exists() |
+ && newFileName.lastModified() > file.lastModified()) { |
+ if (!file.delete()) { |
+ Log.e(TAG, "Failed to delete file: " + file); |
} |
+ } else if (!file.renameTo(newFileName)) { |
+ Log.e(TAG, "Failed to rename file: " + file); |
} |
} |
- |
- mPreferences.edit().putBoolean(PREF_HAS_RUN_FILE_MIGRATION, true).apply(); |
- return null; |
} |
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
+ } |
+ |
+ // Delete other state directory. |
+ if (!otherStateDir.delete()) { |
+ Log.e(TAG, "Failed to delete directory: " + otherStateDir); |
+ } |
} |
+ |
+ setMultiInstanceFileMigrationPref(); |
+ } |
+ |
+ private void setLegacyFileMigrationPref() { |
+ mPreferences.edit().putBoolean(PREF_HAS_RUN_FILE_MIGRATION, true).apply(); |
+ } |
+ |
+ private void setMultiInstanceFileMigrationPref() { |
+ mPreferences.edit().putBoolean(PREF_HAS_RUN_MULTI_INSTANCE_FILE_MIGRATION, true).apply(); |
} |
private boolean isTabUrlContentScheme(Tab tab) { |
@@ -1141,7 +1350,7 @@ public class TabPersistentStore extends TabPersister { |
@Override |
protected DataInputStream doInBackground(Void... params) { |
// This getStateDirectory should be the first call and will cache the result. |
- File stateFile = new File(getStateDirectory(), SAVED_STATE_FILE); |
+ File stateFile = new File(getStateDirectory(), getStateFileName(mSelectorIndex)); |
if (!stateFile.exists()) return null; |
FileInputStream stream = null; |
byte[] data; |
@@ -1156,7 +1365,7 @@ public class TabPersistentStore extends TabPersister { |
} |
return new DataInputStream(new ByteArrayInputStream(data)); |
} |
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
+ }.executeOnExecutor(getPrefetchExecutor()); |
} |
private void startPrefetchActiveTabTask() { |
@@ -1167,7 +1376,11 @@ public class TabPersistentStore extends TabPersister { |
protected TabState doInBackground(Void... params) { |
return TabState.restoreTabState(getStateDirectory(), activeTabId); |
} |
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
+ }.executeOnExecutor(getPrefetchExecutor()); |
+ } |
+ |
+ private Executor getPrefetchExecutor() { |
+ return sMigrationTask == null ? AsyncTask.THREAD_POOL_EXECUTOR : AsyncTask.SERIAL_EXECUTOR; |
} |
/** |
@@ -1177,12 +1390,22 @@ public class TabPersistentStore extends TabPersister { |
* |
* @return The parent state directory. |
*/ |
- private static File getOrCreateBaseStateDirectory() { |
+ @VisibleForTesting |
+ public static File getOrCreateBaseStateDirectory() { |
Context appContext = ContextUtils.getApplicationContext(); |
return appContext.getDir(BASE_STATE_FOLDER, Context.MODE_PRIVATE); |
} |
/** |
+ * @param selectorIndex The index that represents which state file to pull and save state to. |
+ * @return The name of the state file. |
+ */ |
+ @VisibleForTesting |
+ public static String getStateFileName(int selectorIndex) { |
+ return SAVED_STATE_FILE + Integer.toString(selectorIndex); |
+ } |
+ |
+ /** |
* Sets where the base state directory is in tests. |
*/ |
@VisibleForTesting |