| 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;
|
| + }
|
| +}
|
|
|