// 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.ntp;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import org.chromium.base.ContextUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.chrome.browser.UrlConstants;
import org.chromium.chrome.browser.favicon.FaviconHelper;
import org.chromium.chrome.browser.favicon.FaviconHelper.FaviconImageCallback;
import org.chromium.chrome.browser.invalidation.InvalidationController;
import org.chromium.chrome.browser.metrics.StartupMetrics;
import org.chromium.chrome.browser.ntp.ForeignSessionHelper.ForeignSession;
import org.chromium.chrome.browser.ntp.ForeignSessionHelper.ForeignSessionCallback;
import org.chromium.chrome.browser.ntp.ForeignSessionHelper.ForeignSessionTab;
import org.chromium.chrome.browser.ntp.RecentlyClosedBridge.RecentlyClosedCallback;
import org.chromium.chrome.browser.ntp.RecentlyClosedBridge.RecentlyClosedTab;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.signin.SigninManager;
import org.chromium.chrome.browser.signin.SigninManager.SignInStateObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.components.signin.ChromeSigninController;
import org.chromium.components.sync.AndroidSyncSettings;
import org.chromium.components.sync.AndroidSyncSettings.AndroidSyncSettingsObserver;
import org.chromium.content_public.browser.LoadUrlParams;
import java.util.Collections;
import java.util.List;
/**
* Provides the domain logic and data for RecentTabsPage and RecentTabsRowAdapter.
*/
public class RecentTabsManager implements AndroidSyncSettingsObserver, SignInStateObserver {
/**
* Implement this to receive updates when the page contents change.
*/
interface UpdatedCallback {
/**
* Called when the list of recently closed tabs or foreign sessions changes.
*/
void onUpdated();
}
private static final int RECENTLY_CLOSED_MAX_TAB_COUNT = 5;
private static final String PREF_SIGNIN_PROMO_DECLINED =
"recent_tabs_signin_promo_declined";
private final Profile mProfile;
private final Tab mTab;
private final Context mContext;
private FaviconHelper mFaviconHelper;
private ForeignSessionHelper mForeignSessionHelper;
private List<ForeignSession> mForeignSessions;
private List<RecentlyClosedTab> mRecentlyClosedTabs;
private NewTabPagePrefs mNewTabPagePrefs;
private RecentlyClosedBridge mRecentlyClosedBridge;
private SigninManager mSignInManager;
private UpdatedCallback mUpdatedCallback;
private boolean mIsDestroyed;
/**
* Create an RecentTabsManager to be used with RecentTabsPage and RecentTabsRowAdapter.
*
* @param tab The Tab that is showing this recent tabs page.
* @param profile Profile that is associated with the current session.
* @param context the Android context this manager will work in.
*/
public RecentTabsManager(Tab tab, Profile profile, Context context) {
mProfile = profile;
mTab = tab;
mForeignSessionHelper = buildForeignSessionHelper(mProfile);
mNewTabPagePrefs = buildNewTabPagePrefs(mProfile);
mFaviconHelper = buildFaviconHelper();
mRecentlyClosedBridge = buildRecentlyClosedBridge(mProfile);
mSignInManager = SigninManager.get(context);
mContext = context;
updateRecentlyClosedTabs();
registerForForeignSessionUpdates();
updateForeignSessions();
mForeignSessionHelper.triggerSessionSync();
registerForSignInAndSyncNotifications();
InvalidationController.get(mContext).onRecentTabsPageOpened();
}
/**
* Should be called when this object is no longer needed. Performs necessary listener tear down.
*/
public void destroy() {
mIsDestroyed = true;
AndroidSyncSettings.unregisterObserver(mContext, this);
mSignInManager.removeSignInStateObserver(this);
mSignInManager = null;
mFaviconHelper.destroy();
mFaviconHelper = null;
mRecentlyClosedBridge.destroy();
mRecentlyClosedBridge = null;
mForeignSessionHelper.destroy();
mForeignSessionHelper = null;
mUpdatedCallback = null;
mNewTabPagePrefs.destroy();
mNewTabPagePrefs = null;
InvalidationController.get(mContext).onRecentTabsPageClosed();
}
/**
* Returns true if destroy() has been called.
*/
public boolean isDestroyed() {
return mIsDestroyed;
}
private static ForeignSessionHelper buildForeignSessionHelper(Profile profile) {
return new ForeignSessionHelper(profile);
}
private static NewTabPagePrefs buildNewTabPagePrefs(Profile profile) {
return new NewTabPagePrefs(profile);
}
private static FaviconHelper buildFaviconHelper() {
return new FaviconHelper();
}
private RecentlyClosedBridge buildRecentlyClosedBridge(Profile profile) {
RecentlyClosedBridge bridge = new RecentlyClosedBridge(profile);
bridge.setRecentlyClosedCallback(new RecentlyClosedCallback() {
@Override
public void onUpdated() {
updateRecentlyClosedTabs();
postUpdate();
}
});
return bridge;
}
private void registerForForeignSessionUpdates() {
mForeignSessionHelper.setOnForeignSessionCallback(new ForeignSessionCallback() {
@Override
public void onUpdated() {
updateForeignSessions();
postUpdate();
}
});
}
private void registerForSignInAndSyncNotifications() {
AndroidSyncSettings.registerObserver(mContext, this);
mSignInManager.addSignInStateObserver(this);
}
protected void updateCurrentlyOpenTabs() {
}
private void updateRecentlyClosedTabs() {
mRecentlyClosedTabs = mRecentlyClosedBridge.getRecentlyClosedTabs(
RECENTLY_CLOSED_MAX_TAB_COUNT);
}
private void updateForeignSessions() {
mForeignSessions = mForeignSessionHelper.getForeignSessions();
if (mForeignSessions == null) {
mForeignSessions = Collections.emptyList();
}
}
/**
* @return Most up-to-date list of currently open tabs.
*/
public List<CurrentlyOpenTab> getCurrentlyOpenTabs() {
return null;
}
/**
* @return Most up-to-date list of foreign sessions.
*/
public List<ForeignSession> getForeignSessions() {
return mForeignSessions;
}
/**
* @return Most up-to-date list of recently closed tabs.
*/
public List<RecentlyClosedTab> getRecentlyClosedTabs() {
return mRecentlyClosedTabs;
}
/**
* Opens a new tab navigating to ForeignSessionTab.
*
* @param session The foreign session that the tab belongs to.
* @param tab The tab to open.
* @param windowDisposition The WindowOpenDisposition flag.
*/
public void openForeignSessionTab(ForeignSession session, ForeignSessionTab tab,
int windowDisposition) {
if (mIsDestroyed) return;
NewTabPageUma.recordAction(NewTabPageUma.ACTION_OPENED_FOREIGN_SESSION);
mForeignSessionHelper.openForeignSessionTab(mTab, session, tab, windowDisposition);
}
/**
* Restores a recently closed tab.
*
* @param tab The tab to open.
* @param windowDisposition The WindowOpenDisposition value specifying whether the tab should
* be restored into the current tab or a new tab.
*/
public void openRecentlyClosedTab(RecentlyClosedTab tab, int windowDisposition) {
if (mIsDestroyed) return;
NewTabPageUma.recordAction(NewTabPageUma.ACTION_OPENED_RECENTLY_CLOSED_ENTRY);
mRecentlyClosedBridge.openRecentlyClosedTab(mTab, tab, windowDisposition);
}
/**
* Opens the history page.
*/
public void openHistoryPage() {
if (mIsDestroyed) return;
mTab.loadUrl(new LoadUrlParams(UrlConstants.HISTORY_URL));
StartupMetrics.getInstance().recordOpenedHistory();
}
/**
* Returns a 16x16 favicon for a given synced url.
*
* @param url The url to fetch the favicon for.
* @return 16x16 favicon or null if favicon unavailable.
*/
public Bitmap getSyncedFaviconImageForURL(String url) {
return mFaviconHelper.getSyncedFaviconImageForURL(mProfile, url);
}
/**
* Fetches a favicon for snapshot document url which is returned via callback.
*
* @param url The url to fetch a favicon for.
* @param size the desired favicon size.
* @param faviconCallback the callback to be invoked when the favicon is available.
*
* @return may return false if we could not fetch the favicon.
*/
public boolean getLocalFaviconForUrl(String url, int size,
FaviconImageCallback faviconCallback) {
return mFaviconHelper.getLocalFaviconImageForURL(mProfile, url, size, faviconCallback);
}
/**
* Sets a callback to be invoked when recently closed tabs or foreign sessions documents have
* been updated.
*
* @param updatedCallback the listener to be invoked.
*/
public void setUpdatedCallback(UpdatedCallback updatedCallback) {
mUpdatedCallback = updatedCallback;
}
/**
* Sets the persistent expanded/collapsed state of the currently open tabs list.
*
* @param isCollapsed Whether the currently open tabs list is collapsed.
*/
public void setCurrentlyOpenTabsCollapsed(boolean isCollapsed) {
if (mIsDestroyed) return;
mNewTabPagePrefs.setCurrentlyOpenTabsCollapsed(isCollapsed);
}
/**
* Determine the expanded/collapsed state of the currently open tabs list.
*
* @return Whether the currently open tabs list is collapsed.
*/
public boolean isCurrentlyOpenTabsCollapsed() {
return mNewTabPagePrefs.getCurrentlyOpenTabsCollapsed();
}
/**
* Sets the state for showing all tabs in the currently open tabs list. This is intended to
* be overridden in extending classes and set to true when the user clicks the "More" button
* at the end of the list.
* @param showingAll Whether the currently open tabs list should start to show all.
*/
public void setCurrentlyOpenTabsShowAll(boolean showingAll) {
}
/**
* @return Whether the currently open tabs group shows all tabs. If it is not, only a limited
* number of tabs is shown with a "More" button at the end of the list to show all.
*/
public boolean isCurrentlyOpenTabsShowingAll() {
return false;
}
/**
* Closes the specified currently open tab.
* @param tab Information about the tab that should be closed.
*/
public void closeTab(CurrentlyOpenTab tab) {
}
/**
* Sets the persistent expanded/collapsed state of a foreign session list.
*
* @param session foreign session to collapsed.
* @param isCollapsed Whether the session is collapsed or expanded.
*/
public void setForeignSessionCollapsed(ForeignSession session, boolean isCollapsed) {
if (mIsDestroyed) return;
mNewTabPagePrefs.setForeignSessionCollapsed(session, isCollapsed);
}
/**
* Determine the expanded/collapsed state of a foreign session list.
*
* @param session foreign session whose state to obtain.
*
* @return Whether the session is collapsed.
*/
public boolean getForeignSessionCollapsed(ForeignSession session) {
return mNewTabPagePrefs.getForeignSessionCollapsed(session);
}
/**
* Sets the persistent expanded/collapsed state of the recently closed tabs list.
*
* @param isCollapsed Whether the recently closed tabs list is collapsed.
*/
public void setRecentlyClosedTabsCollapsed(boolean isCollapsed) {
if (mIsDestroyed) return;
mNewTabPagePrefs.setRecentlyClosedTabsCollapsed(isCollapsed);
}
/**
* Determine the expanded/collapsed state of the recently closed tabs list.
*
* @return Whether the recently closed tabs list is collapsed.
*/
public boolean isRecentlyClosedTabsCollapsed() {
return mNewTabPagePrefs.getRecentlyClosedTabsCollapsed();
}
/**
* Remove Foreign session to display. Note that it might reappear during the next sync if the
* session is not orphaned.
*
* This is mainly for when user wants to delete an orphaned session.
* @param session Session to be deleted.
*/
public void deleteForeignSession(ForeignSession session) {
if (mIsDestroyed) return;
mForeignSessionHelper.deleteForeignSession(session);
}
/**
* Clears the list of recently closed tabs.
*/
public void clearRecentlyClosedTabs() {
if (mIsDestroyed) return;
mRecentlyClosedBridge.clearRecentlyClosedTabs();
}
/**
* Determine whether the sync promo needs to be displayed.
*
* @return Whether sync promo should be displayed.
*/
public boolean shouldDisplaySyncPromo() {
SigninManager signinManager = SigninManager.get(mContext);
if (signinManager.isSigninDisabledByPolicy() || !signinManager.isSigninSupported()) {
return false;
}
if (ContextUtils.getAppSharedPreferences().getBoolean(
PREF_SIGNIN_PROMO_DECLINED, false)) {
return false;
}
return !AndroidSyncSettings.isSyncEnabled(mContext) || mForeignSessions.isEmpty();
}
/**
* Save that user tapped "No" button on the signin promo.
*/
public void setSigninPromoDeclined() {
SharedPreferences.Editor sharedPreferencesEditor =
ContextUtils.getAppSharedPreferences().edit();
sharedPreferencesEditor.putBoolean(PREF_SIGNIN_PROMO_DECLINED, true);
sharedPreferencesEditor.apply();
}
/**
* Collapse the sync promo.
*
* @param isCollapsed Whether the sync promo is collapsed.
*/
public void setSyncPromoCollapsed(boolean isCollapsed) {
if (mIsDestroyed) return;
mNewTabPagePrefs.setSyncPromoCollapsed(isCollapsed);
}
/**
* Determine whether the sync promo is collapsed.
*
* @return Whether the sync promo is collapsed.
*/
public boolean isSyncPromoCollapsed() {
return mNewTabPagePrefs.getSyncPromoCollapsed();
}
protected void postUpdate() {
if (mUpdatedCallback != null) {
mUpdatedCallback.onUpdated();
}
}
// SignInStateObserver
@Override
public void onSignedIn() {
androidSyncSettingsChanged();
}
@Override
public void onSignedOut() {
androidSyncSettingsChanged();
}
// AndroidSyncSettingsObserver
@Override
public void androidSyncSettingsChanged() {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
if (mIsDestroyed) return;
updateForeignSessions();
postUpdate();
}
});
}
public boolean isSignedIn() {
return ChromeSigninController.get(mContext).isSignedIn();
}
}