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

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

#include <string>
#include <vector>

#include "base/run_loop.h"
#include "base/time/time.h"
#include "chrome/browser/ash/app_list/arc/arc_app_list_prefs.h"
#include "chrome/browser/ash/app_list/search/app_search_provider_test_base.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/web_applications/web_app_id_constants.h"
#include "chrome/test/base/testing_profile.h"
#include "components/crx_file/id_util.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/uninstall_reason.h"
#include "testing/gtest/include/gtest/gtest.h"

using extensions::mojom::ManifestLocation;

namespace app_list::test {

namespace {

constexpr char kNormalAppName[] = "Normal app";
constexpr char kHiddenAppName[] = "Hidden app";

constexpr char kPlayStoreArcName[] = "Play Store";
constexpr char kPlayStorePackageName[] = "com.android.vending";
constexpr char kPlayStoreActivity[] =
    "com.android.vending.AssetBrowserActivity";

base::Time MicrosecondsSinceEpoch(int microseconds) {
  return base::Time::FromDeltaSinceWindowsEpoch(
      base::Microseconds(microseconds));
}

}  // namespace

class AppZeroStateProviderTest : public AppSearchProviderTestBase {
 public:
  AppZeroStateProviderTest()
      : AppSearchProviderTestBase(/*zero_state_provider=*/true) {}
  AppZeroStateProviderTest(const AppZeroStateProviderTest&) = delete;
  AppZeroStateProviderTest& operator=(const AppZeroStateProviderTest&) = delete;
  ~AppZeroStateProviderTest() override = default;
};

TEST_F(AppZeroStateProviderTest, FetchRecommendations) {
  InitializeSearchProvider();

  extensions::ExtensionPrefs* prefs =
      extensions::ExtensionPrefs::Get(profile_.get());

  prefs->SetLastLaunchTime(kHostedAppId, MicrosecondsSinceEpoch(20));
  prefs->SetLastLaunchTime(kPackagedApp1Id, MicrosecondsSinceEpoch(10));
  prefs->SetLastLaunchTime(kPackagedApp2Id, MicrosecondsSinceEpoch(5));
  // Allow async callbacks to run.
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ("Hosted App,Packaged App 1,Packaged App 2", RunZeroStateSearch());

  prefs->SetLastLaunchTime(kHostedAppId, MicrosecondsSinceEpoch(5));
  prefs->SetLastLaunchTime(kPackagedApp1Id, MicrosecondsSinceEpoch(10));
  prefs->SetLastLaunchTime(kPackagedApp2Id, MicrosecondsSinceEpoch(20));
  // Allow async callbacks to run.
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ("Packaged App 2,Packaged App 1,Hosted App", RunZeroStateSearch());

  // Times in the future should just be handled as highest priority.
  prefs->SetLastLaunchTime(kHostedAppId, base::Time::Now() + base::Seconds(5));
  prefs->SetLastLaunchTime(kPackagedApp1Id, MicrosecondsSinceEpoch(10));
  prefs->SetLastLaunchTime(kPackagedApp2Id, MicrosecondsSinceEpoch(5));
  // Allow async callbacks to run.
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ("Hosted App,Packaged App 1,Packaged App 2", RunZeroStateSearch());

  // Validate that queried search does not clear out zero state results.
  RunQuery("No matches");
  EXPECT_EQ("Hosted App,Packaged App 1,Packaged App 2",
            GetSortedResultsString());
}

TEST_F(AppZeroStateProviderTest, DefaultRecommendedAppRanking) {
  // Disable the pre-installed high-priority extensions. This test simulates
  // a brand new profile being added to a device, and should not include these.
  service_->UninstallExtension(
      kHostedAppId, extensions::UNINSTALL_REASON_FOR_TESTING, nullptr);
  service_->UninstallExtension(
      kPackagedApp1Id, extensions::UNINSTALL_REASON_FOR_TESTING, nullptr);
  service_->UninstallExtension(
      kPackagedApp2Id, extensions::UNINSTALL_REASON_FOR_TESTING, nullptr);

  base::RunLoop().RunUntilIdle();

  profile_->SetIsNewProfile(true);
  ASSERT_TRUE(profile()->IsNewProfile());
  arc_test().SetUp(profile());

  // There are four default web apps. We use real app IDs here, as these are
  // used internally by the ranking logic. We can use arbitrary app names.
  const std::vector<std::string> kDefaultRecommendedWebAppIds = {
      web_app::kCanvasAppId, web_app::kHelpAppId, web_app::kOsSettingsAppId,
      web_app::kCameraAppId};
  const std::vector<std::string> kDefaultRecommendedWebAppNames = {
      "Canvas", "Help", "OsSettings", "Camera"};

  ASSERT_EQ(kDefaultRecommendedWebAppNames.size(),
            kDefaultRecommendedWebAppIds.size());

  // Install the default recommended web apps.
  // N.B. These are web apps and not extensions, but these installations are
  // simulated using extensions because it allows us to set the app ID.
  for (size_t i = 0; i < kDefaultRecommendedWebAppNames.size(); ++i) {
    AddExtension(kDefaultRecommendedWebAppIds[i],
                 kDefaultRecommendedWebAppNames[i],
                 ManifestLocation::kExternalPrefDownload,
                 extensions::Extension::WAS_INSTALLED_BY_DEFAULT);
    service_->EnableExtension(kDefaultRecommendedWebAppIds[i]);
  }

  // Install the default ARC app (Play Store). This is marked here as sticky so
  // that its installation time does not count as a ranking activity.
  const std::string playstore_app_id =
      AddArcApp(kPlayStoreArcName, kPlayStorePackageName, kPlayStoreActivity,
                /*sticky=*/true);

  // Allow app installations to finish.
  base::RunLoop().RunUntilIdle();
  InitializeSearchProvider();

  EXPECT_EQ("OsSettings,Help,Play Store,Canvas,Camera", RunZeroStateSearch());

  // Install a normal (non-default-installed) app.
  const std::string normal_app_id =
      crx_file::id_util::GenerateId(kNormalAppName);
  AddExtension(normal_app_id, kNormalAppName,
               ManifestLocation::kExternalPrefDownload,
               extensions::Extension::NO_FLAGS);
  WaitTimeUpdated();

  extensions::ExtensionPrefs* const prefs =
      extensions::ExtensionPrefs::Get(profile());
  ASSERT_TRUE(prefs);

  // Simulate launching the normal app. Expect that an app with a recorded
  // launch time takes precedence over the default-installed apps.
  prefs->SetLastLaunchTime(normal_app_id, base::Time::Now());
  InitializeSearchProvider();
  EXPECT_EQ(
      std::string(kNormalAppName) + ",OsSettings,Help,Play Store,Canvas,Camera",
      RunZeroStateSearch());

  // Simulate launching one of the default apps. Expect that this brings it to
  // higher precedence than all the others.
  prefs->SetLastLaunchTime(web_app::kCanvasAppId, base::Time::Now());
  InitializeSearchProvider();
  EXPECT_EQ("Canvas," + std::string(kNormalAppName) +
                ",OsSettings,Help,Play Store,Camera",
            RunZeroStateSearch());
}

TEST_F(AppZeroStateProviderTest, FetchUnlaunchedRecommendations) {
  InitializeSearchProvider();

  extensions::ExtensionPrefs* prefs =
      extensions::ExtensionPrefs::Get(profile_.get());

  // The order of unlaunched recommendations should be based on the install time
  // order.
  prefs->SetLastLaunchTime(kHostedAppId, base::Time::Now());
  prefs->SetLastLaunchTime(kPackagedApp1Id, MicrosecondsSinceEpoch(0));
  prefs->SetLastLaunchTime(kPackagedApp2Id, MicrosecondsSinceEpoch(0));
  EXPECT_EQ("Hosted App,Packaged App 1,Packaged App 2", RunZeroStateSearch());
}

TEST_F(AppZeroStateProviderTest, HideNotShownInLauncher) {
  // Disable the pre-installed high-priority extensions.
  service_->UninstallExtension(
      kHostedAppId, extensions::UNINSTALL_REASON_FOR_TESTING, nullptr);
  service_->UninstallExtension(
      kPackagedApp1Id, extensions::UNINSTALL_REASON_FOR_TESTING, nullptr);
  service_->UninstallExtension(
      kPackagedApp2Id, extensions::UNINSTALL_REASON_FOR_TESTING, nullptr);

  // Install two apps, one which is hidden and one shown in the launcher.
  const std::string shown_app_id =
      crx_file::id_util::GenerateId(kNormalAppName);
  AddExtension(shown_app_id, kNormalAppName, ManifestLocation::kComponent,
               extensions::Extension::WAS_INSTALLED_BY_DEFAULT,
               /*display_in_launcher=*/true);
  service_->EnableExtension(shown_app_id);

  const std::string hidden_app_id =
      crx_file::id_util::GenerateId(kHiddenAppName);
  AddExtension(hidden_app_id, kHiddenAppName, ManifestLocation::kComponent,
               extensions::Extension::WAS_INSTALLED_BY_DEFAULT,
               /*display_in_launcher=*/false);
  service_->EnableExtension(hidden_app_id);

  base::RunLoop().RunUntilIdle();

  // Only the app that is shown in launcher should be available.
  InitializeSearchProvider();
  EXPECT_EQ(std::string(kNormalAppName), RunZeroStateSearch());
}

}  // namespace app_list::test