chromium/chrome/browser/ui/webui/ash/settings/pages/apps/app_parental_controls_handler.cc

// Copyright 2024 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/ui/webui/ash/settings/pages/apps/app_parental_controls_handler.h"

#include <memory>
#include <utility>
#include <vector>

#include "ash/constants/ash_pref_names.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/ash/child_accounts/on_device_controls/app_controls_notifier.h"
#include "chrome/browser/ash/child_accounts/on_device_controls/blocked_app_registry.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/webui/ash/settings/pages/apps/mojom/app_parental_controls_handler.mojom.h"
#include "components/prefs/pref_service.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/services/app_service/public/cpp/app_update.h"
#include "components/services/app_service/public/cpp/types_util.h"
#include "third_party/re2/src/re2/re2.h"

namespace ash::settings {

namespace {
constexpr size_t kValidPinLength = 6u;
const char kNumericOnlyRegex[] = R"(^\d+$)";

app_parental_controls::mojom::AppPtr CreateAppPtr(
    const apps::AppUpdate& update) {
  auto app = app_parental_controls::mojom::App::New();
  app->id = update.AppId();
  app->title = update.Name();
  app->is_blocked =
      update.Readiness() == apps::Readiness::kDisabledByLocalSettings;
  return app;
}

bool ShouldIncludeApp(const apps::AppUpdate& update) {
  // Only apps shown in the App Management page should be shown.
  return update.ShowInManagement().value_or(false) &&
         update.AppType() == apps::AppType::kArc;
}

app_parental_controls::mojom::PinValidationResult GetPinValidationResult(
    const std::string& pin) {
  if (pin.length() != kValidPinLength) {
    return app_parental_controls::mojom::PinValidationResult::kPinLengthError;
  }
  if (!RE2::FullMatch(pin, kNumericOnlyRegex)) {
    return app_parental_controls::mojom::PinValidationResult::kPinNumericError;
  }
  return app_parental_controls::mojom::PinValidationResult::
      kPinValidationSuccess;
}
}  // namespace

AppParentalControlsHandler::AppParentalControlsHandler(
    apps::AppServiceProxy* app_service_proxy,
    Profile* profile)
    : app_service_proxy_(app_service_proxy),
      app_controls_notifier_(
          std::make_unique<on_device_controls::AppControlsNotifier>(profile)),
      blocked_app_registry_(
          std::make_unique<on_device_controls::BlockedAppRegistry>(
              app_service_proxy,
              profile->GetPrefs())),
      profile_(profile) {
  CHECK(app_service_proxy_);
  CHECK(blocked_app_registry_);

  app_registry_cache_observer_.Observe(&app_service_proxy_->AppRegistryCache());
  app_controls_notifier_->MaybeShowAppControlsNotification();
}

AppParentalControlsHandler::~AppParentalControlsHandler() = default;

void AppParentalControlsHandler::BindInterface(
    mojo::PendingReceiver<
        app_parental_controls::mojom::AppParentalControlsHandler> receiver) {
  if (receiver_.is_bound()) {
    receiver_.reset();
  }
  receiver_.Bind(std::move(receiver));
}

void AppParentalControlsHandler::GetApps(GetAppsCallback callback) {
  std::move(callback).Run(GetAppList());
}

void AppParentalControlsHandler::UpdateApp(const std::string& id,
                                           bool is_blocked) {
  if (is_blocked) {
    blocked_app_registry_->AddApp(id);
    return;
  }
  blocked_app_registry_->RemoveApp(id);
}

void AppParentalControlsHandler::AddObserver(
    mojo::PendingRemote<
        app_parental_controls::mojom::AppParentalControlsObserver> observer) {
  observer_list_.Add(std::move(observer));
}

void AppParentalControlsHandler::OnControlsDisabled() {
  profile_->GetPrefs()->SetString(prefs::kOnDeviceAppControlsPin,
                                  std::string());
  profile_->GetPrefs()->SetBoolean(prefs::kOnDeviceAppControlsSetupCompleted,
                                   false);
  blocked_app_registry_->RemoveAllApps();
}

void AppParentalControlsHandler::ValidatePin(const std::string& pin,
                                             ValidatePinCallback callback) {
  std::move(callback).Run(GetPinValidationResult(pin));
}

void AppParentalControlsHandler::SetUpPin(const std::string& pin,
                                          SetUpPinCallback callback) {
  if (GetPinValidationResult(pin) !=
      app_parental_controls::mojom::PinValidationResult::
          kPinValidationSuccess) {
    std::move(callback).Run(false);
    return;
  }
  profile_->GetPrefs()->SetString(prefs::kOnDeviceAppControlsPin, pin);
  profile_->GetPrefs()->SetBoolean(prefs::kOnDeviceAppControlsSetupCompleted,
                                   true);
  profile_->GetPrefs()->CommitPendingWrite();
  std::move(callback).Run(true);
}

void AppParentalControlsHandler::VerifyPin(const std::string& pin,
                                           VerifyPinCallback callback) {
  std::string stored_pin =
      profile_->GetPrefs()->GetString(prefs::kOnDeviceAppControlsPin);
  std::move(callback).Run(pin == stored_pin);
}

void AppParentalControlsHandler::IsSetupCompleted(
    IsSetupCompletedCallback callback) {
  std::move(callback).Run(profile_->GetPrefs()->GetBoolean(
      prefs::kOnDeviceAppControlsSetupCompleted));
}

void AppParentalControlsHandler::OnAppUpdate(const apps::AppUpdate& update) {
  if (update.ReadinessChanged() && ShouldIncludeApp(update)) {
    if (!apps_util::IsInstalled(update.Readiness())) {
      NotifyAppRemoved(CreateAppPtr(update));
      return;
    }
    NotifyAppInstalledOrUpdated(CreateAppPtr(update));
  }
}

void AppParentalControlsHandler::OnAppRegistryCacheWillBeDestroyed(
    apps::AppRegistryCache* cache) {
  app_registry_cache_observer_.Reset();
}

std::vector<app_parental_controls::mojom::AppPtr>
AppParentalControlsHandler::GetAppList() {
  std::vector<app_parental_controls::mojom::AppPtr> apps;
  app_service_proxy_->AppRegistryCache().ForEachApp(
      [&apps](const apps::AppUpdate& update) {
        if (ShouldIncludeApp(update) &&
            apps_util::IsInstalled(update.Readiness())) {
          apps.push_back(CreateAppPtr(update));
        }
      });
  return apps;
}

void AppParentalControlsHandler::NotifyAppInstalledOrUpdated(
    app_parental_controls::mojom::AppPtr app) {
  for (const auto& observer : observer_list_) {
    observer->OnAppInstalledOrUpdated(app.Clone());
  }
}

void AppParentalControlsHandler::NotifyAppRemoved(
    app_parental_controls::mojom::AppPtr app) {
  for (const auto& observer : observer_list_) {
    observer->OnAppUninstalled(app.Clone());
  }
}

}  // namespace ash::settings