chromium/chrome/browser/ash/policy/handlers/adb_sideloading_allowance_mode_policy_handler.cc

// Copyright 2020 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/policy/handlers/adb_sideloading_allowance_mode_policy_handler.h"

#include <optional>
#include <utility>

#include "ash/constants/ash_features.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "base/values.h"
#include "chrome/browser/ash/login/screens/reset_screen.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "chrome/common/pref_names.h"
#include "chromeos/ash/components/dbus/session_manager/session_manager_client.h"
#include "chromeos/ash/components/settings/cros_settings_names.h"
#include "chromeos/ash/components/settings/cros_settings_provider.h"
#include "components/policy/proto/chrome_device_policy.pb.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user_manager.h"

namespace policy {

namespace {

constexpr base::TimeDelta kAdbSideloadingPlannedNotificationWaitTime =
    base::Days(1);

std::optional<AdbSideloadingAllowanceMode> GetAdbSideloadingDevicePolicyMode(
    const ash::CrosSettings* cros_settings,
    const base::RepeatingClosure callback) {
  auto status = cros_settings->PrepareTrustedValues(callback);

  // If the policy value is still not trusted, return optional null
  if (status != ash::CrosSettingsProvider::TRUSTED) {
    return std::nullopt;
  }

  // Get the trusted policy value.
  int sideloading_mode = -1;
  if (!cros_settings->GetInteger(ash::kDeviceCrostiniArcAdbSideloadingAllowed,
                                 &sideloading_mode)) {
    // Here we do not return null because we want to handle this case separately
    // and to reset all the prefs for the notifications so that they can be
    // displayed again if the policy changes
    return AdbSideloadingAllowanceMode::kNotSet;
  }

  using Mode =
      enterprise_management::DeviceCrostiniArcAdbSideloadingAllowedProto;

  switch (sideloading_mode) {
    case Mode::DISALLOW:
      return AdbSideloadingAllowanceMode::kDisallow;
    case Mode::DISALLOW_WITH_POWERWASH:
      return AdbSideloadingAllowanceMode::kDisallowWithPowerwash;
    case Mode::ALLOW_FOR_AFFILIATED_USERS:
      return AdbSideloadingAllowanceMode::kAllowForAffiliatedUser;
    default:
      return std::nullopt;
  }
}

}  // namespace

// static
void AdbSideloadingAllowanceModePolicyHandler::RegisterPrefs(
    PrefRegistrySimple* registry) {
  registry->RegisterBooleanPref(prefs::kForceFactoryReset, false);
  registry->RegisterBooleanPref(
      prefs::kAdbSideloadingDisallowedNotificationShown, false);
  registry->RegisterTimePref(
      prefs::kAdbSideloadingPowerwashPlannedNotificationShownTime,
      base::Time::Min());
  registry->RegisterBooleanPref(
      prefs::kAdbSideloadingPowerwashOnNextRebootNotificationShown, false);
}

AdbSideloadingAllowanceModePolicyHandler::
    AdbSideloadingAllowanceModePolicyHandler(
        ash::CrosSettings* cros_settings,
        PrefService* local_state,
        chromeos::PowerManagerClient* power_manager_client,
        ash::AdbSideloadingPolicyChangeNotification*
            adb_sideloading_policy_change_notification)
    : cros_settings_(cros_settings),
      local_state_(local_state),
      adb_sideloading_policy_change_notification_(
          adb_sideloading_policy_change_notification),
      power_manager_observer_(this) {
  DCHECK(local_state_);
  policy_subscription_ = cros_settings_->AddSettingsObserver(
      ash::kDeviceCrostiniArcAdbSideloadingAllowed,
      base::BindRepeating(
          &AdbSideloadingAllowanceModePolicyHandler::MaybeShowNotification,
          weak_factory_.GetWeakPtr()));

  check_sideloading_status_callback_ = base::BindRepeating(
      &AdbSideloadingAllowanceModePolicyHandler::CheckSideloadingStatus,
      weak_factory_.GetWeakPtr());

  notification_timer_ = std::make_unique<base::OneShotTimer>();

  DCHECK(power_manager_client);
  power_manager_observer_.Observe(power_manager_client);
}

AdbSideloadingAllowanceModePolicyHandler::
    ~AdbSideloadingAllowanceModePolicyHandler() = default;

void AdbSideloadingAllowanceModePolicyHandler::
    SetCheckSideloadingStatusCallbackForTesting(
        const CheckSideloadingStatusCallback& callback) {
  check_sideloading_status_callback_ = callback;
}

void AdbSideloadingAllowanceModePolicyHandler::SetNotificationTimerForTesting(
    std::unique_ptr<base::OneShotTimer> timer) {
  notification_timer_ = std::move(timer);
}

void AdbSideloadingAllowanceModePolicyHandler::MaybeShowNotification() {
  std::optional<AdbSideloadingAllowanceMode> mode =
      GetAdbSideloadingDevicePolicyMode(
          cros_settings_,
          base::BindRepeating(
              &AdbSideloadingAllowanceModePolicyHandler::MaybeShowNotification,
              weak_factory_.GetWeakPtr()));

  if (!mode.has_value()) {
    return;
  }

  switch (*mode) {
    case AdbSideloadingAllowanceMode::kDisallow:
      // Reset the prefs for the powerwash notifications so they can be shown
      // again if the policy changes
      ClearPowerwashNotificationPrefs();
      check_sideloading_status_callback_.Run(
          base::BindOnce(&AdbSideloadingAllowanceModePolicyHandler::
                             MaybeShowDisallowedNotification,
                         weak_factory_.GetWeakPtr()));
      return;
    case AdbSideloadingAllowanceMode::kDisallowWithPowerwash:
      // Reset the pref for the disallowed notification so it can be shown again
      // if the policy changes
      ClearDisallowedNotificationPref();
      check_sideloading_status_callback_.Run(
          base::BindOnce(&AdbSideloadingAllowanceModePolicyHandler::
                             MaybeShowPowerwashNotification,
                         weak_factory_.GetWeakPtr()));
      return;
    case AdbSideloadingAllowanceMode::kNotSet:
    case AdbSideloadingAllowanceMode::kAllowForAffiliatedUser:
      // Reset all the prefs so the notifications can be shown again if the
      // policy changes
      ClearDisallowedNotificationPref();
      ClearPowerwashNotificationPrefs();
      notification_timer_->Stop();
      return;
  }
}

void AdbSideloadingAllowanceModePolicyHandler::CheckSideloadingStatus(
    base::OnceCallback<void(bool)> callback) {
  // If the feature is not enabled, never show a notification
  if (!base::FeatureList::IsEnabled(
          ash::features::kArcManagedAdbSideloadingSupport)) {
    std::move(callback).Run(false);
    return;
  }

  using ResponseCode = ash::SessionManagerClient::AdbSideloadResponseCode;

  auto* client = ash::SessionManagerClient::Get();
  client->QueryAdbSideload(base::BindOnce(
      [](base::OnceCallback<void(bool)> callback, ResponseCode response_code,
         bool enabled) {
        switch (response_code) {
          case ResponseCode::SUCCESS:
            // Everything is fine, so pass the |enabled| value to |callback|.
            break;
          case ResponseCode::FAILED:
            // Status could not be established so return false so that the
            // notifications would not be shown.
            enabled = false;
            break;
          case ResponseCode::NEED_POWERWASH:
            // This can only happen on devices initialized before M74, i.e. not
            // powerwashed since then. Do not show the notifications there.
            enabled = false;
            break;
        }
        std::move(callback).Run(enabled);
      },
      std::move(callback)));
}

void AdbSideloadingAllowanceModePolicyHandler::
    ShowAdbSideloadingPolicyChangeNotificationIfNeeded() {
  MaybeShowNotification();
}

bool AdbSideloadingAllowanceModePolicyHandler::
    WasDisallowedNotificationShown() {
  return local_state_->GetBoolean(
      prefs::kAdbSideloadingDisallowedNotificationShown);
}

bool AdbSideloadingAllowanceModePolicyHandler::
    WasPowerwashOnNextRebootNotificationShown() {
  return local_state_->GetBoolean(
      prefs::kAdbSideloadingPowerwashOnNextRebootNotificationShown);
}

void AdbSideloadingAllowanceModePolicyHandler::MaybeShowDisallowedNotification(
    bool is_sideloading_enabled) {
  if (!is_sideloading_enabled)
    return;

  if (WasDisallowedNotificationShown())
    return;

  local_state_->SetBoolean(prefs::kAdbSideloadingDisallowedNotificationShown,
                           true);
  adb_sideloading_policy_change_notification_->Show(
      NotificationType::kSideloadingDisallowed);
}

void AdbSideloadingAllowanceModePolicyHandler::MaybeShowPowerwashNotification(
    bool is_sideloading_enabled) {
  if (!is_sideloading_enabled)
    return;

  base::Time notification_shown_time = local_state_->GetTime(
      prefs::kAdbSideloadingPowerwashPlannedNotificationShownTime);

  // If the time has not been set yet, set it and show the planned notification
  if (notification_shown_time.is_min()) {
    notification_shown_time = base::Time::Now();
    local_state_->SetTime(
        prefs::kAdbSideloadingPowerwashPlannedNotificationShownTime,
        notification_shown_time);
    adb_sideloading_policy_change_notification_->Show(
        NotificationType::kPowerwashPlanned);
  }

  // Show update on next reboot notification after at least
  // |kAdbSideloadingPlannedNotificationWaitTime|.
  base::Time show_reboot_notification_time =
      notification_shown_time + kAdbSideloadingPlannedNotificationWaitTime;

  // If this time has already been reached stop the timer and show the
  // notification immediately.
  if (show_reboot_notification_time <= base::Time::Now()) {
    notification_timer_->Stop();
    MaybeShowPowerwashUponRebootNotification();
    return;
  }

  // Otherwise set a timer that will display the |kPowerwashOnNextReboot|
  // notification no earlier than |kAdbSideloadingPlannedNotificationWaitTime|
  // after showing the |kPowerwashPlanned| notification.
  if (notification_timer_->IsRunning())
    return;
  notification_timer_->Start(
      FROM_HERE, show_reboot_notification_time - base::Time::Now(), this,
      &AdbSideloadingAllowanceModePolicyHandler::
          MaybeShowPowerwashUponRebootNotification);
}

void AdbSideloadingAllowanceModePolicyHandler::
    MaybeShowPowerwashUponRebootNotification() {
  if (WasPowerwashOnNextRebootNotificationShown())
    return;

  local_state_->SetBoolean(
      prefs::kAdbSideloadingPowerwashOnNextRebootNotificationShown, true);

  // Set this right away to ensure the user is forced to powerwash on next
  // start even if they ignore the notification and do not click the button
  local_state_->SetBoolean(prefs::kForceFactoryReset, true);
  local_state_->CommitPendingWrite();

  adb_sideloading_policy_change_notification_->Show(
      NotificationType::kPowerwashOnNextReboot);
}

void AdbSideloadingAllowanceModePolicyHandler::
    ClearDisallowedNotificationPref() {
  local_state_->ClearPref(prefs::kAdbSideloadingDisallowedNotificationShown);
}

void AdbSideloadingAllowanceModePolicyHandler::
    ClearPowerwashNotificationPrefs() {
  local_state_->ClearPref(
      prefs::kAdbSideloadingPowerwashPlannedNotificationShownTime);
  local_state_->ClearPref(
      prefs::kAdbSideloadingPowerwashOnNextRebootNotificationShown);
}

void AdbSideloadingAllowanceModePolicyHandler::ScreenIdleStateChanged(
    const power_manager::ScreenIdleState& state) {
  // Try showing the notification when the screen wakes up from idle state
  if (!state.off()) {
    MaybeShowNotification();
  }
}

void AdbSideloadingAllowanceModePolicyHandler::LidEventReceived(
    chromeos::PowerManagerClient::LidState state,
    base::TimeTicks timestamp) {
  // Try showing the notification when the user opens the lid
  if (state == chromeos::PowerManagerClient::LidState::OPEN) {
    MaybeShowNotification();
  }
}

}  // namespace policy