chromium/chrome/browser/ash/app_list/search/app_service_app_result.cc

// Copyright 2018 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_list/search/app_service_app_result.h"

#include "ash/public/cpp/app_list/app_list_types.h"
#include "ash/public/cpp/shelf_item_delegate.h"
#include "ash/public/cpp/shelf_model.h"
#include "ash/strings/grit/ash_strings.h"
#include "base/strings/utf_string_conversions.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/metrics/app_service_metrics.h"
#include "chrome/browser/ash/app_list/app_list_client_impl.h"
#include "chrome/browser/ash/app_list/search/common/icon_constants.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/shelf/chrome_shelf_controller.h"
#include "components/services/app_service/public/cpp/app_launch_util.h"
#include "components/services/app_service/public/cpp/app_update.h"
#include "components/services/app_service/public/cpp/types_util.h"
#include "extensions/common/extension.h"
#include "ui/base/l10n/l10n_util.h"

namespace app_list {

AppServiceAppResult::AppServiceAppResult(Profile* profile,
                                         const std::string& app_id,
                                         AppListControllerDelegate* controller,
                                         bool is_recommendation,
                                         apps::IconLoader* icon_loader)
    : AppResult(profile, app_id, controller, is_recommendation),
      icon_loader_(icon_loader),
      app_type_(apps::AppType::kUnknown),
      is_platform_app_(false),
      show_in_launcher_(false) {
  apps::AppServiceProxyFactory::GetForProfile(profile)
      ->AppRegistryCache()
      .ForOneApp(app_id, [this](const apps::AppUpdate& update) {
        app_type_ = update.AppType();
        is_platform_app_ = update.IsPlatformApp().value_or(false);
        show_in_launcher_ = update.ShowInLauncher().value_or(false);

        if (apps_util::IsDisabled(update.Readiness())) {
          SetAccessibleName(l10n_util::GetStringFUTF16(
              IDS_APP_ACCESSIBILITY_BLOCKED_INSTALLED_APP_ANNOUNCEMENT,
              base::UTF8ToUTF16(update.Name())));
        } else if (update.Paused().value_or(false)) {
          SetAccessibleName(l10n_util::GetStringFUTF16(
              IDS_APP_ACCESSIBILITY_PAUSED_INSTALLED_APP_ANNOUNCEMENT,
              base::UTF8ToUTF16(update.Name())));
        }
      });

  constexpr bool allow_placeholder_icon = true;
  CallLoadIcon(false, allow_placeholder_icon);
  if (is_recommendation) {
    CallLoadIcon(true, allow_placeholder_icon);
  }

  SetMetricsType(GetSearchResultType());
  SetCategory(Category::kApps);

  switch (app_type_) {
    case apps::AppType::kBuiltIn:
      set_id(app_id);
      // TODO(crbug.com/40569217): Is this SetResultType call necessary?? Does
      // anyone care about the kInternalApp vs kInstalledApp distinction?
      SetResultType(ResultType::kInternalApp);
      break;
    case apps::AppType::kChromeApp:
      // TODO(crbug.com/40569217): why do we pass the URL and not the app_id??
      // Can we replace this by the simpler "set_id(app_id)", and therefore
      // pull that out of the switch?
      set_id(extensions::Extension::GetBaseURLFromExtensionId(app_id).spec());
      break;
    default:
      set_id(app_id);
      break;
  }
}

AppServiceAppResult::~AppServiceAppResult() = default;

void AppServiceAppResult::Open(int event_flags) {
  Launch(event_flags,
         (is_recommendation() ? apps::LaunchSource::kFromAppListRecommendation
                              : apps::LaunchSource::kFromAppListQuery));
}

ash::SearchResultType AppServiceAppResult::GetSearchResultType() const {
  switch (app_type_) {
    case apps::AppType::kArc:
      return ash::PLAY_STORE_APP;
    case apps::AppType::kBuiltIn:
      return ash::INTERNAL_APP;
    case apps::AppType::kPluginVm:
      return ash::PLUGIN_VM_APP;
    case apps::AppType::kCrostini:
      return ash::CROSTINI_APP;
    case apps::AppType::kChromeApp:
    case apps::AppType::kWeb:
    case apps::AppType::kSystemWeb:
    case apps::AppType::kStandaloneBrowserChromeApp:
      return ash::EXTENSION_APP;
    case apps::AppType::kStandaloneBrowser:
      return ash::LACROS;
    case apps::AppType::kRemote:
      return ash::REMOTE_APP;
    case apps::AppType::kBorealis:
      return ash::BOREALIS_APP;
    case apps::AppType::kBruschetta:
      return ash::BRUSCHETTA_APP;
    case apps::AppType::kExtension:
    case apps::AppType::kStandaloneBrowserExtension:
    case apps::AppType::kUnknown:
      NOTREACHED_IN_MIGRATION();
      return ash::SEARCH_RESULT_TYPE_BOUNDARY;
  }
}

void AppServiceAppResult::ExecuteLaunchCommand(int event_flags) {
  Launch(event_flags, apps::LaunchSource::kFromAppListQueryContextMenu);
}

void AppServiceAppResult::Launch(int event_flags,
                                 apps::LaunchSource launch_source) {
  apps::AppServiceProxy* proxy =
      apps::AppServiceProxyFactory::GetForProfile(profile());

  // For Crostini apps, non-platform Chrome apps, Web apps, it could be
  // selecting an existing delegate for the app, so call
  // ChromeShelfController's ActivateApp interface. Platform apps or ARC
  // apps, Crostini apps treat activations as a launch. The app can decide
  // whether to show a new window or focus an existing window as it sees fit.
  //
  // TODO(crbug.com/40659878): Move this special case to ExtensionApps,
  // when AppService Instance feature is done.
  bool is_active_app = false;
  proxy->AppRegistryCache().ForOneApp(
      app_id(), [&is_active_app](const apps::AppUpdate& update) {
        if (update.AppType() == apps::AppType::kCrostini ||
            update.AppType() == apps::AppType::kWeb ||
            update.AppType() == apps::AppType::kSystemWeb ||
            (update.AppType() == apps::AppType::kStandaloneBrowserChromeApp &&
             !update.IsPlatformApp().value_or(true)) ||
            (update.AppType() == apps::AppType::kChromeApp &&
             update.IsPlatformApp().value_or(true))) {
          is_active_app = true;
        }
      });
  if (is_active_app) {
    ash::ShelfLaunchSource source =
        is_recommendation() ? ash::LAUNCH_FROM_APP_LIST_RECOMMENDATION
                            : ash::LAUNCH_FROM_APP_LIST_SEARCH;
    ash::ShelfID shelf_id(app_id());
    ash::ShelfModel* model = ChromeShelfController::instance()->shelf_model();
    ash::ShelfItemDelegate* delegate = model->GetShelfItemDelegate(shelf_id);
    if (delegate) {
      delegate->ItemSelected(/*event=*/nullptr,
                             controller()->GetAppListDisplayId(), source,
                             /*callback=*/base::DoNothing(),
                             /*filter_predicate=*/base::NullCallback());
      return;
    }
  }

  proxy->Launch(
      app_id(), event_flags, launch_source,
      std::make_unique<apps::WindowInfo>(controller()->GetAppListDisplayId()));
}

void AppServiceAppResult::CallLoadIcon(bool chip, bool allow_placeholder_icon) {
  if (!icon_loader_) {
    return;
  }

  // If |icon_loader_releaser_| is non-null, assigning to it will signal to
  // |icon_loader_| that the previous icon is no longer being used, as a hint
  // that it could be flushed from any caches.
  icon_loader_releaser_ = icon_loader_->LoadIcon(
      app_id(), apps::IconType::kStandard, kAppIconDimension,
      allow_placeholder_icon,
      base::BindOnce(&AppServiceAppResult::OnLoadIcon,
                     weak_ptr_factory_.GetWeakPtr(), chip));
}

void AppServiceAppResult::OnLoadIcon(bool chip, apps::IconValuePtr icon_value) {
  if (!icon_value || icon_value->icon_type != apps::IconType::kStandard) {
    return;
  }

  if (chip) {
    SetChipIcon(icon_value->uncompressed);
  } else {
    SetIcon(IconInfo(ui::ImageModel::FromImageSkia(icon_value->uncompressed),
                     kAppIconDimension));
  }

  if (icon_value->is_placeholder_icon) {
    constexpr bool allow_placeholder_icon = false;
    CallLoadIcon(chip, allow_placeholder_icon);
  }
}

}  // namespace app_list