Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3473)

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/tabmodel/document/DocumentTabModelImpl.java

Issue 802343003: Upstream DocumentTabModelImpl and related classes (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Moved package, added OWNERS Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/android/java/src/org/chromium/chrome/browser/tabmodel/document/DocumentTabModelImpl.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/document/DocumentTabModelImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/document/DocumentTabModelImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..1a04499d6ac6baf65b1f2cc3200ae07856a6fc71
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/document/DocumentTabModelImpl.java
@@ -0,0 +1,903 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// 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.document;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+
+import com.google.protobuf.nano.MessageNano;
+
+import org.chromium.base.ApplicationStatus;
+import org.chromium.base.ObserverList;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.VisibleForTesting;
+import org.chromium.chrome.browser.Tab;
+import org.chromium.chrome.browser.TabState;
+import org.chromium.chrome.browser.tabmodel.TabList;
+import org.chromium.chrome.browser.tabmodel.TabModel;
+import org.chromium.chrome.browser.tabmodel.TabModelJniBridge;
+import org.chromium.chrome.browser.tabmodel.TabModelObserver;
+import org.chromium.chrome.browser.tabmodel.TabModelUtils;
+import org.chromium.chrome.browser.tabmodel.document.DocumentTabModelInfo.DocumentEntry;
+import org.chromium.chrome.browser.tabmodel.document.DocumentTabModelInfo.DocumentList;
+import org.chromium.chrome.browser.util.MathUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Maintains a list of Tabs displayed when Chrome is running in document-mode.
+ */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class DocumentTabModelImpl extends TabModelJniBridge implements DocumentTabModel {
+ private static final String TAG = "DocumentTabModel";
+
+ @VisibleForTesting
+ public static final String PREF_PACKAGE = "com.google.android.apps.chrome.document";
+
+ @VisibleForTesting
+ public static final String PREF_LAST_SHOWN_TAB_ID_REGULAR = "last_shown_tab_id.regular";
+
+ public static final String PREF_LAST_SHOWN_TAB_ID_INCOGNITO = "last_shown_tab_id.incognito";
+
+ /** TabModel is uninitialized. */
+ public static final int STATE_UNINITIALIZED = 0;
+
+ /** Begin parsing the tasks from Recents and loading persisted state. */
+ public static final int STATE_READ_RECENT_TASKS_START = 1;
+
+ /** Done parsing the tasks from Recents and loading persisted state. */
+ public static final int STATE_READ_RECENT_TASKS_END = 2;
+
+ /** Begin loading the current/prioritized tab state synchronously. */
+ public static final int STATE_LOAD_CURRENT_TAB_STATE_START = 3;
+
+ /** Finish loading the current/prioritized tab state synchronously. */
+ public static final int STATE_LOAD_CURRENT_TAB_STATE_END = 4;
+
+ /** Begin reading TabStates from storage for background tabs. */
+ public static final int STATE_LOAD_TAB_STATE_BG_START = 5;
+
+ /** Done reading TabStates from storage for background tabs. */
+ public static final int STATE_LOAD_TAB_STATE_BG_END = 6;
+
+ /** Begin deserializing the TabState. Requires the native library. */
+ public static final int STATE_DESERIALIZE_START = 7;
+
+ /** Done deserializing the TabState. */
+ public static final int STATE_DESERIALIZE_END = 8;
+
+ /** Begin parsing the historical tabs. */
+ public static final int STATE_DETERMINE_HISTORICAL_TABS_START = 9;
+
+ /** Done parsing the historical tabs. */
+ public static final int STATE_DETERMINE_HISTORICAL_TABS_END = 10;
+
+ /** Clean out old TabState files. */
+ public static final int STATE_CLEAN_UP_OBSOLETE_TABS = 11;
+
+ /** TabModel is fully ready to use. */
+ public static final int STATE_FULLY_LOADED = 12;
+
+ /** List of known tabs. */
+ private final ArrayList<Integer> mTabIdList;
+
+ /** Stores an entry for each DocumentActivity that is alive. Keys are document IDs. */
+ private final SparseArray<Entry> mEntryMap;
+
+ /**
+ * Stores tabIds which have been removed from the ActivityManager while Chrome was not alive.
+ * It is cleared after restoration has been finished.
+ */
+ private final List<Integer> mHistoricalTabs;
+
+ /** Delegate for working with the ActivityManager. */
+ private final ActivityDelegate mActivityDelegate;
+
+ /** Delegate for working with the filesystem. */
+ private final StorageDelegate mStorageDelegate;
+
+ /** Delegate that provides Tabs to the DocumentTabModel. */
+ private final TabDelegate mTabDelegate;
+
+ /** ID of a Tab whose state should be loaded immediately, if it belongs to this TabList. */
+ private final int mPrioritizedTabId;
+
+ /** List of observers watching for a particular loading state. */
+ private final ObserverList<InitializationObserver> mInitializationObservers;
+
+ /** List of observers watching the TabModel. */
+ private final ObserverList<TabModelObserver> mObservers;
+
+ /** Context to use. */
+ private final Context mContext;
+
+ /** Current loading status. */
+ private int mCurrentState;
+
+ /** ID of the last tab that was shown to the user. */
+ private int mLastShownTabId = Tab.INVALID_TAB_ID;
+
+ /**
+ * Construct a DocumentTabModelImpl.
+ * @param activityDelegate Used to interact with DocumentActivities.
+ * @param tabDelegate Used to create/get Tabs.
+ * @param isIncognito Whether or not the TabList is managing incognito tabs.
+ * @param prioritizedTabId ID of the tab to prioritize when loading.
+ */
+ public DocumentTabModelImpl(ActivityDelegate activityDelegate, TabDelegate tabDelegate,
+ boolean isIncognito, int prioritizedTabId) {
+ this(activityDelegate, new StorageDelegate(isIncognito), tabDelegate, isIncognito,
+ prioritizedTabId, ApplicationStatus.getApplicationContext());
+ }
+
+ /**
+ * Construct a DocumentTabModel.
+ * @param activityDelegate Delegate to use for accessing the ActivityManager.
+ * @param storageDelegate Delegate to use for accessing persistent storage.
+ * @param tabDelegate Used to create/get Tabs.
+ * @param isIncognito Whether or not the TabList is managing incognito tabs.
+ * @param prioritizedTabId ID of the tab to prioritize when loading.
+ * @param context Context to use for accessing SharedPreferences.
+ *
+ * TODO(dfalcantara): Reduce visibility once DocumentMigrationHelper is upstreamed.
+ */
+ public DocumentTabModelImpl(ActivityDelegate activityDelegate, StorageDelegate storageDelegate,
+ TabDelegate tabDelegate, boolean isIncognito, int prioritizedTabId, Context context) {
+ super(isIncognito);
+ mActivityDelegate = activityDelegate;
+ mStorageDelegate = storageDelegate;
+ mTabDelegate = tabDelegate;
+ mPrioritizedTabId = prioritizedTabId;
+ mContext = context;
+
+ mCurrentState = STATE_UNINITIALIZED;
+ mTabIdList = new ArrayList<Integer>();
+ mEntryMap = new SparseArray<Entry>();
+ mHistoricalTabs = new ArrayList<Integer>();
+ mInitializationObservers = new ObserverList<InitializationObserver>();
+ mObservers = new ObserverList<TabModelObserver>();
+
+ SharedPreferences prefs = mContext.getSharedPreferences(PREF_PACKAGE, Context.MODE_PRIVATE);
+ mLastShownTabId = prefs.getInt(
+ isIncognito() ? PREF_LAST_SHOWN_TAB_ID_INCOGNITO : PREF_LAST_SHOWN_TAB_ID_REGULAR,
+ Tab.INVALID_TAB_ID);
+
+ initializeTabList();
+ }
+
+ @Override
+ public void initializeNative() {
+ if (!isNativeInitialized()) super.initializeNative();
+ deserializeTabStatesAsync();
+ }
+
+ public StorageDelegate getStorageDelegate() {
+ return mStorageDelegate;
+ }
+
+ /**
+ * Finds the index of the given Tab ID.
+ * @param tabId ID of the Tab to find.
+ * @return Index of the tab, or -1 if it couldn't be found.
+ */
+ private int indexOf(int tabId) {
+ return mTabIdList.indexOf(tabId);
+ }
+
+ @Override
+ public int index() {
+ if (getCount() == 0) return TabList.INVALID_TAB_INDEX;
+ int indexOfLastId = indexOf(mLastShownTabId);
+ if (indexOfLastId != -1) return indexOfLastId;
+
+ // The previous Tab is gone; select a Tab based on MRU ordering.
+ List<Entry> tasks = mActivityDelegate.getTasksFromRecents(isIncognito());
+ if (tasks.size() == 0) return TabList.INVALID_TAB_INDEX;
+
+ for (int i = 0; i < tasks.size(); i++) {
+ int lastKnownId = tasks.get(i).tabId;
+ int indexOfMostRecentlyUsedId = indexOf(lastKnownId);
+ if (indexOfMostRecentlyUsedId != -1) return indexOfMostRecentlyUsedId;
+ }
+
+ return TabList.INVALID_TAB_INDEX;
+ }
+
+ @Override
+ public void setLastShownId(int id) {
+ mLastShownTabId = id;
+
+ String prefName =
+ isIncognito() ? PREF_LAST_SHOWN_TAB_ID_INCOGNITO : PREF_LAST_SHOWN_TAB_ID_REGULAR;
+ SharedPreferences prefs = mContext.getSharedPreferences(PREF_PACKAGE, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putInt(prefName, id);
+ editor.apply();
+ }
+
+ @Override
+ public int indexOf(Tab tab) {
+ if (tab == null) return Tab.INVALID_TAB_ID;
+ return indexOf(tab.getId());
+ }
+
+ @Override
+ public int getCount() {
+ return mTabIdList.size();
+ }
+
+ @Override
+ public boolean isClosurePending(int tabId) {
+ return false;
+ }
+
+ @Override
+ public Tab getTabAt(int index) {
+ if (index < 0 || index >= getCount()) return null;
+
+ // Return a live tab if the corresponding Activity is currently alive.
+ int tabId = mTabIdList.get(index);
+ List<WeakReference<Activity>> activities = ApplicationStatus.getRunningActivities();
+ for (WeakReference<Activity> activityRef : activities) {
+ Tab tab = mTabDelegate.getActivityTab(
+ isIncognito(), mActivityDelegate, activityRef.get());
+ int documentId = tab == null ? Tab.INVALID_TAB_ID : tab.getId();
+ if (documentId == tabId) return tab;
+ }
+
+ // Try to create a Tab that will hold the Tab's info.
+ Entry entry = mEntryMap.get(tabId);
+ if (entry == null) return null;
+
+ // If a tab has already been initialized, use that.
+ if (entry.placeholderTab != null && entry.placeholderTab.isInitialized()) {
+ return entry.placeholderTab;
+ }
+
+ // Create a frozen Tab if we are capable, or if the previous Tab is just a placeholder.
+ if (entry.tabState != null && isNativeInitialized()
+ && (entry.placeholderTab == null || !entry.placeholderTab.isInitialized())) {
+ entry.placeholderTab = mTabDelegate.createFrozenTab(entry);
+ entry.placeholderTab.initialize();
+ }
+
+ // Create a placeholder Tab that just has the ID.
+ if (entry.placeholderTab == null) {
+ entry.placeholderTab = new Tab(tabId, isIncognito(), null, null);
+ }
+
+ return entry.placeholderTab;
+ }
+
+ @Override
+ public void setIndex(int index, TabSelectionType type) {
+ if (index < 0 || index >= getCount()) return;
+ int tabId = mTabIdList.get(index);
+ mActivityDelegate.moveTaskToFront(isIncognito(), tabId);
+ setLastShownId(tabId);
+ }
+
+ @Override
+ public boolean closeTabAt(int index) {
+ ThreadUtils.assertOnUiThread();
+ if (index < 0 || index >= getCount()) return false;
+
+ int tabId = mTabIdList.get(index);
+ mActivityDelegate.finishAndRemoveTask(isIncognito(), tabId);
+ mTabIdList.remove(index);
+ mEntryMap.remove(tabId);
+ return true;
+ }
+
+ @Override
+ public boolean closeTab(Tab tab) {
+ return closeTab(tab, false, false, false);
+ }
+
+ @Override
+ public boolean closeTab(Tab tabToClose, boolean animate, boolean uponExit, boolean canUndo) {
+ // The tab should be destroyed by the DocumentActivity that owns it.
+ return closeTabAt(indexOf(tabToClose.getId()));
+ }
+
+ @Override
+ protected Tab createTabWithNativeContents(
+ boolean isIncognito, long webContentsPtr, int parentTabId) {
+ mTabDelegate.createTabWithNativeContents(isIncognito, webContentsPtr, parentTabId);
+ return null;
+ }
+
+ @Override
+ protected Tab createNewTabForDevTools(String url) {
+ mTabDelegate.createTabForDevTools(url);
+ return null;
+ }
+
+ @Override
+ protected boolean isSessionRestoreInProgress() {
+ return mCurrentState < STATE_FULLY_LOADED;
+ }
+
+ /**
+ * Add the tab ID to the end of the list.
+ * @param tabId ID to add.
+ */
+ private void addTabId(int tabId) {
+ addTabId(mTabIdList.size(), tabId);
+ }
+
+ /**
+ * Adds the Tab ID at the given index.
+ * @param index Where to add the ID.
+ * @param tabId ID to add.
+ */
+ private void addTabId(int index, int tabId) {
+ if (mTabIdList.contains(tabId)) return;
+ mTabIdList.add(index, tabId);
+ }
+
+ @Override
+ public String getInitialUrlForDocument(int tabId) {
+ Entry entry = mEntryMap.get(tabId);
+ return entry == null ? null : entry.initialUrl;
+ }
+
+ @Override
+ public String getCurrentUrlForDocument(int tabId) {
+ Entry entry = mEntryMap.get(tabId);
+ return entry == null ? null : entry.currentUrl;
+ }
+
+ @Override
+ public boolean isTabStateReady(int tabId) {
+ Entry entry = mEntryMap.get(tabId);
+ return entry == null ? true : entry.isTabStateReady;
+ }
+
+ @Override
+ public TabState getTabStateForDocument(int tabId) {
+ Entry entry = mEntryMap.get(tabId);
+ return entry == null ? null : entry.tabState;
+ }
+
+ @Override
+ public boolean hasEntryForTabId(int tabId) {
+ return mEntryMap.get(tabId) != null;
+ }
+
+ @Override
+ public boolean isRetargetable(int tabId) {
+ Entry entry = mEntryMap.get(tabId);
+ return entry == null ? false : !entry.canGoBack;
+ }
+
+ @Override
+ public void addInitializationObserver(InitializationObserver observer) {
+ ThreadUtils.assertOnUiThread();
+ mInitializationObservers.addObserver(observer);
+ }
+
+ @Override
+ public void updateRecentlyClosed() {
+ ThreadUtils.assertOnUiThread();
+ List<Entry> current = mActivityDelegate.getTasksFromRecents(isIncognito());
+ Set<Integer> removed = new HashSet<Integer>();
+ for (int i = 0; i < mEntryMap.size(); i++) {
+ int tabId = mEntryMap.keyAt(i);
+ if (!isTabIdInEntryList(current, tabId)) {
+ Entry entry = mEntryMap.get(tabId);
+ if (!isIncognito() && entry.tabState != null
+ && entry.tabState.contentsState != null) {
+ entry.tabState.contentsState.createHistoricalTab();
+ }
+ removed.add(tabId);
+ }
+ }
+
+ for (Integer tabId : removed) {
+ closeTabAt(indexOf(tabId));
+ }
+ }
+
+ @Override
+ public void updateEntry(Intent intent, Tab tab) {
+ if (!mActivityDelegate.isValidActivity(isIncognito(), intent)) return;
+
+ int id = ActivityDelegate.getTabIdFromIntent(intent);
+ if (id == Tab.INVALID_TAB_ID) return;
+
+ Entry currentEntry = mEntryMap.get(id);
+ String currentUrl = tab.getUrl();
+ boolean canGoBack = tab.canGoBack();
+ TabState state = tab.getState();
+ if (currentEntry != null
+ && currentEntry.tabId == id
+ && TextUtils.equals(currentEntry.currentUrl, currentUrl)
+ && currentEntry.canGoBack == canGoBack
+ && currentEntry.tabState == state
+ && !tab.isTabStateDirty()) {
+ return;
+ }
+
+ if (currentEntry == null) {
+ currentEntry = new Entry(id, ActivityDelegate.getInitialUrlForDocument(intent));
+ mEntryMap.put(id, currentEntry);
+ }
+ currentEntry.isDirty = true;
+ currentEntry.currentUrl = currentUrl;
+ currentEntry.canGoBack = canGoBack;
+ currentEntry.tabState = state;
+
+ // TODO(dfalcantara): This is different from how the normal Tab determines when to save its
+ // state, but this can't be fixed because we cann't hold onto Tabs in this class.
+ tab.setIsTabStateDirty(false);
+
+ if (currentEntry.placeholderTab != null) {
+ if (currentEntry.placeholderTab.isInitialized()) currentEntry.placeholderTab.destroy();
+ currentEntry.placeholderTab = null;
+ }
+
+ writeGeneralDataToStorageAsync();
+ writeTabStatesToStorageAsync();
+ }
+
+ @Override
+ public int getCurrentInitializationStage() {
+ return mCurrentState;
+ }
+
+ /**
+ * Add an entry to the entry map for migration purposes.
+ * @param entry The entry to be added.
+ *
+ * TODO(dfalcantara): Reduce visibility once DocumentMigrationHelper is upstreamed.
+ */
+ public void addEntryForMigration(Entry entry) {
+ addTabId(getCount(), entry.tabId);
+ if (mEntryMap.indexOfKey(entry.tabId) >= 0) return;
+ mEntryMap.put(entry.tabId, entry);
+ }
+
+ private void initializeTabList() {
+ setCurrentState(STATE_READ_RECENT_TASKS_START);
+
+ // Run through Recents to see what tasks exist. Prevent them from being retargeted until we
+ // have had the opportunity to load more information about them.
+ List<Entry> entries = mActivityDelegate.getTasksFromRecents(isIncognito());
+ for (Entry entry : entries) {
+ entry.canGoBack = true;
+ mEntryMap.put(entry.tabId, entry);
+ }
+
+ // Read the file, which saved out the task IDs in regular order.
+ byte[] tabFileBytes = mStorageDelegate.readTaskFileBytes();
+ if (tabFileBytes != null) {
+ try {
+ DocumentList list = MessageNano.mergeFrom(new DocumentList(), tabFileBytes);
+ for (int i = 0; i < list.entries.length; i++) {
+ DocumentEntry savedEntry = list.entries[i];
+ int tabId = savedEntry.tabId;
+
+ if (mEntryMap.indexOfKey(tabId) < 0) {
+ mHistoricalTabs.add(tabId);
+ continue;
+ }
+
+ addTabId(getCount(), tabId);
+ mEntryMap.get(tabId).canGoBack = savedEntry.canGoBack;
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "I/O exception", e);
+ }
+ }
+
+ // Add any missing tasks to the list.
+ for (int i = 0; i < mEntryMap.size(); i++) {
+ int id = mEntryMap.keyAt(i);
+ if (mTabIdList.contains(id)) continue;
+ addTabId(id);
+ }
+
+ setCurrentState(STATE_READ_RECENT_TASKS_END);
+ }
+
+ // TODO(mariakhomenko): we no longer need prioritized tab id in constructor, shift it here.
+ @Override
+ public void startTabStateLoad() {
+ if (mCurrentState != STATE_READ_RECENT_TASKS_END) return;
+ setCurrentState(STATE_LOAD_CURRENT_TAB_STATE_START);
+ // Immediately try loading the requested tab.
+ if (mPrioritizedTabId != Tab.INVALID_TAB_ID) {
+ Entry entry = mEntryMap.get(mPrioritizedTabId);
+ if (entry != null) {
+ entry.tabState = mStorageDelegate.restoreTabState(mPrioritizedTabId);
+ entry.isTabStateReady = true;
+ }
+ }
+ setCurrentState(STATE_LOAD_CURRENT_TAB_STATE_END);
+ loadTabStatesAsync();
+ }
+
+ private void loadTabStatesAsync() {
+ new AsyncTask<Void, Void, Void>() {
+ private final List<Entry> mEntries = new ArrayList<Entry>(getCount());
+
+ @Override
+ public void onPreExecute() {
+ setCurrentState(STATE_LOAD_TAB_STATE_BG_START);
+ for (int i = 0; i < getCount(); i++) {
+ mEntries.add(new Entry(getTabAt(i).getId()));
+ }
+ }
+
+ @Override
+ public Void doInBackground(Void... params) {
+ for (Entry entry : mEntries) {
+ if (mPrioritizedTabId == entry.tabId) continue;
+ entry.tabState = mStorageDelegate.restoreTabState(entry.tabId);
+ entry.isTabStateReady = true;
+ }
+
+ return null;
+ }
+
+ @Override
+ public void onPostExecute(Void result) {
+ for (Entry pair : mEntries) {
+ Entry entry = mEntryMap.get(pair.tabId);
+ if (entry == null) continue;
+
+ if (entry.tabState == null) entry.tabState = pair.tabState;
+ entry.isTabStateReady = true;
+ }
+
+ setCurrentState(STATE_LOAD_TAB_STATE_BG_END);
+ deserializeTabStatesAsync();
+ }
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
+ }
+
+ private void deserializeTabStatesAsync() {
+ if (!shouldStartDeserialization(mCurrentState)) return;
+
+ new AsyncTask<Void, Void, Void>() {
+ private final List<Entry> mCachedEntries = new ArrayList<Entry>(mEntryMap.size());
+
+ @Override
+ public void onPreExecute() {
+ setCurrentState(STATE_DESERIALIZE_START);
+
+ for (int i = 0; i < mEntryMap.size(); i++) {
+ Entry entry = mEntryMap.valueAt(i);
+ if (entry.tabState == null) continue;
+ mCachedEntries.add(new Entry(entry.tabId, entry.tabState));
+ }
+ }
+
+ @Override
+ public Void doInBackground(Void... params) {
+ for (Entry entry : mCachedEntries) {
+ TabState tabState = entry.tabState;
+ updateEntryInfoFromTabState(entry, tabState);
+ }
+ return null;
+ }
+
+ @Override
+ public void onPostExecute(Void result) {
+ for (Entry pair : mCachedEntries) {
+ Entry realEntry = mEntryMap.get(pair.tabId);
+ if (realEntry == null || realEntry.currentUrl != null) continue;
+ realEntry.currentUrl = pair.currentUrl;
+ }
+
+ setCurrentState(STATE_DESERIALIZE_END);
+ if (isNativeInitialized()) {
+ broadcastSessionRestoreComplete();
+ loadHistoricalTabsAsync();
+ }
+ }
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
+ }
+
+ /**
+ * Call for extending classes to override for getting additional information for an entry from
+ * the tab state when it is deserialized.
+ * @param entry The {@link Entry} currently being processed
+ * @param tabState The {@link TabState} that has been deserialized for the entry.
+ */
+ protected void updateEntryInfoFromTabState(Entry entry, TabState tabState) {
+ entry.currentUrl = tabState.getVirtualUrlFromState();
+ }
+
+ /**
+ * Checks whether initialization should move to the deserialization step.
+ * @param currentState Current initialization stage.
+ * @return Whether to proceed or not.
+ */
+ protected boolean shouldStartDeserialization(int currentState) {
+ return isNativeInitialized() && currentState == STATE_LOAD_TAB_STATE_BG_END;
+ }
+
+ private void loadHistoricalTabsAsync() {
+ new AsyncTask<Void, Void, Void>() {
+ private Set<Integer> mHistoricalTabsForBackgroundThread;
+ private List<Entry> mEntries;
+
+ @Override
+ public void onPreExecute() {
+ setCurrentState(STATE_DETERMINE_HISTORICAL_TABS_START);
+ mHistoricalTabsForBackgroundThread = new HashSet<Integer>(mHistoricalTabs.size());
+ mHistoricalTabsForBackgroundThread.addAll(mHistoricalTabs);
+ mEntries = new ArrayList<Entry>(mHistoricalTabsForBackgroundThread.size());
+ }
+
+ @Override
+ public Void doInBackground(Void... params) {
+ for (Integer tabId : mHistoricalTabsForBackgroundThread) {
+ // Read the saved state, then delete the file.
+ TabState state = mStorageDelegate.restoreTabState(tabId);
+ mEntries.add(new Entry(tabId, state));
+ mStorageDelegate.deleteTabStateFile(tabId);
+ }
+
+ return null;
+ }
+
+ @Override
+ public void onPostExecute(Void result) {
+ for (Entry entry : mEntries) {
+ if (entry.tabState == null || entry.tabState.contentsState == null) continue;
+ entry.tabState.contentsState.createHistoricalTab();
+ }
+ mHistoricalTabs.clear();
+ setCurrentState(STATE_DETERMINE_HISTORICAL_TABS_END);
+ cleanUpObsoleteTabStatesAsync();
+ }
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
+ }
+
+ /**
+ * Clears the folder of TabStates that correspond to missing tasks.
+ */
+ private void cleanUpObsoleteTabStatesAsync() {
+ new AsyncTask<Void, Void, Void>() {
+ private List<Entry> mCurrentTabs;
+
+ @Override
+ protected void onPreExecute() {
+ setCurrentState(STATE_CLEAN_UP_OBSOLETE_TABS);
+ mCurrentTabs = mActivityDelegate.getTasksFromRecents(isIncognito());
+ }
+
+ @Override
+ protected Void doInBackground(Void... voids) {
+ File stateDirectory = mStorageDelegate.getStateDirectory();
+ String[] files = stateDirectory.list();
+ for (final String fileName : files) {
+ Pair<Integer, Boolean> tabInfo = TabState.parseInfoFromFilename(fileName);
+ if (tabInfo == null) continue;
+
+ int tabId = tabInfo.first;
+ boolean incognito = tabInfo.second;
+ if (incognito != isIncognito() || isTabIdInEntryList(mCurrentTabs, tabId)) {
+ continue;
+ }
+
+ boolean success = new File(stateDirectory, fileName).delete();
+ if (!success) Log.w(TAG, "Failed to delete: " + fileName);
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ setCurrentState(STATE_FULLY_LOADED);
+ }
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
+ }
+
+ /**
+ * Save out a tiny file with minimal information required for retargeting.
+ */
+ private void writeGeneralDataToStorageAsync() {
+ if (isIncognito()) return;
+
+ new AsyncTask<Void, Void, Void>() {
+ private DocumentList mList;
+
+ @Override
+ protected void onPreExecute() {
+ List<DocumentEntry> entriesList = new ArrayList<DocumentEntry>();
+ for (int i = 0; i < getCount(); i++) {
+ Entry entry = mEntryMap.get(getTabAt(i).getId());
+ if (entry == null) continue;
+
+ DocumentEntry docEntry = new DocumentEntry();
+ docEntry.tabId = entry.tabId;
+ docEntry.canGoBack = entry.canGoBack;
+
+ entriesList.add(docEntry);
+ }
+ mList = new DocumentList();
+ mList.entries = entriesList.toArray(new DocumentEntry[entriesList.size()]);
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ mStorageDelegate.writeTaskFileBytes(MessageNano.toByteArray(mList));
+ return null;
+ }
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
+ }
+
+ /**
+ * Write out all of the TabStates.
+ */
+ private void writeTabStatesToStorageAsync() {
+ new AsyncTask<Void, Void, Void>() {
+ private final SparseArray<TabState> mStatesToWrite = new SparseArray<TabState>();
+
+ @Override
+ protected void onPreExecute() {
+ for (int i = 0; i < mEntryMap.size(); i++) {
+ Entry entry = mEntryMap.valueAt(i);
+ if (!entry.isDirty || entry.tabState == null) continue;
+ mStatesToWrite.put(entry.tabId, entry.tabState);
+ }
+ }
+
+ @Override
+ protected Void doInBackground(Void... voids) {
+ for (int i = 0; i < mStatesToWrite.size(); i++) {
+ int tabId = mStatesToWrite.keyAt(i);
+ mStorageDelegate.saveTabState(tabId, mStatesToWrite.valueAt(i));
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void v) {
+ for (int i = 0; i < mStatesToWrite.size(); i++) {
+ int tabId = mStatesToWrite.keyAt(i);
+ Entry entry = mEntryMap.get(tabId);
+ if (entry == null) continue;
+ entry.isDirty = false;
+ }
+ }
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
+ }
+
+ private void setCurrentState(int newState) {
+ ThreadUtils.assertOnUiThread();
+ assert mCurrentState == newState - 1;
+ mCurrentState = newState;
+
+ for (InitializationObserver observer : mInitializationObservers) {
+ if (observer.isCanceled()) {
+ Log.w(TAG, "Observer alerted after canceled: " + observer);
+ mInitializationObservers.removeObserver(observer);
+ } else if (observer.isSatisfied(mCurrentState)) {
+ observer.runWhenReady();
+ mInitializationObservers.removeObserver(observer);
+ }
+ }
+ }
+
+ @Override
+ public Tab getNextTabIfClosed(int id) {
+ // Tab may not necessarily exist.
+ return null;
+ }
+
+ @Override
+ public void closeAllTabs() {
+ closeAllTabs(true, false);
+ }
+
+ @Override
+ public void closeAllTabs(boolean allowDelegation, boolean uponExit) {
+ for (int i = getCount() - 1; i >= 0; i--) closeTabAt(i);
+ }
+
+ @Override
+ public void moveTab(int id, int newIndex) {
+ newIndex = MathUtils.clamp(newIndex, 0, getCount());
+ int curIndex = TabModelUtils.getTabIndexById(this, id);
+ if (curIndex == INVALID_TAB_INDEX || curIndex == newIndex || curIndex + 1 == newIndex) {
+ return;
+ }
+
+ mTabIdList.remove(curIndex);
+ addTabId(newIndex, id);
+
+ Tab tab = getTabAt(curIndex);
+ if (tab == null) return;
+ for (TabModelObserver obs : mObservers) obs.didMoveTab(tab, newIndex, curIndex);
+ }
+
+ @Override
+ public void destroy() {
+ super.destroy();
+ mInitializationObservers.clear();
+ mObservers.clear();
+ }
+
+ @Override
+ public void addTab(Tab tab) {
+ int parentIndex = indexOf(tab.getParentId());
+ int index = parentIndex == -1 ? getCount() : parentIndex + 1;
+ addTab(tab, index, tab.getLaunchType());
+ }
+
+ @Override
+ public void addTab(Tab tab, int index, TabLaunchType type) {
+ for (TabModelObserver obs : mObservers) obs.willAddTab(tab, type);
+
+ if (index == TabModel.INVALID_TAB_INDEX) {
+ addTabId(getCount(), tab.getId());
+ } else {
+ addTabId(index, tab.getId());
+ }
+
+ tabAddedToModel(tab);
+ for (TabModelObserver obs : mObservers) obs.didAddTab(tab, type);
+ }
+
+ @Override
+ public boolean supportsPendingClosures() {
+ return false;
+ }
+
+ @Override
+ public void commitAllTabClosures() {
+ }
+
+ @Override
+ public void commitTabClosure(int tabId) {
+ }
+
+ @Override
+ public void cancelTabClosure(int tabId) {
+ }
+
+ @Override
+ public TabList getComprehensiveModel() {
+ return this;
+ }
+
+ @Override
+ public void addObserver(TabModelObserver observer) {
+ mObservers.addObserver(observer);
+ }
+
+ @Override
+ public void removeObserver(TabModelObserver observer) {
+ mObservers.removeObserver(observer);
+ }
+
+ private static boolean isTabIdInEntryList(List<Entry> entries, int tabId) {
+ for (int i = 0; i < entries.size(); i++) {
+ if (entries.get(i).tabId == tabId) return true;
+ }
+ return false;
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698