Index: chrome/android/java_staging/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorImpl.java |
diff --git a/chrome/android/java_staging/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorImpl.java b/chrome/android/java_staging/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorImpl.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..4a24a4daf66ccb219963ba63296a48a9c056bdd9 |
--- /dev/null |
+++ b/chrome/android/java_staging/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorImpl.java |
@@ -0,0 +1,431 @@ |
+// Copyright 2015 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; |
+ |
+import android.content.Context; |
+import android.os.Handler; |
+ |
+import org.chromium.base.metrics.RecordHistogram; |
+import org.chromium.chrome.browser.ChromeActivity; |
+import org.chromium.chrome.browser.Tab; |
+import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior; |
+import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager; |
+import org.chromium.chrome.browser.ntp.NativePageFactory; |
+import org.chromium.chrome.browser.tabmodel.OffTheRecordTabModel.OffTheRecordTabModelDelegate; |
+import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType; |
+import org.chromium.chrome.browser.tabmodel.TabModel.TabSelectionType; |
+import org.chromium.chrome.browser.tabmodel.TabPersistentStore.TabPersistentStoreObserver; |
+import org.chromium.content_public.browser.LoadUrlParams; |
+import org.chromium.ui.base.WindowAndroid; |
+ |
+import java.util.concurrent.atomic.AtomicBoolean; |
+ |
+/** |
+ * This class manages all the ContentViews in the app. As it manipulates views, it must be |
+ * instantiated and used in the UI Thread. It acts as a TabModel which delegates all |
+ * TabModel methods to the active model that it contains. |
+ */ |
+public class TabModelSelectorImpl extends TabModelSelectorBase implements TabModelDelegate { |
+ |
+ private final ChromeActivity mActivity; |
+ |
+ /** Flag set to false when the asynchronous loading of tabs is finished. */ |
+ private final AtomicBoolean mSessionRestoreInProgress = |
+ new AtomicBoolean(true); |
+ private final TabPersistentStore mTabSaver; |
+ |
+ // This flag signifies the object has gotten an onNativeReady callback and |
+ // has not been destroyed. |
+ private boolean mActiveState = false; |
+ |
+ private final TabModelOrderController mOrderController; |
+ |
+ private OverviewModeBehavior mOverviewModeBehavior; |
+ |
+ private TabContentManager mTabContentManager; |
+ |
+ private Tab mVisibleTab; |
+ |
+ private final TabModelSelectorUma mUma; |
+ |
+ private CloseAllTabsDelegate mCloseAllTabsDelegate; |
+ |
+ private static class TabModelImplCreator implements OffTheRecordTabModelDelegate { |
+ private final ChromeActivity mActivity; |
+ private final TabModelSelectorUma mUma; |
+ private final TabModelOrderController mOrderController; |
+ private final TabContentManager mTabContentManager; |
+ private final TabPersistentStore mTabSaver; |
+ private final TabModelDelegate mModelDelegate; |
+ |
+ /** |
+ * Constructor for an Incognito TabModelImpl. |
+ * |
+ * @param activity The activity owning this TabModel. |
+ * @param uma Handles UMA tracking for the model. |
+ * @param orderController Determines the order for inserting new Tabs. |
+ * @param tabContentManager Manages the display content of the tab. |
+ * @param tabSaver Handler for saving tabs. |
+ * @param modelDelegate Delegate to handle external dependencies and interactions. |
+ */ |
+ public TabModelImplCreator(ChromeActivity activity, TabModelSelectorUma uma, |
+ TabModelOrderController orderController, TabContentManager tabContentManager, |
+ TabPersistentStore tabSaver, TabModelDelegate modelDelegate) { |
+ mActivity = activity; |
+ mUma = uma; |
+ mOrderController = orderController; |
+ mTabContentManager = tabContentManager; |
+ mTabSaver = tabSaver; |
+ mModelDelegate = modelDelegate; |
+ } |
+ |
+ @Override |
+ public TabModel createTabModel() { |
+ return new TabModelImpl(true, mActivity, mUma, mOrderController, |
+ mTabContentManager, mTabSaver, mModelDelegate); |
+ } |
+ |
+ @Override |
+ public boolean doOffTheRecordTabsExist() { |
+ return TabWindowManager.getInstance().getIncognitoTabCount() > 0; |
+ } |
+ } |
+ |
+ /** |
+ * Builds a {@link TabModelSelectorImpl} instance. |
+ * @param activity The {@link ChromeActivity} this model selector lives in. |
+ * @param selectorIndex The index this selector represents in the list of selectors. |
+ * @param windowAndroid The {@link WindowAndroid} associated with this model selector. |
+ */ |
+ public TabModelSelectorImpl(ChromeActivity activity, int selectorIndex, |
+ WindowAndroid windowAndroid) { |
+ super(); |
+ mActivity = activity; |
+ mUma = new TabModelSelectorUma(mActivity); |
+ final TabPersistentStoreObserver persistentStoreObserver = |
+ new TabPersistentStoreObserver() { |
+ @Override |
+ public void onStateLoaded(Context context) { |
+ markTabStateInitialized(); |
+ } |
+ |
+ @Override |
+ public void onInitialized(int tabCountAtStartup) { |
+ RecordHistogram.recordCountHistogram("Tabs.CountAtStartup", tabCountAtStartup); |
+ } |
+ }; |
+ mTabSaver = new TabPersistentStore(this, selectorIndex, mActivity, mActivity, |
+ persistentStoreObserver); |
+ mOrderController = new TabModelOrderController(this); |
+ ChromeTabCreator regularTabCreator = new ChromeTabCreator(mActivity, windowAndroid, |
+ mOrderController, mTabSaver, false); |
+ ChromeTabCreator incognitoTabCreator = new ChromeTabCreator(mActivity, windowAndroid, |
+ mOrderController, mTabSaver, true); |
+ mActivity.setTabCreators(regularTabCreator, incognitoTabCreator); |
+ } |
+ |
+ @Override |
+ protected void markTabStateInitialized() { |
+ super.markTabStateInitialized(); |
+ if (!mSessionRestoreInProgress.getAndSet(false)) return; |
+ |
+ // This is the first time we set |
+ // |mSessionRestoreInProgress|, so we need to broadcast. |
+ TabModelImpl model = (TabModelImpl) getModel(false); |
+ |
+ if (model != null) { |
+ model.broadcastSessionRestoreComplete(); |
+ } else { |
+ assert false : "Normal tab model is null after tab state loaded."; |
+ } |
+ } |
+ |
+ private void handleOnPageLoadStopped(Tab tab) { |
+ if (tab != null) mTabSaver.addTabToSaveQueue(tab); |
+ } |
+ |
+ /** |
+ * |
+ * @param overviewModeBehavior The {@link OverviewModeBehavior} that should be used to determine |
+ * when the app is in overview mode or not. |
+ */ |
+ public void setOverviewModeBehavior(OverviewModeBehavior overviewModeBehavior) { |
+ assert overviewModeBehavior != null; |
+ mOverviewModeBehavior = overviewModeBehavior; |
+ } |
+ |
+ /** |
+ * Should be called when the app starts showing a view with multiple tabs. |
+ */ |
+ public void onTabsViewShown() { |
+ mUma.onTabsViewShown(); |
+ } |
+ |
+ /** |
+ * Should be called once the native library is loaded so that the actual internals of this |
+ * class can be initialized. |
+ * @param tabContentProvider A {@link TabContentManager} instance. |
+ */ |
+ public void onNativeLibraryReady(TabContentManager tabContentProvider) { |
+ assert !mActiveState : "onNativeLibraryReady called twice!"; |
+ mTabContentManager = tabContentProvider; |
+ |
+ TabModel normalModel = new TabModelImpl(false, mActivity, mUma, mOrderController, |
+ mTabContentManager, mTabSaver, this); |
+ TabModel incognitoModel = new OffTheRecordTabModel(new TabModelImplCreator( |
+ mActivity, mUma, mOrderController, mTabContentManager, mTabSaver, this)); |
+ initialize(isIncognitoSelected(), normalModel, incognitoModel); |
+ mActivity.getTabCreator(false).setTabModel(normalModel, mTabContentManager); |
+ mActivity.getTabCreator(true).setTabModel(incognitoModel, mTabContentManager); |
+ |
+ mTabSaver.setTabContentManager(tabContentProvider); |
+ |
+ addObserver(new EmptyTabModelSelectorObserver() { |
+ @Override |
+ public void onNewTabCreated(Tab tab) { |
+ // Only invalidate if the tab exists in the currently selected model. |
+ if (TabModelUtils.getTabById(getCurrentModel(), tab.getId()) != null) { |
+ mTabContentManager.invalidateIfChanged(tab.getId(), tab.getUrl()); |
+ } |
+ } |
+ }); |
+ |
+ mActiveState = true; |
+ |
+ new TabModelSelectorTabObserver(this) { |
+ @Override |
+ public void onUrlUpdated(Tab tab) { |
+ TabModel model = getModelForTabId(tab.getId()); |
+ if (model == getCurrentModel()) { |
+ mTabContentManager.invalidateIfChanged(tab.getId(), tab.getUrl()); |
+ } |
+ } |
+ |
+ @Override |
+ public void onLoadStopped(Tab tab) { |
+ handleOnPageLoadStopped(tab); |
+ } |
+ |
+ @Override |
+ public void onPageLoadStarted(Tab tab) { |
+ String url = tab.getUrl(); |
+ if (NativePageFactory.isNativePageUrl(url, tab.isIncognito())) { |
+ mTabContentManager.invalidateTabThumbnail(tab.getId(), url); |
+ } else { |
+ mTabContentManager.removeTabThumbnail(tab.getId()); |
+ } |
+ } |
+ |
+ @Override |
+ public void onPageLoadFinished(Tab tab) { |
+ mUma.onPageLoadFinished(tab.getId()); |
+ } |
+ |
+ @Override |
+ public void onPageLoadFailed(Tab tab, int errorCode) { |
+ mUma.onPageLoadFailed(tab.getId()); |
+ } |
+ |
+ @Override |
+ public void onCrash(Tab tab, boolean sadTabShown) { |
+ if (sadTabShown) { |
+ mTabContentManager.removeTabThumbnail(tab.getId()); |
+ } |
+ mUma.onTabCrashed(tab.getId()); |
+ } |
+ }; |
+ } |
+ |
+ @Override |
+ public void setCloseAllTabsDelegate(CloseAllTabsDelegate delegate) { |
+ mCloseAllTabsDelegate = delegate; |
+ } |
+ |
+ @Override |
+ public TabModel getModelAt(int index) { |
+ return mActiveState ? super.getModelAt(index) : EmptyTabModel.getInstance(); |
+ } |
+ |
+ @Override |
+ public void selectModel(boolean incognito) { |
+ TabModel oldModel = getCurrentModel(); |
+ super.selectModel(incognito); |
+ TabModel newModel = getCurrentModel(); |
+ if (oldModel != newModel) { |
+ TabModelUtils.setIndex(newModel, newModel.index()); |
+ |
+ // Make the call to notifyDataSetChanged() after any delayed events |
+ // have had a chance to fire. Otherwise, this may result in some |
+ // drawing to occur before animations have a chance to work. |
+ new Handler().post(new Runnable() { |
+ @Override |
+ public void run() { |
+ notifyChanged(); |
+ } |
+ }); |
+ } |
+ } |
+ |
+ /** |
+ * Commits all pending tab closures for all {@link TabModel}s in this {@link TabModelSelector}. |
+ */ |
+ @Override |
+ public void commitAllTabClosures() { |
+ for (int i = 0; i < getModels().size(); i++) { |
+ getModelAt(i).commitAllTabClosures(); |
+ } |
+ } |
+ |
+ @Override |
+ public boolean closeAllTabsRequest(boolean incognito) { |
+ return mCloseAllTabsDelegate.closeAllTabsRequest(incognito); |
+ } |
+ |
+ public void saveState() { |
+ commitAllTabClosures(); |
+ mTabSaver.saveState(); |
+ } |
+ |
+ /** |
+ * Load the saved tab state. This should be called before any new tabs are created. The saved |
+ * tabs shall not be restored until {@link #restoreTabs} is called. |
+ */ |
+ public void loadState() { |
+ int nextId = mTabSaver.loadState(); |
+ if (nextId >= 0) Tab.incrementIdCounterTo(nextId); |
+ } |
+ |
+ /** |
+ * Restore the saved tabs which were loaded by {@link #loadState}. |
+ * |
+ * @param setActiveTab If true, synchronously load saved active tab and set it as the current |
+ * active tab. |
+ */ |
+ public void restoreTabs(boolean setActiveTab) { |
+ mTabSaver.restoreTabs(setActiveTab); |
+ } |
+ |
+ /** |
+ * If there is an asynchronous session restore in-progress, try to synchronously restore |
+ * the state of a tab with the given url as a frozen tab. This method has no effect if |
+ * there isn't a tab being restored with this url, or the tab has already been restored. |
+ * |
+ * @return true if there exists a tab with the url. |
+ */ |
+ public boolean tryToRestoreTabStateForUrl(String url) { |
+ if (!isSessionRestoreInProgress()) return false; |
+ return mTabSaver.restoreTabStateForUrl(url); |
+ } |
+ |
+ /** |
+ * If there is an asynchronous session restore in-progress, try to synchronously restore |
+ * the state of a tab with the given id as a frozen tab. This method has no effect if |
+ * there isn't a tab being restored with this id, or the tab has already been restored. |
+ * |
+ * @return true if there exists a tab with the id. |
+ */ |
+ public boolean tryToRestoreTabStateForId(int id) { |
+ if (!isSessionRestoreInProgress()) return false; |
+ return mTabSaver.restoreTabStateForId(id); |
+ } |
+ |
+ public void clearState() { |
+ mTabSaver.clearState(); |
+ } |
+ |
+ public void clearEncryptedState() { |
+ mTabSaver.clearEncryptedState(); |
+ } |
+ |
+ @Override |
+ public void destroy() { |
+ mTabSaver.destroy(); |
+ mUma.destroy(); |
+ super.destroy(); |
+ mActiveState = false; |
+ } |
+ |
+ @Override |
+ public Tab openNewTab(LoadUrlParams loadUrlParams, TabLaunchType type, Tab parent, |
+ boolean incognito) { |
+ return mActivity.getTabCreator(incognito).createNewTab(loadUrlParams, type, parent); |
+ } |
+ |
+ /** |
+ * @return Number of restored tabs on cold startup. |
+ */ |
+ public int getRestoredTabCount() { |
+ return mTabSaver.getRestoredTabCount(); |
+ } |
+ |
+ @Override |
+ public void requestToShowTab(Tab tab, TabSelectionType type) { |
+ boolean isFromExternalApp = tab != null |
+ && tab.getLaunchType() == TabLaunchType.FROM_EXTERNAL_APP; |
+ |
+ if (mVisibleTab != tab && tab != null && !tab.isNativePage()) { |
+ TabModelBase.startTabSwitchLatencyTiming(type); |
+ } |
+ if (mVisibleTab != null && mVisibleTab != tab && !mVisibleTab.needsReload()) { |
+ // TODO(dtrainor): Once we figure out why we can't grab a snapshot from the current |
+ // tab when we have other tabs loading from external apps remove the checks for |
+ // FROM_EXTERNAL_APP/FROM_NEW. |
+ if (!mVisibleTab.isClosing() |
+ && (!isFromExternalApp || type != TabSelectionType.FROM_NEW)) { |
+ cacheTabBitmap(mVisibleTab); |
+ } |
+ mVisibleTab.hide(); |
+ mVisibleTab.setFullscreenManager(null); |
+ mTabSaver.addTabToSaveQueue(mVisibleTab); |
+ mVisibleTab = null; |
+ } |
+ |
+ if (tab == null) { |
+ notifyChanged(); |
+ return; |
+ } |
+ |
+ // We hit this case when the user enters tab switcher and comes back to the current tab |
+ // without actual tab switch. |
+ if (mVisibleTab == tab && !mVisibleTab.isHidden()) { |
+ // The current tab might have been killed by the os while in tab switcher. |
+ tab.loadIfNeeded(); |
+ return; |
+ } |
+ |
+ tab.setFullscreenManager(mActivity.getFullscreenManager()); |
+ mVisibleTab = tab; |
+ |
+ // Don't execute the tab display part if Chrome has just been sent to background. This |
+ // avoids uneccessary work (tab restore) and prevents pollution of tab display metrics - see |
+ // http://crbug.com/316166. |
+ if (type != TabSelectionType.FROM_EXIT) { |
+ tab.show(type); |
+ mUma.onShowTab(tab.getId(), tab.isBeingRestored()); |
+ } |
+ } |
+ |
+ private void cacheTabBitmap(Tab tabToCache) { |
+ // Trigger a capture of this tab. |
+ if (tabToCache == null) return; |
+ mTabContentManager.cacheTabThumbnail(tabToCache); |
+ } |
+ |
+ @Override |
+ public boolean isInOverviewMode() { |
+ return mOverviewModeBehavior != null && mOverviewModeBehavior.overviewVisible(); |
+ } |
+ |
+ @Override |
+ public boolean isSessionRestoreInProgress() { |
+ return mSessionRestoreInProgress.get(); |
+ } |
+ |
+ // TODO(tedchoc): Remove the need for this to be exposed. |
+ @Override |
+ public void notifyChanged() { |
+ super.notifyChanged(); |
+ } |
+} |