blob: 4fd7122b372dbcb19b5c6961c870d5b88901a566 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/controls/contextual_tooltip.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/shelf_config.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "base/json/values_util.h"
#include "base/no_destructor.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "components/prefs/scoped_user_pref_update.h"
namespace ash {
namespace contextual_tooltip {
namespace {
// Keys for tooltip sub-preferences for shown count and last time shown.
constexpr char kShownCount[] = "shown_count";
constexpr char kLastTimeShown[] = "last_time_shown";
// Keys for tooltip sub-preferences of how many times a gesture has been
// successfully performed by the user.
constexpr char kSuccessCount[] = "success_count";
// Whether the drag handle nudge cannot be shown because the shelf is currently
// hidden - used to unblock showing back gesture when shelf is hidden (the back
// gesture will normally show only if the drag handle nudge has already been
// shown within the last nudge show interval).
bool g_drag_handle_nudge_disabled_for_hidden_shelf = false;
// Whether the back gesture nudge is currently being shown.
bool g_back_gesture_nudge_showing = false;
base::Clock* g_clock_override = nullptr;
base::Time GetTime() {
if (g_clock_override)
return g_clock_override->Now();
return base::Time::Now();
}
std::string TooltipTypeToString(TooltipType type) {
switch (type) {
case TooltipType::kBackGesture:
return "back_gesture";
case TooltipType::kHomeToOverview:
return "home_to_overview";
case TooltipType::kInAppToHome:
return "in_app_to_home";
case TooltipType::kKeyboardBacklightColor:
return "keyboard_backlight_color";
case TooltipType::kKeyboardBacklightWallpaperColor:
return "keyboard_backlight_wallpaper_color";
case TooltipType::kTimeOfDayFeatureBanner:
return "time_of_day_feature_banner";
case TooltipType::kTimeOfDayWallpaperDialog:
return "time_of_day_wallpaper_dialog";
case TooltipType::kSeaPenWallpaperTermsDialog:
return "sea_pen_wallpaper_terms_dialog";
}
return "invalid";
}
// Creates the path to the dictionary value from the contextual tooltip type and
// the sub-preference.
std::string GetPath(TooltipType type, const std::string& sub_pref) {
return base::JoinString({TooltipTypeToString(type), sub_pref}, ".");
}
base::Time GetLastShownTime(PrefService* prefs, TooltipType type) {
const base::Value* last_shown_time =
prefs->GetDict(prefs::kContextualTooltips)
.FindByDottedPath(GetPath(type, kLastTimeShown));
if (!last_shown_time)
return base::Time();
return *base::ValueToTime(last_shown_time);
}
int GetSuccessCount(PrefService* prefs, TooltipType type) {
std::optional<int> success_count =
prefs->GetDict(prefs::kContextualTooltips)
.FindIntByDottedPath(GetPath(type, kSuccessCount));
return success_count.value_or(0);
}
const std::optional<base::TimeDelta>& GetMinIntervalOverride() {
// Overridden minimum time between showing contextual nudges to the user.
static std::optional<base::TimeDelta> min_interval_override;
if (!min_interval_override) {
min_interval_override = switches::ContextualNudgesInterval();
}
return min_interval_override;
}
} // namespace
void RegisterProfilePrefs(PrefRegistrySimple* registry) {
if (features::IsHideShelfControlsInTabletModeEnabled()) {
registry->RegisterDictionaryPref(prefs::kContextualTooltips);
}
}
bool ShouldShowNudge(PrefService* prefs,
TooltipType type,
base::TimeDelta* recheck_delay) {
auto set_recheck_delay = [&recheck_delay](base::TimeDelta delay) {
if (recheck_delay)
*recheck_delay = delay;
};
if (!features::IsHideShelfControlsInTabletModeEnabled()) {
set_recheck_delay(base::TimeDelta());
return false;
}
if (type == TooltipType::kInAppToHome &&
g_drag_handle_nudge_disabled_for_hidden_shelf) {
set_recheck_delay(base::TimeDelta());
return false;
}
const int success_count = GetSuccessCount(prefs, type);
if ((type == TooltipType::kHomeToOverview &&
success_count >= kSuccessLimitHomeToOverview) ||
(type == TooltipType::kBackGesture &&
success_count >= kSuccessLimitBackGesture) ||
(type == TooltipType::kInAppToHome &&
success_count >= kSuccessLimitInAppToHome) ||
(type == TooltipType::kKeyboardBacklightColor &&
success_count >= kSuccessLimitKeyboardBacklightColor) ||
(type == TooltipType::kTimeOfDayFeatureBanner &&
success_count >= kSuccessLimitTimeOfDayFeatureBanner) ||
(type == TooltipType::kTimeOfDayWallpaperDialog &&
success_count >= kSuccessLimitTimeOfDayWallpaperDialog) ||
(type == TooltipType::kSeaPenWallpaperTermsDialog &&
success_count >= kSuccessLimitSeaPenWallpaperTermsDialog)) {
set_recheck_delay(base::TimeDelta());
return false;
}
const int shown_count = GetShownCount(prefs, type);
if (shown_count >= kNotificationLimit) {
set_recheck_delay(base::TimeDelta());
return false;
}
// Before showing back gesture nudge, do not show it if in-app to shelf nudge
// should be shown (to prevent two nudges from showing up at the same time).
// Verify that the in-app to home nudge was shown within the last show
// interval.
if (type == TooltipType::kBackGesture) {
if (!g_drag_handle_nudge_disabled_for_hidden_shelf &&
ShouldShowNudge(prefs, TooltipType::kInAppToHome, nullptr)) {
set_recheck_delay(kMinIntervalBetweenBackAndDragHandleNudge);
return false;
}
// Verify that drag handle nudge has been shown at least a minute ago.
const base::Time drag_handle_nudge_last_shown_time =
GetLastShownTime(prefs, TooltipType::kInAppToHome);
if (!drag_handle_nudge_last_shown_time.is_null()) {
const base::TimeDelta time_since_drag_handle_nudge =
GetTime() - drag_handle_nudge_last_shown_time;
if (time_since_drag_handle_nudge <
kMinIntervalBetweenBackAndDragHandleNudge) {
set_recheck_delay(kMinIntervalBetweenBackAndDragHandleNudge -
time_since_drag_handle_nudge);
return false;
}
}
}
// Make sure that drag handle nudge is not shown within a minute of back
// gesture nudge.
if (type == TooltipType::kInAppToHome) {
if (g_back_gesture_nudge_showing) {
set_recheck_delay(kMinIntervalBetweenBackAndDragHandleNudge);
return false;
}
const base::Time back_nudge_last_shown_time =
GetLastShownTime(prefs, TooltipType::kBackGesture);
if (!back_nudge_last_shown_time.is_null()) {
const base::TimeDelta time_since_back_nudge =
GetTime() - back_nudge_last_shown_time;
if (time_since_back_nudge < kMinIntervalBetweenBackAndDragHandleNudge) {
set_recheck_delay(kMinIntervalBetweenBackAndDragHandleNudge -
time_since_back_nudge);
return false;
}
}
}
if (shown_count == 0)
return true;
const base::Time last_shown_time = GetLastShownTime(prefs, type);
const base::TimeDelta min_interval =
GetMinIntervalOverride().value_or(kMinInterval);
const base::TimeDelta time_since_last_nudge = GetTime() - last_shown_time;
if (time_since_last_nudge < min_interval) {
set_recheck_delay(min_interval - time_since_last_nudge);
return false;
}
return true;
}
base::TimeDelta GetNudgeTimeout(PrefService* prefs, TooltipType type) {
const int shown_count = GetShownCount(prefs, type);
if (shown_count == 0)
return base::TimeDelta();
return kNudgeShowDuration;
}
int GetShownCount(PrefService* prefs, TooltipType type) {
std::optional<int> shown_count =
prefs->GetDict(prefs::kContextualTooltips)
.FindIntByDottedPath(GetPath(type, kShownCount));
return shown_count.value_or(0);
}
void HandleNudgeShown(PrefService* prefs, TooltipType type) {
const int shown_count = GetShownCount(prefs, type);
ScopedDictPrefUpdate update(prefs, prefs::kContextualTooltips);
update->SetByDottedPath(GetPath(type, kShownCount), shown_count + 1);
update->SetByDottedPath(GetPath(type, kLastTimeShown),
base::TimeToValue(GetTime()));
}
void HandleGesturePerformed(PrefService* prefs, TooltipType type) {
const int success_count = GetSuccessCount(prefs, type);
ScopedDictPrefUpdate update(prefs, prefs::kContextualTooltips);
update->SetByDottedPath(GetPath(type, kSuccessCount), success_count + 1);
}
void SetDragHandleNudgeDisabledForHiddenShelf(bool nudge_disabled) {
g_drag_handle_nudge_disabled_for_hidden_shelf = nudge_disabled;
}
void SetBackGestureNudgeShowing(bool showing) {
g_back_gesture_nudge_showing = showing;
}
void ClearPrefs() {
DCHECK(Shell::Get()->session_controller()->GetLastActiveUserPrefService());
ScopedDictPrefUpdate update(
Shell::Get()->session_controller()->GetLastActiveUserPrefService(),
prefs::kContextualTooltips);
base::Value::Dict& nudges_dict = update.Get();
if (!nudges_dict.empty())
nudges_dict.clear();
}
void OverrideClockForTesting(base::Clock* test_clock) {
DCHECK(!g_clock_override);
g_clock_override = test_clock;
}
void ClearClockOverrideForTesting() {
DCHECK(g_clock_override);
g_clock_override = nullptr;
}
} // namespace contextual_tooltip
} // namespace ash