blob: a1b524e7974262356d178e98e51a6b48a9b7be9d [file] [log] [blame]
// 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.accounts.Account;
import android.content.Context;
import android.graphics.Bitmap;
import android.support.annotation.IntDef;
import org.chromium.base.ThreadUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.favicon.FaviconHelper;
import org.chromium.chrome.browser.favicon.FaviconHelper.FaviconImageCallback;
import org.chromium.chrome.browser.history.HistoryManagerUtils;
import org.chromium.chrome.browser.invalidation.InvalidationController;
import org.chromium.chrome.browser.ntp.ForeignSessionHelper.ForeignSession;
import org.chromium.chrome.browser.ntp.ForeignSessionHelper.ForeignSessionTab;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.signin.DisplayableProfileData;
import org.chromium.chrome.browser.signin.PersonalizedSigninPromoView;
import org.chromium.chrome.browser.signin.ProfileDataCache;
import org.chromium.chrome.browser.signin.SigninAccessPoint;
import org.chromium.chrome.browser.signin.SigninManager;
import org.chromium.chrome.browser.signin.SigninManager.SignInStateObserver;
import org.chromium.chrome.browser.signin.SigninPromoController;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.components.signin.AccountManagerFacade;
import org.chromium.components.signin.AccountsChangeObserver;
import org.chromium.components.signin.ChromeSigninController;
import org.chromium.components.sync.AndroidSyncSettings;
import org.chromium.components.sync.AndroidSyncSettings.AndroidSyncSettingsObserver;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.List;
/**
* Provides the domain logic and data for RecentTabsPage and RecentTabsRowAdapter.
*/
public class RecentTabsManager implements AndroidSyncSettingsObserver, SignInStateObserver,
ProfileDataCache.Observer, AccountsChangeObserver {
/**
* 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();
}
@Retention(RetentionPolicy.SOURCE)
@IntDef({PromoState.PROMO_NONE, PromoState.PROMO_SIGNIN_PERSONALIZED, PromoState.PROMO_SYNC})
@interface PromoState {
int PROMO_NONE = 0;
int PROMO_SIGNIN_PERSONALIZED = 1;
int PROMO_SYNC = 2;
}
private static final int RECENTLY_CLOSED_MAX_TAB_COUNT = 5;
private static RecentlyClosedTabManager sRecentlyClosedTabManagerForTests;
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 RecentTabsPagePrefs mPrefs;
private RecentlyClosedTabManager mRecentlyClosedTabManager;
private SigninManager mSignInManager;
private UpdatedCallback mUpdatedCallback;
private boolean mIsDestroyed;
private final ProfileDataCache mProfileDataCache;
private final SigninPromoController mSigninPromoController;
/**
* 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 = new ForeignSessionHelper(profile);
mPrefs = new RecentTabsPagePrefs(profile);
mFaviconHelper = new FaviconHelper();
mRecentlyClosedTabManager = sRecentlyClosedTabManagerForTests != null
? sRecentlyClosedTabManagerForTests
: new RecentlyClosedBridge(profile);
mSignInManager = SigninManager.get();
mContext = context;
int imageSize = context.getResources().getDimensionPixelSize(R.dimen.user_picture_size);
mProfileDataCache = new ProfileDataCache(mContext, imageSize);
mSigninPromoController = new SigninPromoController(SigninAccessPoint.RECENT_TABS);
mRecentlyClosedTabManager.setTabsUpdatedRunnable(() -> {
updateRecentlyClosedTabs();
postUpdate();
});
updateRecentlyClosedTabs();
registerForForeignSessionUpdates();
updateForeignSessions();
mForeignSessionHelper.triggerSessionSync();
registerObservers();
InvalidationController.get().onRecentTabsPageOpened();
}
/**
* Should be called when this object is no longer needed. Performs necessary listener tear down.
*/
public void destroy() {
mIsDestroyed = true;
AndroidSyncSettings.unregisterObserver(this);
mSignInManager.removeSignInStateObserver(this);
mSignInManager = null;
mProfileDataCache.removeObserver(this);
AccountManagerFacade.get().removeObserver(this);
mFaviconHelper.destroy();
mFaviconHelper = null;
mRecentlyClosedTabManager.destroy();
mRecentlyClosedTabManager = null;
mForeignSessionHelper.destroy();
mForeignSessionHelper = null;
mUpdatedCallback = null;
mPrefs.destroy();
mPrefs = null;
InvalidationController.get().onRecentTabsPageClosed();
}
private void registerForForeignSessionUpdates() {
mForeignSessionHelper.setOnForeignSessionCallback(() -> {
updateForeignSessions();
postUpdate();
});
}
private void registerObservers() {
AndroidSyncSettings.registerObserver(this);
mSignInManager.addSignInStateObserver(this);
mProfileDataCache.addObserver(this);
AccountManagerFacade.get().addObserver(this);
}
private void updateRecentlyClosedTabs() {
mRecentlyClosedTabs =
mRecentlyClosedTabManager.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 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;
RecordUserAction.record("MobileRecentTabManagerTabFromOtherDeviceOpened");
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;
RecordUserAction.record("MobileRecentTabManagerRecentTabOpened");
mRecentlyClosedTabManager.openRecentlyClosedTab(mTab, tab, windowDisposition);
}
/**
* Opens the history page.
*/
public void openHistoryPage() {
if (mIsDestroyed) return;
HistoryManagerUtils.showHistoryManager(mTab.getActivity(), mTab);
}
/**
* 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 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;
mPrefs.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 mPrefs.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;
mPrefs.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 mPrefs.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;
mRecentlyClosedTabManager.clearRecentlyClosedTabs();
}
/**
* Collapse the promo.
*
* @param isCollapsed Whether the promo is collapsed.
*/
public void setPromoCollapsed(boolean isCollapsed) {
if (mIsDestroyed) return;
mPrefs.setSyncPromoCollapsed(isCollapsed);
}
/**
* Determine whether the promo is collapsed.
*
* @return Whether the promo is collapsed.
*/
public boolean isPromoCollapsed() {
return mPrefs.getSyncPromoCollapsed();
}
/**
* @return The promo type that will be displayed on the screen.
*/
@PromoState
int getPromoType() {
if (!ChromeSigninController.get().isSignedIn()) {
if (!SigninManager.get().isSignInAllowed()) {
return PromoState.PROMO_NONE;
}
return PromoState.PROMO_SIGNIN_PERSONALIZED;
}
if (AndroidSyncSettings.isSyncEnabled() && AndroidSyncSettings.isChromeSyncEnabled()
&& !mForeignSessions.isEmpty()) {
return PromoState.PROMO_NONE;
}
return PromoState.PROMO_SYNC;
}
void recordRecentTabMetrics() {
RecordHistogram.recordCountHistogram(
"Android.RecentTabsManager.RecentlyClosedTabs", mRecentlyClosedTabs.size());
RecordHistogram.recordCountHistogram(
"Android.RecentTabsManager.OtherDevices", mForeignSessions.size());
int totalCount = mRecentlyClosedTabs.size();
for (int i = 0; i < mForeignSessions.size(); i++) {
ForeignSession foreignSession = mForeignSessions.get(i);
for (int j = 0; j < foreignSession.windows.size(); j++) {
totalCount += foreignSession.windows.get(j).tabs.size();
}
}
RecordHistogram.recordCountHistogram("Android.RecentTabsManager.TotalTabs", totalCount);
}
/**
* Sets up the personalized signin promo and records user actions for promo impressions.
* @param view The view to be configured.
*/
void setupPersonalizedSigninPromo(PersonalizedSigninPromoView view) {
DisplayableProfileData profileData = null;
Account[] accounts = AccountManagerFacade.get().tryGetGoogleAccounts();
if (accounts.length > 0) {
String defaultAccountName = accounts[0].name;
mProfileDataCache.update(Collections.singletonList(defaultAccountName));
profileData = mProfileDataCache.getProfileDataOrDefault(defaultAccountName);
}
mSigninPromoController.detach();
mSigninPromoController.setupPromoView(mContext, view, profileData, null);
}
// SignInStateObserver implementation.
@Override
public void onSignedIn() {
update();
}
@Override
public void onSignedOut() {
update();
}
// AccountsChangeObserver implementation.
@Override
public void onAccountsChanged() {
update();
}
// ProfileDataCache.Observer implementation.
@Override
public void onProfileDataUpdated(String accountId) {
update();
}
// AndroidSyncSettingsObserver implementation.
@Override
public void androidSyncSettingsChanged() {
update();
}
private void postUpdate() {
if (mUpdatedCallback != null) {
mUpdatedCallback.onUpdated();
}
}
private void update() {
ThreadUtils.runOnUiThread(() -> {
if (mIsDestroyed) return;
updateForeignSessions();
postUpdate();
});
}
@VisibleForTesting
public static void setRecentlyClosedTabManagerForTests(RecentlyClosedTabManager manager) {
sRecentlyClosedTabManagerForTests = manager;
}
}