| // Copyright 2018 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.feed; |
| |
| import android.support.annotation.Nullable; |
| |
| import com.google.android.libraries.feed.api.host.config.ApplicationInfo; |
| import com.google.android.libraries.feed.api.host.config.Configuration; |
| import com.google.android.libraries.feed.api.host.config.DebugBehavior; |
| import com.google.android.libraries.feed.api.host.network.NetworkClient; |
| import com.google.android.libraries.feed.api.host.stream.TooltipSupportedApi; |
| import com.google.android.libraries.feed.api.scope.FeedProcessScope; |
| import com.google.android.libraries.feed.common.functional.Consumer; |
| |
| import org.chromium.base.ContextUtils; |
| import org.chromium.base.Log; |
| import org.chromium.base.VisibleForTesting; |
| import org.chromium.chrome.browser.preferences.Pref; |
| import org.chromium.chrome.browser.preferences.PrefChangeRegistrar; |
| import org.chromium.chrome.browser.preferences.PrefServiceBridge; |
| import org.chromium.chrome.browser.profiles.Profile; |
| |
| import java.util.concurrent.Executors; |
| |
| /** Holds singleton {@link FeedProcessScope} and some of the scope's host implementations. */ |
| public class FeedProcessScopeFactory { |
| private static final String TAG = "FeedProcessScopeFtry"; |
| |
| /** |
| * Flag that tracks whether we've ever been disabled via enterprise policy. Should only be |
| * accessed through isFeedProcessScopeEnabled(). |
| */ |
| private static boolean sEverDisabledForPolicy; |
| |
| /** |
| * Tracks whether the article suggestions should be visible to the user during the current |
| * session. If user opts in to the suggestions during the current session, the suggestions |
| * services will be immediately warmed up. If user opts out during the current session, |
| * the suggestions services will not shut down until the next session. |
| */ |
| private static boolean sArticlesVisibleDuringSession; |
| |
| private static PrefChangeRegistrar sPrefChangeRegistrar; |
| private static FeedAppLifecycle sFeedAppLifecycle; |
| private static FeedProcessScope sFeedProcessScope; |
| private static FeedScheduler sFeedScheduler; |
| private static FeedOfflineIndicator sFeedOfflineIndicator; |
| private static NetworkClient sTestNetworkClient; |
| private static FeedLoggingBridge sFeedLoggingBridge; |
| |
| /** @return The shared {@link FeedProcessScope} instance. Null if the Feed is disabled. */ |
| public static @Nullable FeedProcessScope getFeedProcessScope() { |
| if (sFeedProcessScope == null) { |
| initialize(); |
| } |
| return sFeedProcessScope; |
| } |
| |
| /** @return The {@link FeedScheduler} that was given to the {@link FeedProcessScope}. Null if |
| * the Feed is disabled. */ |
| public static @Nullable FeedScheduler getFeedScheduler() { |
| if (sFeedScheduler == null) { |
| initialize(); |
| } |
| return sFeedScheduler; |
| } |
| |
| /** @return The {@link FeedOfflineIndicator} that was given to the {@link FeedProcessScope}. |
| * Null if the Feed is disabled. */ |
| public static @Nullable FeedOfflineIndicator getFeedOfflineIndicator() { |
| if (sFeedOfflineIndicator == null) { |
| initialize(); |
| } |
| return sFeedOfflineIndicator; |
| } |
| |
| /* |
| * @return The global instance of {@link FeedAppLifecycle} for the process. |
| * Null if the Feed is disabled. |
| */ |
| public static @Nullable FeedAppLifecycle getFeedAppLifecycle() { |
| if (sFeedAppLifecycle == null) { |
| initialize(); |
| } |
| return sFeedAppLifecycle; |
| } |
| |
| /** @return The {@link FeedLoggingBridge} that was given to the {@link FeedStreamScope}. Null if |
| * the Feed is disabled. */ |
| public static @Nullable FeedLoggingBridge getFeedLoggingBridge() { |
| if (sFeedLoggingBridge == null) { |
| initialize(); |
| } |
| return sFeedLoggingBridge; |
| } |
| |
| /** |
| * @return Whether the dependencies provided by this class are allowed to be created. The feed |
| * process is disabled if supervised user or enterprise policy has once been added |
| * within the current session. |
| */ |
| public static boolean isFeedProcessEnabled() { |
| // Once true, sEverDisabledForPolicy will be true forever. If it isn't true yet, we need to |
| // check the pref every time. Two reasons for this. 1) We want to notice when we start in a |
| // disabled state, shouldn't allow Feed to enabled until a restart. 2) A different |
| // subscriber to this pref change event might check in with this method, and we cannot |
| // assume who will be called first. See https://crbug.com/896468. |
| if (!sEverDisabledForPolicy) { |
| sEverDisabledForPolicy = |
| !PrefServiceBridge.getInstance().getBoolean(Pref.NTP_ARTICLES_SECTION_ENABLED); |
| } |
| return !sEverDisabledForPolicy; |
| } |
| |
| private static void initialize() { |
| assert sFeedProcessScope == null && sFeedScheduler == null && sFeedOfflineIndicator == null |
| && sFeedAppLifecycle == null && sFeedLoggingBridge == null; |
| if (!isFeedProcessEnabled()) return; |
| |
| sArticlesVisibleDuringSession = |
| PrefServiceBridge.getInstance().getBoolean(Pref.NTP_ARTICLES_LIST_VISIBLE); |
| sPrefChangeRegistrar = new PrefChangeRegistrar(); |
| sPrefChangeRegistrar.addObserver(Pref.NTP_ARTICLES_SECTION_ENABLED, |
| FeedProcessScopeFactory::articlesEnabledPrefChange); |
| |
| Profile profile = Profile.getLastUsedProfile().getOriginalProfile(); |
| Configuration configHostApi = FeedConfiguration.createConfiguration(); |
| ApplicationInfo applicationInfo = FeedApplicationInfo.createApplicationInfo(); |
| |
| FeedSchedulerBridge schedulerBridge = new FeedSchedulerBridge(profile); |
| sFeedScheduler = schedulerBridge; |
| FeedContentStorage contentStorage = new FeedContentStorage(profile); |
| FeedJournalStorage journalStorage = new FeedJournalStorage(profile); |
| NetworkClient networkClient = sTestNetworkClient == null ? |
| new FeedNetworkBridge(profile) : sTestNetworkClient; |
| sFeedLoggingBridge = new FeedLoggingBridge(profile); |
| sFeedProcessScope = |
| new FeedProcessScope |
| .Builder(configHostApi, Executors.newSingleThreadExecutor(), |
| sFeedLoggingBridge, networkClient, schedulerBridge, |
| DebugBehavior.SILENT, ContextUtils.getApplicationContext(), |
| applicationInfo, new StubFeedTooltiSupportedApi()) |
| .setContentStorage(contentStorage) |
| .setJournalStorage(journalStorage) |
| .build(); |
| schedulerBridge.initializeFeedDependencies(sFeedProcessScope.getRequestManager()); |
| |
| sFeedOfflineIndicator = new FeedOfflineBridge(profile, sFeedProcessScope.getKnownContent()); |
| |
| sFeedAppLifecycle = new FeedAppLifecycle(sFeedProcessScope.getAppLifecycleListener(), |
| new FeedLifecycleBridge(profile), sFeedScheduler); |
| } |
| |
| /** |
| * Creates a {@link FeedProcessScope} using the provided host implementations. Call {@link |
| * #clearFeedProcessScopeForTesting()} to reset the FeedProcessScope after testing is complete. |
| * |
| * @param feedScheduler A {@link FeedScheduler} to use for testing. |
| * @param networkClient A {@link NetworkClient} to use for testing. |
| * @param feedOfflineIndicator A {@link FeedOfflineIndicator} to use for testing. |
| * @param feedAppLifecycle A {@link FeedAppLifecycle} to use for testing. |
| * @param loggingBridge A {@link FeedLoggingBridge} to use for testing. |
| */ |
| @VisibleForTesting |
| static void createFeedProcessScopeForTesting(FeedScheduler feedScheduler, |
| NetworkClient networkClient, FeedOfflineIndicator feedOfflineIndicator, |
| FeedAppLifecycle feedAppLifecycle, FeedLoggingBridge loggingBridge) { |
| Configuration configHostApi = FeedConfiguration.createConfiguration(); |
| |
| sFeedScheduler = feedScheduler; |
| sFeedLoggingBridge = loggingBridge; |
| sFeedOfflineIndicator = feedOfflineIndicator; |
| sFeedAppLifecycle = feedAppLifecycle; |
| ApplicationInfo applicationInfo = |
| new ApplicationInfo.Builder(ContextUtils.getApplicationContext()).build(); |
| |
| sFeedProcessScope = |
| new FeedProcessScope |
| .Builder(configHostApi, Executors.newSingleThreadExecutor(), |
| sFeedLoggingBridge, networkClient, sFeedScheduler, |
| DebugBehavior.SILENT, ContextUtils.getApplicationContext(), |
| applicationInfo, new StubFeedTooltiSupportedApi()) |
| .build(); |
| } |
| |
| /** Use supplied NetworkClient instead of real one, for tests. */ |
| @VisibleForTesting |
| public static void setTestNetworkClient(NetworkClient client) { |
| if (client == null) { |
| sTestNetworkClient = null; |
| } else if (sFeedProcessScope == null) { |
| sTestNetworkClient = client; |
| } else { |
| throw(new IllegalStateException( |
| "TestNetworkClient can not be set after FeedProcessScope has initialized.")); |
| } |
| } |
| |
| /** Resets the FeedProcessScope after testing is complete. */ |
| @VisibleForTesting |
| static void clearFeedProcessScopeForTesting() { |
| destroy(); |
| } |
| |
| /** |
| * @return Whether article suggestions are prepared to be shown based on user preference. If |
| * article suggestions are set hidden within a session, this will still return true |
| * until the next restart. |
| */ |
| static boolean areArticlesVisibleDuringSession() { |
| // Skip the native call if sArticlesVisibleDuringSession is already true to reduce overhead. |
| if (!sArticlesVisibleDuringSession |
| && PrefServiceBridge.getInstance().getBoolean(Pref.NTP_ARTICLES_LIST_VISIBLE)) { |
| sArticlesVisibleDuringSession = true; |
| } |
| |
| return sArticlesVisibleDuringSession; |
| } |
| |
| private static void articlesEnabledPrefChange() { |
| // Cannot assume this is called because of an actual change. May be going from true to true. |
| if (!PrefServiceBridge.getInstance().getBoolean(Pref.NTP_ARTICLES_SECTION_ENABLED)) { |
| // There have been quite a few crashes/bugs that happen when code does not correctly |
| // handle the scenario where Feed suddenly becomes disabled and the above getters start |
| // returning nulls. Having this log a warning helps diagnose this pattern from the |
| // logcat. |
| Log.w(TAG, "Disabling Feed because of policy."); |
| sEverDisabledForPolicy = true; |
| destroy(); |
| } |
| } |
| |
| /** Clears out all static state. */ |
| private static void destroy() { |
| if (sPrefChangeRegistrar != null) { |
| sPrefChangeRegistrar.removeObserver(Pref.NTP_ARTICLES_SECTION_ENABLED); |
| sPrefChangeRegistrar.destroy(); |
| sPrefChangeRegistrar = null; |
| } |
| if (sFeedProcessScope != null) { |
| sFeedProcessScope.onDestroy(); |
| sFeedProcessScope = null; |
| } |
| if (sFeedScheduler != null) { |
| sFeedScheduler.destroy(); |
| sFeedScheduler = null; |
| } |
| if (sFeedOfflineIndicator != null) { |
| sFeedOfflineIndicator.destroy(); |
| sFeedOfflineIndicator = null; |
| } |
| if (sFeedAppLifecycle != null) { |
| sFeedAppLifecycle.destroy(); |
| sFeedAppLifecycle = null; |
| } |
| if (sFeedLoggingBridge != null) { |
| sFeedLoggingBridge.destroy(); |
| sFeedLoggingBridge = null; |
| } |
| } |
| |
| private static class StubFeedTooltiSupportedApi implements TooltipSupportedApi { |
| @Override |
| public void wouldTriggerHelpUi(String featureName, Consumer<Boolean> consumer) {} |
| } |
| } |