| Index: chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageTabObserver.java
|
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageTabObserver.java b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageTabObserver.java
|
| index 16c95854140136747d91bb5a29c339554ede0351..618e90da68e259f660813b803b613fe5d1e72a36 100644
|
| --- a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageTabObserver.java
|
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageTabObserver.java
|
| @@ -7,13 +7,16 @@ package org.chromium.chrome.browser.offlinepages;
|
| import android.content.Context;
|
|
|
| import org.chromium.base.Log;
|
| +import org.chromium.base.VisibleForTesting;
|
| import org.chromium.chrome.browser.snackbar.SnackbarManager;
|
| import org.chromium.chrome.browser.snackbar.SnackbarManager.SnackbarController;
|
| import org.chromium.chrome.browser.tab.EmptyTabObserver;
|
| import org.chromium.chrome.browser.tab.Tab;
|
| +import org.chromium.chrome.browser.tab.TabObserver;
|
| +import org.chromium.net.NetworkChangeNotifier;
|
|
|
| -import java.util.Map;
|
| -import java.util.WeakHashMap;
|
| +import java.util.HashSet;
|
| +import java.util.Set;
|
|
|
| /**
|
| * A class that observes events for a tab which has an associated offline page. This will be
|
| @@ -21,115 +24,123 @@ import java.util.WeakHashMap;
|
| * to show a snackbar if the device connects). It will be removed when the user navigates away
|
| * from the offline tab.
|
| */
|
| -public class OfflinePageTabObserver extends EmptyTabObserver {
|
| +public class OfflinePageTabObserver
|
| + extends EmptyTabObserver implements NetworkChangeNotifier.ConnectionTypeObserver {
|
| private static final String TAG = "OfflinePageTO";
|
| private Context mContext;
|
| private SnackbarManager mSnackbarManager;
|
| - private boolean mConnected;
|
| private SnackbarController mSnackbarController;
|
| - private boolean mWasHidden;
|
| - private OfflinePageConnectivityListener mListener;
|
| + private final Set<Integer> mObservedTabs = new HashSet<Integer>();
|
| + private boolean mWasSnackbarShown;
|
| + private Tab mCurrentTab;
|
| + private boolean mIsObservingNetworkChanges;
|
|
|
| - private static final Map<Integer, OfflinePageTabObserver> sTabObservers =
|
| - new WeakHashMap<Integer, OfflinePageTabObserver>();
|
| + private static TabHelper sTabHelper;
|
| + private static OfflinePageTabObserver sInstance;
|
|
|
| /**
|
| - * Create and attach a tab observer if we don't already have one, otherwise update it.
|
| - * @param context Android context.
|
| - * @param snackbarManager The snackbar manager to show and dismiss snackbars.
|
| - * @param tab The tab we are adding an observer for.
|
| - * @param connected True if we were connected when this call was made.
|
| - * @param snackbarController The snackbar controller to control snackbar behavior.
|
| + * Helper class that allows mocking access to the tab methods, without having to have a tab,
|
| + * which makes things testable.
|
| */
|
| - public static void addObserverForTab(Context context, SnackbarManager snackbarManager, Tab tab,
|
| - boolean connected, SnackbarController snackbarController) {
|
| - // See if we already have an observer for this tab.
|
| - int tabId = tab.getId();
|
| - OfflinePageTabObserver observer = sTabObservers.get(tabId);
|
| -
|
| - if (observer == null) {
|
| - // If we don't have an observer, build one and attach it to the tab.
|
| - observer = new OfflinePageTabObserver(
|
| - context, snackbarManager, tab, connected, snackbarController);
|
| - sTabObservers.put(tabId, observer);
|
| - tab.addObserver(observer);
|
| - } else {
|
| - // If we already have an observer, update the connected state.
|
| - observer.setConnected(connected);
|
| - // If we already have an observer and addObserver was called, we should re-enable
|
| - // the connectivity listener in case we go offline again. This typically happens
|
| - // if a background page comes back to the foreground.
|
| - // To make testing work properly, replace the snackbar controller. Otherwise the test
|
| - // will use the release version of the snackbar controller instead of its mock.
|
| - if (!connected) {
|
| - observer.enableConnectivityListener(snackbarController);
|
| + static class TabHelper {
|
| + public int getTabId(Tab tab) {
|
| + if (tab == null) return Tab.INVALID_TAB_ID;
|
| + return tab.getId();
|
| + }
|
| +
|
| + public boolean isOfflinePage(Tab tab) {
|
| + return tab != null && tab.isOfflinePage();
|
| + }
|
| +
|
| + public boolean isTabShowing(Tab tab) {
|
| + return tab != null && !tab.isFrozen() && !tab.isHidden();
|
| + }
|
| +
|
| + public void addObserver(Tab tab, TabObserver observer) {
|
| + if (tab != null && observer != null) {
|
| + tab.addObserver(observer);
|
| + }
|
| + }
|
| +
|
| + public void removeObserver(Tab tab, TabObserver observer) {
|
| + if (tab != null && observer != null) {
|
| + tab.removeObserver(observer);
|
| }
|
| }
|
| }
|
|
|
| + static void setTabHelperForTesting(TabHelper tabHelper) {
|
| + sTabHelper = tabHelper;
|
| + }
|
| +
|
| + static TabHelper getTabHelper() {
|
| + if (sTabHelper == null) {
|
| + sTabHelper = new TabHelper();
|
| + }
|
| + return sTabHelper;
|
| + }
|
| +
|
| + static void init(Context context, SnackbarManager manager, SnackbarController controller) {
|
| + sInstance = new OfflinePageTabObserver(context, manager, controller);
|
| + }
|
| +
|
| + static OfflinePageTabObserver getInstance() {
|
| + return sInstance;
|
| + }
|
| +
|
| + @VisibleForTesting
|
| + static void setInstanceForTesting(OfflinePageTabObserver instance) {
|
| + sInstance = instance;
|
| + }
|
| +
|
| /**
|
| - * Removes the observer for a tab with the specified tabId.
|
| - * @param tab The tab to remove an observer for.
|
| + * Create and attach a tab observer if we don't already have one, otherwise update it.
|
| + * @param tab The tab we are adding an observer for.
|
| */
|
| - private static void removeObserverForTab(Tab tab) {
|
| - OfflinePageTabObserver observer = sTabObservers.get(tab.getId());
|
| - if (observer != null) {
|
| - tab.removeObserver(observer);
|
| - }
|
| - sTabObservers.remove(tab.getId());
|
| + public static void addObserverForTab(Tab tab) {
|
| + assert getInstance() != null;
|
| + getInstance().startObservingTab(tab);
|
| }
|
|
|
| /**
|
| * Builds a new OfflinePageTabObserver.
|
| * @param context Android context.
|
| * @param snackbarManager The snackbar manager to show and dismiss snackbars.
|
| - * @param tab The tab we are adding an observer for.
|
| - * @param connected True if the phone is connected when the observer is created.
|
| * @param snackbarController Controller to use to build the snackbar.
|
| */
|
| - OfflinePageTabObserver(Context context, SnackbarManager snackbarManager, Tab tab,
|
| - boolean connected, SnackbarController snackbarController) {
|
| + OfflinePageTabObserver(Context context, SnackbarManager snackbarManager,
|
| + SnackbarController snackbarController) {
|
| mContext = context;
|
| mSnackbarManager = snackbarManager;
|
| - mConnected = connected;
|
| - // Remember if the tab was hidden when we started, so we can show the snackbar when
|
| - // the tab becomes visible.
|
| - mWasHidden = tab.isHidden();
|
| -
|
| - mListener = new OfflinePageConnectivityListener(
|
| - context, snackbarManager, tab, snackbarController);
|
| mSnackbarController = snackbarController;
|
| - }
|
| -
|
| - private void setConnected(boolean connected) {
|
| - mConnected = connected;
|
| - }
|
|
|
| - private void enableConnectivityListener(SnackbarController snackbarController) {
|
| - mListener.enable(snackbarController);
|
| + // The first time observer is created snackbar has net yet been shown.
|
| + mWasSnackbarShown = false;
|
| + mIsObservingNetworkChanges = false;
|
| }
|
|
|
| // Methods from EmptyTabObserver
|
| @Override
|
| - public void onShown(Tab visibleTab) {
|
| - // If there is no bookmark on this tab, there is nothing to do.
|
| - long bookmarkNum = visibleTab.getBookmarkId();
|
| - if (bookmarkNum == Tab.INVALID_BOOKMARK_ID) return;
|
| -
|
| - if (mWasHidden) {
|
| - if (mConnected) {
|
| - OfflinePageUtils.showReloadSnackbar(
|
| - mContext, mSnackbarManager, mSnackbarController);
|
| - }
|
| + public void onShown(Tab tab) {
|
| + if (!getTabHelper().isOfflinePage(tab)) return;
|
|
|
| - mWasHidden = false;
|
| + // Whenever we get a new tab shown, we will give a reload snackbar a chance to be shown,
|
| + // therefor the state is reset to false. Also the currently shown tab is captured.
|
| + mWasSnackbarShown = false;
|
| + mCurrentTab = tab;
|
| + if (isConnected() && !wasSnackbarShown()) {
|
| Log.d(TAG, "onShown, showing 'delayed' snackbar");
|
| + showReloadSnackbar();
|
| + // TODO(fgorski): Move the variable assignment to the method above, once
|
| + // OfflinePageUtils can be mocked.
|
| + mWasSnackbarShown = true;
|
| }
|
| }
|
|
|
| @Override
|
| public void onHidden(Tab hiddenTab) {
|
| - mWasHidden = true;
|
| + mWasSnackbarShown = false;
|
| + mCurrentTab = null;
|
| // In case any snackbars are showing, dismiss them before we switch tabs.
|
| mSnackbarManager.dismissSnackbars(mSnackbarController);
|
| }
|
| @@ -140,30 +151,119 @@ public class OfflinePageTabObserver extends EmptyTabObserver {
|
| }
|
|
|
| @Override
|
| - public void onDestroyed(Tab destroyedTab) {
|
| - // Unregister this tab for OS connectivity notifications.
|
| - mListener.disable();
|
| - destroyedTab.removeObserver(this);
|
| - sTabObservers.remove(destroyedTab.getId());
|
| + public void onDestroyed(Tab tab) {
|
| Log.d(TAG, "onDestroyed");
|
| + stopObservingTab(tab);
|
| }
|
|
|
| @Override
|
| - public void onUrlUpdated(Tab reloadingTab) {
|
| - // Unregister this tab for OS connectivity notifications.
|
| - mListener.disable();
|
| + public void onUrlUpdated(Tab tab) {
|
| Log.d(TAG, "onUrlUpdated");
|
| - removeObserverForTab(reloadingTab);
|
| + if (!getTabHelper().isOfflinePage(tab)) {
|
| + stopObservingTab(tab);
|
| + }
|
| // In case any snackbars are showing, dismiss them before we navigate away.
|
| mSnackbarManager.dismissSnackbars(mSnackbarController);
|
| }
|
|
|
| + void startObservingTab(Tab tab) {
|
| + // If the tab does not contain an offline page, we don't care to track it.
|
| + if (!getTabHelper().isOfflinePage(tab)) return;
|
| +
|
| + // TODO(fgorski): check for one of 2 things:
|
| + // 1. can we presume that we start observing the current tab, always
|
| + // 2. will onShown happen right after and then we don't need the next 2 lines:
|
| + mCurrentTab = tab;
|
| + mWasSnackbarShown = false;
|
| +
|
| + // If we are not observing the tab yet, let's.
|
| + if (!isObservingTab(tab)) {
|
| + int tabId = getTabHelper().getTabId(tab);
|
| + mObservedTabs.add(tabId);
|
| + getTabHelper().addObserver(tab, this);
|
| + }
|
| + if (!isObservingNetworkChanges()) {
|
| + startObservingNetworkChanges();
|
| + mIsObservingNetworkChanges = true;
|
| + }
|
| + if (getTabHelper().isTabShowing(tab) && isConnected()) {
|
| + showReloadSnackbar();
|
| + // TODO(fgorski): Move the variable assignment to the method above, once
|
| + // OfflinePageUtils can be mocked.
|
| + mWasSnackbarShown = true;
|
| + }
|
| + }
|
| +
|
| /**
|
| - * Attaches a connectivity listener if needed by this observer.
|
| - * @param tabId The index of the tab that this listener is listening to.
|
| - * @param listener The listener itself.
|
| + * Removes the observer for a tab with the specified tabId.
|
| + * @param tabId ID of a tab that was observed.
|
| */
|
| - private void setConnectivityListener(int tabId, OfflinePageConnectivityListener listener) {
|
| - mListener = listener;
|
| + void stopObservingTab(Tab tab) {
|
| + if (isObservingTab(tab)) {
|
| + int tabId = getTabHelper().getTabId(tab);
|
| + mObservedTabs.remove(tabId);
|
| + getTabHelper().removeObserver(tab, this);
|
| + }
|
| + if (mObservedTabs.isEmpty() && isObservingNetworkChanges()) {
|
| + stopObservingNetworkChanges();
|
| + mIsObservingNetworkChanges = false;
|
| + }
|
| + mWasSnackbarShown = false;
|
| + mCurrentTab = null;
|
| + }
|
| +
|
| + // Methods from ConnectionTypeObserver.
|
| + @Override
|
| + public void onConnectionTypeChanged(int connectionType) {
|
| + Log.d(TAG, "Got connectivity event, connectionType: " + connectionType + ", controller "
|
| + + mSnackbarController);
|
| +
|
| + Log.d(TAG, "Connection changed, connected " + isConnected());
|
| + // Shows or hides the snackbar as needed. This also adds some hysterisis - if we keep
|
| + // connecting and disconnecting, we don't want to flash the snackbar. It will timeout after
|
| + // several seconds.
|
| + if (isConnected() && getTabHelper().isTabShowing(mCurrentTab) && !wasSnackbarShown()) {
|
| + Log.d(TAG, "Connection became available, show reload snackbar.");
|
| + showReloadSnackbar();
|
| + // TODO(fgorski): Move the variable assignment to the method above, once
|
| + // OfflinePageUtils can be mocked.
|
| + mWasSnackbarShown = true;
|
| + }
|
| + }
|
| +
|
| + @VisibleForTesting
|
| + boolean isObservingTab(Tab tab) {
|
| + return mObservedTabs.contains(getTabHelper().getTabId(tab));
|
| + }
|
| +
|
| + @VisibleForTesting
|
| + boolean isObservingNetworkChanges() {
|
| + return mIsObservingNetworkChanges;
|
| + }
|
| +
|
| + @VisibleForTesting
|
| + boolean isConnected() {
|
| + return OfflinePageUtils.isConnected();
|
| + }
|
| +
|
| + @VisibleForTesting
|
| + boolean wasSnackbarShown() {
|
| + return mWasSnackbarShown;
|
| + }
|
| +
|
| + @VisibleForTesting
|
| + void showReloadSnackbar() {
|
| + OfflinePageUtils.showReloadSnackbar(mContext, mSnackbarManager, mSnackbarController,
|
| + getTabHelper().getTabId(mCurrentTab));
|
| + }
|
| +
|
| + @VisibleForTesting
|
| + void startObservingNetworkChanges() {
|
| + NetworkChangeNotifier.addConnectionTypeObserver(this);
|
| + }
|
| +
|
| + @VisibleForTesting
|
| + void stopObservingNetworkChanges() {
|
| + NetworkChangeNotifier.removeConnectionTypeObserver(this);
|
| }
|
| }
|
|
|