chromium/chrome/browser/ash/app_list/app_service/app_service_app_item_browsertest.cc

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/app_list/app_list_model_provider.h"
#include "ash/app_list/model/app_list_item.h"
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/accelerators.h"
#include "ash/public/cpp/app_list/app_list_types.h"
#include "ash/public/cpp/shelf_types.h"
#include "ash/public/cpp/tablet_mode.h"
#include "ash/public/cpp/test/app_list_test_api.h"
#include "ash/shell.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "chrome/browser/apps/app_service/app_launch_params.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/platform_apps/app_browsertest_util.h"
#include "chrome/browser/ash/app_list/app_list_client_impl.h"
#include "chrome/browser/ash/app_list/app_service/app_service_app_item.h"
#include "chrome/browser/ash/app_list/apps_collections_util.h"
#include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/web_applications/web_app_launch_process.h"
#include "chrome/browser/web_applications/test/with_crosapi_param.h"
#include "chrome/browser/web_applications/web_app_id_constants.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/account_id/account_id.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/services/app_service/public/cpp/app_update.h"
#include "components/webapps/common/web_app_id.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_utils.h"
#include "ui/display/screen.h"
#include "ui/events/event_constants.h"

using web_app::test::CrosapiParam;
using web_app::test::WithCrosapiParam;

namespace {

void UpdateAppRegistryCache(Profile* profile,
                            const std::string& app_id,
                            bool block,
                            bool pause) {
  apps::AppPtr app =
      std::make_unique<apps::App>(apps::AppType::kChromeApp, app_id);
  app->readiness =
      block ? apps::Readiness::kDisabledByPolicy : apps::Readiness::kReady;
  app->paused = pause;

  std::vector<apps::AppPtr> apps;
  apps.push_back(std::move(app));
  apps::AppServiceProxyFactory::GetForProfile(profile)->OnApps(
      std::move(apps), apps::AppType::kChromeApp,
      false /* should_notify_initialized */);
}

void UpdateShortNameInRegistryCache(Profile* profile,
                                    const std::string& app_id,
                                    const std::string& short_name) {
  apps::AppPtr app =
      std::make_unique<apps::App>(apps::AppType::kChromeApp, app_id);
  app->short_name = short_name;

  std::vector<apps::AppPtr> apps;
  apps.push_back(std::move(app));
  apps::AppServiceProxyFactory::GetForProfile(profile)->OnApps(
      std::move(apps), apps::AppType::kChromeApp,
      false /* should_notify_initialized */);
}

ash::AppListItem* GetAppListItem(const std::string& id) {
  return ash::AppListModelProvider::Get()->model()->FindItem(id);
}

}  // namespace

class AppServiceAppItemBrowserTest : public extensions::PlatformAppBrowserTest {
 public:
  AppServiceAppItemBrowserTest() = default;
  ~AppServiceAppItemBrowserTest() override = default;

  // extensions::PlatformAppBrowserTest:
  void SetUpOnMainThread() override {
    extensions::PlatformAppBrowserTest::SetUpOnMainThread();

    AppListClientImpl* client = AppListClientImpl::GetInstance();
    ASSERT_TRUE(client);

    // Associate |client| with the current profile.
    client->UpdateProfile();
  }

  std::unique_ptr<AppServiceAppItem> CreateUserInstalledChromeApp() {
    auto app = std::make_unique<apps::App>(apps::AppType::kChromeApp,
                                           "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
    app->install_reason = apps::InstallReason::kUser;
    apps::AppUpdate app_update(/*state=*/nullptr, /*delta=*/app.get(),
                               EmptyAccountId());
    return std::make_unique<AppServiceAppItem>(
        profile(), /*model_updater=*/nullptr,
        /*sync_item=*/nullptr, app_update);
  }
};

// Test the app status when the paused app is blocked, un-paused, and un-blocked
IN_PROC_BROWSER_TEST_F(AppServiceAppItemBrowserTest,
                       VerifyAppStatusForPausedApp) {
  const extensions::Extension* extension_app =
      LoadAndLaunchPlatformApp("launch", "Launched");
  ASSERT_TRUE(extension_app);

  ash::AppListItem* item = GetAppListItem(extension_app->id());
  ASSERT_TRUE(item);

  // Set the app as paused.
  UpdateAppRegistryCache(profile(), extension_app->id(), false /* block */,
                         true /* pause */);
  EXPECT_EQ(ash::AppStatus::kPaused, item->app_status());

  // Set the app as blocked, and paused.
  UpdateAppRegistryCache(profile(), extension_app->id(), true /* block */,
                         true /* pause */);
  EXPECT_EQ(ash::AppStatus::kBlocked, item->app_status());

  // Set the app as blocked, but not paused.
  UpdateAppRegistryCache(profile(), extension_app->id(), true /* block */,
                         false /* pause */);
  EXPECT_EQ(ash::AppStatus::kBlocked, item->app_status());

  // Set the app as neither blocked, nor paused.
  UpdateAppRegistryCache(profile(), extension_app->id(), false /* block */,
                         false /* pause */);
  EXPECT_EQ(ash::AppStatus::kReady, item->app_status());
}

// Test the app status when the blocked app is paused, un-blocked, and un-paused
IN_PROC_BROWSER_TEST_F(AppServiceAppItemBrowserTest,
                       VerifyAppStatusForBlockedApp) {
  const extensions::Extension* extension_app =
      LoadAndLaunchPlatformApp("launch", "Launched");
  ASSERT_TRUE(extension_app);

  ash::AppListItem* item = GetAppListItem(extension_app->id());
  ASSERT_TRUE(item);

  // Set the app as blocked.
  UpdateAppRegistryCache(profile(), extension_app->id(), true /* block */,
                         false /* pause */);
  EXPECT_EQ(ash::AppStatus::kBlocked, item->app_status());

  // Set the app as blocked, and paused.
  UpdateAppRegistryCache(profile(), extension_app->id(), true /* block */,
                         true /* pause */);
  EXPECT_EQ(ash::AppStatus::kBlocked, item->app_status());

  // Set the app as not blocked, but paused.
  UpdateAppRegistryCache(profile(), extension_app->id(), false /* block */,
                         true /* pause */);
  EXPECT_EQ(ash::AppStatus::kPaused, item->app_status());

  // Set the app as neither blocked, nor paused.
  UpdateAppRegistryCache(profile(), extension_app->id(), false /* block */,
                         false /* pause */);
  EXPECT_EQ(ash::AppStatus::kReady, item->app_status());
}

// Test the app status when the app is both blocked and paused.
IN_PROC_BROWSER_TEST_F(AppServiceAppItemBrowserTest,
                       VerifyAppStatusForBlockedAndPausedApp) {
  const extensions::Extension* extension_app =
      LoadAndLaunchPlatformApp("launch", "Launched");
  ASSERT_TRUE(extension_app);

  ash::AppListItem* item = GetAppListItem(extension_app->id());
  ASSERT_TRUE(item);

  // Set the app as blocked, and paused.
  UpdateAppRegistryCache(profile(), extension_app->id(), true /* block */,
                         true /* pause */);
  EXPECT_EQ(ash::AppStatus::kBlocked, item->app_status());

  // Set the app as neither blocked, nor paused.
  UpdateAppRegistryCache(profile(), extension_app->id(), false /* block */,
                         false /* pause */);
  EXPECT_EQ(ash::AppStatus::kReady, item->app_status());
}

// Test app name changes get propagated to launcher UI.
IN_PROC_BROWSER_TEST_F(AppServiceAppItemBrowserTest, UpdateAppNameInLauncher) {
  const extensions::Extension* extension_app =
      LoadAndLaunchPlatformApp("launch", "Launched");
  ASSERT_TRUE(extension_app);

  ash::AcceleratorController::Get()->PerformActionIfEnabled(
      ash::AcceleratorAction::kToggleAppList, {});
  ash::AppListTestApi app_list_test_api;
  app_list_test_api.WaitForBubbleWindow(/*wait_for_opening_animation=*/false);

  UpdateShortNameInRegistryCache(profile(), extension_app->id(),
                                 "Updated Name");

  EXPECT_EQ(u"Updated Name",
            app_list_test_api.GetAppListItemViewName(extension_app->id()));
}

IN_PROC_BROWSER_TEST_F(AppServiceAppItemBrowserTest,
                       ActivateAppRecordsNewInstallHistogram) {
  base::HistogramTester histograms;
  {
    ASSERT_FALSE(display::Screen::GetScreen()->InTabletMode());

    // Simulate a user-installed chrome app item.
    std::unique_ptr<AppServiceAppItem> app_item =
        CreateUserInstalledChromeApp();
    ASSERT_TRUE(app_item->is_new_install());

    // Activate (launch) the app, which marks it as not a new install and
    // records metrics.
    app_item->PerformActivate(ui::EF_NONE);
    EXPECT_FALSE(app_item->is_new_install());
    histograms.ExpectTotalCount(
        "Apps.TimeBetweenAppInstallAndLaunch.ClamshellMode", 1);
  }
  {
    ash::TabletMode::Get()->SetEnabledForTest(true);

    // Simulate a user-installed chrome app item.
    std::unique_ptr<AppServiceAppItem> app_item =
        CreateUserInstalledChromeApp();
    ASSERT_TRUE(app_item->is_new_install());

    // Activate (launch) the app, which marks it as not a new install and
    // records metrics.
    app_item->PerformActivate(ui::EF_NONE);
    EXPECT_FALSE(app_item->is_new_install());
    histograms.ExpectTotalCount(
        "Apps.TimeBetweenAppInstallAndLaunch.TabletMode", 1);
  }
}

// Test app collection name is set for item in the launcher.
IN_PROC_BROWSER_TEST_F(AppServiceAppItemBrowserTest,
                       AppCollectionIsPassedToLauncher) {
  apps::AppPtr app = std::make_unique<apps::App>(
      apps::AppType::kUnknown, apps_util::kTestAppIdWithCollection);
  app->readiness = apps::Readiness::kReady;
  app->show_in_launcher = true;

  std::vector<apps::AppPtr> apps;
  apps.push_back(std::move(app));
  apps::AppServiceProxyFactory::GetForProfile(profile())->OnApps(
      std::move(apps), apps::AppType::kUnknown,
      false /* should_notify_initialized */);

  ash::AppListItem* item = GetAppListItem(apps_util::kTestAppIdWithCollection);
  ASSERT_TRUE(item);

  EXPECT_EQ(item->collection_id(), ash::AppCollection::kEssentials);
}

class AppServiceSystemWebAppItemBrowserTest
    : public AppServiceAppItemBrowserTest,
      public WithCrosapiParam {
  void SetUpOnMainThread() override {
    AppServiceAppItemBrowserTest::SetUpOnMainThread();
    if (browser() == nullptr) {
      // Create a new Ash browser window so test code using browser() can work
      // even when Lacros is the only browser.
      // TODO(crbug.com/40270051): Remove uses of browser() from such tests.
      chrome::NewEmptyWindow(ProfileManager::GetActiveUserProfile());
      SelectFirstBrowser();
    }
    VerifyLacrosStatus();
  }
};

IN_PROC_BROWSER_TEST_P(AppServiceSystemWebAppItemBrowserTest, Activate) {
  Profile* const profile = browser()->profile();
  ash::SystemWebAppManager::GetForTest(profile)->InstallSystemAppsForTesting();
  const webapps::AppId app_id = web_app::kHelpAppId;

  auto help_app = std::make_unique<apps::App>(apps::AppType::kWeb, app_id);
  apps::AppUpdate app_update(/*state=*/nullptr, /*delta=*/help_app.get(),
                             EmptyAccountId());
  AppServiceAppItem app_item(profile, /*model_updater=*/nullptr,
                             /*sync_item=*/nullptr, app_update);
  app_item.SetChromePosition(app_item.CalculateDefaultPositionForTest());

  ui_test_utils::BrowserChangeObserver browser_opened(
      nullptr, ui_test_utils::BrowserChangeObserver::ChangeType::kAdded);
  app_item.PerformActivate(ui::EF_NONE);
  browser_opened.Wait();

  // Verify that a launch no longer occurs.
  web_app::WebAppLaunchProcess::SetOpenApplicationCallbackForTesting(
      base::BindLambdaForTesting(
          [](apps::AppLaunchParams params) { NOTREACHED_IN_MIGRATION(); }));

  app_item.PerformActivate(ui::EF_NONE);
}

INSTANTIATE_TEST_SUITE_P(All,
                         AppServiceSystemWebAppItemBrowserTest,
                         ::testing::Values(CrosapiParam::kDisabled,
                                           CrosapiParam::kEnabled),
                         WithCrosapiParam::ParamToString);