910e62b5创建于 1月15日历史提交
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ash/privacy_hub/privacy_hub_util.h"

#include <optional>
#include <string>

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/public/cpp/privacy_hub_delegate.h"
#include "ash/public/cpp/session/session_controller.h"
#include "ash/public/cpp/session/session_observer.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/system/geolocation/geolocation_controller.h"
#include "ash/system/privacy_hub/camera_privacy_switch_controller.h"
#include "ash/system/privacy_hub/geolocation_privacy_switch_controller.h"
#include "ash/system/privacy_hub/microphone_privacy_switch_controller.h"
#include "ash/system/privacy_hub/privacy_hub_controller.h"
#include "ash/system/privacy_hub/privacy_hub_notification_controller.h"
#include "ash/system/privacy_hub/sensor_disabled_notification_delegate.h"
#include "ash/webui/settings/public/constants/routes.mojom-forward.h"
#include "base/check_deref.h"
#include "base/functional/callback.h"
#include "base/notreached.h"
#include "base/supports_user_data.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/ash/app_access/app_access_notifier.h"
#include "chrome/browser/ui/settings_window_manager_chromeos.h"
#include "chromeos/ash/components/camera_presence_notifier/camera_presence_notifier.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_service.h"

namespace ash::privacy_hub_util {

namespace {

PrefService* ActivePrefService() {
  auto* session_controller =
      CHECK_DEREF(ash::Shell::Get()).session_controller();
  return CHECK_DEREF(session_controller).GetActivePrefService();
}

ScopedUserPermissionPrefForTest::AccessLevel GetCurrentAccessLevel(
    ContentType type) {
  if (type == ContentType::MEDIASTREAM_CAMERA) {
    const bool allowed =
        CHECK_DEREF(ActivePrefService()).GetBoolean(prefs::kUserCameraAllowed);
    return allowed ? ScopedUserPermissionPrefForTest::AccessLevel::kAllowed
                   : ScopedUserPermissionPrefForTest::AccessLevel::kDisallowed;
  }
  if (type == ContentType::MEDIASTREAM_MIC) {
    const bool allowed = CHECK_DEREF(ActivePrefService())
                             .GetBoolean(prefs::kUserMicrophoneAllowed);
    return allowed ? ScopedUserPermissionPrefForTest::AccessLevel::kAllowed
                   : ScopedUserPermissionPrefForTest::AccessLevel::kDisallowed;
  }
  CHECK(type == ContentType::GEOLOCATION);
  return static_cast<ScopedUserPermissionPrefForTest::AccessLevel>(
      CHECK_DEREF(ActivePrefService())
          .GetInteger(prefs::kUserGeolocationAccessLevel));
}

void SetCurrentAccessLevel(
    ContentType type,
    ScopedUserPermissionPrefForTest::AccessLevel access_level) {
  if (type == ContentType::MEDIASTREAM_CAMERA) {
    CHECK(access_level !=
          ScopedUserPermissionPrefForTest::AccessLevel::kOnlyAllowedForSystem);
    CHECK_DEREF(ActivePrefService())
        .SetBoolean(prefs::kUserCameraAllowed,
                    access_level ==
                        ScopedUserPermissionPrefForTest::AccessLevel::kAllowed);
    return;
  }
  if (type == ContentType::MEDIASTREAM_MIC) {
    CHECK(access_level !=
          ScopedUserPermissionPrefForTest::AccessLevel::kOnlyAllowedForSystem);
    CHECK_DEREF(ActivePrefService())
        .SetBoolean(prefs::kUserMicrophoneAllowed,
                    access_level ==
                        ScopedUserPermissionPrefForTest::AccessLevel::kAllowed);
    return;
  }
  CHECK(type == ContentType::GEOLOCATION);
  CHECK_DEREF(ActivePrefService())
      .SetInteger(prefs::kUserGeolocationAccessLevel,
                  static_cast<int>(access_level));
}

class ContentBlockObservationImpl : public ContentBlockObservation {
 public:
  // Access restricted constructor.
  ContentBlockObservationImpl(SessionController* session_controller,
                              SystemPermissionChangedCallback callback)
      : callback_(std::move(callback)) {
    // Microphone and camera settings are following the active user's
    // preferences. Subscribing to the active user's pref changes.
    CHECK(Shell::Get()->session_controller());
    active_user_pref_change_registrar_ =
        std::make_unique<PrefChangeRegistrar>();
    active_user_pref_change_registrar_->Init(
        Shell::Get()->session_controller()->GetActivePrefService());
    active_user_pref_change_registrar_->Add(
        prefs::kUserCameraAllowed,
        base::BindRepeating(&ContentBlockObservationImpl::OnPreferenceChanged,
                            base::Unretained(this)));
    active_user_pref_change_registrar_->Add(
        prefs::kUserMicrophoneAllowed,
        base::BindRepeating(&ContentBlockObservationImpl::OnPreferenceChanged,
                            base::Unretained(this)));

    // Geolocation setting is exclusively controlled by the primary user of the
    // session. This is different from the camera and microphone implementation.
    // Subscribing to the primary user's pref changes.
    primary_user_pref_change_registrar_ =
        std::make_unique<PrefChangeRegistrar>();
    primary_user_pref_change_registrar_->Init(
        Shell::Get()->session_controller()->GetPrimaryUserPrefService());
    primary_user_pref_change_registrar_->Add(
        prefs::kUserGeolocationAccessLevel,
        base::BindRepeating(&ContentBlockObservationImpl::OnPreferenceChanged,
                            base::Unretained(this)));
  }

  ~ContentBlockObservationImpl() override = default;

 private:
  // Handles changes in the user pref ( e.g. toggling the camera switch on
  // Privacy Hub UI).
  void OnPreferenceChanged(const std::string& pref_name) {
    if (pref_name == prefs::kUserCameraAllowed) {
      callback_.Run(
          ContentType::MEDIASTREAM_CAMERA,
          privacy_hub_util::ContentBlocked(ContentType::MEDIASTREAM_CAMERA));
      return;
    }
    if (pref_name == prefs::kUserMicrophoneAllowed) {
      callback_.Run(
          ContentType::MEDIASTREAM_MIC,
          privacy_hub_util::ContentBlocked(ContentType::MEDIASTREAM_MIC));
      return;
    }
    if (pref_name == prefs::kUserGeolocationAccessLevel) {
      callback_.Run(ContentType::GEOLOCATION,
                    privacy_hub_util::ContentBlocked(ContentType::GEOLOCATION));
      return;
    }
    NOTREACHED();
  }

  SystemPermissionChangedCallback callback_;
  std::unique_ptr<PrefChangeRegistrar> active_user_pref_change_registrar_;
  std::unique_ptr<PrefChangeRegistrar> primary_user_pref_change_registrar_;
  base::WeakPtrFactory<ContentBlockObservationImpl> weak_ptr_factory_{this};
};
}  // namespace

void SetFrontend(PrivacyHubDelegate* ptr) {
  PrivacyHubController* const controller = PrivacyHubController::Get();
  if (controller != nullptr) {
    // Controller may not be available when used from a test.
    controller->SetFrontend(ptr);
  }
}

bool MicrophoneSwitchState() {
  return ui::MicrophoneMuteSwitchMonitor::Get()->microphone_mute_switch_on();
}

bool ShouldForceDisableCameraSwitch() {
  PrivacyHubController* controller = PrivacyHubController::Get();
  if (!controller || !controller->camera_controller()) {
    return false;
  }
  return controller->camera_controller()->IsCameraAccessForceDisabled();
}

void SetUpCameraCountObserver() {
  auto* camera_controller = CameraPrivacySwitchController::Get();
  CHECK(camera_controller);

  base::RepeatingCallback<void(int)> update_camera_count_in_privacy_hub =
      base::BindRepeating(
          [](CameraPrivacySwitchController* controller, int camera_count) {
            controller->OnCameraCountChanged(camera_count);
          },
          camera_controller);
  auto notifier = std::make_unique<CameraPresenceNotifier>(
      std::move(update_camera_count_in_privacy_hub));
  notifier->Start();

  static const char kUserDataKey = '\0';
  camera_controller->SetUserData(&kUserDataKey, std::move(notifier));
}

// Notifies the Privacy Hub controller.
void TrackGeolocationAttempted(const std::string& name) {
  if (!features::IsCrosPrivacyHubLocationEnabled()) {
    return;
  }
  GeolocationPrivacySwitchController* controller =
      GeolocationPrivacySwitchController::Get();
  // TODO(b/288854399): Remove this if.
  if (controller) {
    controller->TrackGeolocationAttempted(name);
  }
}

// Notifies the Privacy Hub controller.
void TrackGeolocationRelinquished(const std::string& name) {
  if (!features::IsCrosPrivacyHubLocationEnabled()) {
    return;
  }
  GeolocationPrivacySwitchController* controller =
      GeolocationPrivacySwitchController::Get();
  // TODO(b/288854399): Remove this if.
  if (controller) {
    controller->TrackGeolocationRelinquished(name);
  }
}

bool IsCrosLocationOobeNegotiationNeeded() {
  // No negotiation needed, if the PH Location feature is not yet enabled.
  if (!ash::features::IsCrosPrivacyHubLocationEnabled()) {
    return false;
  }

  const Profile* profile = ProfileManager::GetPrimaryUserProfile();
  if (profile->GetPrefs()->IsManagedPreference(
          ash::prefs::kUserGeolocationAccessLevel)) {
    return false;
  }

  return true;
}

GeolocationAccessLevel GetSystemGeolocationAccessLevel() {
  return GeolocationPrivacySwitchController::Get()->AccessLevel();
}

namespace {
std::optional<bool> camera_led_fallback_for_testing{};
}

// TODO(b/289510726): remove when all cameras fully support the software
// switch.
bool UsingCameraLEDFallback() {
  if (!camera_led_fallback_for_testing.has_value()) {
    CameraPrivacySwitchController* const controller =
        CameraPrivacySwitchController::Get();
    CHECK(controller);
    return controller->UsingCameraLEDFallback();
  }

  // Can happen in some testing environments
  CHECK(camera_led_fallback_for_testing.has_value());
  return camera_led_fallback_for_testing.value();
}

ScopedCameraLedFallbackForTesting::ScopedCameraLedFallbackForTesting(
    bool value) {
  CHECK(!camera_led_fallback_for_testing.has_value());
  camera_led_fallback_for_testing = value;
}

ScopedCameraLedFallbackForTesting::~ScopedCameraLedFallbackForTesting() {
  CHECK(camera_led_fallback_for_testing.has_value());
  camera_led_fallback_for_testing.reset();
  CHECK(!camera_led_fallback_for_testing.has_value());
}

void SetAppAccessNotifier(AppAccessNotifier* app_access_notifier) {
  // Wraps the `AppAccessNotifier` to be used from
  // `PrivacyHubNotificationController`.
  class Wrapper : public SensorDisabledNotificationDelegate {
   public:
    explicit Wrapper(AppAccessNotifier* notifier)
        : notifier_(raw_ref<AppAccessNotifier>::from_ptr(notifier)) {}

    std::vector<std::u16string> GetAppsAccessingSensor(Sensor sensor) override {
      switch (sensor) {
        case Sensor::kCamera:
          return notifier_->GetAppsAccessingCamera();
        case Sensor::kMicrophone:
          return notifier_->GetAppsAccessingMicrophone();
        case Sensor::kLocation:
          break;
      }
      NOTREACHED();
    }

   private:
    raw_ref<AppAccessNotifier> notifier_;
  };

  PrivacyHubNotificationController* controller =
      PrivacyHubNotificationController::Get();
  CHECK(controller);
  controller->SetSensorDisabledNotificationDelegate(
      app_access_notifier ? std::make_unique<Wrapper>(app_access_notifier)
                          : nullptr);
}

std::pair<base::Time, base::Time> SunriseSunsetSchedule() {
  auto default_sunrise_time =
      base::Time::Now().LocalMidnight() + base::Hours(6);
  SunRiseSetTime default_times = {
      .sunrise = default_sunrise_time,
      .sunset = default_sunrise_time + base::Hours(12)};

  const ash::GeolocationController* geolocation_controller =
      ash::GeolocationController::Get();
  const auto times =
      geolocation_controller
          ? geolocation_controller->GetSunRiseSetTime().value_or(default_times)
          : default_times;
  return std::make_pair(times.sunrise, times.sunset);
}

bool ContentBlocked(ContentType type) {
  switch (type) {
    case ContentType::MEDIASTREAM_CAMERA: {
      auto* const controller = CameraPrivacySwitchController::Get();
      CHECK(controller);
      return !controller->IsCameraUsageAllowed();
    }
    case ContentType::MEDIASTREAM_MIC: {
      auto* const controller = MicrophonePrivacySwitchController::Get();
      CHECK(controller);
      return !controller->IsMicrophoneUsageAllowed();
    }
    case ContentType::GEOLOCATION: {
      if (!features::IsCrosPrivacyHubLocationEnabled()) {
        return false;  // content not blocked as geo blocking not supported.
      }
      auto* const controller = GeolocationPrivacySwitchController::Get();
      CHECK(controller);
      return !controller->IsGeolocationUsageAllowedForApps();
    }
    default: {
      // If the provided content type is not controllable in ChromeOS, then it
      // is not blocked.
      return false;
    }
  }
}

std::unique_ptr<ContentBlockObservation> CreateObservationForBlockedContent(
    SystemPermissionChangedCallback callback) {
  if (!ash::Shell::HasInstance()) {
    return nullptr;
  }
  auto* shell = ash::Shell::Get();
  CHECK(shell);
  auto* session_controller = shell->session_controller();
  if (!session_controller) {
    return nullptr;
  }
  PrefService* pref_service =
      session_controller->GetLastActiveUserPrefService();
  if (!pref_service) {
    return nullptr;
  }

  auto observation = std::make_unique<ContentBlockObservationImpl>(
      session_controller, std::move(callback));
  return observation;
}

void OpenSystemSettings(ContentType type) {
  const char* settings_path = "";
  switch (type) {
    case ContentType::MEDIASTREAM_CAMERA: {
      settings_path = chromeos::settings::mojom::kPrivacyHubCameraSubpagePath;
      break;
    }
    case ContentType::MEDIASTREAM_MIC: {
      settings_path =
          chromeos::settings::mojom::kPrivacyHubMicrophoneSubpagePath;
      break;
    }
    case ContentType::GEOLOCATION: {
      settings_path =
          chromeos::settings::mojom::kPrivacyHubGeolocationSubpagePath;
      break;
    }
    default: {
      // This should only be called for camera, microphone, or geolocation.
      NOTREACHED();
    }
  }

  chrome::SettingsWindowManager::GetInstance()->ShowOSSettings(
      ProfileManager::GetActiveUserProfile(), settings_path);
}

ScopedUserPermissionPrefForTest::ScopedUserPermissionPrefForTest(
    ContentType type,
    ScopedUserPermissionPrefForTest::AccessLevel access_level)
    : content_type_(type), previous_access_level_(GetCurrentAccessLevel(type)) {
  // Only Geolocation, camera and mic are supported.
  CHECK((ContentType::GEOLOCATION == type) ||
        (ContentType::MEDIASTREAM_CAMERA == type) ||
        (ContentType::MEDIASTREAM_MIC == type));
  if (ContentType::GEOLOCATION != type) {
    CHECK(access_level !=
          ScopedUserPermissionPrefForTest::AccessLevel::kOnlyAllowedForSystem);
  }
  SetCurrentAccessLevel(type, access_level);
}

ScopedUserPermissionPrefForTest::~ScopedUserPermissionPrefForTest() {
  SetCurrentAccessLevel(content_type_, previous_access_level_);
}

}  // namespace ash::privacy_hub_util