blob: 21fde01c6580323a2fcdb48a457d639b42d3099f [file] [log] [blame]
// Copyright 2020 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.
#include "chrome/browser/updates/announcement_notification/announcement_notification_service.h"
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/field_trial_params.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/clock.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile_attributes_entry.h"
#include "chrome/browser/profiles/profile_attributes_storage.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/grit/generated_resources.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "ui/base/l10n/l10n_util.h"
namespace {
// Default value for Finch parameter |kVersion|.
const int kInvalidVersion = -1;
// Whether the announcement notification is shown.
static bool notification_shown = false;
} // namespace
class AnnouncementNotificationServiceImpl
: public AnnouncementNotificationService {
public:
AnnouncementNotificationServiceImpl(Profile* profile,
PrefService* pref_service,
std::unique_ptr<Delegate> delegate,
base::Clock* clock)
: profile_(profile),
pref_service_(pref_service),
delegate_(std::move(delegate)),
clock_(clock),
skip_first_run_(true),
skip_new_profile_(false),
skip_display_(false),
remote_version_(kInvalidVersion),
require_signout_(false),
show_one_(false) {
DCHECK(delegate_);
DCHECK(profile_);
if (!IsFeatureEnabled())
return;
// By default do nothing in first run.
skip_first_run_ = base::GetFieldTrialParamByFeatureAsBool(
kAnnouncementNotification, kSkipFirstRun, true);
skip_new_profile_ = base::GetFieldTrialParamByFeatureAsBool(
kAnnouncementNotification, kSkipNewProfile, false);
skip_display_ = base::GetFieldTrialParamByFeatureAsBool(
kAnnouncementNotification, kSkipDisplay, false);
remote_version_ = base::GetFieldTrialParamByFeatureAsInt(
kAnnouncementNotification, kVersion, kInvalidVersion);
require_signout_ = base::GetFieldTrialParamByFeatureAsBool(
kAnnouncementNotification, kRequireSignout, false);
show_one_ = base::GetFieldTrialParamByFeatureAsBool(
kAnnouncementNotification, kShowOneAllProfiles, false);
remote_url_ = base::GetFieldTrialParamValueByFeature(
kAnnouncementNotification, kAnnouncementUrl);
bool success = base::Time::FromUTCString(
base::GetFieldTrialParamValueByFeature(kAnnouncementNotification,
kSkipFirstRunAfterTime)
.c_str(),
&skip_first_run_after_);
if (!success)
skip_first_run_after_ = base::Time();
}
~AnnouncementNotificationServiceImpl() override = default;
private:
// AnnouncementNotificationService implementation.
void MaybeShowNotification() override {
// Finch config may not be delivered on first run. Records the first run
// timestamp to check whether we want to skip the notification.
RecordFirstRunIfNeeded();
if (!IsFeatureEnabled())
return;
// No valid version Finch parameter.
if (!IsVersionValid(remote_version_))
return;
// Update the version preference. This happens earilier than checks in
// ShowNotification.
int current_version = pref_service_->GetInteger(kCurrentVersionPrefName);
pref_service_->SetInteger(kCurrentVersionPrefName, remote_version_);
if (remote_version_ > current_version) {
OnNewVersion();
}
}
void RecordFirstRunIfNeeded() {
if (!delegate_->IsFirstRun())
return;
pref_service_->SetTime(kAnnouncementFirstRunTimePrefName, clock_->Now());
}
bool IsFeatureEnabled() const {
return base::FeatureList::IsEnabled(kAnnouncementNotification);
}
bool IsVersionValid(int version) const { return version >= 0; }
void OnNewVersion() {
if (ShouldSkipDueToFirstRun())
return;
if (skip_display_)
return;
// Skip new profile if needed.
if (skip_new_profile_ && profile_->IsNewProfile())
return;
// Require signed out but the user signed in.
if (require_signout_ && IsUserSignIn())
return;
// Check if we only want to show once.
if (show_one_ && notification_shown)
return;
// Check profile type, some types can't create new navigation.
if (!CanOpenAnnouncement(profile_))
return;
ShowNotification();
}
void ShowNotification() {
notification_shown = true;
delegate_->ShowNotification();
}
// Returns whether the notification should be skipped based on first run
// controls defined in Finch.
bool ShouldSkipDueToFirstRun() const {
// Don't show notification for first run if Finch parameter specified
// "skip_first_run" to true.
if (delegate_->IsFirstRun() && skip_first_run_)
return true;
// Finch parameters is not guaranteed to receive by Chrome on first run.
// Don't show notification if first run happens after the timestamp
// specified in Finch parameter "skip_first_run_after_time".
base::Time first_run_time =
pref_service_->GetTime(kAnnouncementFirstRunTimePrefName);
if (!first_run_time.is_null() && !skip_first_run_after_.is_null() &&
first_run_time >= skip_first_run_after_) {
return true;
}
// Notice that the first run can happen before
// kAnnouncementFirstRunTimePrefName was added to the code base . In this
// case, we do want to show the announcement.
return false;
}
bool IsUserSignIn() {
DCHECK(g_browser_process);
// No browser process, assume the user is not signed in.
if (!g_browser_process)
return false;
ProfileAttributesStorage& storage =
g_browser_process->profile_manager()->GetProfileAttributesStorage();
// Can't find the profile path, assume the user is not signed in.
DCHECK(profile_);
ProfileAttributesEntry* entry = nullptr;
if (!storage.GetProfileAttributesWithPath(profile_->GetPath(), &entry))
return false;
return entry->GetSigninState() != SigninState::kNotSignedIn;
}
Profile* profile_;
PrefService* pref_service_;
std::unique_ptr<Delegate> delegate_;
base::Clock* clock_;
// Whether to skip first Chrome launch. Parsed from Finch.
bool skip_first_run_;
// Whether to skip new profile. Parsed from Finch.
bool skip_new_profile_;
// Don't show notification if true. Parsed from Finch.
bool skip_display_;
// The new notification version. Parsed from Finch.
int remote_version_;
// Whether only show notification to signed out users. Parsed from Finch.
bool require_signout_;
// Whether only show one notification in each Chrome launch. Parsed from
// Finch.
bool show_one_;
// The announcement URL parsed from Finch. If empty then we use the default
// URL.
std::string remote_url_;
// If first run happens after this time, then notification should not show.
base::Time skip_first_run_after_;
base::WeakPtrFactory<AnnouncementNotificationServiceImpl> weak_ptr_factory_{
this};
DISALLOW_COPY_AND_ASSIGN(AnnouncementNotificationServiceImpl);
};
const base::Feature kAnnouncementNotification{
"AnnouncementNotificationService", base::FEATURE_DISABLED_BY_DEFAULT};
// static
void AnnouncementNotificationService::RegisterProfilePrefs(
PrefRegistrySimple* registry) {
DCHECK(registry);
registry->RegisterIntegerPref(kCurrentVersionPrefName, kInvalidVersion);
registry->RegisterTimePref(kAnnouncementFirstRunTimePrefName, base::Time());
}
// static
AnnouncementNotificationService* AnnouncementNotificationService::Create(
Profile* profile,
PrefService* pref_service,
std::unique_ptr<Delegate> delegate,
base::Clock* clock) {
return new AnnouncementNotificationServiceImpl(profile, pref_service,
std::move(delegate), clock);
}
// static
GURL AnnouncementNotificationService::GetAnnouncementURL() {
std::string remote_url = base::GetFieldTrialParamValueByFeature(
kAnnouncementNotification, kAnnouncementUrl);
// Fallback to default URL if |remote_url| is empty.
std::string url = remote_url.empty()
? l10n_util::GetStringUTF8(IDS_TOS_NOTIFICATION_LINK)
: remote_url;
return GURL(url);
}
// static
bool AnnouncementNotificationService::CanOpenAnnouncement(Profile* profile) {
DCHECK(profile);
if (!profile)
return false;
return !(profile->IsGuestSession() || profile->IsEphemeralGuestProfile() ||
profile->IsSystemProfile());
}
AnnouncementNotificationService::AnnouncementNotificationService() = default;
AnnouncementNotificationService::~AnnouncementNotificationService() = default;