// 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.app.Activity;
import android.content.Context;
import android.util.SparseArray;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.ThreadUtils;
import org.chromium.chrome.browser.document.DocumentActivity;
import org.chromium.chrome.browser.document.DocumentUtils;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
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.content_public.browser.WebContents;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
/**
* Maintains a list of Tabs displayed when Chrome is running in document-mode.
*/
public class DocumentTabModelImpl extends TabModelJniBridge implements DocumentTabModel {
private static final String TAG = "DocumentTabModel";
public static final String PREF_PACKAGE = "com.google.android.apps.chrome.document";
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;
/** 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;
/**
* Pre-load shared prefs to avoid being blocked on the
* disk access async task in the future.
*/
public static void warmUpSharedPrefs(Context context) {
context.getSharedPreferences(PREF_PACKAGE, Context.MODE_PRIVATE);
}
/**
* Construct a DocumentTabModel.
* @param activityDelegate Delegate to use for accessing the ActivityManager.
* @param storageDelegate Delegate to use for accessing persistent storage.
* @param tabCreatorManager Used to create 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.
*/
public DocumentTabModelImpl(ActivityDelegate activityDelegate, StorageDelegate storageDelegate,
TabCreatorManager tabCreatorManager, boolean isIncognito, int prioritizedTabId,
Context context) {
super(isIncognito);
mActivityDelegate = activityDelegate;
mStorageDelegate = storageDelegate;
mContext = context;
mCurrentState = STATE_UNINITIALIZED;
mTabIdList = new ArrayList<Integer>();
mEntryMap = new SparseArray<Entry>();
mHistoricalTabs = new ArrayList<Integer>();
mLastShownTabId = DocumentUtils.getLastShownTabIdFromPrefs(mContext, isIncognito());
// Restore the tab list.
setCurrentState(STATE_READ_RECENT_TASKS_START);
mStorageDelegate.restoreTabEntries(
isIncognito, activityDelegate, mEntryMap, mTabIdList, mHistoricalTabs);
setCurrentState(STATE_READ_RECENT_TASKS_END);
}
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 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 DocumentActivity is currently alive.
int tabId = mTabIdList.get(index);
List<WeakReference<Activity>> activities = ApplicationStatus.getRunningActivities();
for (WeakReference<Activity> activityRef : activities) {
Activity activity = activityRef.get();
if (!(activity instanceof DocumentActivity)
|| !mActivityDelegate.isValidActivity(isIncognito(), activity.getIntent())) {
continue;
}
Tab tab = ((DocumentActivity) activity).getActivityTab();
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);
assert entry != 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.getTabState() != null && isNativeInitialized()
&& (entry.placeholderTab == null || !entry.placeholderTab.isInitialized())) {
entry.placeholderTab = getTabCreator(isIncognito()).createFrozenTab(
entry.getTabState(), entry.tabId, TabModel.INVALID_TAB_INDEX);
entry.placeholderTab.initializeNative();
}
// Create a placeholder Tab that just has the ID.
if (entry.placeholderTab == null) {
entry.placeholderTab = new Tab(tabId, isIncognito(), null);
}
return entry.placeholderTab;
}
@Override
public void setIndex(int index, TabSelectionType type) {
}
@Override
protected boolean closeTabAt(int index) {
return false;
}
@Override
public boolean closeTab(Tab tab) {
return false;
}
@Override
public boolean closeTab(Tab tabToClose, boolean animate, boolean uponExit, boolean canUndo) {
return false;
}
@Override
protected TabDelegate getTabCreator(boolean incognito) {
return null;
}
@Override
protected boolean createTabWithWebContents(Tab parent, boolean isIncognito,
WebContents webContents, int parentTabId) {
return false;
}
@Override
protected boolean isSessionRestoreInProgress() {
return mCurrentState < STATE_FULLY_LOADED;
}
@Override
public String getInitialUrlForDocument(int tabId) {
Entry entry = mEntryMap.get(tabId);
return entry == null ? null : entry.initialUrl;
}
private void setCurrentState(int newState) {
ThreadUtils.assertOnUiThread();
assert mCurrentState == newState - 1;
mCurrentState = newState;
}
@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) {
}
@Override
public void moveTab(int id, int newIndex) {
assert false;
}
@Override
public void addTab(Tab tab, int index, TabLaunchType type) {
assert false;
}
@Override
public void removeTab(Tab tab) {
assert false;
}
@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) {
}
@Override
public void removeObserver(TabModelObserver observer) {
}
@Override
public void openMostRecentlyClosedTab() {
}
}