blob: e052ef615fe97ea568bc3d9998947b52457d6775 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/synced_set_up/utils/utils.h"
#import <Foundation/Foundation.h>
#import <optional>
#import <tuple>
#import "base/containers/fixed_flat_map.h"
#import "base/notreached.h"
#import "base/values.h"
#import "components/commerce/core/pref_names.h"
#import "components/ntp_tiles/pref_names.h"
#import "components/omnibox/browser/omnibox_pref_names.h"
#import "components/safety_check/safety_check_pref_names.h"
#import "components/sync_device_info/device_info_tracker.h"
#import "components/sync_preferences/cross_device_pref_tracker/cross_device_pref_tracker.h"
#import "components/sync_preferences/cross_device_pref_tracker/prefs/cross_device_pref_names.h"
#import "components/sync_preferences/cross_device_pref_tracker/timestamped_pref_value.h"
#import "ios/chrome/app/profile/profile_init_stage.h"
#import "ios/chrome/app/profile/profile_state.h"
#import "ios/chrome/browser/shared/coordinator/scene/scene_state.h"
#import "ios/chrome/browser/shared/model/browser/browser_provider.h"
#import "ios/chrome/browser/shared/model/browser/browser_provider_interface.h"
namespace {
// Map of cross device synced prefs considered by Synced Set Up mapped to their
// corresponding platform tracked pref.
inline constexpr auto kCrossDeviceToTrackedPrefMap =
base::MakeFixedFlatMap<std::string_view, std::string_view>({
// keep-sorted start
{prefs::kCrossDeviceMagicStackHomeModuleEnabled,
ntp_tiles::prefs::kMagicStackHomeModuleEnabled},
{prefs::kCrossDeviceMostVisitedHomeModuleEnabled,
ntp_tiles::prefs::kMostVisitedHomeModuleEnabled},
{prefs::kCrossDeviceOmniboxIsInBottomPosition,
omnibox::kIsOmniboxInBottomPosition},
{prefs::kCrossDevicePriceTrackingHomeModuleEnabled,
commerce::kPriceTrackingHomeModuleEnabled},
{prefs::kCrossDeviceSafetyCheckHomeModuleEnabled,
safety_check::prefs::kSafetyCheckHomeModuleEnabled},
{prefs::kCrossDeviceTabResumptionHomeModuleEnabled,
ntp_tiles::prefs::kTabResumptionHomeModuleEnabled},
{prefs::kCrossDeviceTipsHomeModuleEnabled,
ntp_tiles::prefs::kTipsHomeModuleEnabled},
// keep-sorted end
});
// Struct representing a unique device, containing a map of cross device pref
// names and values, and the total number of changes observed by the
// `CrossDevicePrefTracker` for the device.
struct DeviceData {
// Map of all cross devices prefs and their values associated with this
// device.
std::map<std::string_view, sync_preferences::TimestampedPrefValue> pref_map;
// Total number of observed pref changes on this device.
size_t observed_change_count = 0;
};
// A map of device GUID's to device data containing the devices' respective sets
// of synced prefs and number of observed remote pref changes.
using DeviceDataMap = std::map<std::string, DeviceData>;
// Helper for copying a `TimestampedPrefValue`.
sync_preferences::TimestampedPrefValue CloneTimestampedPrefValue(
const sync_preferences::TimestampedPrefValue& timestamped_value) {
sync_preferences::TimestampedPrefValue cloned_value;
cloned_value.value = timestamped_value.value.Clone();
cloned_value.last_observed_change_time =
timestamped_value.last_observed_change_time;
cloned_value.device_sync_cache_guid =
timestamped_value.device_sync_cache_guid;
return cloned_value;
}
// Returns a map of synced device GUID's to the devices' associated synced pref
// data.
DeviceDataMap MapPrefsToDevices(
const sync_preferences::CrossDevicePrefTracker* pref_tracker) {
DeviceDataMap device_data_map;
sync_preferences::CrossDevicePrefTracker::DeviceFilter filter;
// Query the tracker for tracked prefs and construct a `DeviceDataMap`
// associating device GUID's with sets of prefs.
for (const auto& tracked_pref : kCrossDeviceToTrackedPrefMap) {
std::vector<sync_preferences::TimestampedPrefValue> pref_values =
pref_tracker->GetValues(tracked_pref.first, filter);
// Populate the device data map with device GUID's mapped to their set of
// synced prefs and number of recent observed changes.
for (const sync_preferences::TimestampedPrefValue& pref_value :
pref_values) {
DeviceData& device_data_entry =
device_data_map[pref_value.device_sync_cache_guid];
// Ensure that only the most recent observed pref change is preserved in
// the DeviceData pref map.
auto it = device_data_entry.pref_map.find(tracked_pref.first);
if (it == device_data_entry.pref_map.end() ||
pref_value.last_observed_change_time >=
it->second.last_observed_change_time) {
device_data_entry.pref_map.insert_or_assign(
tracked_pref.first, CloneTimestampedPrefValue(pref_value));
}
// Count total observed pref changes.
if (!pref_value.last_observed_change_time.is_null()) {
device_data_entry.observed_change_count++;
}
}
}
return device_data_map;
}
// Returns the best fitting device in a `DeviceDataMap` of considered devices
// and their associated prefs.
DeviceData GetBestMatchDeviceData(
DeviceDataMap& synced_devices,
const syncer::DeviceInfoTracker* device_info_tracker,
const syncer::DeviceInfo* local_device) {
// Criteria for scoring devices against the local device as the best match for
// applying synced prefs.
using MatchesFormFactor = bool;
using MatchesOsType = bool;
using RecentPrefChangeCount = size_t;
using DeviceScore =
std::tuple<MatchesFormFactor, MatchesOsType, RecentPrefChangeCount>;
// Devices cannot be scored.
if (synced_devices.empty() || !device_info_tracker || !local_device) {
return {};
}
// Use a std::set to automatically order the scores.
// We store {score, guid} pairs. The set orders primarily by score.
using ScoredDevice = std::pair<DeviceScore, std::string_view>;
std::set<ScoredDevice> scored_remote_devices;
const std::string& local_device_guid = local_device->guid();
for (const auto& [guid, data] : synced_devices) {
// 1. Skip local device and devices with no prefs.
if (guid == local_device_guid || data.pref_map.empty()) {
continue;
}
// 2. Ensure the device info exists.
const syncer::DeviceInfo* device_info =
device_info_tracker->GetDeviceInfo(guid);
if (!device_info) {
continue;
}
// 3. Calculate and insert the score.
DeviceScore score = {
device_info->form_factor() == local_device->form_factor(),
device_info->os_type() == local_device->os_type(),
data.observed_change_count};
scored_remote_devices.insert({score, guid});
}
if (scored_remote_devices.empty()) {
return {};
}
// The best device is the last element in the ordered set.
const auto& best_match = *scored_remote_devices.rbegin();
const std::string best_guid = std::string(best_match.second);
// Final check: Compare activity level with the local device, if present.
auto local_it = synced_devices.find(local_device_guid);
if (local_it != synced_devices.end()) {
if (local_it->second.observed_change_count >
synced_devices.at(best_guid).observed_change_count) {
// Local device has more activity; prefer to keep local settings.
return {};
}
}
return std::move(synced_devices.at(best_guid));
}
// Returns a map of tracked pref names and their values extracted from a set of
// `DeviceData`.
std::map<std::string_view, base::Value> GetTrackedPrefValuesForDevice(
DeviceData& device) {
std::map<std::string_view, base::Value> prefs;
for (const auto& [cross_device_pref_name, timestamped_pref_value] :
device.pref_map) {
prefs.insert(std::make_pair(GetTrackedPrefName(cross_device_pref_name),
timestamped_pref_value.value.Clone()));
}
return prefs;
}
} // namespace
std::string_view GetTrackedPrefName(
const std::string_view& cross_device_pref_name) {
auto it = kCrossDeviceToTrackedPrefMap.find(cross_device_pref_name);
if (it != kCrossDeviceToTrackedPrefMap.end()) {
return it->second;
}
NOTREACHED();
}
std::map<std::string_view, base::Value> GetRemoteDevicePrefs(
const sync_preferences::CrossDevicePrefTracker* pref_tracker,
const syncer::DeviceInfoTracker* device_info_tracker,
const syncer::DeviceInfo* local_device) {
CHECK(pref_tracker);
CHECK(device_info_tracker);
CHECK(local_device);
DeviceDataMap device_data_map = MapPrefsToDevices(pref_tracker);
DeviceData best_match_device_data = GetBestMatchDeviceData(
device_data_map, device_info_tracker, local_device);
std::map<std::string_view, base::Value> tracked_pref_values =
GetTrackedPrefValuesForDevice(best_match_device_data);
return tracked_pref_values;
}
SceneState* GetEligibleSceneForSyncedSetUp(ProfileState* profile_state) {
if (!profile_state) {
return nil;
}
if (profile_state.initStage != ProfileInitStage::kFinal) {
return nil;
}
if (profile_state.currentUIBlocker) {
return nil;
}
SceneState* active_scene = profile_state.foregroundActiveScene;
if (!active_scene) {
return nil;
}
id<BrowserProviderInterface> provider_interface =
active_scene.browserProviderInterface;
id<BrowserProvider> presenting_interface =
provider_interface.currentBrowserProvider;
if (presenting_interface != provider_interface.mainBrowserProvider) {
return nil;
}
// All preconditions met.
return active_scene;
}