123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378 |
- // Copyright 2021 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "ash/system/human_presence/snooping_protection_controller.h"
- #include <memory>
- #include "ash/constants/ash_pref_names.h"
- #include "ash/public/cpp/session/session_observer.h"
- #include "ash/session/session_controller_impl.h"
- #include "ash/shell.h"
- #include "ash/system/human_presence/human_presence_metrics.h"
- #include "ash/system/human_presence/snooping_protection_notification_blocker.h"
- #include "base/bind.h"
- #include "base/callback.h"
- #include "base/logging.h"
- #include "base/metrics/histogram_functions.h"
- #include "base/time/time.h"
- #include "base/timer/timer.h"
- #include "chromeos/ash/components/dbus/hps/hps_service.pb.h"
- #include "chromeos/ash/components/human_presence/human_presence_configuration.h"
- #include "components/account_id/account_id.h"
- #include "components/pref_registry/pref_registry_syncable.h"
- #include "components/prefs/pref_change_registrar.h"
- #include "components/prefs/pref_registry_simple.h"
- #include "components/prefs/pref_service.h"
- #include "components/session_manager/session_manager_types.h"
- #include "third_party/abseil-cpp/absl/types/optional.h"
- #include "ui/message_center/message_center.h"
- namespace ash {
- namespace metrics = ash::snooping_protection_metrics;
- SnoopingProtectionController::SnoopingProtectionController()
- : notification_blocker_(
- std::make_unique<SnoopingProtectionNotificationBlocker>(
- message_center::MessageCenter::Get(),
- this)),
- pos_window_(hps::GetSnoopingProtectionPositiveWindow()) {
- // When the controller is initialized, we are never in an active user session
- // and we never have any user preferences active. Hence, our default state
- // values are correct.
- // Session controller is instantiated before us in the shell.
- SessionControllerImpl* session_controller =
- Shell::Get()->session_controller();
- DCHECK(session_controller);
- session_observation_.Observe(session_controller);
- // Wait for the service to be available before subscribing to its events. If
- // we directly subscribe here, we will attempt to configure the DBus service
- // twice (once via this callback and once via |OnRestart|) if it's slow to
- // start. Configuring snooping protection without first disabling it is an
- // error.
- //
- // Might not exist in unit tests.
- if (HumanPresenceDBusClient::Get()) {
- HumanPresenceDBusClient::Get()->WaitForServiceToBeAvailable(
- base::BindOnce(&SnoopingProtectionController::StartServiceObservation,
- weak_ptr_factory_.GetWeakPtr()));
- }
- // Orientation controller is instantiated before us in the shell.
- HumanPresenceOrientationController* orientation_controller =
- Shell::Get()->human_presence_orientation_controller();
- state_.orientation_suitable = orientation_controller->IsOrientationSuitable();
- orientation_observation_.Observe(orientation_controller);
- }
- SnoopingProtectionController::~SnoopingProtectionController() {
- // This is a no-op if the service isn't available or isn't enabled.
- // TODO(crbug.com/1241704): only disable if the service is enabled.
- //
- // Might not exist in unit tests.
- if (HumanPresenceDBusClient::Get())
- HumanPresenceDBusClient::Get()->DisableHpsNotify();
- for (auto& observer : observers_)
- observer.OnSnoopingProtectionControllerDestroyed();
- // We want to log current presence/absence duration since we'll not get
- // another event anymore.
- LogPresenceWindow(state_.present);
- }
- // static
- void SnoopingProtectionController::RegisterProfilePrefs(
- PrefRegistrySimple* registry) {
- registry->RegisterBooleanPref(
- prefs::kSnoopingProtectionEnabled,
- /*default_value=*/false,
- user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
- registry->RegisterBooleanPref(
- prefs::kSnoopingProtectionNotificationSuppressionEnabled,
- /*default_value=*/true,
- user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
- }
- void SnoopingProtectionController::OnSessionStateChanged(
- session_manager::SessionState session_state) {
- const bool session_active =
- session_state == session_manager::SessionState::ACTIVE;
- State new_state = state_;
- new_state.session_active = session_active;
- ReconfigureService(&new_state);
- UpdateSnooperStatus(new_state);
- }
- void SnoopingProtectionController::OnActiveUserPrefServiceChanged(
- PrefService* pref_service) {
- DCHECK(pref_service);
- const bool pref_enabled =
- pref_service->GetBoolean(prefs::kSnoopingProtectionEnabled);
- State new_state = state_;
- new_state.pref_enabled = pref_enabled;
- ReconfigureService(&new_state);
- UpdateSnooperStatus(new_state);
- // Re-subscribe to pref changes.
- pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
- pref_change_registrar_->Init(pref_service);
- pref_change_registrar_->Add(
- prefs::kSnoopingProtectionEnabled,
- base::BindRepeating(&SnoopingProtectionController::UpdatePrefState,
- weak_ptr_factory_.GetWeakPtr()));
- }
- void SnoopingProtectionController::OnOrientationChanged(
- bool suitable_for_human_presence) {
- State new_state = state_;
- new_state.orientation_suitable = suitable_for_human_presence;
- ReconfigureService(&new_state);
- UpdateSnooperStatus(new_state);
- }
- void SnoopingProtectionController::OnHpsSenseChanged(
- const hps::HpsResultProto&) {}
- void SnoopingProtectionController::OnHpsNotifyChanged(
- const hps::HpsResultProto& result) {
- const bool present = result.value() == hps::HpsResult::POSITIVE;
- State new_state = state_;
- new_state.present = present;
- // Prevent snooping status from becoming negative within a window of time.
- if (present) {
- new_state.within_pos_window = true;
- // Cancels previous task if it is already scheduled.
- pos_window_timer_.Start(FROM_HERE, pos_window_, this,
- &SnoopingProtectionController::OnMinWindowExpired);
- }
- UpdateSnooperStatus(new_state);
- }
- void SnoopingProtectionController::OnRestart() {
- DCHECK(!state_.present);
- State new_state = state_;
- new_state.service_available = true;
- ReconfigureService(&new_state);
- UpdateSnooperStatus(new_state);
- }
- void SnoopingProtectionController::OnShutdown() {
- // Log current presence window and reset the report time so that the next
- // present/absent duration will not be logged, because the duration will be
- // incorrect.
- // This has to be done before UpdateSnooperStatus below.
- LogPresenceWindow(state_.present);
- last_presence_report_time_ = base::TimeTicks();
- State new_state = state_;
- new_state.service_available = false;
- ReconfigureService(&new_state);
- UpdateSnooperStatus(new_state);
- // We will be notified of the service starting back up again via our ongoing
- // observation of the DBus client.
- }
- void SnoopingProtectionController::AddObserver(Observer* observer) {
- observers_.AddObserver(observer);
- }
- void SnoopingProtectionController::RemoveObserver(Observer* observer) {
- observers_.RemoveObserver(observer);
- }
- bool SnoopingProtectionController::SnooperPresent() const {
- return state_.within_pos_window ||
- (state_.session_active && state_.present && state_.pref_enabled &&
- state_.orientation_suitable);
- }
- void SnoopingProtectionController::UpdateSnooperStatus(const State& new_state) {
- // Clean up new state to be consistent.
- const bool detection_active =
- new_state.session_active && new_state.pref_enabled &&
- new_state.service_available && new_state.service_configured &&
- new_state.orientation_suitable;
- State clean_state = new_state;
- clean_state.present = new_state.present && detection_active;
- clean_state.within_pos_window =
- new_state.within_pos_window && detection_active;
- // If the present state changes to false while within_pos_window, we would
- // have got a flakey disappearing of the eyecon without pos_window.
- if (clean_state.within_pos_window && !clean_state.present) {
- base::UmaHistogramBoolean("ChromeOS.HPS.SnoopingProtection.FlakeyDetection",
- false);
- }
- const bool was_present = SnooperPresent();
- state_ = clean_state;
- const bool is_present = SnooperPresent();
- if (was_present == is_present)
- return;
- LogPresenceWindow(was_present);
- for (auto& observer : observers_)
- observer.OnSnoopingStatusChanged(is_present);
- }
- void SnoopingProtectionController::ReconfigureService(State* new_state) {
- // Can't configure or de-configure the service if it's unavailable.
- if (!new_state->service_available) {
- new_state->service_configured = false;
- return;
- }
- // We have correctly cached that the service is available; now handle
- // configuring its signal.
- const bool want_configured = new_state->pref_enabled &&
- new_state->session_active &&
- new_state->orientation_suitable;
- if (state_.service_configured == want_configured) {
- new_state->service_configured = want_configured;
- return;
- }
- if (want_configured) {
- // Configure the snooping started/stopped signals that the service will
- // emit.
- const absl::optional<hps::FeatureConfig> config =
- hps::GetEnableSnoopingProtectionConfig();
- if (!config.has_value()) {
- LOG(ERROR) << "SnoopingProtectionController: couldn't parse HpsNotify "
- "configuration.";
- return;
- }
- LOG(ERROR)
- << "SnoopingProtectionController: enabling HpsNotify from chrome.";
- HumanPresenceDBusClient::Get()->EnableHpsNotify(*config);
- // Populate our initial HPS state for consistency with the service.
- HumanPresenceDBusClient::Get()->GetResultHpsNotify(
- base::BindOnce(&SnoopingProtectionController::UpdateServiceState,
- weak_ptr_factory_.GetWeakPtr()));
- new_state->service_configured = true;
- return;
- }
- // No longer need signals to be emitted.
- HumanPresenceDBusClient::Get()->DisableHpsNotify();
- new_state->service_configured = false;
- }
- void SnoopingProtectionController::StartServiceObservation(
- bool service_is_available) {
- state_.service_available = service_is_available;
- state_.service_configured = false;
- if (!service_is_available) {
- LOG(ERROR) << "Could not make initial connection to HPS service";
- return;
- }
- // Special case: at this point, the service could have been left in an enabled
- // state by a previous session that crashed (and hence didn't clean up
- // properly). Disable it here, which is a no-op if it is already disabled.
- HumanPresenceDBusClient::Get()->DisableHpsNotify();
- // Start listening for state updates and restarts/shutdowns.
- human_presence_dbus_observation_.Observe(HumanPresenceDBusClient::Get());
- // Configure the service and poll its initial value if necessary.
- ReconfigureService(&state_);
- UpdateSnooperStatus(state_);
- }
- // This callback almost always runs as the service is starting up.
- // LogPresenceWindow is purposefully not called inside ths function, because
- // during startup the service reports an UNKNOWN state, so there's a risk of
- // logging a spurious window of absence.
- void SnoopingProtectionController::UpdateServiceState(
- absl::optional<hps::HpsResultProto> response) {
- LOG_IF(WARNING, !response.has_value())
- << "Polling the presence daemon failed";
- const bool present =
- response.has_value() && response->value() == hps::HpsResult::POSITIVE;
- State new_state = state_;
- new_state.present = present;
- // Prevent snooping status from becoming negative within a window of time.
- if (present) {
- new_state.within_pos_window = true;
- // Cancels previous task if it is already scheduled.
- pos_window_timer_.Start(FROM_HERE, pos_window_, this,
- &SnoopingProtectionController::OnMinWindowExpired);
- }
- UpdateSnooperStatus(new_state);
- }
- void SnoopingProtectionController::UpdatePrefState() {
- DCHECK(pref_change_registrar_);
- DCHECK(pref_change_registrar_->prefs());
- const bool pref_enabled = pref_change_registrar_->prefs()->GetBoolean(
- prefs::kSnoopingProtectionEnabled);
- State new_state = state_;
- new_state.pref_enabled = pref_enabled;
- ReconfigureService(&new_state);
- UpdateSnooperStatus(new_state);
- base::UmaHistogramBoolean(metrics::kEnabledHistogramName, pref_enabled);
- }
- void SnoopingProtectionController::OnMinWindowExpired() {
- State new_state = state_;
- new_state.within_pos_window = false;
- UpdateSnooperStatus(new_state);
- }
- void SnoopingProtectionController::LogPresenceWindow(bool was_present) {
- const auto now = base::TimeTicks::Now();
- // Set last_presence_report_time_ and return if it is the first time reported.
- if (last_presence_report_time_.is_null()) {
- last_presence_report_time_ = now;
- return;
- }
- const auto time_since_last_report = now - last_presence_report_time_;
- last_presence_report_time_ = now;
- if (was_present) {
- base::UmaHistogramCustomTimes(metrics::kPositiveDurationHistogramName,
- time_since_last_report, metrics::kDurationMin,
- metrics::kPositiveDurationMax,
- metrics::kDurationNumBuckets);
- } else {
- base::UmaHistogramCustomTimes(metrics::kNegativeDurationHistogramName,
- time_since_last_report, metrics::kDurationMin,
- metrics::kNegativeDurationMax,
- metrics::kDurationNumBuckets);
- }
- }
- } // namespace ash
|