chromium/chrome/browser/ash/app_restore/app_launch_handler.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_restore/app_launch_handler.h"

#include <utility>
#include <vector>

#include "apps/launcher.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/task/single_thread_task_runner.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/browser_app_launcher.h"
#include "chrome/browser/apps/app_service/metrics/app_platform_metrics.h"
#include "chrome/browser/profiles/profile.h"
#include "components/app_constants/constants.h"
#include "components/app_restore/full_restore_read_handler.h"
#include "components/services/app_service/public/cpp/app_launch_util.h"
#include "components/services/app_service/public/cpp/intent.h"
#include "components/services/app_service/public/cpp/types_util.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension.h"

namespace ash {

namespace {

// Returns apps::AppTypeName used for metrics.
apps::AppTypeName GetHistogrameAppType(apps::AppType app_type) {
  switch (app_type) {
    case apps::AppType::kUnknown:
      return apps::AppTypeName::kUnknown;
    case apps::AppType::kArc:
      return apps::AppTypeName::kArc;
    case apps::AppType::kBuiltIn:
    case apps::AppType::kCrostini:
      return apps::AppTypeName::kUnknown;
    case apps::AppType::kChromeApp:
      return apps::AppTypeName::kChromeApp;
    case apps::AppType::kWeb:
      return apps::AppTypeName::kWeb;
    case apps::AppType::kPluginVm:
    case apps::AppType::kStandaloneBrowser:
    case apps::AppType::kStandaloneBrowserChromeApp:
    case apps::AppType::kRemote:
    case apps::AppType::kBorealis:
    case apps::AppType::kBruschetta:
    case apps::AppType::kExtension:
    case apps::AppType::kStandaloneBrowserExtension:
      return apps::AppTypeName::kUnknown;
    case apps::AppType::kSystemWeb:
      return apps::AppTypeName::kSystemWeb;
  }
}

}  // namespace

AppLaunchHandler::AppLaunchHandler(Profile* profile) : profile_(profile) {}

AppLaunchHandler::~AppLaunchHandler() = default;

bool AppLaunchHandler::HasRestoreData() const {
  return restore_data_ && !restore_data_->app_id_to_launch_list().empty();
}

void AppLaunchHandler::OnAppUpdate(const apps::AppUpdate& update) {
  if (update.AppId() == app_constants::kChromeAppId || !restore_data_ ||
      !update.ReadinessChanged()) {
    return;
  }

  if (!apps_util::IsInstalled(update.Readiness())) {
    restore_data_->RemoveApp(update.AppId());
    return;
  }

  // If the app is not ready, don't launch the app for the restoration.
  if (update.Readiness() != apps::Readiness::kReady)
    return;

  // If there is no restore data or the launch list for the app is empty, don't
  // launch the app.
  const auto& app_id_to_launch_list = restore_data_->app_id_to_launch_list();
  if (app_id_to_launch_list.find(update.AppId()) ==
      app_id_to_launch_list.end()) {
    return;
  }

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&AppLaunchHandler::LaunchApp, GetWeakPtrAppLaunchHandler(),
                     update.AppType(), update.AppId()));
}

void AppLaunchHandler::OnAppTypeInitialized(apps::AppType app_type) {
  // Do nothing: overridden by subclasses.
}

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

void AppLaunchHandler::LaunchApps() {
  // If there is no launch list from the restore data, we don't need to handle
  // launching.
  const auto& launch_list = restore_data_->app_id_to_launch_list();
  if (launch_list.empty())
    return;

  // Observe AppRegistryCache to get the notification when the app is ready.
  DCHECK(
      apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile_));
  auto* cache = &apps::AppServiceProxyFactory::GetForProfile(profile_)
                     ->AppRegistryCache();
  ObserveCache(cache);
  for (const auto app_type : cache->InitializedAppTypes()) {
    OnAppTypeInitialized(app_type);
  }

  // Add the app to `app_ids` if there is a launch list from the restore data
  // for the app.
  std::set<std::string> app_ids;
  cache->ForEachApp([&app_ids, &launch_list](const apps::AppUpdate& update) {
    if (update.Readiness() == apps::Readiness::kReady &&
        launch_list.find(update.AppId()) != launch_list.end()) {
      app_ids.insert(update.AppId());
    }
  });

  for (const auto& app_id : app_ids) {
    // Chrome browser web pages are restored separately, so we don't need to
    // launch browser windows.
    if (app_id == app_constants::kChromeAppId)
      continue;

    LaunchApp(cache->GetAppType(app_id), app_id);
  }
}

bool AppLaunchHandler::ShouldLaunchSystemWebAppOrChromeApp(
    const std::string& app_id,
    const ::app_restore::RestoreData::LaunchList& launch_list) {
  return true;
}

void AppLaunchHandler::LaunchApp(apps::AppType app_type,
                                 const std::string& app_id) {
  DCHECK(restore_data_);
  DCHECK_NE(app_id, app_constants::kChromeAppId);

  const auto it = restore_data_->app_id_to_launch_list().find(app_id);
  if (it == restore_data_->app_id_to_launch_list().end() ||
      it->second.empty()) {
    restore_data_->RemoveApp(app_id);
    return;
  }

  switch (app_type) {
    case apps::AppType::kArc:
      // ArcAppLaunchHandler handles ARC apps restoration and ARC apps
      // restoration could be delayed, so return to preserve the restore data
      // for ARC apps.
      return;
    case apps::AppType::kChromeApp:
    case apps::AppType::kWeb:
    case apps::AppType::kSystemWeb:
    case apps::AppType::kStandaloneBrowserChromeApp:
      if (ShouldLaunchSystemWebAppOrChromeApp(app_id, it->second))
        LaunchSystemWebAppOrChromeApp(app_type, app_id, it->second);
      break;
    case apps::AppType::kStandaloneBrowser:
      // For Lacros, we can't use the AppService launch interface to restore,
      // but call Lacros interface to restore with session restore.
      return;
    case apps::AppType::kBuiltIn:
    case apps::AppType::kCrostini:
    case apps::AppType::kPluginVm:
    case apps::AppType::kUnknown:
    case apps::AppType::kRemote:
    case apps::AppType::kBorealis:
    case apps::AppType::kBruschetta:
    case apps::AppType::kExtension:
    case apps::AppType::kStandaloneBrowserExtension:
      NOTREACHED_IN_MIGRATION();
      break;
  }
  restore_data_->RemoveApp(app_id);
}

void AppLaunchHandler::ObserveCache(apps::AppRegistryCache* source) {
  DCHECK(source);
  if (!app_registry_cache_observer_.IsObservingSource(source)) {
    app_registry_cache_observer_.Reset();
    app_registry_cache_observer_.Observe(source);
  }
}

void AppLaunchHandler::LaunchSystemWebAppOrChromeApp(
    apps::AppType app_type,
    const std::string& app_id,
    const app_restore::RestoreData::LaunchList& launch_list) {
  auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile_);
  DCHECK(proxy);

  if (app_type == apps::AppType::kChromeApp ||
      app_type == apps::AppType::kStandaloneBrowserChromeApp) {
    OnExtensionLaunching(app_id);
  }

  for (const auto& it : launch_list) {
    RecordRestoredAppLaunch(GetHistogrameAppType(app_type));

    if (it.second->handler_id.has_value()) {
      const extensions::Extension* extension =
          extensions::ExtensionRegistry::Get(profile_)->GetInstalledExtension(
              app_id);
      if (extension) {
        DCHECK(!it.second->file_paths.empty());
        apps::LaunchPlatformAppWithFileHandler(profile_, extension,
                                               it.second->handler_id.value(),
                                               it.second->file_paths);
      }
      continue;
    }

    // Desk templates may have partial data. See http://crbug/1232520
    if (!it.second->container.has_value() ||
        !it.second->disposition.has_value() ||
        !it.second->display_id.has_value()) {
      continue;
    }

    apps::AppLaunchParams params(
        app_id,
        static_cast<apps::LaunchContainer>(it.second->container.value()),
        static_cast<WindowOpenDisposition>(it.second->disposition.value()),
        it.second->override_url.value_or(GURL()),
        apps::LaunchSource::kFromFullRestore, it.second->display_id.value(),
        it.second->file_paths,
        it.second->intent ? it.second->intent->Clone() : nullptr);
    params.restore_id = it.first;
    proxy->LaunchAppWithParams(std::move(params));
  }
}

}  // namespace ash