chromium/chrome/browser/ui/ash/user_education/chrome_user_education_delegate.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/ui/ash/user_education/chrome_user_education_delegate.h"

#include <optional>

#include "ash/ash_element_identifiers.h"
#include "ash/user_education/user_education_types.h"
#include "ash/user_education/user_education_util.h"
#include "base/check.h"
#include "base/values.h"
#include "chrome/browser/ash/app_list/app_list_syncable_service.h"
#include "chrome/browser/ash/app_list/app_list_syncable_service_factory.h"
#include "chrome/browser/ash/file_manager/app_id.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
#include "chrome/browser/ui/views/user_education/browser_user_education_service.h"
#include "chrome/browser/user_education/user_education_service.h"
#include "chrome/browser/user_education/user_education_service_factory.h"
#include "chrome/browser/web_applications/web_app_id_constants.h"
#include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
#include "components/account_id/account_id.h"
#include "components/services/app_service/public/cpp/app_launch_util.h"
#include "components/user_education/common/help_bubble.h"
#include "components/user_education/common/help_bubble_factory_registry.h"
#include "components/user_education/common/help_bubble_params.h"
#include "components/user_education/common/tutorial_registry.h"
#include "components/user_education/common/tutorial_service.h"
#include "components/user_manager/user_manager.h"
#include "ui/base/interaction/element_tracker.h"

namespace {

// Helpers ---------------------------------------------------------------------

app_list::AppListSyncableService* GetAppListSyncableService(Profile* profile) {
  return app_list::AppListSyncableServiceFactory::GetForProfile(profile);
}

Profile* GetProfile(const AccountId& account_id) {
  return Profile::FromBrowserContext(
      ash::BrowserContextHelper::Get()->GetBrowserContextByAccountId(
          account_id));
}

bool IsPrimaryProfile(Profile* profile) {
  return user_manager::UserManager::Get()->IsPrimaryUser(
      ash::BrowserContextHelper::Get()->GetUserByBrowserContext(profile));
}

std::optional<std::string> ToString(
    std::optional<ash::TutorialId> tutorial_id) {
  return tutorial_id ? std::make_optional(ash::user_education_util::ToString(
                           tutorial_id.value()))
                     : std::nullopt;
}

}  // namespace

// ChromeUserEducationDelegate -------------------------------------------------

ChromeUserEducationDelegate::ChromeUserEducationDelegate() {
  ProfileManager* profile_manager = g_browser_process->profile_manager();
  profile_manager_observation_.Observe(profile_manager);
  for (Profile* profile : profile_manager->GetLoadedProfiles()) {
    OnProfileAdded(profile);
  }
}

ChromeUserEducationDelegate::~ChromeUserEducationDelegate() = default;

std::optional<ui::ElementIdentifier>
ChromeUserEducationDelegate::GetElementIdentifierForAppId(
    const std::string& app_id) const {
  if (!strcmp(file_manager::kFileManagerSwaAppId, app_id.c_str())) {
    return ash::kFilesAppElementId;
  }
  if (!strcmp(web_app::kHelpAppId, app_id.c_str())) {
    return ash::kExploreAppElementId;
  }
  if (!strcmp(web_app::kOsSettingsAppId, app_id.c_str())) {
    return ash::kSettingsAppElementId;
  }
  return std::nullopt;
}

const std::optional<bool>& ChromeUserEducationDelegate::IsNewUser(
    const AccountId& account_id) const {
  // NOTE: User education in Ash is currently only supported for the primary
  // user profile. This is a self-imposed restriction.
  auto* const profile = GetProfile(account_id);
  CHECK(IsPrimaryProfile(profile));
  return is_primary_profile_new_user_;
}

bool ChromeUserEducationDelegate::IsTutorialRegistered(
    const AccountId& account_id,
    ash::TutorialId tutorial_id) const {
  // NOTE: User education in Ash is currently only supported for the primary
  // user profile. This is a self-imposed restriction.
  auto* const profile = GetProfile(account_id);
  CHECK(IsPrimaryProfile(profile));
  return UserEducationServiceFactory::GetForBrowserContext(profile)
      ->tutorial_registry()
      .IsTutorialRegistered(ash::user_education_util::ToString(tutorial_id));
}

void ChromeUserEducationDelegate::RegisterTutorial(
    const AccountId& account_id,
    ash::TutorialId tutorial_id,
    user_education::TutorialDescription tutorial_description) {
  // NOTE: User education in Ash is currently only supported for the primary
  // user profile. This is a self-imposed restriction.
  auto* const profile = GetProfile(account_id);
  CHECK(IsPrimaryProfile(profile));
  UserEducationServiceFactory::GetForBrowserContext(profile)
      ->tutorial_registry()
      .AddTutorial(ash::user_education_util::ToString(tutorial_id),
                   std::move(tutorial_description));
}

void ChromeUserEducationDelegate::StartTutorial(
    const AccountId& account_id,
    ash::TutorialId tutorial_id,
    ui::ElementContext element_context,
    base::OnceClosure completed_callback,
    base::OnceClosure aborted_callback) {
  // NOTE: User education in Ash is currently only supported for the primary
  // user profile. This is a self-imposed restriction.
  auto* const profile = GetProfile(account_id);
  CHECK(IsPrimaryProfile(profile));
  UserEducationServiceFactory::GetForBrowserContext(profile)
      ->tutorial_service()
      .StartTutorial(ash::user_education_util::ToString(tutorial_id),
                     std::move(element_context), std::move(completed_callback),
                     std::move(aborted_callback));
}

void ChromeUserEducationDelegate::AbortTutorial(
    const AccountId& account_id,
    std::optional<ash::TutorialId> tutorial_id) {
  // NOTE: User education in Ash is currently only supported for the primary
  // user profile. This is a self-imposed restriction.
  auto* const profile = GetProfile(account_id);
  CHECK(IsPrimaryProfile(profile));

  auto& tutorial_service =
      UserEducationServiceFactory::GetForBrowserContext(profile)
          ->tutorial_service();
  tutorial_service.CancelTutorialIfRunning(ToString(tutorial_id));
}

void ChromeUserEducationDelegate::LaunchSystemWebAppAsync(
    const AccountId& account_id,
    ash::SystemWebAppType system_web_app_type,
    apps::LaunchSource launch_source,
    int64_t display_id) {
  // NOTE: User education in Ash is currently only supported for the primary
  // user profile. This is a self-imposed restriction.
  auto* const profile = GetProfile(account_id);
  CHECK(IsPrimaryProfile(profile));

  ash::SystemAppLaunchParams launch_params;
  launch_params.launch_source = launch_source;
  ash::LaunchSystemWebAppAsync(profile, system_web_app_type, launch_params,
                               std::make_unique<apps::WindowInfo>(display_id));
}

bool ChromeUserEducationDelegate::IsRunningTutorial(
    const AccountId& account_id,
    std::optional<ash::TutorialId> tutorial_id) const {
  return UserEducationServiceFactory::GetForBrowserContext(
             GetProfile(account_id))
      ->tutorial_service()
      .IsRunningTutorial(ToString(tutorial_id));
}

void ChromeUserEducationDelegate::OnProfileAdded(Profile* profile) {
  // NOTE: User eduction in Ash is currently only supported for the primary
  // user profile. This is a self-imposed restriction.
  if (!IsPrimaryProfile(profile)) {
    return;
  }

  // Since we only currently support the primary user profile, we can stop
  // observing the profile manager once it has been added.
  profile_manager_observation_.Reset();

  // Register tutorial dependencies.
  RegisterChromeHelpBubbleFactories(
      UserEducationServiceFactory::GetForBrowserContext(profile)
          ->help_bubble_factory_registry());

  // Cache whether the user associated with the primary profile is considered
  // new, based on whether the first app list sync in the session was the first
  // sync ever across all ChromeOS devices and sessions for the given user.
  if (auto* app_list_syncable_service = GetAppListSyncableService(profile)) {
    app_list_syncable_service->OnFirstSync(base::BindOnce(
        [](const base::WeakPtr<ChromeUserEducationDelegate>& self,
           bool was_first_sync_ever) {
          if (self) {
            self->is_primary_profile_new_user_ = was_first_sync_ever;
          }
        },
        weak_ptr_factory_.GetWeakPtr()));
  }
}

void ChromeUserEducationDelegate::OnProfileManagerDestroying() {
  profile_manager_observation_.Reset();
}