[go: nahoru, domu]

blob: 7a768d8e35513b7d30630dbbceba33c968dc137a [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 "ash/wm/desks/desks_restore_util.h"
#include <tuple>
#include "ash/constants/ash_pref_names.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/wm/desks/desk.h"
#include "ash/wm/desks/desks_controller.h"
#include "ash/wm/desks/desks_histogram_enums.h"
#include "ash/wm/desks/desks_util.h"
#include "base/auto_reset.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/clock.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
namespace ash {
namespace desks_restore_util {
namespace {
// |kDesksMetricsList| stores a list of dictionaries with the following
// key value pairs (<key> : <entry>):
// |kCreationTimeKey| : an int which represents the number of minutes for
// base::Time::FromDeltaSinceWindowsEpoch().
// |kFirstDayVisitedKey| : an int which represents the number of days since
// local epoch (Jan 1, 2010).
// |kLastDayVisitedKey| : an int which represents the number of days since
// local epoch (Jan 1, 2010).
// |kInteractedWithThisWeekKey| : a boolean tracking whether this desk has been
// interacted with in the last week.
constexpr char kCreationTimeKey[] = "creation_time";
constexpr char kFirstDayVisitedKey[] = "first_day";
constexpr char kLastDayVisitedKey[] = "last_day";
constexpr char kInteractedWithThisWeekKey[] = "interacted_week";
// |kDesksWeeklyActiveDesksMetrics| stores a dictionary with the following key
// value pairs (<key> : <entry>):
// |kWeeklyActiveDesksKey| : an int representing the number of weekly active
// desks.
// |kReportTimeKey| : an int respresenting the time a user's weekly active desks
// metric is scheduled to go off at. The value is the time left on the
// scheduler + the user's current time stored as the number of minutes for
// base::Time::FromDeltaSinceWindowsEpoch().
constexpr char kWeeklyActiveDesksKey[] = "weekly_active_desks";
constexpr char kReportTimeKey[] = "report_time";
// A boolean pref that indicates whether the user has used desks recently.
// A user has `used` desks means that there are desks added, removed or renamed
// by the user. `Recently` means the `used` action happens between 07/27/2021
// and 09/07/2021. Only the users that used desks in this period of time will be
// included in the experiment of bento bar and overview button. Note, this pref
// will not be set to false once it has been set to true. But this perf could be
// removed after the experiment.
constexpr char kUserHasUsedDesksRecently[] = "ash.user_has_used_desks_recently";
// While restore is in progress, changes are being made to the desks and their
// names. Those changes should not trigger an update to the prefs.
bool g_pause_desks_prefs_updates = false;
// A clock that can be overridden by tests. This is a global variable, reset it
// to nullptr when overridden is not needed anymore.
base::Clock* g_override_clock_ = nullptr;
PrefService* GetPrimaryUserPrefService() {
return Shell::Get()->session_controller()->GetPrimaryUserPrefService();
}
// Check if the desk index is valid against a list of existing desks in
// DesksController.
bool IsValidDeskIndex(int desk_index) {
return desk_index >= 0 &&
desk_index <
static_cast<int>(DesksController::Get()->desks().size()) &&
desk_index < int{desks_util::kMaxNumberOfDesks};
}
base::Time GetTime(int year, int month, int day_of_month, int day_of_week) {
base::Time::Exploded time_exploded;
time_exploded.year = year;
time_exploded.month = month;
time_exploded.day_of_week = day_of_week;
time_exploded.day_of_month = day_of_month;
time_exploded.hour = 0;
time_exploded.minute = 0;
time_exploded.second = 0;
time_exploded.millisecond = 0;
base::Time time;
const bool result = base::Time::FromLocalExploded(time_exploded, &time);
DCHECK(result);
return time;
}
// Check if GetTimeNow() is during the time period 07/27/2021 to 09/07/2021 (not
// included).
bool IsNowInValidTimePeriod() {
base::Time now = GetTimeNow();
return now <= GetTime(2021, 9, 7, /*Tuesday=*/2) &&
now >= GetTime(2021, 7, 27, /*Tuesday=*/2);
}
// Returns Jan 1, 2010 00:00:00 as a base::Time object in the local timezone.
base::Time GetLocalEpoch() {
static const base::Time local_epoch = [] {
base::Time local_epoch;
std::ignore = base::Time::FromLocalExploded({2010, 1, 5, 1, 0, 0, 0, 0},
&local_epoch);
return local_epoch;
}();
return local_epoch;
}
} // namespace
void RegisterProfilePrefs(PrefRegistrySimple* registry) {
constexpr int kDefaultActiveDeskIndex = 0;
registry->RegisterListPref(prefs::kDesksNamesList);
registry->RegisterListPref(prefs::kDesksMetricsList);
registry->RegisterDictionaryPref(prefs::kDesksWeeklyActiveDesksMetrics);
registry->RegisterIntegerPref(prefs::kDesksActiveDesk,
kDefaultActiveDeskIndex);
registry->RegisterBooleanPref(kUserHasUsedDesksRecently,
/*default_value=*/false);
}
void RestorePrimaryUserDesks() {
base::AutoReset<bool> in_progress(&g_pause_desks_prefs_updates, true);
PrefService* primary_user_prefs = GetPrimaryUserPrefService();
if (!primary_user_prefs) {
// Can be null in tests.
return;
}
if (primary_user_prefs->GetBoolean(kUserHasUsedDesksRecently))
UMA_HISTOGRAM_BOOLEAN("Ash.Desks.UserHasUsedDesksRecently", true);
const base::Value* desks_names =
primary_user_prefs->GetList(prefs::kDesksNamesList);
const base::Value* desks_metrics =
primary_user_prefs->GetList(prefs::kDesksMetricsList);
// First create the same number of desks.
const size_t restore_size = desks_names->GetListDeprecated().size();
// If we don't have any restore data, or the list is corrupt for some reason,
// abort.
if (!restore_size || restore_size > desks_util::kMaxNumberOfDesks)
return;
auto* desks_controller = DesksController::Get();
while (desks_controller->desks().size() < restore_size)
desks_controller->NewDesk(DesksCreationRemovalSource::kDesksRestore);
const auto& desks_names_list = desks_names->GetListDeprecated();
const auto& desks_metrics_list = desks_metrics->GetListDeprecated();
const size_t desks_metrics_list_size =
desks_metrics->GetListDeprecated().size();
const auto now = base::Time::Now();
for (size_t index = 0; index < restore_size; ++index) {
const std::string& desk_name = desks_names_list[index].GetString();
// Empty desks names are just place holders for desks whose names haven't
// been modified by the user. Those don't need to be restored; they already
// have the correct default names based on their positions in the desks
// list.
if (!desk_name.empty()) {
desks_controller->RestoreNameOfDeskAtIndex(base::UTF8ToUTF16(desk_name),
index);
}
// Only restore metrics if there is existing data.
if (index >= desks_metrics_list_size)
continue;
const auto& desks_metrics_dict = desks_metrics_list[index];
// Restore creation time.
const auto& creation_time_entry =
desks_metrics_dict.FindIntPath(kCreationTimeKey);
if (creation_time_entry.has_value()) {
const auto creation_time = base::Time::FromDeltaSinceWindowsEpoch(
base::Minutes(*creation_time_entry));
if (!creation_time.is_null() && creation_time < now)
desks_controller->RestoreCreationTimeOfDeskAtIndex(creation_time,
index);
}
// Restore consecutive daily metrics.
const auto& first_day_visited_entry =
desks_metrics_dict.FindIntPath(kFirstDayVisitedKey);
const int first_day_visited = first_day_visited_entry.value_or(-1);
const auto& last_day_visited_entry =
desks_metrics_dict.FindIntPath(kLastDayVisitedKey);
const int last_day_visited = last_day_visited_entry.value_or(-1);
if (first_day_visited <= last_day_visited && first_day_visited != -1 &&
last_day_visited != -1) {
// Only restore the values if they haven't been corrupted.
desks_controller->RestoreVisitedMetricsOfDeskAtIndex(
first_day_visited, last_day_visited, index);
}
// Restore weekly active desks metrics.
const auto& interacted_with_this_week_entry =
desks_metrics_dict.FindBoolPath(kInteractedWithThisWeekKey);
const bool interacted_with_this_week =
interacted_with_this_week_entry.value_or(false);
if (interacted_with_this_week) {
desks_controller->RestoreWeeklyInteractionMetricOfDeskAtIndex(
interacted_with_this_week, index);
}
}
// Restore an active desk for the primary user.
const int active_desk_index =
primary_user_prefs->GetInteger(prefs::kDesksActiveDesk);
// A crash in between prefs::kDesksNamesList and prefs::kDesksActiveDesk
// can cause an invalid active desk index.
if (!IsValidDeskIndex(active_desk_index))
return;
desks_controller->RestorePrimaryUserActiveDeskIndex(active_desk_index);
// Restore weekly active desks metrics.
auto* weekly_active_desks_dict =
primary_user_prefs->GetDictionary(prefs::kDesksWeeklyActiveDesksMetrics);
if (weekly_active_desks_dict) {
const int report_time =
weekly_active_desks_dict->FindIntPath(kReportTimeKey).value_or(-1);
const int num_weekly_active_desks =
weekly_active_desks_dict->FindIntPath(kWeeklyActiveDesksKey)
.value_or(-1);
// Discard stored metrics if either are corrupted.
if (report_time != -1 && num_weekly_active_desks != -1) {
desks_controller->RestoreWeeklyActiveDesksMetrics(
num_weekly_active_desks,
base::Time::FromDeltaSinceWindowsEpoch(base::Minutes(report_time)));
}
}
}
void UpdatePrimaryUserDeskNamesPrefs() {
if (g_pause_desks_prefs_updates)
return;
PrefService* primary_user_prefs = GetPrimaryUserPrefService();
if (!primary_user_prefs) {
// Can be null in tests.
return;
}
ListPrefUpdate name_update(primary_user_prefs, prefs::kDesksNamesList);
base::Value* name_pref_data = name_update.Get();
name_pref_data->ClearList();
const auto& desks = DesksController::Get()->desks();
for (const auto& desk : desks) {
// Desks whose names were not changed by the user, are stored as empty
// strings. They're just place holders to restore the correct desks count.
// RestorePrimaryUserDesks() restores only non-empty desks names.
name_pref_data->Append(desk->is_name_set_by_user()
? base::UTF16ToUTF8(desk->name())
: std::string());
}
DCHECK_EQ(name_pref_data->GetListDeprecated().size(), desks.size());
if (IsNowInValidTimePeriod() &&
!primary_user_prefs->GetBoolean(kUserHasUsedDesksRecently)) {
primary_user_prefs->SetBoolean(kUserHasUsedDesksRecently, true);
}
}
void UpdatePrimaryUserDeskMetricsPrefs() {
if (g_pause_desks_prefs_updates)
return;
PrefService* primary_user_prefs = GetPrimaryUserPrefService();
if (!primary_user_prefs) {
// Can be null in tests.
return;
}
// Save per-desk metrics.
ListPrefUpdate metrics_update(primary_user_prefs, prefs::kDesksMetricsList);
base::Value* metrics_pref_data = metrics_update.Get();
metrics_pref_data->ClearList();
auto* desks_controller = DesksController::Get();
const auto& desks = desks_controller->desks();
for (const auto& desk : desks) {
base::Value metrics_dict(base::Value::Type::DICTIONARY);
metrics_dict.SetIntKey(
kCreationTimeKey,
desk->creation_time().ToDeltaSinceWindowsEpoch().InMinutes());
metrics_dict.SetIntKey(kFirstDayVisitedKey, desk->first_day_visited());
metrics_dict.SetIntKey(kLastDayVisitedKey, desk->last_day_visited());
metrics_dict.SetBoolKey(kInteractedWithThisWeekKey,
desk->interacted_with_this_week());
metrics_pref_data->Append(std::move(metrics_dict));
}
DCHECK_EQ(metrics_pref_data->GetListDeprecated().size(), desks.size());
// Save weekly active report time.
DictionaryPrefUpdate weekly_active_desks_update(
primary_user_prefs, prefs::kDesksWeeklyActiveDesksMetrics);
weekly_active_desks_update->SetIntPath(
kReportTimeKey, desks_controller->GetWeeklyActiveReportTime()
.ToDeltaSinceWindowsEpoch()
.InMinutes());
weekly_active_desks_update->SetIntPath(kWeeklyActiveDesksKey,
Desk::GetWeeklyActiveDesks());
}
void UpdatePrimaryUserActiveDeskPrefs(int active_desk_index) {
DCHECK(IsValidDeskIndex(active_desk_index));
if (g_pause_desks_prefs_updates)
return;
PrefService* primary_user_prefs = GetPrimaryUserPrefService();
if (!primary_user_prefs) {
// Can be null in tests.
return;
}
primary_user_prefs->SetInteger(prefs::kDesksActiveDesk, active_desk_index);
}
bool HasPrimaryUserUsedDesksRecently() {
PrefService* primary_user_prefs = GetPrimaryUserPrefService();
if (!primary_user_prefs) {
// Can be null in tests.
return false;
}
return primary_user_prefs->GetBoolean(kUserHasUsedDesksRecently);
}
const base::Time GetTimeNow() {
return g_override_clock_ ? g_override_clock_->Now() : base::Time::Now();
}
int GetDaysFromLocalEpoch() {
return (GetTimeNow() - GetLocalEpoch()).InDays();
}
void OverrideClockForTesting(base::Clock* test_clock) {
g_override_clock_ = test_clock;
}
void SetPrimaryUserHasUsedDesksRecentlyForTesting(bool value) {
PrefService* primary_user_prefs = GetPrimaryUserPrefService();
DCHECK(primary_user_prefs);
primary_user_prefs->SetBoolean(kUserHasUsedDesksRecently, value);
}
} // namespace desks_restore_util
} // namespace ash