| // Copyright (c) 2012 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 "services/device/geolocation/network_location_provider.h" |
| |
| #include <stddef.h> |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/json/json_reader.h" |
| #include "base/json/json_writer.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/task_environment.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "net/base/net_errors.h" |
| #include "services/device/geolocation/fake_position_cache.h" |
| #include "services/device/geolocation/location_arbitrator.h" |
| #include "services/device/geolocation/wifi_data_provider.h" |
| #include "services/device/public/cpp/geolocation/geoposition.h" |
| #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" |
| #include "services/network/public/mojom/url_response_head.mojom.h" |
| #include "services/network/test/test_url_loader_factory.h" |
| #include "services/network/test/test_utils.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| #if defined(OS_MAC) |
| #include "services/device/public/cpp/test/fake_geolocation_manager.h" |
| #endif |
| |
| namespace device { |
| |
| // Records the most recent position update and counts the number of times |
| // OnLocationUpdate is called. |
| struct LocationUpdateListener { |
| LocationUpdateListener() |
| : callback(base::BindRepeating(&LocationUpdateListener::OnLocationUpdate, |
| base::Unretained(this))) {} |
| |
| void OnLocationUpdate(const LocationProvider* provider, |
| const mojom::Geoposition& position) { |
| last_position = position; |
| update_count++; |
| if (position.error_code != mojom::Geoposition::ErrorCode::NONE) |
| error_count++; |
| } |
| |
| const LocationProvider::LocationProviderUpdateCallback callback; |
| mojom::Geoposition last_position; |
| int update_count = 0; |
| int error_count = 0; |
| }; |
| |
| // A mock implementation of WifiDataProvider for testing. Adapted from |
| // http://gears.googlecode.com/svn/trunk/gears/geolocation/geolocation_test.cc |
| class MockWifiDataProvider : public WifiDataProvider { |
| public: |
| // Factory method for use with WifiDataProvider::SetFactoryForTesting. |
| static WifiDataProvider* GetInstance() { |
| CHECK(instance_); |
| return instance_; |
| } |
| |
| static MockWifiDataProvider* CreateInstance() { |
| CHECK(!instance_); |
| instance_ = new MockWifiDataProvider; |
| return instance_; |
| } |
| |
| MockWifiDataProvider() : start_calls_(0), stop_calls_(0), got_data_(true) {} |
| |
| MockWifiDataProvider(const MockWifiDataProvider&) = delete; |
| MockWifiDataProvider& operator=(const MockWifiDataProvider&) = delete; |
| |
| // WifiDataProvider implementation. |
| void StartDataProvider() override { ++start_calls_; } |
| |
| void StopDataProvider() override { ++stop_calls_; } |
| |
| bool DelayedByPolicy() override { return false; } |
| |
| bool GetData(WifiData* data_out) override { |
| CHECK(data_out); |
| *data_out = data_; |
| return got_data_; |
| } |
| |
| void ForceRescan() override {} |
| |
| void SetData(const WifiData& new_data) { |
| got_data_ = true; |
| const bool differs = data_.DiffersSignificantly(new_data); |
| data_ = new_data; |
| if (differs) |
| this->RunCallbacks(); |
| } |
| |
| void set_got_data(bool got_data) { got_data_ = got_data; } |
| int start_calls_; |
| int stop_calls_; |
| |
| private: |
| ~MockWifiDataProvider() override { |
| CHECK(this == instance_); |
| instance_ = nullptr; |
| } |
| |
| static MockWifiDataProvider* instance_; |
| |
| WifiData data_; |
| bool got_data_; |
| }; |
| |
| MockWifiDataProvider* MockWifiDataProvider::instance_ = nullptr; |
| |
| // Main test fixture |
| class GeolocationNetworkProviderTest : public testing::Test { |
| public: |
| void TearDown() override { |
| WifiDataProviderManager::ResetFactoryForTesting(); |
| grant_system_permission_by_default_ = true; |
| } |
| |
| std::unique_ptr<LocationProvider> CreateProvider( |
| bool set_permission_granted, |
| const std::string& api_key = std::string()) { |
| #if defined(OS_MAC) |
| fake_geolocation_manager_ = std::make_unique<FakeGeolocationManager>(); |
| auto provider = std::make_unique<NetworkLocationProvider>( |
| test_url_loader_factory_.GetSafeWeakWrapper(), |
| fake_geolocation_manager_.get(), base::ThreadTaskRunnerHandle::Get(), |
| api_key, &position_cache_); |
| // For macOS we must simulate the granting of location permission |
| if (grant_system_permission_by_default_) { |
| fake_geolocation_manager_->SetSystemPermission( |
| LocationSystemPermissionStatus::kAllowed); |
| base::RunLoop().RunUntilIdle(); |
| } |
| #else |
| auto provider = std::make_unique<NetworkLocationProvider>( |
| test_url_loader_factory_.GetSafeWeakWrapper(), |
| /*geolocation_system_permission_manager=*/nullptr, |
| base::ThreadTaskRunnerHandle::Get(), api_key, &position_cache_); |
| #endif |
| if (set_permission_granted) |
| provider->OnPermissionGranted(); |
| |
| return provider; |
| } |
| |
| bool grant_system_permission_by_default_ = true; |
| |
| #if defined(OS_MAC) |
| std::unique_ptr<FakeGeolocationManager> fake_geolocation_manager_; |
| #endif |
| |
| protected: |
| GeolocationNetworkProviderTest() |
| : wifi_data_provider_(MockWifiDataProvider::CreateInstance()) { |
| // TODO(joth): Really these should be in SetUp, not here, but they take no |
| // effect on Mac OS Release builds if done there. I kid not. Figure out why. |
| WifiDataProviderManager::SetFactoryForTesting( |
| MockWifiDataProvider::GetInstance); |
| } |
| |
| static int IndexToChannel(int index) { return index + 4; } |
| |
| // Creates wifi data containing the specified number of access points, with |
| // some differentiating charactistics in each. |
| static WifiData CreateReferenceWifiScanData(int ap_count) { |
| WifiData data; |
| for (int i = 0; i < ap_count; ++i) { |
| AccessPointData ap; |
| ap.mac_address = |
| base::ASCIIToUTF16(base::StringPrintf("%02d-34-56-78-54-32", i)); |
| ap.radio_signal_strength = ap_count - i; |
| ap.channel = IndexToChannel(i); |
| ap.signal_to_noise = i + 42; |
| ap.ssid = u"Some nice+network|name\\"; |
| data.access_point_data.insert(ap); |
| } |
| return data; |
| } |
| |
| static WifiData CreateReferenceWifiScanDataWithNoMACAddress(int ap_count) { |
| WifiData data; |
| for (int i = 0; i < ap_count; ++i) { |
| AccessPointData ap; |
| ap.radio_signal_strength = ap_count - i; |
| ap.channel = IndexToChannel(i); |
| ap.signal_to_noise = i + 42; |
| ap.ssid = u"Some nice+network|name\\"; |
| data.access_point_data.insert(ap); |
| } |
| return data; |
| } |
| |
| static void CreateReferenceWifiScanDataJson( |
| int ap_count, |
| int start_index, |
| base::ListValue* wifi_access_point_list) { |
| std::vector<std::string> wifi_data; |
| for (int i = 0; i < ap_count; ++i) { |
| std::unique_ptr<base::DictionaryValue> ap(new base::DictionaryValue()); |
| ap->SetString("macAddress", base::StringPrintf("%02d-34-56-78-54-32", i)); |
| ap->SetInteger("signalStrength", start_index + ap_count - i); |
| ap->SetInteger("age", 0); |
| ap->SetInteger("channel", IndexToChannel(i)); |
| ap->SetInteger("signalToNoiseRatio", i + 42); |
| wifi_access_point_list->Append(std::move(ap)); |
| } |
| } |
| |
| static mojom::Geoposition CreateReferencePosition(int id) { |
| mojom::Geoposition pos; |
| pos.latitude = id; |
| pos.longitude = -(id + 1); |
| pos.altitude = 2 * id; |
| pos.accuracy = 3 * id; |
| // Ensure last_position.timestamp be earlier than any future calls to |
| // base::time::Now() as well as not old enough to be considered invalid |
| // (kLastPositionMaxAgeSeconds) |
| pos.timestamp = base::Time::Now() - base::Minutes(5); |
| return pos; |
| } |
| |
| static std::string PrettyJson(const base::Value& value) { |
| std::string pretty; |
| base::JSONWriter::WriteWithOptions( |
| value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &pretty); |
| return pretty; |
| } |
| |
| static testing::AssertionResult JsonGetList( |
| const std::string& field, |
| const base::DictionaryValue& dict, |
| const base::ListValue** output_list) { |
| if (!dict.GetList(field, output_list)) |
| return testing::AssertionFailure() << "Dictionary " << PrettyJson(dict) |
| << " is missing list field " << field; |
| return testing::AssertionSuccess(); |
| } |
| |
| static testing::AssertionResult JsonFieldEquals( |
| const std::string& field, |
| const base::DictionaryValue& expected, |
| const base::DictionaryValue& actual) { |
| const base::Value* expected_value; |
| const base::Value* actual_value; |
| if (!expected.Get(field, &expected_value)) |
| return testing::AssertionFailure() |
| << "Expected dictionary " << PrettyJson(expected) |
| << " is missing field " << field; |
| if (!expected.Get(field, &actual_value)) |
| return testing::AssertionFailure() |
| << "Actual dictionary " << PrettyJson(actual) |
| << " is missing field " << field; |
| if (!expected_value->Equals(actual_value)) |
| return testing::AssertionFailure() |
| << "Field " << field |
| << " mismatch: " << PrettyJson(*expected_value) |
| << " != " << PrettyJson(*actual_value); |
| return testing::AssertionSuccess(); |
| } |
| |
| // Checks that current pending request contains valid JSON upload data. The |
| // WiFi access points specified in the JSON are validated against the first |
| // |expected_wifi_aps| access points, starting from position |
| // |wifi_start_index|, that are generated by CreateReferenceWifiScanDataJson. |
| void CheckRequestIsValid(int expected_wifi_aps, int wifi_start_index) { |
| ASSERT_EQ(1, test_url_loader_factory_.NumPending()); |
| const network::TestURLLoaderFactory::PendingRequest& pending_request = |
| test_url_loader_factory_.pending_requests()->back(); |
| EXPECT_TRUE(pending_request.client.is_connected()); |
| std::string upload_data = network::GetUploadData(pending_request.request); |
| ASSERT_FALSE(upload_data.empty()); |
| |
| absl::optional<base::Value> parsed_json = |
| base::JSONReader::Read(upload_data); |
| ASSERT_TRUE(parsed_json); |
| |
| const base::DictionaryValue* request_json; |
| ASSERT_TRUE(parsed_json->GetAsDictionary(&request_json)); |
| |
| if (expected_wifi_aps) { |
| base::ListValue expected_wifi_aps_json; |
| CreateReferenceWifiScanDataJson(expected_wifi_aps, wifi_start_index, |
| &expected_wifi_aps_json); |
| EXPECT_EQ(size_t(expected_wifi_aps), |
| expected_wifi_aps_json.GetList().size()); |
| |
| const base::ListValue* wifi_aps_json; |
| ASSERT_TRUE( |
| JsonGetList("wifiAccessPoints", *request_json, &wifi_aps_json)); |
| for (size_t i = 0; i < expected_wifi_aps_json.GetList().size(); ++i) { |
| const base::Value& expected_json_value = |
| expected_wifi_aps_json.GetList()[i]; |
| ASSERT_TRUE(expected_json_value.is_dict()); |
| const base::DictionaryValue& expected_json = |
| base::Value::AsDictionaryValue(expected_json_value); |
| const base::Value& actual_json_value = wifi_aps_json->GetList()[i]; |
| ASSERT_TRUE(actual_json_value.is_dict()); |
| const base::DictionaryValue& actual_json = |
| base::Value::AsDictionaryValue(actual_json_value); |
| ASSERT_TRUE(JsonFieldEquals("macAddress", expected_json, actual_json)); |
| ASSERT_TRUE( |
| JsonFieldEquals("signalStrength", expected_json, actual_json)); |
| ASSERT_TRUE(JsonFieldEquals("channel", expected_json, actual_json)); |
| ASSERT_TRUE( |
| JsonFieldEquals("signalToNoiseRatio", expected_json, actual_json)); |
| } |
| } else { |
| ASSERT_FALSE(request_json->FindKey("wifiAccessPoints")); |
| } |
| } |
| |
| const base::test::SingleThreadTaskEnvironment task_environment_; |
| network::TestURLLoaderFactory test_url_loader_factory_; |
| const scoped_refptr<MockWifiDataProvider> wifi_data_provider_; |
| FakePositionCache position_cache_; |
| }; |
| |
| // Tests that fixture members were SetUp correctly. |
| TEST_F(GeolocationNetworkProviderTest, CreateDestroy) { |
| std::unique_ptr<LocationProvider> provider(CreateProvider(true)); |
| EXPECT_TRUE(provider); |
| provider.reset(); |
| SUCCEED(); |
| } |
| |
| // Tests that, with an empty api_key, no query string parameter is included in |
| // the request. |
| TEST_F(GeolocationNetworkProviderTest, EmptyApiKey) { |
| const std::string api_key = ""; |
| std::unique_ptr<LocationProvider> provider(CreateProvider(true, api_key)); |
| provider->StartProvider(false); |
| |
| ASSERT_EQ(1, test_url_loader_factory_.NumPending()); |
| const GURL& request_url = |
| test_url_loader_factory_.pending_requests()->back().request.url; |
| EXPECT_FALSE(request_url.has_query()); |
| } |
| |
| // Tests that, with non-empty api_key, a "key" query string parameter is |
| // included in the request. |
| TEST_F(GeolocationNetworkProviderTest, NonEmptyApiKey) { |
| const std::string api_key = "something"; |
| std::unique_ptr<LocationProvider> provider(CreateProvider(true, api_key)); |
| provider->StartProvider(false); |
| |
| ASSERT_EQ(1, test_url_loader_factory_.NumPending()); |
| const GURL& request_url = |
| test_url_loader_factory_.pending_requests()->back().request.url; |
| EXPECT_TRUE(request_url.has_query()); |
| EXPECT_TRUE(base::StartsWith(request_url.query_piece(), "key=")); |
| } |
| |
| // Tests that, after StartProvider(), a TestURLFetcher can be extracted, |
| // representing a valid request. |
| TEST_F(GeolocationNetworkProviderTest, StartProvider) { |
| std::unique_ptr<LocationProvider> provider(CreateProvider(true)); |
| provider->StartProvider(false); |
| |
| CheckRequestIsValid(0, 0); |
| } |
| |
| // Tests that, with a very large number of access points, the set of access |
| // points represented in the request is truncated to fit within 2048 characters. |
| TEST_F(GeolocationNetworkProviderTest, StartProviderLongRequest) { |
| std::unique_ptr<LocationProvider> provider(CreateProvider(true)); |
| provider->StartProvider(false); |
| // Create Wifi scan data with too many access points. |
| const int kFirstScanAps = 20; |
| wifi_data_provider_->SetData(CreateReferenceWifiScanData(kFirstScanAps)); |
| base::RunLoop().RunUntilIdle(); |
| |
| ASSERT_EQ(1, test_url_loader_factory_.NumPending()); |
| const std::string& request_url = |
| test_url_loader_factory_.pending_requests()->back().request.url.spec(); |
| // The request url should have been shortened to less than 2048 characters |
| // in length by not including access points with the lowest signal strength |
| // in the request. |
| EXPECT_LT(request_url.size(), size_t(2048)); |
| // Expect only 16 out of 20 original access points. |
| CheckRequestIsValid(16, 4); |
| } |
| |
| TEST_F(GeolocationNetworkProviderTest, StartProviderNoMacAddress) { |
| std::unique_ptr<LocationProvider> provider(CreateProvider(true)); |
| provider->StartProvider(false); |
| // Create Wifi scan data with no MAC Addresses. |
| const int kFirstScanAps = 5; |
| wifi_data_provider_->SetData( |
| CreateReferenceWifiScanDataWithNoMACAddress(kFirstScanAps)); |
| base::RunLoop().RunUntilIdle(); |
| |
| ASSERT_EQ(1, test_url_loader_factory_.NumPending()); |
| |
| test_url_loader_factory_.pending_requests()->back().request.url.spec(); |
| |
| // Expect only 0 out of 5 original access points. since none of them have |
| // MAC Addresses. |
| CheckRequestIsValid(0, 0); |
| } |
| |
| // Tests that the provider issues the right requests, and provides the right |
| // GetPosition() results based on the responses, for the following complex |
| // sequence of Wifi data situations: |
| // 1. Initial "no fix" response -> provide 'invalid' position. |
| // 2. Wifi data arrives -> make new request. |
| // 3. Response has good fix -> provide corresponding position. |
| // 4. Wifi data changes slightly -> no new request. |
| // 5. Wifi data changes a lot -> new request. |
| // 6. Response is error -> provide 'invalid' position. |
| // 7. Wifi data changes back to (2.) -> no new request, provide cached position. |
| TEST_F(GeolocationNetworkProviderTest, MultipleWifiScansComplete) { |
| std::unique_ptr<LocationProvider> provider(CreateProvider(true)); |
| provider->StartProvider(false); |
| ASSERT_EQ(1, test_url_loader_factory_.NumPending()); |
| |
| // 1. Complete the network request with bad position fix. |
| const std::string& request_url_1 = |
| test_url_loader_factory_.pending_requests()->back().request.url.spec(); |
| const char* kNoFixNetworkResponse = |
| "{" |
| " \"status\": \"ZERO_RESULTS\"" |
| "}"; |
| test_url_loader_factory_.AddResponse(request_url_1, kNoFixNetworkResponse); |
| base::RunLoop().RunUntilIdle(); |
| test_url_loader_factory_.ClearResponses(); |
| |
| mojom::Geoposition position = provider->GetPosition(); |
| EXPECT_FALSE(ValidateGeoposition(position)); |
| |
| // 2. Now wifi data arrives -- SetData will notify listeners. |
| const int kFirstScanAps = 6; |
| wifi_data_provider_->SetData(CreateReferenceWifiScanData(kFirstScanAps)); |
| base::RunLoop().RunUntilIdle(); |
| |
| // The request should have the wifi data. |
| CheckRequestIsValid(kFirstScanAps, 0); |
| |
| // 3. Send a reply with good position fix. |
| ASSERT_EQ(1, test_url_loader_factory_.NumPending()); |
| const std::string& request_url_2 = |
| test_url_loader_factory_.pending_requests()->back().request.url.spec(); |
| const char* kReferenceNetworkResponse = |
| "{" |
| " \"accuracy\": 1200.4," |
| " \"location\": {" |
| " \"lat\": 51.0," |
| " \"lng\": -0.1" |
| " }" |
| "}"; |
| test_url_loader_factory_.AddResponse(request_url_2, |
| kReferenceNetworkResponse); |
| base::RunLoop().RunUntilIdle(); |
| test_url_loader_factory_.ClearResponses(); |
| |
| position = provider->GetPosition(); |
| EXPECT_EQ(51.0, position.latitude); |
| EXPECT_EQ(-0.1, position.longitude); |
| EXPECT_EQ(1200.4, position.accuracy); |
| EXPECT_FALSE(position.timestamp.is_null()); |
| EXPECT_TRUE(ValidateGeoposition(position)); |
| |
| // 4. Wifi updated again, with one less AP. This is 'close enough' to the |
| // previous scan, so no new request made. |
| const int kSecondScanAps = kFirstScanAps - 1; |
| wifi_data_provider_->SetData(CreateReferenceWifiScanData(kSecondScanAps)); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(0, test_url_loader_factory_.NumPending()); |
| |
| position = provider->GetPosition(); |
| EXPECT_EQ(51.0, position.latitude); |
| EXPECT_EQ(-0.1, position.longitude); |
| EXPECT_TRUE(ValidateGeoposition(position)); |
| |
| // 5. Now a third scan with more than twice the original APs -> new request. |
| const int kThirdScanAps = kFirstScanAps * 2 + 1; |
| wifi_data_provider_->SetData(CreateReferenceWifiScanData(kThirdScanAps)); |
| base::RunLoop().RunUntilIdle(); |
| CheckRequestIsValid(kThirdScanAps, 0); |
| |
| // 6. ...reply with a network error. |
| ASSERT_EQ(1, test_url_loader_factory_.NumPending()); |
| const GURL& request_url_3 = |
| test_url_loader_factory_.pending_requests()->back().request.url; |
| test_url_loader_factory_.AddResponse( |
| request_url_3, network::mojom::URLResponseHead::New(), std::string(), |
| network::URLLoaderCompletionStatus(net::ERR_FAILED)); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Error means we now no longer have a fix. |
| position = provider->GetPosition(); |
| EXPECT_FALSE(ValidateGeoposition(position)); |
| |
| // 7. Wifi scan returns to original set: should be serviced from cache. |
| wifi_data_provider_->SetData(CreateReferenceWifiScanData(kFirstScanAps)); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(0, test_url_loader_factory_.NumPending()); |
| |
| position = provider->GetPosition(); |
| EXPECT_EQ(51.0, position.latitude); |
| EXPECT_EQ(-0.1, position.longitude); |
| EXPECT_TRUE(ValidateGeoposition(position)); |
| } |
| |
| // Tests that, if no Wifi scan data is available at startup, the provider |
| // doesn't initiate a request, until Wifi data later becomes available. |
| TEST_F(GeolocationNetworkProviderTest, NoRequestOnStartupUntilWifiData) { |
| LocationUpdateListener listener; |
| wifi_data_provider_->set_got_data(false); // No initial Wifi data. |
| std::unique_ptr<LocationProvider> provider(CreateProvider(true)); |
| provider->StartProvider(false); |
| |
| provider->SetUpdateCallback(listener.callback); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(0, test_url_loader_factory_.NumPending()) |
| << "Network request should not be created right away on startup when " |
| "wifi data has not yet arrived"; |
| |
| // Now Wifi data becomes available. |
| wifi_data_provider_->SetData(CreateReferenceWifiScanData(1)); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1, test_url_loader_factory_.NumPending()); |
| } |
| |
| // Tests that, even if a request is already in flight, new wifi data results in |
| // a new request being sent. |
| TEST_F(GeolocationNetworkProviderTest, NewDataReplacesExistingNetworkRequest) { |
| // Send initial request with empty data |
| std::unique_ptr<LocationProvider> provider(CreateProvider(true)); |
| provider->StartProvider(false); |
| CheckRequestIsValid(0, 0); |
| |
| // Now wifi data arrives; new request should be sent. |
| wifi_data_provider_->SetData(CreateReferenceWifiScanData(4)); |
| base::RunLoop().RunUntilIdle(); |
| CheckRequestIsValid(4, 0); |
| } |
| |
| // Tests that, if user geolocation permission hasn't been granted during |
| // startup, the provider doesn't initiate a request until it is notified of the |
| // user granting permission. |
| TEST_F(GeolocationNetworkProviderTest, NetworkRequestDeferredForPermission) { |
| std::unique_ptr<LocationProvider> provider(CreateProvider(false)); |
| provider->StartProvider(false); |
| EXPECT_EQ(0, test_url_loader_factory_.NumPending()); |
| |
| provider->OnPermissionGranted(); |
| EXPECT_EQ(1, test_url_loader_factory_.NumPending()); |
| } |
| |
| // Tests that, even if new Wifi data arrives, the provider doesn't initiate its |
| // first request unless & until the user grants permission. |
| TEST_F(GeolocationNetworkProviderTest, |
| NetworkRequestWithWifiDataDeferredForPermission) { |
| std::unique_ptr<LocationProvider> provider(CreateProvider(false)); |
| provider->StartProvider(false); |
| EXPECT_EQ(0, test_url_loader_factory_.NumPending()); |
| |
| static const int kScanCount = 4; |
| wifi_data_provider_->SetData(CreateReferenceWifiScanData(kScanCount)); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(0, test_url_loader_factory_.NumPending()); |
| |
| provider->OnPermissionGranted(); |
| CheckRequestIsValid(kScanCount, 0); |
| } |
| |
| #if defined(OS_MAC) |
| // Tests that, callbacks and network requests are never made until we have |
| // system location permission. |
| TEST_F(GeolocationNetworkProviderTest, MacOSSystemPermissionsTest) { |
| // Do not grant system permission when creating the provider. |
| grant_system_permission_by_default_ = false; |
| |
| LocationUpdateListener listener; |
| mojom::Geoposition last_position = CreateReferencePosition(0); |
| EXPECT_TRUE(ValidateGeoposition(last_position)); |
| // Set up a fake cached position so the NetworkLocationProvider would be able |
| // to call the update callback if permission was allowed. |
| position_cache_.SetLastUsedNetworkPosition(last_position); |
| |
| wifi_data_provider_->set_got_data(false); |
| |
| std::unique_ptr<LocationProvider> provider( |
| CreateProvider(/*set_permission_granted=*/false)); |
| provider->StartProvider(/*high_accuracy=*/false); |
| provider->SetUpdateCallback(listener.callback); |
| |
| // Under normal circumstances, when there is no initial wifi data |
| // RequestPosition is not called until a few seconds after the provider is |
| // started to allow time for the wifi scan to complete. To avoid waiting, |
| // grant permissions once the provider is running to cause RequestPosition to |
| // be called immediately. |
| provider->OnPermissionGranted(); |
| |
| // Ensure there was an error callback. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1, listener.error_count); |
| |
| // Now try to make a request for new wifi data. |
| wifi_data_provider_->set_got_data(true); |
| provider->StopProvider(); |
| provider->StartProvider(false); |
| |
| // Normally when starting the provider a network request should be sent |
| // out. This is tested in other tests. However, when we do not have system |
| // permission we should not send out any requests. |
| ASSERT_EQ(0, test_url_loader_factory_.NumPending()); |
| |
| // Now wifi data arrives; new request will try to send but should not |
| // because we do not have permission. |
| wifi_data_provider_->SetData(CreateReferenceWifiScanData(2)); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_EQ(0, test_url_loader_factory_.NumPending()); |
| |
| // Ensure that we immediately make a new network request to acquire the |
| // location when permission is granted. |
| static_cast<NetworkLocationProvider*>(provider.get()) |
| ->OnSystemPermissionUpdated(LocationSystemPermissionStatus::kAllowed); |
| ASSERT_EQ(1, test_url_loader_factory_.NumPending()); |
| |
| // Clear pending requests for later testing. |
| const std::string& request_url = |
| test_url_loader_factory_.pending_requests()->back().request.url.spec(); |
| const char* kReferenceNetworkResponse = |
| R"({ |
| "accuracy": 1200.4, |
| "location": { |
| "lat": 51.0, |
| "lng": -0.1 |
| } |
| })"; |
| test_url_loader_factory_.AddResponse(request_url, kReferenceNetworkResponse); |
| base::RunLoop().RunUntilIdle(); |
| test_url_loader_factory_.ClearResponses(); |
| |
| // Ensure more network requests are not sent out when permission is denied |
| // again. |
| static_cast<NetworkLocationProvider*>(provider.get()) |
| ->OnSystemPermissionUpdated(LocationSystemPermissionStatus::kDenied); |
| provider->StartProvider(false); |
| wifi_data_provider_->SetData(CreateReferenceWifiScanData(4)); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_EQ(0, test_url_loader_factory_.NumPending()); |
| } |
| #endif |
| |
| // Tests that the provider's last position cache delegate is correctly used to |
| // cache the most recent network position estimate, and that this estimate is |
| // not lost when the provider is torn down and recreated. |
| TEST_F(GeolocationNetworkProviderTest, LastPositionCache) { |
| std::unique_ptr<LocationProvider> provider(CreateProvider(true)); |
| provider->StartProvider(false); |
| |
| // Check that the provider is initialized with an invalid position. |
| mojom::Geoposition position = provider->GetPosition(); |
| EXPECT_FALSE(ValidateGeoposition(position)); |
| |
| // Check that the cached value is also invalid. |
| position = position_cache_.GetLastUsedNetworkPosition(); |
| EXPECT_FALSE(ValidateGeoposition(position)); |
| |
| // Now wifi data arrives -- SetData will notify listeners. |
| const int kFirstScanAps = 6; |
| wifi_data_provider_->SetData(CreateReferenceWifiScanData(kFirstScanAps)); |
| base::RunLoop().RunUntilIdle(); |
| // The request should have the wifi data. |
| CheckRequestIsValid(kFirstScanAps, 0); |
| |
| // Send a reply with good position fix. |
| ASSERT_EQ(1, test_url_loader_factory_.NumPending()); |
| const std::string& request_url = |
| test_url_loader_factory_.pending_requests()->back().request.url.spec(); |
| const char* kReferenceNetworkResponse = |
| "{" |
| " \"accuracy\": 1200.4," |
| " \"location\": {" |
| " \"lat\": 51.0," |
| " \"lng\": -0.1" |
| " }" |
| "}"; |
| test_url_loader_factory_.AddResponse(request_url, kReferenceNetworkResponse); |
| base::RunLoop().RunUntilIdle(); |
| |
| // The provider should return the position as the current best estimate. |
| position = provider->GetPosition(); |
| EXPECT_EQ(51.0, position.latitude); |
| EXPECT_EQ(-0.1, position.longitude); |
| EXPECT_EQ(1200.4, position.accuracy); |
| EXPECT_FALSE(position.timestamp.is_null()); |
| EXPECT_TRUE(ValidateGeoposition(position)); |
| |
| // Shut down the provider. This typically happens whenever there are no active |
| // Geolocation API calls. |
| provider->StopProvider(); |
| provider = nullptr; |
| |
| // The cache preserves the last estimate while the provider is inactive. |
| position = position_cache_.GetLastUsedNetworkPosition(); |
| EXPECT_EQ(51.0, position.latitude); |
| EXPECT_EQ(-0.1, position.longitude); |
| EXPECT_EQ(1200.4, position.accuracy); |
| EXPECT_FALSE(position.timestamp.is_null()); |
| EXPECT_TRUE(ValidateGeoposition(position)); |
| |
| // Restart the provider. |
| provider = CreateProvider(true); |
| provider->StartProvider(false); |
| |
| // Check that the most recent position estimate is retained. |
| position = provider->GetPosition(); |
| EXPECT_EQ(51.0, position.latitude); |
| EXPECT_EQ(-0.1, position.longitude); |
| EXPECT_EQ(1200.4, position.accuracy); |
| EXPECT_FALSE(position.timestamp.is_null()); |
| EXPECT_TRUE(ValidateGeoposition(position)); |
| } |
| |
| // Tests that when the last network position estimate is sufficiently recent and |
| // we do not expect to receive a fresh estimate soon (no new wifi data available |
| // and no pending geolocation service request) then the provider may return the |
| // last position instead of waiting to acquire a fresh estimate. |
| TEST_F(GeolocationNetworkProviderTest, LastPositionCacheUsed) { |
| LocationUpdateListener listener; |
| |
| // Seed the last position cache with a valid geoposition value and the |
| // timestamp set to the current time. |
| mojom::Geoposition last_position = CreateReferencePosition(0); |
| EXPECT_TRUE(ValidateGeoposition(last_position)); |
| position_cache_.SetLastUsedNetworkPosition(last_position); |
| |
| // Simulate no initial wifi data. |
| wifi_data_provider_->set_got_data(false); |
| |
| // Start the provider without geolocation permission. |
| std::unique_ptr<LocationProvider> provider(CreateProvider(false)); |
| provider->StartProvider(false); |
| |
| // Register a location update callback. The listener will count how many times |
| // OnLocationUpdate is called. |
| provider->SetUpdateCallback(listener.callback); |
| |
| // Under normal circumstances, when there is no initial wifi data |
| // RequestPosition is not called until a few seconds after the provider is |
| // started to allow time for the wifi scan to complete. To avoid waiting, |
| // grant permissions once the provider is running to cause RequestPosition to |
| // be called immediately. |
| provider->OnPermissionGranted(); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| // Check that the listener received the position update and that the position |
| // is the same as the seeded value except for the timestamp, which should be |
| // newer. |
| EXPECT_EQ(1, listener.update_count); |
| EXPECT_TRUE(ValidateGeoposition(listener.last_position)); |
| EXPECT_EQ(last_position.latitude, listener.last_position.latitude); |
| EXPECT_EQ(last_position.longitude, listener.last_position.longitude); |
| EXPECT_EQ(last_position.accuracy, listener.last_position.accuracy); |
| EXPECT_LT(last_position.timestamp, listener.last_position.timestamp); |
| } |
| |
| // Tests that the last network position estimate is not returned if the |
| // estimate is too old. |
| TEST_F(GeolocationNetworkProviderTest, LastPositionNotUsedTooOld) { |
| LocationUpdateListener listener; |
| |
| // Seed the last position cache with a geoposition value with the timestamp |
| // set to 20 minutes ago. |
| mojom::Geoposition last_position = CreateReferencePosition(0); |
| last_position.timestamp = base::Time::Now() - base::Minutes(20); |
| EXPECT_TRUE(ValidateGeoposition(last_position)); |
| position_cache_.SetLastUsedNetworkPosition(last_position); |
| |
| // Simulate no initial wifi data. |
| wifi_data_provider_->set_got_data(false); |
| |
| // Start the provider without geolocation permission. |
| std::unique_ptr<LocationProvider> provider(CreateProvider(false)); |
| provider->StartProvider(false); |
| |
| // Register a location update callback. The listener will count how many times |
| // OnLocationUpdate is called. |
| provider->SetUpdateCallback(listener.callback); |
| |
| // Under normal circumstances, when there is no initial wifi data |
| // RequestPosition is not called until a few seconds after the provider is |
| // started to allow time for the wifi scan to complete. To avoid waiting, |
| // grant permissions once the provider is running to cause RequestPosition to |
| // be called immediately. |
| provider->OnPermissionGranted(); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| // Check that the listener received no updates. |
| EXPECT_EQ(0, listener.update_count); |
| EXPECT_FALSE(ValidateGeoposition(listener.last_position)); |
| } |
| |
| // Tests that the last network position estimate is not returned if there is |
| // new wifi data or a pending geolocation service request. |
| TEST_F(GeolocationNetworkProviderTest, LastPositionNotUsedNewData) { |
| LocationUpdateListener listener; |
| |
| // Seed the last position cache with a valid geoposition value. The timestamp |
| // of the cached position is set to the current time. |
| mojom::Geoposition last_position = CreateReferencePosition(0); |
| EXPECT_TRUE(ValidateGeoposition(last_position)); |
| position_cache_.SetLastUsedNetworkPosition(last_position); |
| |
| // Simulate a completed wifi scan. |
| const int kFirstScanAps = 6; |
| wifi_data_provider_->SetData(CreateReferenceWifiScanData(kFirstScanAps)); |
| |
| // Create the provider without permissions enabled. |
| std::unique_ptr<LocationProvider> provider(CreateProvider(false)); |
| |
| // Register a location update callback. The callback will count how many times |
| // OnLocationUpdate is called. |
| provider->SetUpdateCallback(listener.callback); |
| |
| // Start the provider. |
| provider->StartProvider(false); |
| base::RunLoop().RunUntilIdle(); |
| |
| // The listener should not receive any updates. There is a valid cached value |
| // but it should not be sent while we have pending wifi data. |
| EXPECT_EQ(0, listener.update_count); |
| EXPECT_FALSE(ValidateGeoposition(listener.last_position)); |
| |
| // Check that there is no pending network request. |
| EXPECT_EQ(0, test_url_loader_factory_.NumPending()); |
| |
| // Simulate no new wifi data. |
| wifi_data_provider_->set_got_data(false); |
| |
| // Grant permission to allow the network request to proceed. |
| provider->OnPermissionGranted(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // The listener should still not receive any updates. There is a valid cached |
| // value and no new wifi data, but the cached value should not be sent while |
| // we have a pending request to the geolocation service. |
| EXPECT_EQ(0, listener.update_count); |
| EXPECT_FALSE(ValidateGeoposition(listener.last_position)); |
| |
| // Check that a network request is pending. |
| EXPECT_EQ(1, test_url_loader_factory_.NumPending()); |
| } |
| |
| } // namespace device |