chromium/chrome/browser/ash/borealis/borealis_survey_handler.cc

// Copyright 2023 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/borealis/borealis_survey_handler.h"

#include "base/containers/flat_map.h"
#include "base/feature_list.h"
#include "base/functional/bind_internal.h"
#include "base/logging.h"
#include "base/system/sys_info.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/ash/borealis/borealis_service.h"
#include "chrome/browser/ash/borealis/borealis_util.h"
#include "chrome/browser/ash/borealis/borealis_window_manager.h"
#include "chrome/browser/ash/guest_os/guest_os_registry_service.h"
#include "chrome/browser/ash/guest_os/guest_os_registry_service_factory.h"
#include "chrome/browser/ash/hats/hats_config.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/display/screen.h"

namespace borealis {

BorealisSurveyHandler::BorealisSurveyHandler(
    Profile* profile,
    BorealisWindowManager* window_manager)
    : profile_(profile) {
  if (!base::FeatureList::IsEnabled(ash::kHatsBorealisGamesSurvey.feature)) {
    VLOG(1) << "Borealis survey feature is not enabled";
    return;
  }
  lifetime_observation_.Observe(window_manager);
}
BorealisSurveyHandler::~BorealisSurveyHandler() = default;

std::optional<int> BorealisSurveyHandler::GetGameId(const std::string& app_id) {
  // Attempt to get the Borealis app ID.
  // TODO(b/173977876): Implement this in a more reliable way.
  std::optional<int> game_id;
  std::optional<guest_os::GuestOsRegistryService::Registration> registration =
      guest_os::GuestOsRegistryServiceFactory::GetForProfile(profile_)
          ->GetRegistration(app_id);
  if (registration.has_value()) {
    game_id = borealis::ParseSteamGameId(registration->Exec());
  }
  return game_id;
}

base::flat_map<std::string, std::string> BorealisSurveyHandler::GetSurveyData(
    std::string owner_id,
    const std::string app_id,
    std::string window_title,
    std::optional<int> game_id) {
  // Number of monitors
  int internal_displays = 0;
  int external_displays = 0;
  for (const display::Display& d :
       display::Screen::GetScreen()->GetAllDisplays()) {
    if (d.IsInternal()) {
      internal_displays++;
    } else {
      external_displays++;
    }
  }

  // Proton/SLR versions
  borealis::CompatToolInfo compat_tool_info;
  std::string output;
  if (borealis::GetCompatToolInfo(owner_id, &output)) {
    compat_tool_info = borealis::ParseCompatToolInfo(game_id, output);
  } else {
    LOG(WARNING) << "Failed to get compat tool version info:";
    LOG(WARNING) << output;
  }

  // Steam GameID
  if (!game_id.has_value() && compat_tool_info.game_id.has_value()) {
    game_id = compat_tool_info.game_id.value();
  }
  std::string game_id_value = "";
  if (game_id.has_value()) {
    game_id_value = base::StringPrintf("%d", game_id.value());
  }

  base::flat_map<std::string, std::string> survey_data = {
      {"appName", window_title},
      {"board", base::SysInfo::HardwareModelName()},
      {"specs",
       base::StringPrintf("%ldGB; %s",
                          (long)(base::SysInfo::AmountOfPhysicalMemory() /
                                 (1000 * 1000 * 1000)),
                          base::SysInfo::CPUModelName().c_str())},
      {"monitorsInternal", base::NumberToString(internal_displays)},
      {"monitorsExternal", base::NumberToString(external_displays)},
      {"proton", compat_tool_info.proton},
      {"steam", compat_tool_info.slr},
      {"gameId", game_id_value}};
  return survey_data;
}

void BorealisSurveyHandler::OnAppFinished(const std::string& app_id,
                                          aura::Window* last_window) {
  if (IsNonGameBorealisApp(app_id)) {
    return;
  }
  if (ash::HatsNotificationController::ShouldShowSurveyToProfile(
          profile_, ash::kHatsBorealisGamesSurvey)) {
    base::ThreadPool::PostTaskAndReplyWithResult(
        FROM_HERE, base::MayBlock(),
        base::BindOnce(&GetSurveyData,
                       ash::ProfileHelper::GetUserIdHashFromProfile(profile_),
                       std::string(app_id),
                       base::UTF16ToUTF8(last_window->GetTitle()),
                       GetGameId(app_id)),
        base::BindOnce(&BorealisSurveyHandler::CreateNotification,
                       weak_factory_.GetWeakPtr()));
  }
}

void BorealisSurveyHandler::CreateNotification(
    base::flat_map<std::string, std::string> survey_data) {
  hats_notification_controller_ = new ash::HatsNotificationController(
      profile_, ash::kHatsBorealisGamesSurvey, survey_data,
      l10n_util::GetStringUTF16(IDS_BOREALIS_HATS_TITLE),
      l10n_util::GetStringUTF16(IDS_BOREALIS_HATS_BODY));
}

void BorealisSurveyHandler::OnWindowManagerDeleted(
    BorealisWindowManager* window_manager) {
  lifetime_observation_.Reset();
}

}  // namespace borealis