diff options
author | bartfab@chromium.org <bartfab@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-05-04 15:34:03 +0000 |
---|---|---|
committer | bartfab@chromium.org <bartfab@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-05-04 15:34:03 +0000 |
commit | b2f0bb484b12d50613019ac84beeebb9942dc7f8 (patch) | |
tree | 4b648785066ca7f946a38b20e08c234b48114880 | |
parent | 72f582938721f2ad69e3eb034d0562938ae1c4f7 (diff) | |
download | chromium_src-b2f0bb484b12d50613019ac84beeebb9942dc7f8.zip chromium_src-b2f0bb484b12d50613019ac84beeebb9942dc7f8.tar.gz chromium_src-b2f0bb484b12d50613019ac84beeebb9942dc7f8.tar.bz2 |
Add device location reporting
This CL implements device location reporting for Enterprise-enrolled Chrome OS devices. The position is determined using the existing Chrome geolocation stack.
This CL depends on https://chromiumcodereview.appspot.com/10344016/
ISSUE=chromium-os:18710
TEST=unit_tests including new DeviceStatusCollectorTest.Location
Review URL: http://codereview.chromium.org/10103029
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@135348 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/chromeos/cros_settings_names.cc | 8 | ||||
-rw-r--r-- | chrome/browser/chromeos/cros_settings_names.h | 1 | ||||
-rw-r--r-- | chrome/browser/chromeos/device_settings_provider.cc | 17 | ||||
-rw-r--r-- | chrome/browser/chromeos/login/version_info_updater.cc | 1 | ||||
-rw-r--r-- | chrome/browser/chromeos/stub_cros_settings_provider.cc | 1 | ||||
-rw-r--r-- | chrome/browser/policy/browser_policy_connector.cc | 3 | ||||
-rw-r--r-- | chrome/browser/policy/device_status_collector.cc | 184 | ||||
-rw-r--r-- | chrome/browser/policy/device_status_collector.h | 45 | ||||
-rw-r--r-- | chrome/browser/policy/device_status_collector_unittest.cc | 287 | ||||
-rw-r--r-- | chrome/browser/policy/proto/device_management_backend.proto | 41 | ||||
-rw-r--r-- | chrome/common/pref_names.cc | 14 | ||||
-rw-r--r-- | chrome/common/pref_names.h | 3 |
12 files changed, 484 insertions, 121 deletions
diff --git a/chrome/browser/chromeos/cros_settings_names.cc b/chrome/browser/chromeos/cros_settings_names.cc index e619787..fd6a0fb 100644 --- a/chrome/browser/chromeos/cros_settings_names.cc +++ b/chrome/browser/chromeos/cros_settings_names.cc @@ -42,10 +42,14 @@ const char kReportDeviceVersionInfo[] = const char kReportDeviceActivityTimes[] = "cros.device_status.report_activity_times"; -// A boolean pref that indicates whether device the state of the dev switch -// at boot mode should be reported along with device policy requests. +// A boolean pref that indicates whether the state of the dev mode switch at +// boot should be reported along with device policy requests. const char kReportDeviceBootMode[] = "cros.device_status.report_boot_mode"; +// A boolean pref that indicates whether the current location should be reported +// along with device policy requests. +const char kReportDeviceLocation[] = "cros.device_status.report_location"; + // A list of dictionaries, each detailing one extension to install as part of // the AppPack and including the following fields: // "extension-id": ID of the extension to install diff --git a/chrome/browser/chromeos/cros_settings_names.h b/chrome/browser/chromeos/cros_settings_names.h index ac672d5..9284886 100644 --- a/chrome/browser/chromeos/cros_settings_names.h +++ b/chrome/browser/chromeos/cros_settings_names.h @@ -32,6 +32,7 @@ extern const char kReleaseChannelDelegated[]; extern const char kReportDeviceVersionInfo[]; extern const char kReportDeviceActivityTimes[]; extern const char kReportDeviceBootMode[]; +extern const char kReportDeviceLocation[]; extern const char kAppPack[]; diff --git a/chrome/browser/chromeos/device_settings_provider.cc b/chrome/browser/chromeos/device_settings_provider.cc index c24d778..60faf48 100644 --- a/chrome/browser/chromeos/device_settings_provider.cc +++ b/chrome/browser/chromeos/device_settings_provider.cc @@ -53,6 +53,7 @@ const char* kKnownSettings[] = { kReleaseChannelDelegated, kReportDeviceActivityTimes, kReportDeviceBootMode, + kReportDeviceLocation, kReportDeviceVersionInfo, kScreenSaverExtensionId, kScreenSaverTimeout, @@ -287,14 +288,15 @@ void DeviceSettingsProvider::SetInPolicy() { // The remaining settings don't support Set(), since they are not // intended to be customizable by the user: // kAppPack - // kIdleLogoutTimeout, - // kIdleLogoutWarningDuration, - // kReleaseChannelDelegated, + // kIdleLogoutTimeout + // kIdleLogoutWarningDuration + // kReleaseChannelDelegated // kReportDeviceVersionInfo // kReportDeviceActivityTimes // kReportDeviceBootMode - // kScreenSaverExtensionId, - // kScreenSaverTimeout, + // kReportDeviceLocation + // kScreenSaverExtensionId + // kScreenSaverTimeout // kStartUpUrls NOTREACHED(); @@ -490,6 +492,11 @@ void DeviceSettingsProvider::DecodeReportingPolicies( kReportDeviceBootMode, policy.device_reporting().report_boot_mode()); } + if (policy.device_reporting().has_report_location()) { + new_values_cache->SetBoolean( + kReportDeviceLocation, + policy.device_reporting().report_location()); + } } } diff --git a/chrome/browser/chromeos/login/version_info_updater.cc b/chrome/browser/chromeos/login/version_info_updater.cc index dde2af2..f429916 100644 --- a/chrome/browser/chromeos/login/version_info_updater.cc +++ b/chrome/browser/chromeos/login/version_info_updater.cc @@ -38,6 +38,7 @@ const char* kReportingFlags[] = { chromeos::kReportDeviceVersionInfo, chromeos::kReportDeviceActivityTimes, chromeos::kReportDeviceBootMode, + chromeos::kReportDeviceLocation, }; } diff --git a/chrome/browser/chromeos/stub_cros_settings_provider.cc b/chrome/browser/chromeos/stub_cros_settings_provider.cc index b20d8f7..64a5318 100644 --- a/chrome/browser/chromeos/stub_cros_settings_provider.cc +++ b/chrome/browser/chromeos/stub_cros_settings_provider.cc @@ -26,6 +26,7 @@ const char* kHandledSettings[] = { kReportDeviceVersionInfo, kReportDeviceActivityTimes, kReportDeviceBootMode, + kReportDeviceLocation, kSettingProxyEverywhere, kSignedDataRoamingEnabled, kStatsReportingPref, diff --git a/chrome/browser/policy/browser_policy_connector.cc b/chrome/browser/policy/browser_policy_connector.cc index 1ca3af7..080d941 100644 --- a/chrome/browser/policy/browser_policy_connector.cc +++ b/chrome/browser/policy/browser_policy_connector.cc @@ -494,7 +494,8 @@ void BrowserPolicyConnector::CompleteInitialization() { device_data_store_->set_device_status_collector( new DeviceStatusCollector( g_browser_process->local_state(), - chromeos::system::StatisticsProvider::GetInstance())); + chromeos::system::StatisticsProvider::GetInstance(), + NULL)); #endif } diff --git a/chrome/browser/policy/device_status_collector.cc b/chrome/browser/policy/device_status_collector.cc index 5a1be4a..cb547cf 100644 --- a/chrome/browser/policy/device_status_collector.cc +++ b/chrome/browser/policy/device_status_collector.cc @@ -5,8 +5,12 @@ #include "chrome/browser/policy/device_status_collector.h" #include "base/bind.h" -#include "base/callback.h" +#include "base/bind_helpers.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" #include "base/string_number_conversions.h" +#include "base/values.h" #include "chrome/browser/chromeos/cros_settings.h" #include "chrome/browser/chromeos/cros_settings_names.h" #include "chrome/browser/chromeos/system/statistics_provider.h" @@ -16,6 +20,8 @@ #include "chrome/common/chrome_notification_types.h" #include "chrome/common/chrome_version_info.h" #include "chrome/common/pref_names.h" +#include "content/public/browser/notification_details.h" +#include "content/public/browser/notification_source.h" using base::Time; using base::TimeDelta; @@ -33,10 +39,29 @@ const unsigned int kMaxStoredPastActivityDays = 30; // How many days in the future to store active periods for. const unsigned int kMaxStoredFutureActivityDays = 2; +// How often, in seconds, to update the device location. +const unsigned int kGeolocationPollIntervalSeconds = 30 * 60; + const int64 kMillisecondsPerDay = Time::kMicrosecondsPerDay / 1000; +const char kLatitude[] = "latitude"; + +const char kLongitude[] = "longitude"; + +const char kAltitude[] = "altitude"; + +const char kAccuracy[] = "accuracy"; + +const char kAltitudeAccuracy[] = "altitude_accuracy"; + +const char kHeading[] = "heading"; + +const char kSpeed[] = "speed"; + +const char kTimestamp[] = "timestamp"; + // Record device activity for the specified day into the given dictionary. -void AddDeviceActivity(DictionaryValue* activity_times, +void AddDeviceActivity(base::DictionaryValue* activity_times, Time day_midnight, TimeDelta activity) { DCHECK(activity.InMilliseconds() < kMillisecondsPerDay); @@ -55,18 +80,25 @@ namespace policy { DeviceStatusCollector::DeviceStatusCollector( PrefService* local_state, - chromeos::system::StatisticsProvider* provider) + chromeos::system::StatisticsProvider* provider, + LocationUpdateRequester location_update_requester) : max_stored_past_activity_days_(kMaxStoredPastActivityDays), max_stored_future_activity_days_(kMaxStoredFutureActivityDays), local_state_(local_state), last_idle_check_(Time()), + geolocation_update_in_progress_(false), statistics_provider_(provider), + ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)), + location_update_requester_(location_update_requester), report_version_info_(false), report_activity_times_(false), - report_boot_mode_(false) { - timer_.Start(FROM_HERE, - TimeDelta::FromSeconds(kPollIntervalSeconds), - this, &DeviceStatusCollector::CheckIdleState); + report_boot_mode_(false), + report_location_(false) { + if (!location_update_requester_) + location_update_requester_ = &content::RequestLocationUpdate; + idle_poll_timer_.Start(FROM_HERE, + TimeDelta::FromSeconds(kIdlePollIntervalSeconds), + this, &DeviceStatusCollector::CheckIdleState); cros_settings_ = chromeos::CrosSettings::Get(); @@ -76,6 +108,28 @@ DeviceStatusCollector::DeviceStatusCollector( cros_settings_->AddSettingsObserver(chromeos::kReportDeviceActivityTimes, this); cros_settings_->AddSettingsObserver(chromeos::kReportDeviceBootMode, this); + cros_settings_->AddSettingsObserver(chromeos::kReportDeviceLocation, this); + + // The last known location is persisted in local state. This makes location + // information available immediately upon startup and avoids the need to + // reacquire the location on every user session change or browser crash. + content::Geoposition position; + std::string timestamp_str; + int64 timestamp; + const base::DictionaryValue* location = + local_state_->GetDictionary(prefs::kDeviceLocation); + if (location->GetDouble(kLatitude, &position.latitude) && + location->GetDouble(kLongitude, &position.longitude) && + location->GetDouble(kAltitude, &position.altitude) && + location->GetDouble(kAccuracy, &position.accuracy) && + location->GetDouble(kAltitudeAccuracy, &position.altitude_accuracy) && + location->GetDouble(kHeading, &position.heading) && + location->GetDouble(kSpeed, &position.speed) && + location->GetString(kTimestamp, ×tamp_str) && + base::StringToInt64(timestamp_str, ×tamp)) { + position.timestamp = Time::FromInternalValue(timestamp); + position_ = position; + } // Fetch the current values of the policies. UpdateReportingSettings(); @@ -96,12 +150,15 @@ DeviceStatusCollector::~DeviceStatusCollector() { cros_settings_->RemoveSettingsObserver(chromeos::kReportDeviceActivityTimes, this); cros_settings_->RemoveSettingsObserver(chromeos::kReportDeviceBootMode, this); + cros_settings_->RemoveSettingsObserver(chromeos::kReportDeviceLocation, this); } // static void DeviceStatusCollector::RegisterPrefs(PrefService* local_state) { local_state->RegisterDictionaryPref(prefs::kDeviceActivityTimes, - new DictionaryValue); + new base::DictionaryValue); + local_state->RegisterDictionaryPref(prefs::kDeviceLocation, + new base::DictionaryValue); } void DeviceStatusCollector::CheckIdleState() { @@ -125,6 +182,16 @@ void DeviceStatusCollector::UpdateReportingSettings() { chromeos::kReportDeviceActivityTimes, &report_activity_times_); cros_settings_->GetBoolean( chromeos::kReportDeviceBootMode, &report_boot_mode_); + cros_settings_->GetBoolean( + chromeos::kReportDeviceLocation, &report_location_); + + if (report_location_) { + ScheduleGeolocationUpdateRequest(); + } else { + geolocation_update_timer_.Stop(); + position_ = content::Geoposition(); + local_state_->ClearPref(prefs::kDeviceLocation); + } } Time DeviceStatusCollector::GetCurrentTime() { @@ -133,7 +200,7 @@ Time DeviceStatusCollector::GetCurrentTime() { // Remove all out-of-range activity times from the local store. void DeviceStatusCollector::PruneStoredActivityPeriods(Time base_time) { - const DictionaryValue* activity_times = + const base::DictionaryValue* activity_times = local_state_->GetDictionary(prefs::kDeviceActivityTimes); if (activity_times->size() <= max_stored_past_activity_days_ + max_stored_future_activity_days_) @@ -145,8 +212,8 @@ void DeviceStatusCollector::PruneStoredActivityPeriods(Time base_time) { base_time + TimeDelta::FromDays(max_stored_future_activity_days_); const Time epoch = Time::UnixEpoch(); - scoped_ptr<DictionaryValue> copy(activity_times->DeepCopy()); - for (DictionaryValue::key_iterator it = activity_times->begin_keys(); + scoped_ptr<base::DictionaryValue> copy(activity_times->DeepCopy()); + for (base::DictionaryValue::key_iterator it = activity_times->begin_keys(); it != activity_times->end_keys(); ++it) { int64 timestamp; @@ -167,7 +234,7 @@ void DeviceStatusCollector::AddActivePeriod(Time start, Time end) { // Maintain the list of active periods in a local_state pref. DictionaryPrefUpdate update(local_state_, prefs::kDeviceActivityTimes); - DictionaryValue* activity_times = update.Get(); + base::DictionaryValue* activity_times = update.Get(); Time midnight = end.LocalMidnight(); @@ -201,8 +268,9 @@ void DeviceStatusCollector::IdleStateCallback(IdleState state) { // interval of activity. int active_seconds = (now - last_idle_check_).InSeconds(); if (active_seconds < 0 || - active_seconds >= static_cast<int>((2 * kPollIntervalSeconds))) - AddActivePeriod(now - TimeDelta::FromSeconds(kPollIntervalSeconds), now); + active_seconds >= static_cast<int>((2 * kIdlePollIntervalSeconds))) + AddActivePeriod(now - TimeDelta::FromSeconds(kIdlePollIntervalSeconds), + now); else AddActivePeriod(last_idle_check_, now); @@ -214,9 +282,9 @@ void DeviceStatusCollector::IdleStateCallback(IdleState state) { void DeviceStatusCollector::GetActivityTimes( em::DeviceStatusReportRequest* request) { DictionaryPrefUpdate update(local_state_, prefs::kDeviceActivityTimes); - DictionaryValue* activity_times = update.Get(); + base::DictionaryValue* activity_times = update.Get(); - for (DictionaryValue::key_iterator it = activity_times->begin_keys(); + for (base::DictionaryValue::key_iterator it = activity_times->begin_keys(); it != activity_times->end_keys(); ++it) { int64 start_timestamp; int activity_milliseconds; @@ -258,6 +326,32 @@ void DeviceStatusCollector::GetBootMode( } } +void DeviceStatusCollector::GetLocation( + em::DeviceStatusReportRequest* request) { + em::DeviceLocation* location = request->mutable_device_location(); + if (!position_.Validate()) { + location->set_error_code( + em::DeviceLocation::ERROR_CODE_POSITION_UNAVAILABLE); + location->set_error_message(position_.error_message); + } else { + location->set_latitude(position_.latitude); + location->set_longitude(position_.longitude); + location->set_accuracy(position_.accuracy); + location->set_timestamp( + (position_.timestamp - Time::UnixEpoch()).InMilliseconds()); + // Lowest point on land is at approximately -400 meters. + if (position_.altitude > -10000.) + location->set_altitude(position_.altitude); + if (position_.altitude_accuracy >= 0.) + location->set_altitude_accuracy(position_.altitude_accuracy); + if (position_.heading >= 0. && position_.heading <= 360) + location->set_heading(position_.heading); + if (position_.speed >= 0.) + location->set_speed(position_.speed); + location->set_error_code(em::DeviceLocation::ERROR_CODE_NONE); + } +} + void DeviceStatusCollector::GetStatus(em::DeviceStatusReportRequest* request) { if (report_activity_times_) GetActivityTimes(request); @@ -267,6 +361,9 @@ void DeviceStatusCollector::GetStatus(em::DeviceStatusReportRequest* request) { if (report_boot_mode_) GetBootMode(request); + + if (report_location_) + GetLocation(request); } void DeviceStatusCollector::OnOSVersion(VersionLoader::Handle handle, @@ -289,4 +386,59 @@ void DeviceStatusCollector::Observe( NOTREACHED(); } +void DeviceStatusCollector::ScheduleGeolocationUpdateRequest() { + if (geolocation_update_timer_.IsRunning() || geolocation_update_in_progress_) + return; + + if (position_.Validate()) { + TimeDelta elapsed = Time::Now() - position_.timestamp; + TimeDelta interval = + TimeDelta::FromSeconds(kGeolocationPollIntervalSeconds); + if (elapsed > interval) { + geolocation_update_in_progress_ = true; + location_update_requester_(base::Bind( + &DeviceStatusCollector::ReceiveGeolocationUpdate, + weak_factory_.GetWeakPtr())); + } else { + geolocation_update_timer_.Start( + FROM_HERE, + interval - elapsed, + this, + &DeviceStatusCollector::ScheduleGeolocationUpdateRequest); + } + } else { + geolocation_update_in_progress_ = true; + location_update_requester_(base::Bind( + &DeviceStatusCollector::ReceiveGeolocationUpdate, + weak_factory_.GetWeakPtr())); + + } +} + +void DeviceStatusCollector::ReceiveGeolocationUpdate( + const content::Geoposition& position) { + geolocation_update_in_progress_ = false; + + // Ignore update if device location reporting has since been disabled. + if (!report_location_) + return; + + if (position.Validate()) { + position_ = position; + base::DictionaryValue location; + location.SetDouble(kLatitude, position.latitude); + location.SetDouble(kLongitude, position.longitude); + location.SetDouble(kAltitude, position.altitude); + location.SetDouble(kAccuracy, position.accuracy); + location.SetDouble(kAltitudeAccuracy, position.altitude_accuracy); + location.SetDouble(kHeading, position.heading); + location.SetDouble(kSpeed, position.speed); + location.SetString(kTimestamp, + base::Int64ToString(position.timestamp.ToInternalValue())); + local_state_->Set(prefs::kDeviceLocation, location); + } + + ScheduleGeolocationUpdateRequest(); +} + } // namespace policy diff --git a/chrome/browser/policy/device_status_collector.h b/chrome/browser/policy/device_status_collector.h index 4afffcb..254a318 100644 --- a/chrome/browser/policy/device_status_collector.h +++ b/chrome/browser/policy/device_status_collector.h @@ -6,11 +6,19 @@ #define CHROME_BROWSER_POLICY_DEVICE_STATUS_COLLECTOR_H_ #pragma once +#include <string> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/weak_ptr.h" #include "base/time.h" #include "base/timer.h" +#include "chrome/browser/cancelable_request.h" #include "chrome/browser/chromeos/version_loader.h" #include "chrome/browser/idle.h" +#include "content/public/browser/geolocation.h" #include "content/public/browser/notification_observer.h" +#include "content/public/common/geoposition.h" namespace chromeos { class CrosSettings; @@ -19,6 +27,11 @@ class StatisticsProvider; } } +namespace content { +class NotificationDetails; +class NotificationSource; +} + namespace enterprise_management { class DeviceStatusReportRequest; } @@ -30,8 +43,14 @@ namespace policy { // Collects and summarizes the status of an enterprised-managed ChromeOS device. class DeviceStatusCollector : public content::NotificationObserver { public: + // TODO(bartfab): Remove this once crbug.com/125931 is addressed and a proper + // way to mock geolocation exists. + typedef void(*LocationUpdateRequester)( + const content::GeolocationUpdateCallback& callback); + DeviceStatusCollector(PrefService* local_state, - chromeos::system::StatisticsProvider* provider); + chromeos::system::StatisticsProvider* provider, + LocationUpdateRequester location_update_requester); virtual ~DeviceStatusCollector(); void GetStatus(enterprise_management::DeviceStatusReportRequest* request); @@ -39,7 +58,7 @@ class DeviceStatusCollector : public content::NotificationObserver { static void RegisterPrefs(PrefService* local_state); // How often, in seconds, to poll to see if the user is idle. - static const unsigned int kPollIntervalSeconds = 30; + static const unsigned int kIdlePollIntervalSeconds = 30; protected: // Check whether the user has been idle for a certain period of time. @@ -80,6 +99,8 @@ class DeviceStatusCollector : public content::NotificationObserver { enterprise_management::DeviceStatusReportRequest* request); void GetBootMode( enterprise_management::DeviceStatusReportRequest* request); + void GetLocation( + enterprise_management::DeviceStatusReportRequest* request); // Update the cached values of the reporting settings. void UpdateReportingSettings(); @@ -90,6 +111,11 @@ class DeviceStatusCollector : public content::NotificationObserver { const content::NotificationSource& source, const content::NotificationDetails& details) OVERRIDE; + void ScheduleGeolocationUpdateRequest(); + + // content::GeolocationUpdateCallback implementation. + void ReceiveGeolocationUpdate(const content::Geoposition&); + // How often to poll to see if the user is idle. int poll_interval_seconds_; @@ -98,7 +124,11 @@ class DeviceStatusCollector : public content::NotificationObserver { // The last time an idle state check was performed. base::Time last_idle_check_; - base::RepeatingTimer<DeviceStatusCollector> timer_; + // Whether a geolocation update is currently in progress. + bool geolocation_update_in_progress_; + + base::RepeatingTimer<DeviceStatusCollector> idle_poll_timer_; + base::OneShotTimer<DeviceStatusCollector> geolocation_update_timer_; chromeos::VersionLoader version_loader_; CancelableRequestConsumer consumer_; @@ -106,14 +136,23 @@ class DeviceStatusCollector : public content::NotificationObserver { std::string os_version_; std::string firmware_version_; + content::Geoposition position_; + chromeos::system::StatisticsProvider* statistics_provider_; chromeos::CrosSettings* cros_settings_; + base::WeakPtrFactory<DeviceStatusCollector> weak_factory_; + + // TODO(bartfab): Remove this once crbug.com/125931 is addressed and a proper + // way to mock geolocation exists. + LocationUpdateRequester location_update_requester_; + // Cached values of the reporting settings from the device policy. bool report_version_info_; bool report_activity_times_; bool report_boot_mode_; + bool report_location_; DISALLOW_COPY_AND_ASSIGN(DeviceStatusCollector); }; diff --git a/chrome/browser/policy/device_status_collector_unittest.cc b/chrome/browser/policy/device_status_collector_unittest.cc index 3dcfb3f..ca4ff99 100644 --- a/chrome/browser/policy/device_status_collector_unittest.cc +++ b/chrome/browser/policy/device_status_collector_unittest.cc @@ -4,21 +4,29 @@ #include "chrome/browser/policy/device_status_collector.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" #include "base/message_loop.h" -#include "base/time.h" -#include "chrome/browser/idle.h" #include "chrome/browser/chromeos/cros_settings.h" #include "chrome/browser/chromeos/cros_settings_names.h" #include "chrome/browser/chromeos/cros_settings_provider.h" #include "chrome/browser/chromeos/stub_cros_settings_provider.h" #include "chrome/browser/chromeos/system/mock_statistics_provider.h" +#include "chrome/browser/chromeos/system/statistics_provider.h" #include "chrome/browser/policy/proto/device_management_backend.pb.h" #include "chrome/browser/prefs/pref_service.h" +#include "chrome/common/pref_names.h" #include "chrome/test/base/testing_pref_service.h" +#include "content/public/browser/browser_thread.h" #include "content/test/test_browser_thread.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" +using ::testing::_; +using ::testing::DoAll; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::SetArgPointee; using base::TimeDelta; using base::Time; @@ -28,13 +36,36 @@ namespace { const int64 kMillisecondsPerDay = Time::kMicrosecondsPerDay / 1000; +scoped_ptr<content::Geoposition> mock_position_to_return_next; + +void SetMockPositionToReturnNext(const content::Geoposition &position) { + mock_position_to_return_next.reset(new content::Geoposition(position)); +} + +void MockPositionUpdateRequester( + const content::GeolocationUpdateCallback& callback) { + if (!mock_position_to_return_next.get()) + return; + + // If the fix is invalid, the DeviceStatusCollector will immediately request + // another update when it receives the callback. This is desirable and safe in + // real life where geolocation updates arrive asynchronously. In this testing + // harness, the callback is invoked synchronously upon request, leading to a + // request-callback loop. The loop is broken by returning the mock position + // only once. + scoped_ptr<content::Geoposition> position( + mock_position_to_return_next.release()); + callback.Run(*position); +} + class TestingDeviceStatusCollector : public policy::DeviceStatusCollector { public: TestingDeviceStatusCollector( PrefService* local_state, chromeos::system::StatisticsProvider* provider) - : policy::DeviceStatusCollector(local_state, provider), - local_state_(local_state) { + : policy::DeviceStatusCollector(local_state, + provider, + &MockPositionUpdateRequester) { // Set the baseline time to a fixed value (1 AM) to prevent test flakiness // due to a single activity period spanning two days. SetBaselineTime(Time::Now().LocalMidnight() + TimeDelta::FromHours(1)); @@ -68,14 +99,12 @@ class TestingDeviceStatusCollector : public policy::DeviceStatusCollector { // Each time this is called, returns a time that is a fixed increment // later than the previous time. virtual Time GetCurrentTime() OVERRIDE { - int poll_interval = policy::DeviceStatusCollector::kPollIntervalSeconds; + int poll_interval = policy::DeviceStatusCollector::kIdlePollIntervalSeconds; return baseline_time_ + TimeDelta::FromSeconds(poll_interval * baseline_offset_periods_++); } private: - PrefService* local_state_; - // Baseline time for the fake times returned from GetCurrentTime(). Time baseline_time_; @@ -97,32 +126,28 @@ int64 GetActiveMilliseconds(em::DeviceStatusReportRequest& status) { namespace policy { -using ::testing::_; -using ::testing::NotNull; -using ::testing::Return; -using ::testing::SetArgPointee; - class DeviceStatusCollectorTest : public testing::Test { public: DeviceStatusCollectorTest() : message_loop_(MessageLoop::TYPE_UI), ui_thread_(content::BrowserThread::UI, &message_loop_), file_thread_(content::BrowserThread::FILE, &message_loop_), - status_collector_(&prefs_, &statistics_provider_) { + io_thread_(content::BrowserThread::IO, &message_loop_) { + TestingDeviceStatusCollector::RegisterPrefs(&prefs_); - DeviceStatusCollector::RegisterPrefs(&prefs_); EXPECT_CALL(statistics_provider_, GetMachineStatistic(_, NotNull())) .WillRepeatedly(Return(false)); - cros_settings_ = chromeos::CrosSettings::Get(); - // Remove the real DeviceSettingsProvider and replace it with a stub. + cros_settings_ = chromeos::CrosSettings::Get(); device_settings_provider_ = cros_settings_->GetProvider(chromeos::kReportDeviceVersionInfo); EXPECT_TRUE(device_settings_provider_ != NULL); EXPECT_TRUE( cros_settings_->RemoveSettingsProvider(device_settings_provider_)); cros_settings_->AddSettingsProvider(&stub_settings_provider_); + + RestartStatusCollector(); } ~DeviceStatusCollectorTest() { @@ -132,19 +157,69 @@ class DeviceStatusCollectorTest : public testing::Test { cros_settings_->AddSettingsProvider(device_settings_provider_); } + void RestartStatusCollector() { + status_collector_.reset( + new TestingDeviceStatusCollector(&prefs_, &statistics_provider_)); + } + + void GetStatus() { + status_.Clear(); + status_collector_->GetStatus(&status_); + } + + void CheckThatNoLocationIsReported() { + GetStatus(); + EXPECT_FALSE(status_.has_device_location()); + } + + void CheckThatAValidLocationIsReported() { + // Checks that a location is being reported which matches the valid fix + // set using SetMockPositionToReturnNext(). + GetStatus(); + EXPECT_TRUE(status_.has_device_location()); + em::DeviceLocation location = status_.device_location(); + if (location.has_error_code()) + EXPECT_EQ(em::DeviceLocation::ERROR_CODE_NONE, location.error_code()); + EXPECT_TRUE(location.has_latitude()); + EXPECT_TRUE(location.has_longitude()); + EXPECT_TRUE(location.has_accuracy()); + EXPECT_TRUE(location.has_timestamp()); + EXPECT_FALSE(location.has_altitude()); + EXPECT_FALSE(location.has_altitude_accuracy()); + EXPECT_FALSE(location.has_heading()); + EXPECT_FALSE(location.has_speed()); + EXPECT_FALSE(location.has_error_message()); + EXPECT_DOUBLE_EQ(4.3, location.latitude()); + EXPECT_DOUBLE_EQ(-7.8, location.longitude()); + EXPECT_DOUBLE_EQ(3., location.accuracy()); + // Check that the timestamp is not older than ten minutes. + EXPECT_TRUE(Time::Now() - Time::FromDoubleT(location.timestamp() / 1000.) < + TimeDelta::FromMinutes(10)); + } + + void CheckThatALocationErrorIsReported() { + GetStatus(); + EXPECT_TRUE(status_.has_device_location()); + em::DeviceLocation location = status_.device_location(); + EXPECT_TRUE(location.has_error_code()); + EXPECT_EQ(em::DeviceLocation::ERROR_CODE_POSITION_UNAVAILABLE, + location.error_code()); + } + protected: // Convenience method. int64 ActivePeriodMilliseconds() { - return policy::DeviceStatusCollector::kPollIntervalSeconds * 1000; + return policy::DeviceStatusCollector::kIdlePollIntervalSeconds * 1000; } MessageLoop message_loop_; content::TestBrowserThread ui_thread_; content::TestBrowserThread file_thread_; + content::TestBrowserThread io_thread_; TestingPrefService prefs_; chromeos::system::MockStatisticsProvider statistics_provider_; - TestingDeviceStatusCollector status_collector_; + scoped_ptr<TestingDeviceStatusCollector> status_collector_; em::DeviceStatusReportRequest status_; chromeos::CrosSettings* cros_settings_; chromeos::CrosSettingsProvider* device_settings_provider_; @@ -160,20 +235,20 @@ TEST_F(DeviceStatusCollectorTest, AllIdle) { cros_settings_->SetBoolean(chromeos::kReportDeviceActivityTimes, true); // Test reporting with no data. - status_collector_.GetStatus(&status_); + GetStatus(); EXPECT_EQ(0, status_.active_period_size()); EXPECT_EQ(0, GetActiveMilliseconds(status_)); // Test reporting with a single idle sample. - status_collector_.Simulate(test_states, 1); - status_collector_.GetStatus(&status_); + status_collector_->Simulate(test_states, 1); + GetStatus(); EXPECT_EQ(0, status_.active_period_size()); EXPECT_EQ(0, GetActiveMilliseconds(status_)); // Test reporting with multiple consecutive idle samples. - status_collector_.Simulate(test_states, - sizeof(test_states) / sizeof(IdleState)); - status_collector_.GetStatus(&status_); + status_collector_->Simulate(test_states, + sizeof(test_states) / sizeof(IdleState)); + GetStatus(); EXPECT_EQ(0, status_.active_period_size()); EXPECT_EQ(0, GetActiveMilliseconds(status_)); } @@ -187,16 +262,16 @@ TEST_F(DeviceStatusCollectorTest, AllActive) { cros_settings_->SetBoolean(chromeos::kReportDeviceActivityTimes, true); // Test a single active sample. - status_collector_.Simulate(test_states, 1); - status_collector_.GetStatus(&status_); + status_collector_->Simulate(test_states, 1); + GetStatus(); EXPECT_EQ(1, status_.active_period_size()); EXPECT_EQ(1 * ActivePeriodMilliseconds(), GetActiveMilliseconds(status_)); status_.clear_active_period(); // Clear the result protobuf. // Test multiple consecutive active samples. - status_collector_.Simulate(test_states, - sizeof(test_states) / sizeof(IdleState)); - status_collector_.GetStatus(&status_); + status_collector_->Simulate(test_states, + sizeof(test_states) / sizeof(IdleState)); + GetStatus(); EXPECT_EQ(1, status_.active_period_size()); EXPECT_EQ(3 * ActivePeriodMilliseconds(), GetActiveMilliseconds(status_)); } @@ -212,9 +287,9 @@ TEST_F(DeviceStatusCollectorTest, MixedStates) { IDLE_STATE_ACTIVE }; cros_settings_->SetBoolean(chromeos::kReportDeviceActivityTimes, true); - status_collector_.Simulate(test_states, - sizeof(test_states) / sizeof(IdleState)); - status_collector_.GetStatus(&status_); + status_collector_->Simulate(test_states, + sizeof(test_states) / sizeof(IdleState)); + GetStatus(); EXPECT_EQ(4 * ActivePeriodMilliseconds(), GetActiveMilliseconds(status_)); } @@ -228,18 +303,17 @@ TEST_F(DeviceStatusCollectorTest, StateKeptInPref) { IDLE_STATE_IDLE }; cros_settings_->SetBoolean(chromeos::kReportDeviceActivityTimes, true); - status_collector_.Simulate(test_states, - sizeof(test_states) / sizeof(IdleState)); - - // Process the list a second time with a different collector. - // It should be able to count the active periods found by the first - // collector, because the results are stored in a pref. - TestingDeviceStatusCollector second_collector(&prefs_, - &statistics_provider_); - second_collector.Simulate(test_states, - sizeof(test_states) / sizeof(IdleState)); - - second_collector.GetStatus(&status_); + status_collector_->Simulate(test_states, + sizeof(test_states) / sizeof(IdleState)); + + // Process the list a second time after restarting the collector. It should be + // able to count the active periods found by the original collector, because + // the results are stored in a pref. + RestartStatusCollector(); + status_collector_->Simulate(test_states, + sizeof(test_states) / sizeof(IdleState)); + + GetStatus(); EXPECT_EQ(6 * ActivePeriodMilliseconds(), GetActiveMilliseconds(status_)); } @@ -253,9 +327,9 @@ TEST_F(DeviceStatusCollectorTest, Times) { IDLE_STATE_IDLE }; cros_settings_->SetBoolean(chromeos::kReportDeviceActivityTimes, true); - status_collector_.Simulate(test_states, - sizeof(test_states) / sizeof(IdleState)); - status_collector_.GetStatus(&status_); + status_collector_->Simulate(test_states, + sizeof(test_states) / sizeof(IdleState)); + GetStatus(); EXPECT_EQ(3 * ActivePeriodMilliseconds(), GetActiveMilliseconds(status_)); } @@ -267,41 +341,41 @@ TEST_F(DeviceStatusCollectorTest, MaxStoredPeriods) { unsigned int max_days = 10; cros_settings_->SetBoolean(chromeos::kReportDeviceActivityTimes, true); - status_collector_.set_max_stored_past_activity_days(max_days - 1); - status_collector_.set_max_stored_future_activity_days(1); + status_collector_->set_max_stored_past_activity_days(max_days - 1); + status_collector_->set_max_stored_future_activity_days(1); Time baseline = Time::Now().LocalMidnight(); // Simulate 12 active periods. for (int i = 0; i < static_cast<int>(max_days) + 2; i++) { - status_collector_.Simulate(test_states, - sizeof(test_states) / sizeof(IdleState)); + status_collector_->Simulate(test_states, + sizeof(test_states) / sizeof(IdleState)); // Advance the simulated clock by a day. baseline += TimeDelta::FromDays(1); - status_collector_.SetBaselineTime(baseline); + status_collector_->SetBaselineTime(baseline); } // Check that we don't exceed the max number of periods. - status_collector_.GetStatus(&status_); + GetStatus(); EXPECT_EQ(static_cast<int>(max_days), status_.active_period_size()); // Simulate some future times. for (int i = 0; i < static_cast<int>(max_days) + 2; i++) { - status_collector_.Simulate(test_states, - sizeof(test_states) / sizeof(IdleState)); + status_collector_->Simulate(test_states, + sizeof(test_states) / sizeof(IdleState)); // Advance the simulated clock by a day. baseline += TimeDelta::FromDays(1); - status_collector_.SetBaselineTime(baseline); + status_collector_->SetBaselineTime(baseline); } // Set the clock back so the previous simulated times are in the future. baseline -= TimeDelta::FromDays(20); - status_collector_.SetBaselineTime(baseline); + status_collector_->SetBaselineTime(baseline); // Collect one more data point to trigger pruning. - status_collector_.Simulate(test_states, 1); + status_collector_->Simulate(test_states, 1); // Check that we don't exceed the max number of periods. status_.clear_active_period(); - status_collector_.GetStatus(&status_); + GetStatus(); EXPECT_LT(status_.active_period_size(), static_cast<int>(max_days)); } @@ -314,9 +388,9 @@ TEST_F(DeviceStatusCollectorTest, ActivityTimesDisabledByDefault) { IDLE_STATE_ACTIVE, IDLE_STATE_ACTIVE }; - status_collector_.Simulate(test_states, - sizeof(test_states) / sizeof(IdleState)); - status_collector_.GetStatus(&status_); + status_collector_->Simulate(test_states, + sizeof(test_states) / sizeof(IdleState)); + GetStatus(); EXPECT_EQ(0, status_.active_period_size()); EXPECT_EQ(0, GetActiveMilliseconds(status_)); } @@ -328,11 +402,11 @@ TEST_F(DeviceStatusCollectorTest, ActivityCrossingMidnight) { cros_settings_->SetBoolean(chromeos::kReportDeviceActivityTimes, true); // Set the baseline time to 10 seconds after midnight. - status_collector_.SetBaselineTime( + status_collector_->SetBaselineTime( Time::Now().LocalMidnight() + TimeDelta::FromSeconds(10)); - status_collector_.Simulate(test_states, 1); - status_collector_.GetStatus(&status_); + status_collector_->Simulate(test_states, 1); + GetStatus(); ASSERT_EQ(2, status_.active_period_size()); em::ActiveTimePeriod period0 = status_.active_period(0); @@ -354,13 +428,11 @@ TEST_F(DeviceStatusCollectorTest, ActivityCrossingMidnight) { TEST_F(DeviceStatusCollectorTest, DevSwitchBootMode) { // Test that boot mode data is not reported if the pref is not turned on. - status_collector_.GetStatus(&status_); - EXPECT_EQ(false, status_.has_boot_mode()); - EXPECT_CALL(statistics_provider_, GetMachineStatistic("devsw_boot", NotNull())) .WillRepeatedly(DoAll(SetArgPointee<1>("0"), Return(true))); - EXPECT_EQ(false, status_.has_boot_mode()); + GetStatus(); + EXPECT_FALSE(status_.has_boot_mode()); // Turn the pref on, and check that the status is reported iff the // statistics provider returns valid data. @@ -369,41 +441,41 @@ TEST_F(DeviceStatusCollectorTest, DevSwitchBootMode) { EXPECT_CALL(statistics_provider_, GetMachineStatistic("devsw_boot", NotNull())) .WillOnce(DoAll(SetArgPointee<1>("(error)"), Return(true))); - status_collector_.GetStatus(&status_); - EXPECT_EQ(false, status_.has_boot_mode()); + GetStatus(); + EXPECT_FALSE(status_.has_boot_mode()); EXPECT_CALL(statistics_provider_, GetMachineStatistic("devsw_boot", NotNull())) .WillOnce(DoAll(SetArgPointee<1>(" "), Return(true))); - status_collector_.GetStatus(&status_); - EXPECT_EQ(false, status_.has_boot_mode()); + GetStatus(); + EXPECT_FALSE(status_.has_boot_mode()); EXPECT_CALL(statistics_provider_, GetMachineStatistic("devsw_boot", NotNull())) .WillOnce(DoAll(SetArgPointee<1>("0"), Return(true))); - status_collector_.GetStatus(&status_); + GetStatus(); EXPECT_EQ("Verified", status_.boot_mode()); EXPECT_CALL(statistics_provider_, GetMachineStatistic("devsw_boot", NotNull())) .WillOnce(DoAll(SetArgPointee<1>("1"), Return(true))); - status_collector_.GetStatus(&status_); + GetStatus(); EXPECT_EQ("Dev", status_.boot_mode()); } TEST_F(DeviceStatusCollectorTest, VersionInfo) { // When the pref to collect this data is not enabled, expect that none of // the fields are present in the protobuf. - status_collector_.GetStatus(&status_); - EXPECT_EQ(false, status_.has_browser_version()); - EXPECT_EQ(false, status_.has_os_version()); - EXPECT_EQ(false, status_.has_firmware_version()); + GetStatus(); + EXPECT_FALSE(status_.has_browser_version()); + EXPECT_FALSE(status_.has_os_version()); + EXPECT_FALSE(status_.has_firmware_version()); cros_settings_->SetBoolean(chromeos::kReportDeviceVersionInfo, true); - status_collector_.GetStatus(&status_); - EXPECT_EQ(true, status_.has_browser_version()); - EXPECT_EQ(true, status_.has_os_version()); - EXPECT_EQ(true, status_.has_firmware_version()); + GetStatus(); + EXPECT_TRUE(status_.has_browser_version()); + EXPECT_TRUE(status_.has_os_version()); + EXPECT_TRUE(status_.has_firmware_version()); // Check that the browser version is not empty. OS version & firmware // don't have any reasonable values inside the unit test, so those @@ -411,4 +483,53 @@ TEST_F(DeviceStatusCollectorTest, VersionInfo) { EXPECT_NE("", status_.browser_version()); } +TEST_F(DeviceStatusCollectorTest, Location) { + content::Geoposition valid_fix; + valid_fix.latitude = 4.3; + valid_fix.longitude = -7.8; + valid_fix.accuracy = 3.; + valid_fix.timestamp = Time::Now(); + + content::Geoposition invalid_fix; + invalid_fix.error_code = + content::Geoposition::ERROR_CODE_POSITION_UNAVAILABLE; + invalid_fix.timestamp = Time::Now(); + + // Check that when device location reporting is disabled, no location is + // reported. + SetMockPositionToReturnNext(valid_fix); + CheckThatNoLocationIsReported(); + + // Check that when device location reporting is enabled and a valid fix is + // available, the location is reported and is stored in local state. + SetMockPositionToReturnNext(valid_fix); + cros_settings_->SetBoolean(chromeos::kReportDeviceLocation, true); + EXPECT_FALSE(prefs_.GetDictionary(prefs::kDeviceLocation)->empty()); + CheckThatAValidLocationIsReported(); + + // Restart the status collector. Check that the last known location has been + // retrieved from local state without requesting a geolocation update. + SetMockPositionToReturnNext(valid_fix); + RestartStatusCollector(); + CheckThatAValidLocationIsReported(); + EXPECT_TRUE(mock_position_to_return_next.get()); + + // Check that after disabling location reporting again, the last known + // location has been cleared from local state and is no longer reported. + SetMockPositionToReturnNext(valid_fix); + cros_settings_->SetBoolean(chromeos::kReportDeviceLocation, false); + // Allow the new pref to propagate to the status collector. + message_loop_.RunAllPending(); + EXPECT_TRUE(prefs_.GetDictionary(prefs::kDeviceLocation)->empty()); + CheckThatNoLocationIsReported(); + + // Check that after enabling location reporting again, an error is reported + // if no valid fix is available. + SetMockPositionToReturnNext(invalid_fix); + cros_settings_->SetBoolean(chromeos::kReportDeviceLocation, true); + // Allow the new pref to propagate to the status collector. + message_loop_.RunAllPending(); + CheckThatALocationErrorIsReported(); +} + } // namespace policy diff --git a/chrome/browser/policy/proto/device_management_backend.proto b/chrome/browser/policy/proto/device_management_backend.proto index b6396020..0d0cfb9 100644 --- a/chrome/browser/policy/proto/device_management_backend.proto +++ b/chrome/browser/policy/proto/device_management_backend.proto @@ -264,6 +264,44 @@ message InstallableLaunch { optional int64 total_count = 4; } +// Used to report the device location. +message DeviceLocation { + enum ErrorCode { + ERROR_CODE_NONE = 0; + ERROR_CODE_POSITION_UNAVAILABLE = 1; + } + + // Latitude in decimal degrees north (WGS84 coordinate frame). + optional double latitude = 1; + + // Longitude in decimal degrees west (WGS84 coordinate frame). + optional double longitude = 2; + + // Altitude in meters (above WGS84 datum). + optional double altitude = 3; + + // Accuracy of horizontal position in meters. + optional double accuracy = 4; + + // Accuracy of altitude in meters. + optional double altitude_accuracy = 5; + + // Heading in decimal degrees clockwise from true north. + optional double heading = 6; + + // Horizontal component of device velocity in meters per second. + optional double speed = 7; + + // Time of position measurement in milisecons since Epoch in UTC time. + optional int64 timestamp = 8; + + // Error code, see enum above. + optional ErrorCode error_code = 9; + + // Human-readable error message. + optional string error_message = 10; +} + // Report device level status. message DeviceStatusReportRequest { optional string os_version = 1; @@ -281,6 +319,9 @@ message DeviceStatusReportRequest { // A list of periods when the device was active, aggregated by day. repeated ActiveTimePeriod active_period = 6; + + // The device location. + optional DeviceLocation device_location = 7; } // Report session (a user on one device) level status. diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc index 9bc1859..10cf870 100644 --- a/chrome/common/pref_names.cc +++ b/chrome/common/pref_names.cc @@ -1655,17 +1655,13 @@ const char kShouldAutoEnroll[] = "ShouldAutoEnroll"; // modulus, then it will retry auto-enrollment using the updated value. const char kAutoEnrollmentPowerLimit[] = "AutoEnrollmentPowerLimit"; -// A boolean pref that indicates whether OS & firmware version info should be -// reported along with device policy requests. -const char kReportDeviceVersionInfo[] = "device_status.report_version_info"; - -// A boolean pref that indicates whether device activity times should be -// recorded and reported along with device policy requests. -const char kReportDeviceActivityTimes[] = "device_status.report_activity_times"; - // The local state pref that stores device activity times before reporting // them to the policy server. -extern const char kDeviceActivityTimes[] = "device_status.activity_times"; +const char kDeviceActivityTimes[] = "device_status.activity_times"; + +// A pref holding the last known location when device location reporting is +// enabled. +const char kDeviceLocation[] = "device_status.location"; // A string that is used to store first-time sync startup after once sync is // disabled. This will be refreshed every sign-in. diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h index b71b068..87a5411 100644 --- a/chrome/common/pref_names.h +++ b/chrome/common/pref_names.h @@ -628,9 +628,8 @@ extern const char kHardwareKeyboardLayout[]; extern const char kCarrierDealPromoShown[]; extern const char kShouldAutoEnroll[]; extern const char kAutoEnrollmentPowerLimit[]; -extern const char kReportDeviceVersionInfo[]; -extern const char kReportDeviceActivityTimes[]; extern const char kDeviceActivityTimes[]; +extern const char kDeviceLocation[]; extern const char kSyncSpareBootstrapToken[]; #endif |