chromium/chrome/browser/ash/app_mode/kiosk_system_session.cc

// Copyright 2021 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/app_mode/kiosk_system_session.h"

#include <memory>
#include <optional>
#include <string>

#include "ash/accessibility/accessibility_controller.h"
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/notimplemented.h"
#include "base/scoped_observation.h"
#include "chrome/browser/ash/app_mode/app_launch_utils.h"
#include "chrome/browser/ash/app_mode/auto_sleep/device_weekly_scheduled_suspend_controller.h"
#include "chrome/browser/ash/app_mode/crash_recovery_launcher.h"
#include "chrome/browser/ash/app_mode/kiosk_app_types.h"
#include "chrome/browser/ash/app_mode/kiosk_app_update_service.h"
#include "chrome/browser/ash/app_mode/kiosk_chrome_app_manager.h"
#include "chrome/browser/ash/app_mode/kiosk_mode_idle_app_name_notification.h"
#include "chrome/browser/ash/app_mode/metrics/network_connectivity_metrics_service.h"
#include "chrome/browser/ash/app_mode/metrics/periodic_metrics_service.h"
#include "chrome/browser/ash/crosapi/browser_manager.h"
#include "chrome/browser/ash/crosapi/browser_manager_observer.h"
#include "chrome/browser/ash/crosapi/browser_util.h"
#include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/common/pref_names.h"
#include "chromeos/ash/components/kiosk/vision/kiosk_vision.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/manifest_handlers/offline_enabled_info.h"

namespace ash {

namespace {

// Start the floating accessibility menu in ash-chrome if the
// `FloatingAccessibilityMenuEnabled` policy is enabled.
void StartFloatingAccessibilityMenu() {
  auto* accessibility_controller = AccessibilityController::Get();
  if (accessibility_controller) {
    accessibility_controller->ShowFloatingMenuIfEnabled();
  }
}

bool IsOfflineEnabledForApp(const std::string& app_id, Profile* profile) {
  extensions::ExtensionRegistry* extension_registry =
      extensions::ExtensionRegistry::Get(profile);
  if (!extension_registry) {
    // If Lacros is enabled, extensions are running in Lacros. So Ash does not
    // have `extension_registry`.
    return false;
  }

  const extensions::Extension* primary_app =
      extension_registry->GetInstalledExtension(app_id);
  if (!primary_app) {
    return false;
  }

  return extensions::OfflineEnabledInfo::IsOfflineEnabled(primary_app);
}

bool IsLacrosEnabled() {
  return crosapi::browser_util::IsLacrosEnabledInChromeKioskSession() ||
         crosapi::browser_util::IsLacrosEnabledInWebKioskSession();
}

}  // namespace

class KioskSystemSession::LacrosWatcher
    : public crosapi::BrowserManagerObserver {
 public:
  explicit LacrosWatcher(Profile* profile, const ash::KioskAppId& kiosk_app_id)
      : profile_(profile), kiosk_app_id_(kiosk_app_id) {
    observation_.Observe(crosapi::BrowserManager::Get());
  }

  LacrosWatcher(const LacrosWatcher&) = delete;
  LacrosWatcher& operator=(const LacrosWatcher&) = delete;
  ~LacrosWatcher() override = default;

  // `crosapi::BrowserManagerObserver`:
  void OnStateChanged() override {
    if (!crosapi::BrowserManager::Get()->IsRunningOrWillRun()) {
      LOG(WARNING) << "Lacros crashed, restarting Kiosk app in Lacros";
      RestartKioskSession();
    }
  }

 private:
  void RestartKioskSession() {
    recovery_launcher_ =
        std::make_unique<CrashRecoveryLauncher>(*profile_, kiosk_app_id_);
    recovery_launcher_->Start(
        base::BindOnce(&LacrosWatcher::OnKioskRelaunchComplete,
                       weak_ptr_factory_.GetWeakPtr()));
  }

  void OnKioskRelaunchComplete(bool success,
                               const std::optional<std::string>& _) {
    recovery_launcher_.reset();
    if (!success) {
      LOG(WARNING) << "Unable to restart kiosk, ending kiosk session";
      chrome::AttemptUserExit();
    }
  }

  const raw_ptr<Profile> profile_;
  const ash::KioskAppId kiosk_app_id_;
  std::unique_ptr<CrashRecoveryLauncher> recovery_launcher_;
  base::ScopedObservation<crosapi::BrowserManager,
                          crosapi::BrowserManagerObserver>
      observation_{this};
  base::WeakPtrFactory<LacrosWatcher> weak_ptr_factory_{this};
};

KioskSystemSession::KioskSystemSession(
    Profile* profile,
    const KioskAppId& kiosk_app_id,
    const std::optional<std::string>& app_name)
    : profile_(profile),
      browser_session_(profile),
      kiosk_app_id_(kiosk_app_id),
      network_metrics_service_(
          std::make_unique<NetworkConnectivityMetricsService>()),
      periodic_metrics_service_(std::make_unique<PeriodicMetricsService>(
          g_browser_process->local_state())),
      device_weekly_scheduled_suspend_controller_(
          std::make_unique<DeviceWeeklyScheduledSuspendController>(
              g_browser_process->local_state())),
      kiosk_vision_(g_browser_process->local_state()) {
  switch (kiosk_app_id_.type) {
    case KioskAppType::kChromeApp:
      InitForChromeAppKiosk();
      break;
    case KioskAppType::kWebApp:
      InitForWebKiosk(app_name);
      break;
    case KioskAppType::kIsolatedWebApp:
      // TODO(crbug.com/361017777): call InitForIwaKiosk or reus existing init.
      NOTIMPLEMENTED();
      break;
  }
  if (IsLacrosEnabled()) {
    lacros_watcher_ = std::make_unique<LacrosWatcher>(profile, kiosk_app_id_);
  }
}

KioskSystemSession::~KioskSystemSession() = default;

void KioskSystemSession::InitForChromeAppKiosk() {
  const std::string& app_id = kiosk_app_id_.app_id.value();
  browser_session_.InitForChromeAppKiosk(app_id);
  StartFloatingAccessibilityMenu();
  InitKioskAppUpdateService(app_id);
  SetRebootAfterUpdateIfNecessary();

  periodic_metrics_service_->RecordPreviousSessionMetrics();
  periodic_metrics_service_->StartRecordingPeriodicMetrics(
      IsOfflineEnabledForApp(app_id, profile()));
}

void KioskSystemSession::InitForWebKiosk(
    const std::optional<std::string>& app_name) {
  browser_session_.InitForWebKiosk(app_name);
  StartFloatingAccessibilityMenu();

  periodic_metrics_service_->RecordPreviousSessionMetrics();
  // Web apps always support offline mode.
  periodic_metrics_service_->StartRecordingPeriodicMetrics(
      /*is_offline_enabled=*/true);
}

void KioskSystemSession::ShuttingDown() {
  network_metrics_service_.reset();
}

void KioskSystemSession::InitKioskAppUpdateService(const std::string& app_id) {
  // Set the app_id for the current instance of KioskAppUpdateService.
  auto* update_service = KioskAppUpdateServiceFactory::GetForProfile(profile());
  DCHECK(update_service);
  if (update_service) {
    update_service->Init(app_id);
  }

  // Start to monitor external update from usb stick.
  KioskChromeAppManager::Get()->MonitorKioskExternalUpdate();
}

void KioskSystemSession::SetRebootAfterUpdateIfNecessary() {
  policy::BrowserPolicyConnectorAsh* connector =
      g_browser_process->platform_part()->browser_policy_connector_ash();
  if (!connector->IsDeviceEnterpriseManaged()) {
    PrefService* local_state = g_browser_process->local_state();
    local_state->SetBoolean(::prefs::kRebootAfterUpdate, true);
    KioskModeIdleAppNameNotification::Initialize();
  }
}

void KioskSystemSession::OnGuestAdded(
    content::WebContents* guest_web_contents) {
  browser_session_.OnGuestAdded(guest_web_contents);
}

void KioskSystemSession::RegisterProfilePrefs(
    user_prefs::PrefRegistrySyncable* registry) {
  registry->RegisterBooleanPref(
      prefs::kKioskActiveWiFiCredentialsScopeChangeEnabled, false);
}

Profile* KioskSystemSession::profile() const {
  CHECK(profile_);
  return profile_;
}

bool KioskSystemSession::is_shutting_down() const {
  return browser_session_.is_shutting_down();
}

Browser* KioskSystemSession::GetSettingsBrowserForTesting() {
  return browser_session_.GetSettingsBrowserForTesting();  // IN-TEST
}

void KioskSystemSession::SetOnHandleBrowserCallbackForTesting(
    base::RepeatingCallback<void(bool)> callback) {
  browser_session_.SetOnHandleBrowserCallbackForTesting(callback);  // IN-TEST
}

}  // namespace ash