#include "ash/shelf/launcher_nudge_controller.h"
#include <memory>
#include "ash/app_list/app_list_controller_impl.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/session/session_types.h"
#include "ash/root_window_controller.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shelf/home_button.h"
#include "ash/shelf/home_button_controller.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_navigation_widget.h"
#include "ash/shell.h"
#include "base/command_line.h"
#include "base/json/values_util.h"
#include "base/time/time.h"
#include "base/timer/wall_clock_timer.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "ui/display/tablet_state.h"
#include "ui/gfx/scoped_animation_duration_scale_mode.h"
namespace ash {
namespace {
constexpr char kShownCount[] = "shown_count";
constexpr char kLastShownTime[] = "last_shown_time";
constexpr char kFirstLoginTime[] = "first_login_time";
constexpr char kWasLauncherShown[] = "was_launcher_shown";
constexpr base::TimeDelta kFirstTimeShowNudgeInterval = base::Days(1);
constexpr base::TimeDelta kShowNudgeInterval = base::Days(1);
constexpr base::TimeDelta kFirstTimeShowNudgeIntervalForTest = base::Minutes(3);
constexpr base::TimeDelta kShowNudgeIntervalForTest = base::Minutes(3);
PrefService* GetPrefs() {
return Shell::Get()->session_controller()->GetLastActiveUserPrefService();
}
base::Time GetLastShownTime(PrefService* prefs) {
const base::Value::Dict& dictionary =
prefs->GetDict(prefs::kShelfLauncherNudge);
std::optional<base::Time> last_shown_time =
base::ValueToTime(dictionary.Find(kLastShownTime));
return last_shown_time.value_or(base::Time());
}
base::Time GetFirstLoginTime(PrefService* prefs) {
const base::Value::Dict& dictionary =
prefs->GetDict(prefs::kShelfLauncherNudge);
std::optional<base::Time> first_login_time =
base::ValueToTime(dictionary.Find(kFirstLoginTime));
return first_login_time.value_or(base::Time());
}
bool WasLauncherShownPreviously(PrefService* prefs) {
const base::Value::Dict& dictionary =
prefs->GetDict(prefs::kShelfLauncherNudge);
return dictionary.FindBool(kWasLauncherShown).value_or(false);
}
}
constexpr base::TimeDelta
LauncherNudgeController::kMinIntervalAfterHomeButtonAppears;
LauncherNudgeController::LauncherNudgeController()
: show_nudge_timer_(std::make_unique<base::WallClockTimer>()) {
Shell::Get()->app_list_controller()->AddObserver(this);
}
LauncherNudgeController::~LauncherNudgeController() {
if (Shell::Get()->app_list_controller())
Shell::Get()->app_list_controller()->RemoveObserver(this);
}
void LauncherNudgeController::RegisterProfilePrefs(
PrefRegistrySimple* registry) {
registry->RegisterDictionaryPref(prefs::kShelfLauncherNudge);
}
HomeButton* LauncherNudgeController::GetHomeButtonForDisplay(
int64_t display_id) {
return Shell::Get()
->GetRootWindowControllerWithDisplayId(display_id)
->shelf()
->navigation_widget()
->GetHomeButton();
}
int LauncherNudgeController::GetShownCount(PrefService* prefs) {
const base::Value::Dict& dictionary =
prefs->GetDict(prefs::kShelfLauncherNudge);
return dictionary.FindInt(kShownCount).value_or(0);
}
base::TimeDelta LauncherNudgeController::GetNudgeInterval(
bool is_first_time) const {
if (features::IsLauncherNudgeShortIntervalEnabled()) {
return is_first_time ? kFirstTimeShowNudgeIntervalForTest
: kShowNudgeIntervalForTest;
}
return is_first_time ? kFirstTimeShowNudgeInterval : kShowNudgeInterval;
}
void LauncherNudgeController::SetClockForTesting(
const base::Clock* clock,
const base::TickClock* timer_clock) {
DCHECK(!show_nudge_timer_->IsRunning());
show_nudge_timer_ =
std::make_unique<base::WallClockTimer>(clock, timer_clock);
clock_for_test_ = clock;
}
bool LauncherNudgeController::IsRecheckTimerRunningForTesting() {
return show_nudge_timer_->IsRunning();
}
bool LauncherNudgeController::ShouldShowNudge(base::Time& recheck_time) const {
PrefService* prefs = GetPrefs();
if (!prefs)
return false;
if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshNoNudges))
return false;
if (GetFirstLoginTime(prefs).is_null()) {
return false;
}
if (display::Screen::Get()->InTabletMode()) {
return false;
}
if (GetShownCount(prefs) >= kMaxShownCount ||
WasLauncherShownPreviously(prefs)) {
return false;
}
base::Time last_shown_time;
base::TimeDelta interval;
if (GetShownCount(prefs) == 0) {
last_shown_time = GetFirstLoginTime(prefs);
interval = GetNudgeInterval(true);
} else {
last_shown_time = GetLastShownTime(prefs);
interval = GetNudgeInterval(false);
}
DCHECK(!last_shown_time.is_null());
base::Time expect_shown_time =
std::max(last_shown_time + interval, earliest_available_time_);
if (GetNow() < expect_shown_time) {
recheck_time = expect_shown_time;
return false;
}
return true;
}
void LauncherNudgeController::HandleNudgeShown() {
PrefService* prefs = GetPrefs();
if (!prefs)
return;
const int shown_count = GetShownCount(prefs);
ScopedDictPrefUpdate update(prefs, prefs::kShelfLauncherNudge);
update->Set(kShownCount, shown_count + 1);
update->Set(kLastShownTime, base::TimeToValue(GetNow()));
}
void LauncherNudgeController::MaybeShowNudge() {
if (!features::IsShelfLauncherNudgeEnabled())
return;
base::Time recheck_time;
if (!ShouldShowNudge(recheck_time)) {
if (!recheck_time.is_null())
ScheduleShowNudgeAttempt(recheck_time);
return;
}
if (gfx::ScopedAnimationDurationScaleMode::duration_multiplier() != 0) {
int64_t display_id_for_nudge =
Shell::Get()->cursor_manager()->GetDisplay().id();
HomeButton* home_button = GetHomeButtonForDisplay(display_id_for_nudge);
home_button->StartNudgeAnimation();
HandleNudgeShown();
}
PrefService* prefs = GetPrefs();
if (GetShownCount(prefs) < kMaxShownCount) {
ScheduleShowNudgeAttempt(GetLastShownTime(prefs) +
GetNudgeInterval(false));
}
}
void LauncherNudgeController::ScheduleShowNudgeAttempt(
base::Time recheck_time) {
show_nudge_timer_->Start(
FROM_HERE, recheck_time,
base::BindOnce(&LauncherNudgeController::MaybeShowNudge,
weak_ptr_factory_.GetWeakPtr()));
}
void LauncherNudgeController::OnActiveUserPrefServiceChanged(
PrefService* prefs) {
if (Shell::Get()
->session_controller()
->GetUserSession(0)
->user_info.is_ephemeral) {
return;
}
if (Shell::Get()->session_controller()->IsUserFirstLogin()) {
ScopedDictPrefUpdate update(prefs, prefs::kShelfLauncherNudge);
update->Set(kFirstLoginTime, base::TimeToValue(GetNow()));
} else if (GetFirstLoginTime(prefs).is_null()) {
return;
}
earliest_available_time_ = GetNow() + kMinIntervalAfterHomeButtonAppears;
MaybeShowNudge();
}
void LauncherNudgeController::OnAppListVisibilityChanged(bool shown,
int64_t display_id) {
PrefService* prefs = GetPrefs();
if (!prefs)
return;
if (display::Screen::Get()->InTabletMode()) {
return;
}
if (!WasLauncherShownPreviously(prefs) && shown) {
ScopedDictPrefUpdate update(prefs, prefs::kShelfLauncherNudge);
update->Set(kWasLauncherShown, true);
}
}
void LauncherNudgeController::OnDisplayTabletStateChanged(
display::TabletState state) {
if (state != display::TabletState::kInClamshellMode) {
return;
}
earliest_available_time_ = GetNow() + kMinIntervalAfterHomeButtonAppears;
MaybeShowNudge();
}
base::Time LauncherNudgeController::GetNow() const {
if (clock_for_test_)
return clock_for_test_->Now();
return base::Time::Now();
}
}