blob: e26b6bb33309242d45f1b7e52229ad57632c1577 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_UI_STARTUP_FIRST_RUN_SERVICE_H_
#define CHROME_BROWSER_UI_STARTUP_FIRST_RUN_SERVICE_H_
#include <memory>
#include "base/functional/callback_forward.h"
#include "base/metrics/field_trial.h"
#include "base/no_destructor.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/profiles/profile_keyed_service_factory.h"
#include "chrome/browser/ui/profile_picker.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/signin/public/base/signin_buildflags.h"
class PrefRegistrySimple;
class Profile;
class SilentSyncEnabler;
class ProfileNameResolver;
namespace base {
class FeatureList;
}
namespace version_info {
enum class Channel;
}
// Task to run after the FRE is exited, with `proceed` indicating whether it
// should be aborted or resumed.
using ResumeTaskCallback = base::OnceCallback<void(bool proceed)>;
// Service handling the First Run Experience for the primary profile on Lacros.
// It is not available on the other profiles.
class FirstRunService : public KeyedService {
public:
static constexpr char kSyntheticTrialName[] = "ForYouFreSynthetic";
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class EntryPoint {
// Indicates misc, undifferentiated entry points to the FRE that we don't
// particularly worry about. If we have a concern about a specific entry
// point, we should register a dedicated value for it to track how often it
// gets triggered.
kOther = 0,
kProcessStartup = 1,
kWebAppLaunch = 2,
kWebAppContextMenu = 3,
kMaxValue = kWebAppContextMenu
};
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class FinishedReason {
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
kExperimentCounterfactual = 0,
#endif
kFinishedFlow = 1,
kProfileAlreadySetUp = 2,
kSkippedByPolicies = 3,
kMaxValue = kSkippedByPolicies,
};
static void RegisterLocalStatePrefs(PrefRegistrySimple* registry);
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
// Creates a field trial to control the ForYouFre and
// ForYouFreSyntheticTrialRegistration features. The trial is client
// controlled because ForYouFre controls the First Run Experience (FRE), which
// shows up before a variations seed is available.
//
// No persistence happens here, instead it happens if/when the attempt to show
// the FRE happens, and the client joins an experiment cohort through
// `JoinFirstRunCohort()`.
static void SetUpClientSideFieldTrialIfNeeded(
const base::FieldTrial::EntropyProvider& entropy_provider,
base::FeatureList* feature_list);
// Ensures that the user's experiment group is appropriately reported
// to track the effect of the first run experience over time. Should be called
// once per browser process startup.
static void EnsureStickToFirstRunCohort();
#endif
explicit FirstRunService(Profile* profile);
~FirstRunService() override;
// Runs `::ShouldOpenFirstRun(Profile*)` with the profile associated with this
// service instance.
bool ShouldOpenFirstRun() const;
// This function takes the user through the browser FRE.
// 1) First, it checks whether the FRE flow can be skipped in the first place.
// This is the case when sync consent is already given (true for existing
// users that migrated to lacros) or when enterprise policies forbid the
// FRE. If so, the call directly 'finishes' the flow (see below).
// 2) Then, it opens the FRE UI (in the profile picker window) and
// asynchronously 'finishes' the flow (sets a flag in the local prefs) once
// the user chooses any action on the sync consent screen. If the user
// exits the FRE UI via the generic 'Close window' affordances, it is
// interpreted as an intent to exit the app and `callback` will be called
// with `proceed` set to false. If they exit it via the dedicated options
// in the flow, it will be considered 'completed' and `callback` will be
// run with `proceed` set to true. If the FRE flow is exited before the
// sync consent screen, the flow is considered 'aborted', and can be shown
// again at the next startup.
// When this method is called again while FRE is in progress, the previous
// callback is aborted (called with false), and is replaced by `callback`.
void OpenFirstRunIfNeeded(EntryPoint entry_point,
ResumeTaskCallback callback);
// Terminates the first run without re-opening a browser window.
void FinishFirstRunWithoutResumeTask();
private:
friend class FirstRunServiceFactory;
FRIEND_TEST_ALL_PREFIXES(FirstRunFieldTrialCreatorTest, SetUpFromClientSide);
FRIEND_TEST_ALL_PREFIXES(FirstRunCohortSetupTest, JoinFirstRunCohort);
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
// Internal interface for `SetUpClientSideFieldTrialIfNeeded()`, exposed to
// allow for channel-independent testing.
static void SetUpClientSideFieldTrial(
const base::FieldTrial::EntropyProvider& entropy_provider,
base::FeatureList* feature_list,
version_info::Channel channel);
// Enrolls this client with a synthetic field trial based on the Finch params.
// Should be called when the FRE is launched, then the client needs to
// register again on each process startup by calling
// `RegisterSyntheticFieldTrial()`.
static void JoinFirstRunCohort();
// Reports to the launch study for the First Run rollout.
// Notes:
// - This is declared here so it can have access to some private functions
// that need to be friended to be used.
// - The function is Dice-only as on Lacros (where this build flag is not set)
// the ForYouFre feature rollout will not go through this study process. The
// feature only guards an internal refactoring that does not have a
// user-visible effect. If will only have a killswitch.
static void RegisterSyntheticFieldTrial(const std::string& group_name);
#endif
// Asynchronously attempts to complete the first run silently.
// By the time `callback` is run (if non-null), either:
// - the first run has been marked finished because it can't be run for this
// profile (e.g. policies) or because we want to enable Sync silently (on
// Lacros only)
// - the first run is ready to be opened.
// The finished state can be checked by calling `ShouldOpenFirstRun()`.
void TryMarkFirstRunAlreadyFinished(base::OnceClosure callback);
void OpenFirstRunInternal(EntryPoint entry_point);
// Processes the outcome from the FRE and resumes the user's interrupted task.
void OnFirstRunHasExited(ProfilePicker::FirstRunExitStatus status);
// Marks the first run as finished and updates the profile entry based on
// the info obtained during the first run.
// Noting that the latter part is done by calling `FinishProfileSetUp()`,
// which will be done asynchronously in most cases.
void FinishFirstRun(FinishedReason reason);
void FinishProfileSetUp(std::u16string profile_name);
#if BUILDFLAG(IS_CHROMEOS_LACROS)
void StartSilentSync(base::OnceClosure callback);
void ClearSilentSyncEnabler();
#endif
// Owns of this instance via the KeyedService mechanism.
const raw_ptr<Profile> profile_;
#if BUILDFLAG(IS_CHROMEOS_LACROS)
std::unique_ptr<SilentSyncEnabler> silent_sync_enabler_;
#endif
std::unique_ptr<ProfileNameResolver> profile_name_resolver_;
ResumeTaskCallback resume_task_callback_;
base::WeakPtrFactory<FirstRunService> weak_ptr_factory_{this};
};
class FirstRunServiceFactory : public ProfileKeyedServiceFactory {
public:
FirstRunServiceFactory(const FirstRunServiceFactory&) = delete;
FirstRunServiceFactory& operator=(const FirstRunServiceFactory&) = delete;
static FirstRunService* GetForBrowserContext(
content::BrowserContext* context);
static FirstRunService* GetForBrowserContextIfExists(
content::BrowserContext* context);
static FirstRunServiceFactory* GetInstance();
private:
friend class base::NoDestructor<FirstRunServiceFactory>;
friend class FirstRunServiceBrowserTestBase;
FirstRunServiceFactory();
~FirstRunServiceFactory() override;
KeyedService* BuildServiceInstanceFor(
content::BrowserContext* context) const override;
bool ServiceIsCreatedWithBrowserContext() const override;
};
// Returns whether the first run experience (including sync promo) might be
// opened for `profile`. It should be checked before
// `FirstRunService::OpenFirstRunIfNeeded()` is called.
//
// Even if this method returns `true`, the FRE can still be skipped if for
// example the feature is disabled, a policy suppresses it, etc.
bool ShouldOpenFirstRun(Profile* profile);
#endif // CHROME_BROWSER_UI_STARTUP_FIRST_RUN_SERVICE_H_