diff --git a/connections/c/nc.cc b/connections/c/nc.cc index c4f63af3..274846d5 100644 --- a/connections/c/nc.cc +++ b/connections/c/nc.cc @@ -844,3 +844,12 @@ void NcSetPhenotypeFlagReader(READER_CONTEXT context, context, phenotype_flag_reader); nearby::NearbyFlags::GetInstance().SetFlagReader(*kNearbyFlags); } + +void NcSaveStates(NC_INSTANCE instance) { + NcContext* nc_context = GetContext(instance); + if (nc_context == nullptr) { + return; + } + + nc_context->core->SaveStates(); +} diff --git a/connections/c/nc.h b/connections/c/nc.h index 1265fcba..24e70ef9 100644 --- a/connections/c/nc.h +++ b/connections/c/nc.h @@ -196,6 +196,10 @@ NC_API void NcSetCustomSavePath(NC_INSTANCE instance, const NC_DATA* save_path, NC_API void NcSetPhenotypeFlagReader( READER_CONTEXT context, NC_PHENOTYPE_FLAG_READER phenotype_flag_reader); +// Saves the states of Nearby Connections to preferences. This is used to +// restore the states when restart the Nearby Connections. +NC_API void NcSaveStates(NC_INSTANCE instance); + #ifdef __cplusplus } // extern "C" #endif // __cplusplus diff --git a/connections/core.cc b/connections/core.cc index 61b77422..ffb3a468 100644 --- a/connections/core.cc +++ b/connections/core.cc @@ -207,6 +207,8 @@ void Core::SetCustomSavePath(absl::string_view path, ResultCallback callback) { router_->SetCustomSavePath(&client_, path, std::move(callback)); } +void Core::SaveStates() { client_.SaveClientInfoToPreferences(); } + std::string Core::Dump() { return client_.Dump(); } // V3 diff --git a/connections/core.h b/connections/core.h index d159767a..ea94b3b4 100644 --- a/connections/core.h +++ b/connections/core.h @@ -258,6 +258,10 @@ class Core { // Gets the local endpoint generated by Nearby Connections. std::string GetLocalEndpointId() { return client_.GetLocalEndpointId(); } + // Saves the states of Nearby Connections to preferences. This is used to + // restore the states when restart the Nearby Connections. + void SaveStates(); + std::string Dump(); //******************************* V3 ******************************* diff --git a/connections/implementation/BUILD b/connections/implementation/BUILD index 2e08cc45..158f4c3e 100644 --- a/connections/implementation/BUILD +++ b/connections/implementation/BUILD @@ -166,6 +166,7 @@ cc_library( "//connections/v3:v3_types", "//internal/analytics:event_logger", "//internal/base:file_path", + "//internal/base:files", "//internal/base:masker", "//internal/flags:nearby_flags", "//internal/interop:authentication_status", diff --git a/connections/implementation/client_proxy.cc b/connections/implementation/client_proxy.cc index 22d20b6b..e9bde163 100644 --- a/connections/implementation/client_proxy.cc +++ b/connections/implementation/client_proxy.cc @@ -31,6 +31,7 @@ #include "absl/random/random.h" #include "absl/strings/escaping.h" #include "absl/strings/string_view.h" +#include "absl/time/time.h" #include "absl/types/span.h" #include "connections/advertising_options.h" #include "connections/connection_options.h" @@ -53,6 +54,7 @@ #include "connections/v3/listeners.h" #include "internal/analytics/event_logger.h" #include "internal/base/file_path.h" +#include "internal/base/files.h" #include "internal/flags/nearby_flags.h" #include "internal/interop/device.h" #include "internal/platform/byte_array.h" @@ -63,6 +65,7 @@ #include "internal/platform/error_code_recorder.h" #include "internal/platform/feature_flags.h" #include "internal/platform/implementation/platform.h" +#include "internal/platform/implementation/system_clock.h" #include "internal/platform/logging.h" #include "internal/platform/mac_address.h" #include "internal/platform/mutex_lock.h" @@ -85,6 +88,13 @@ constexpr char kEndpointIdChars[] = { constexpr absl::string_view kPreferencesFilePath = "Google/Nearby/Connections"; +constexpr absl::string_view kAdvertisingEndpointId = + "nc.advertising.endpoint_id"; + +constexpr absl::string_view kAdvertisingTimestamp = "nc.advertising.timestamp"; + +constexpr absl::Duration kAdvertisingKeepAliveDuration = absl::Seconds(30); + bool IsFeatureUseStableEndpointIdEnabled() { return NearbyFlags::GetInstance().GetBoolFlag( connections::config_package_nearby::nearby_connections_feature:: @@ -125,6 +135,9 @@ ClientProxy::ClientProxy(::nearby::analytics::EventLogger* event_logger) // Generate a 7 bits dedup value. absl::BitGen bitgen; dct_dedup_ = absl::Uniform(bitgen, 0, 1 << 7); + + // Load advertising info from preferences. + LoadClientInfoFromPreferences(); } ClientProxy::~ClientProxy() { Reset(); } @@ -1360,10 +1373,16 @@ void ClientProxy::InitializePreferencesManager() { LOG(INFO) << "ClientProxy [InitializePreferencesManager]: client=" << GetClientId(); auto device_info_ = std::make_unique(); + + FilePath preferences_path = + device_info_->GetAppDataPath().append(FilePath(kPreferencesFilePath)); + + if (!Files::FileExists(preferences_path)) { + Files::CreateDirectories(preferences_path); + } + preferences_manager_ = api::ImplementationPlatform::CreatePreferencesManager( - device_info_->GetAppDataPath() - .append(FilePath(kPreferencesFilePath)) - .ToString()); + preferences_path.ToString()); if (preferences_manager_ == nullptr) { LOG(ERROR) << "ClientProxy [Failed to initialize preferences manager]: " @@ -1372,6 +1391,61 @@ void ClientProxy::InitializePreferencesManager() { } } +void ClientProxy::SaveClientInfoToPreferences() { + MutexLock lock(&mutex_); + if (preferences_manager_ == nullptr) { + return; + } + + if (advertising_info_.IsEmpty()) { + preferences_manager_->Remove(kAdvertisingEndpointId); + preferences_manager_->Remove(kAdvertisingTimestamp); + return; + } + + preferences_manager_->SetString(kAdvertisingEndpointId, local_endpoint_id_); + preferences_manager_->SetTime(kAdvertisingTimestamp, + SystemClock::ElapsedRealtime()); + + LOG(INFO) << "ClientProxy [SaveClientInfoToPreferences]: client=" + << GetClientId() << "; local_endpoint_id_=" << local_endpoint_id_; +} + +void ClientProxy::LoadClientInfoFromPreferences() { + MutexLock lock(&mutex_); + if (preferences_manager_ == nullptr) { + return; + } + + absl::Time last_advertising_time = preferences_manager_->GetTime( + kAdvertisingTimestamp, absl::InfinitePast()); + if (SystemClock::ElapsedRealtime() - last_advertising_time < + kAdvertisingKeepAliveDuration) { + std::string endpoint_id = + preferences_manager_->GetString(kAdvertisingEndpointId, ""); + if (!endpoint_id.empty() && endpoint_id.length() == kEndpointIdLength) { + bool is_valid_endpoint_id = true; + for (const auto& c : endpoint_id) { + if (!((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))) { + is_valid_endpoint_id = false; + break; + } + } + + if (is_valid_endpoint_id) { + local_endpoint_id_ = endpoint_id; + cached_endpoint_id_ = local_endpoint_id_; + LOG(INFO) << "ClientProxy [LoadClientInfoFromPreferences]: client=" + << GetClientId() + << "; local_endpoint_id_=" << local_endpoint_id_; + } + } + } + + preferences_manager_->Remove(kAdvertisingEndpointId); + preferences_manager_->Remove(kAdvertisingTimestamp); +} + std::string ClientProxy::ToString(PayloadProgressInfo::Status status) const { switch (status) { case PayloadProgressInfo::Status::kSuccess: diff --git a/connections/implementation/client_proxy.h b/connections/implementation/client_proxy.h index aa004633..3817afe2 100644 --- a/connections/implementation/client_proxy.h +++ b/connections/implementation/client_proxy.h @@ -372,6 +372,9 @@ class ClientProxy final { // Forces client to regenerate a new local endpoint id. void ClearCachedLocalEndpointId(); + // Saves the client information to preferences. + void SaveClientInfoToPreferences(); + private: struct Connection { // Status: may be either: @@ -460,6 +463,7 @@ class ClientProxy final { std::optional GetEndpointIdForDct() const; void InitializePreferencesManager(); + void LoadClientInfoFromPreferences(); // The device name used for DCT advertising. std::string dct_device_name_; diff --git a/connections/implementation/client_proxy_test.cc b/connections/implementation/client_proxy_test.cc index 53bc6521..05eac36b 100644 --- a/connections/implementation/client_proxy_test.cc +++ b/connections/implementation/client_proxy_test.cc @@ -162,7 +162,8 @@ class ClientProxyTest : public ::testing::TestWithParam { void SetUp() override { EnvironmentConfig config{/*webrtc_enabled=*/false, - /*use_simulated_clock=*/true}; + /*use_simulated_clock=*/true, + /*use_temporary_directory_for_app_path=*/true}; env_.Start(config); client1_ = std::make_unique(&event_logger1_); client2_ = std::make_unique(&event_logger2_); @@ -1621,6 +1622,66 @@ TEST_F(ClientProxyTest, TestRemoteMultiplexSocketBitmask) { false); } +TEST_F(ClientProxyTest, SaveClientInfoFromPreferences) { + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_connections_feature::kUseStableEndpointId, + true); + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_connections_feature:: + kEnableNearbyConnectionsPreferences, + true); + client1_ = std::make_unique(&event_logger1_); + Endpoint advertising_endpoint = + StartAdvertising(client1(), advertising_connection_listener_); + std::string endpoint_id = advertising_endpoint.id; + client1_->SaveClientInfoToPreferences(); + + // Destroy the client and create a new one. + client1_.reset(); + client1_ = std::make_unique(&event_logger1_); + + // The new client should load the same endpoint ID. + EXPECT_EQ(client1()->GetLocalEndpointId(), endpoint_id); + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_connections_feature::kUseStableEndpointId, + false); + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_connections_feature:: + kEnableNearbyConnectionsPreferences, + false); +} + +TEST_F(ClientProxyTest, NotLoadClientInfoFromPreferencesOnExpired) { + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_connections_feature::kUseStableEndpointId, + true); + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_connections_feature:: + kEnableNearbyConnectionsPreferences, + true); + client1_ = std::make_unique(&event_logger1_); + Endpoint advertising_endpoint = + StartAdvertising(client1(), advertising_connection_listener_); + std::string endpoint_id = advertising_endpoint.id; + client1_->SaveClientInfoToPreferences(); + + // Destroy the client and create a new one. + client1_.reset(); + FastForward(absl::Hours(25)); + + client1_ = std::make_unique(&event_logger1_); + + // The new client should load the same endpoint ID. + EXPECT_NE(client1()->GetLocalEndpointId(), endpoint_id); + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_connections_feature::kUseStableEndpointId, + false); + NearbyFlags::GetInstance().OverrideBoolFlagValue( + config_package_nearby::nearby_connections_feature:: + kEnableNearbyConnectionsPreferences, + false); +} + } // namespace } // namespace connections } // namespace nearby diff --git a/internal/platform/implementation/g3/device_info.h b/internal/platform/implementation/g3/device_info.h index be5b4fa6..5ff18863 100644 --- a/internal/platform/implementation/g3/device_info.h +++ b/internal/platform/implementation/g3/device_info.h @@ -26,6 +26,7 @@ #include "internal/base/files.h" #include "internal/platform/implementation/device_info.h" #include "internal/platform/implementation/g3/linux_path_util.h" +#include "internal/platform/medium_environment.h" namespace nearby { namespace g3 { @@ -49,6 +50,12 @@ class DeviceInfo : public api::DeviceInfo { } std::optional GetLocalAppDataPath() const override { + if (MediumEnvironment::Instance() + .GetEnvironmentConfig() + .use_temporary_directory_for_app_path) { + return Files::GetTemporaryDirectory(); + } + return GetAppDataPath(); } diff --git a/internal/platform/medium_environment.h b/internal/platform/medium_environment.h index d64e795f..d32a42be 100644 --- a/internal/platform/medium_environment.h +++ b/internal/platform/medium_environment.h @@ -63,6 +63,10 @@ struct EnvironmentConfig { // The simulated clock is automatically picked up by SystemClock, Timer and // ScheduledExecutor implementations. bool use_simulated_clock = false; + + // If true, the app data path will be a temporary directory, instead of the + // actual app data path, so that we can test the preferences manager under G3. + bool use_temporary_directory_for_app_path = false; }; // MediumEnvironment is a simulated environment which allows multiple instances